Remove os.Exit callsites below the entrypoint: phase 4 (plans/exit.md) (#2205)

Phase 4 of plans/exit.md: the aux/terminal sub-entrypoints. pkg/auxents and
pkg/terminals are now os.Exit-free; even the dispatchers return exit codes
instead of exiting.

- auxents.Dispatch returns (handled bool, exitCode int); entrypoint.Main --
  the one sanctioned exit point -- exits with the code.
- terminals.Dispatch returns an exit code; the climain caller wraps it in a
  lib.ExitRequest, which also retires the 'terminal did not exit the process'
  panic.
- Usage functions (hex, unhex, lecat, termcvt, repl, script, regtest) no
  longer take an exitCode parameter and exit; call sites print usage and
  return the code explicitly.
- Inner I/O helpers (hexDumpFile, unhexFile, lecatFile, termcvtFile) return
  errors; each sub-main prints 'mlr {verb}: ...' as before and returns 1.
  lecatFile formerly looped forever on a non-EOF read error; it now returns
  the error.
- The regtest directory walk (Execute / executeSinglePath /
  executeSingleDirectory / hasCaseSubdirectories) plumbs errors up to
  RegTestMain; regression_test.go updated for the new Execute signature.

Stderr messages and exit codes are unchanged across mlr aux-list, hex, unhex,
lecat, termcvt, help, version, repl, script, and regtest surfaces (checked by
hand); all 4779 regression cases pass; make lint is clean; docs rebuild with
zero churn.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
John Kerl 2026-07-15 15:27:26 -04:00 committed by GitHub
parent 314b26cfaa
commit 2f3e54d1f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 195 additions and 133 deletions

View file

@ -32,22 +32,23 @@ func init() {
}
}
// Dispatch is called from Miller main. Here we indicate if argv[1] is handled
// by us, or not. If so, we handle it and exit, not returning control to Miller
// main.
func Dispatch(args []string) {
// Dispatch is called from Miller main. The boolean return says whether
// argv[1] named an auxent handled here; if so, the int is the process exit
// code for the entrypoint layer to exit with. Otherwise control returns to
// main for the rest of Miller.
func Dispatch(args []string) (bool, int) {
if len(args) < 2 {
return
return false, 0
}
verb := args[1]
for _, entry := range _AUX_LOOKUP_TABLE {
if verb == entry.name {
os.Exit(entry.main(args))
return true, entry.main(args)
}
}
// Else, return control to main for the rest of Miller.
return false, 0
}
// auxListMain is the handler for 'mlr aux-list'.

View file

@ -23,14 +23,13 @@ import (
// 00000060: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|
// 00000070: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|
func hexUsage(verbName string, o *os.File, exitCode int) {
func hexUsage(verbName string, o *os.File) {
fmt.Fprintf(o, "Usage: mlr %s [options] {zero or more file names}\n", verbName)
fmt.Fprintf(o, "Simple hex-dump.\n")
fmt.Fprintf(o, "If zero file names are supplied, standard input is read.\n")
fmt.Fprintf(o, "Options:\n")
fmt.Fprintf(o, "-r: print only raw hex without leading offset indicators or trailing ASCII dump.\n")
fmt.Fprintf(o, "-h or --help: print this message\n")
os.Exit(exitCode)
}
func hexMain(args []string) int {
@ -45,12 +44,16 @@ func hexMain(args []string) int {
doRaw = true
args = args[1:]
case "-h", "--help":
hexUsage(verb, os.Stdout, 0)
hexUsage(verb, os.Stdout)
return 0
}
}
if len(args) == 0 {
hexDumpFile(os.Stdin, doRaw)
if err := hexDumpFile(os.Stdin, doRaw); err != nil {
fmt.Fprintf(os.Stderr, "mlr hex: %v\n", err)
return 1
}
} else {
for _, filename := range args {
// Print filename if there is more than one file, unless raw output
@ -61,14 +64,17 @@ func hexMain(args []string) int {
istream, err := os.Open(filename)
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr hex: %v\n", err)
os.Exit(1)
return 1
}
hexDumpFile(istream, doRaw)
err = hexDumpFile(istream, doRaw)
_ = istream.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr hex: %v\n", err)
return 1
}
if !doRaw && len(args) > 1 {
fmt.Println()
}
@ -78,7 +84,7 @@ func hexMain(args []string) int {
return 0
}
func hexDumpFile(istream *os.File, doRaw bool) {
func hexDumpFile(istream *os.File, doRaw bool) error {
const bytesPerClump = 4
const clumpsPerLine = 4
const bufferSize = bytesPerClump * clumpsPerLine
@ -97,8 +103,7 @@ func hexDumpFile(istream *os.File, doRaw bool) {
// exact multiple of our buffer size. We'll break the loop after
// hex-dumping this last, partial fragment.
if err != nil && err != io.ErrUnexpectedEOF {
fmt.Fprintf(os.Stderr, "mlr hex: %v\n", err)
os.Exit(1)
return err
}
// Print offset "pre" part
@ -148,4 +153,5 @@ func hexDumpFile(istream *os.File, doRaw bool) {
offset += numBytesRead
}
return nil
}

View file

@ -7,14 +7,13 @@ import (
"os"
)
func lecatUsage(verbName string, o *os.File, exitCode int) {
func lecatUsage(verbName string, o *os.File) {
fmt.Fprintf(o, "Usage: mlr %s [options] {zero or more file names}\n", verbName)
fmt.Fprintf(o, "Simply echoes input, but flags CR characters in red and LF characters in green.\n")
fmt.Fprintf(o, "If zero file names are supplied, standard input is read.\n")
fmt.Fprintf(o, "Options:\n")
fmt.Fprintf(o, "--mono: don't try to colorize the output\n")
fmt.Fprintf(o, "-h or --help: print this message\n")
os.Exit(exitCode)
}
func lecatMain(args []string) int {
@ -25,7 +24,8 @@ func lecatMain(args []string) int {
args = args[2:]
if len(args) >= 1 {
if args[0] == "-h" || args[0] == "--help" {
lecatUsage(verb, os.Stdout, 0)
lecatUsage(verb, os.Stdout)
return 0
}
if args[0][0] == '-' {
@ -36,37 +36,46 @@ func lecatMain(args []string) int {
fmt.Fprintf(os.Stderr, "mlr %s: unrecognized option \"%s\".\n",
verb, args[0],
)
os.Exit(1)
return 1
}
}
}
if len(args) == 0 {
lecatFile(os.Stdin, doColor)
if err := lecatFile(os.Stdin, doColor); err != nil {
fmt.Fprintf(os.Stderr, "mlr lecat: %v\n", err)
return 1
}
} else {
for _, filename := range args {
istream, err := os.Open(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr lecat: %v\n", err)
os.Exit(1)
return 1
}
lecatFile(istream, doColor)
err = lecatFile(istream, doColor)
_ = istream.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr lecat: %v\n", err)
return 1
}
}
}
return 0
}
func lecatFile(istream *os.File, doColor bool) {
func lecatFile(istream *os.File, doColor bool) error {
reader := bufio.NewReader(istream)
for {
c, err := reader.ReadByte()
if err == io.EOF {
break
}
if err != nil {
return err
}
switch c {
case '\r':
if doColor {
@ -88,4 +97,5 @@ func lecatFile(istream *os.File, doColor bool) {
fmt.Printf("%c", c)
}
}
return nil
}

View file

@ -8,7 +8,7 @@ import (
"strings"
)
func termcvtUsage(verbName string, o *os.File, exitCode int) {
func termcvtUsage(verbName string, o *os.File) {
fmt.Fprintf(o, "Usage: mlr %s [option] {zero or more file names}\n", verbName)
fmt.Fprintf(o, "Option (exactly one is required):\n")
fmt.Fprintf(o, "--cr2crlf\n")
@ -21,7 +21,6 @@ func termcvtUsage(verbName string, o *os.File, exitCode int) {
fmt.Fprintf(o, "-h or --help: print this message\n")
fmt.Fprintf(o, "Zero file names means read from standard input.\n")
fmt.Fprintf(o, "Output is always to standard output; files are not written in-place.\n")
os.Exit(exitCode)
}
func termcvtMain(args []string) int {
@ -33,7 +32,8 @@ func termcvtMain(args []string) int {
verb := args[1]
args = args[2:]
if len(args) < 1 {
termcvtUsage(verb, os.Stderr, 1)
termcvtUsage(verb, os.Stderr)
return 1
}
for len(args) >= 1 {
@ -45,7 +45,8 @@ func termcvtMain(args []string) int {
switch opt {
case "-h", "--help":
termcvtUsage(verb, os.Stdout, 0)
termcvtUsage(verb, os.Stdout)
return 0
case "-I":
doInPlace = true
case "--cr2crlf":
@ -67,12 +68,16 @@ func termcvtMain(args []string) int {
inputTerminator = "\r"
outputTerminator = "\n"
default:
termcvtUsage(verb, os.Stderr, 1)
termcvtUsage(verb, os.Stderr)
return 1
}
}
if len(args) == 0 {
termcvtFile(os.Stdin, os.Stdout, inputTerminator, outputTerminator)
if err := termcvtFile(os.Stdin, os.Stdout, inputTerminator, outputTerminator); err != nil {
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
return 1
}
} else if doInPlace {
for _, filename := range args {
@ -82,31 +87,31 @@ func termcvtMain(args []string) int {
istream, err := os.Open(filename)
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return 1
}
ostream, err := os.Open(tempname)
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return 1
}
termcvtFile(istream, ostream, inputTerminator, outputTerminator)
if err := termcvtFile(istream, ostream, inputTerminator, outputTerminator); err != nil {
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
return 1
}
_ = istream.Close()
if err := ostream.Close(); err != nil {
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return 1
}
err = os.Rename(tempname, filename)
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return 1
}
}
@ -115,20 +120,22 @@ func termcvtMain(args []string) int {
istream, err := os.Open(filename)
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return 1
}
termcvtFile(istream, os.Stdout, inputTerminator, outputTerminator)
err = termcvtFile(istream, os.Stdout, inputTerminator, outputTerminator)
_ = istream.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
return 1
}
}
}
return 0
}
func termcvtFile(istream *os.File, ostream *os.File, inputTerminator string, outputTerminator string) {
func termcvtFile(istream *os.File, ostream *os.File, inputTerminator string, outputTerminator string) error {
lineReader := bufio.NewReader(istream)
inputTerminatorBytes := []byte(inputTerminator[len(inputTerminator)-1:])[0] // bufio.Reader.ReadString takes char not string delimiter :(
@ -139,16 +146,14 @@ func termcvtFile(istream *os.File, ostream *os.File, inputTerminator string, out
}
if err != nil {
// TODO: "mlr"
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return err
}
// This is how to do a chomp:
line = strings.TrimRight(line, inputTerminator)
if _, err := ostream.Write([]byte(line + outputTerminator)); err != nil {
fmt.Fprintf(os.Stderr, "mlr termcvt: %v\n", err)
os.Exit(1)
return err
}
}
return nil
}

View file

@ -2,6 +2,7 @@ package auxents
import (
"bufio"
"errors"
"fmt"
"io"
"os"
@ -9,13 +10,12 @@ import (
"strings"
)
func unhexUsage(verbName string, o *os.File, exitCode int) {
func unhexUsage(verbName string, o *os.File) {
fmt.Fprintf(o, "Usage: mlr %s [option] {zero or more file names}\n", verbName)
fmt.Fprintf(o, "Options:\n")
fmt.Fprintf(o, "-h or --help: print this message\n")
fmt.Fprintf(o, "Zero file names means read from standard input.\n")
fmt.Fprintf(o, "Output is always to standard output; files are not written in-place.\n")
os.Exit(exitCode)
}
func unhexMain(args []string) int {
@ -25,28 +25,36 @@ func unhexMain(args []string) int {
if len(args) >= 1 {
if args[0] == "-h" || args[0] == "--help" {
unhexUsage(verb, os.Stdout, 0)
unhexUsage(verb, os.Stdout)
return 0
}
}
if len(args) == 0 {
unhexFile(os.Stdin, os.Stdout)
if err := unhexFile(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "mlr unhex: %v\n", err)
return 1
}
} else {
for _, filename := range args {
istream, err := os.Open(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr unhex: %v\n", err)
os.Exit(1)
return 1
}
unhexFile(istream, os.Stdout)
err = unhexFile(istream, os.Stdout)
_ = istream.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr unhex: %v\n", err)
return 1
}
}
}
return 0
}
func unhexFile(istream *os.File, ostream *os.File) {
func unhexFile(istream *os.File, ostream *os.File) error {
// Key insight is os.File implements io.Reader
lineReader := bufio.NewReader(istream)
@ -62,8 +70,7 @@ func unhexFile(istream *os.File, ostream *os.File) {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "mlr unhex: %v\n", err)
os.Exit(1)
return err
}
// This is how to do a chomp:
@ -75,19 +82,17 @@ func unhexFile(istream *os.File, ostream *os.File) {
if field != "" {
n, err := fmt.Sscanf(field, "%x", &scanValue)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr unhex: %v\n", err)
os.Exit(1)
return err
}
if n != 1 {
fmt.Fprintln(os.Stderr, "mlr unhex: internal coding error")
os.Exit(1)
return errors.New("internal coding error")
}
byteArray[0] = byte(scanValue)
if _, err := ostream.Write(byteArray); err != nil {
fmt.Fprintln(os.Stderr, "mlr unhex:", err)
os.Exit(1)
return err
}
}
}
}
return nil
}

View file

@ -396,9 +396,9 @@ func parseCommandLinePassTwo(
}
if terminalSequence != nil {
terminals.Dispatch(terminalSequence)
// They are expected to exit the process
panic("mlr: internal coding error: terminal did not exit the process")
// The terminal (help, repl, regtest, ...) runs here; its exit code
// rides an ExitRequest up to the entrypoint layer.
return nil, nil, &lib.ExitRequest{Code: terminals.Dispatch(terminalSequence)}
}
// Now process the verb-sequences from pass one, with options-struct set up

View file

@ -38,8 +38,10 @@ func Main() MainReturn {
// 'mlr repl' or 'mlr lecat' or any other non-miller-per-se toolery which
// is delivered (for convenience) within the mlr executable. If argv[1] is
// found then this function will not return.
auxents.Dispatch(os.Args)
// an auxent name, it's run here and its exit code is the process's.
if handled, exitCode := auxents.Dispatch(os.Args); handled {
os.Exit(exitCode)
}
if lib.IsTruthyEnvValue(os.Getenv("MLR_NO_SHELL")) {
lib.DisableShellOut()

View file

@ -11,7 +11,7 @@ import (
const defaultPath = "./test/cases"
func regTestUsage(verbName string, o *os.File, exitCode int) {
func regTestUsage(verbName string, o *os.File) {
fmt.Fprintf(o, "Usage: mlr %s [options] [one or more directories/files]\n", verbName)
fmt.Fprintf(o, "If no directories/files are specified, the directory %s is used by default.\n", defaultPath)
fmt.Fprintf(o, "Recursively walks the directory/ies looking for foo.cmd files having Miller command-lines,\n")
@ -30,7 +30,6 @@ func regTestUsage(verbName string, o *os.File, exitCode int) {
fmt.Fprintf(o, "-j Just show the Miller command-line, put/filter script if any, and output.\n")
fmt.Fprintf(o, "-s {n} After running tests, re-run first n failed .cmd files with verbosity level 3.\n")
fmt.Fprintf(o, "-S After running tests, re-run all failed .cmd files with verbosity level 3.\n")
os.Exit(exitCode)
}
// Here the args are the full Miller command line: "mlr regtest --foo bar".
@ -55,22 +54,26 @@ func RegTestMain(args []string) int {
switch arg {
case "-h", "--help":
regTestUsage(verbName, os.Stdout, 0)
regTestUsage(verbName, os.Stdout)
return 0
case "-m":
if argi >= argc {
regTestUsage(verbName, os.Stderr, 1)
regTestUsage(verbName, os.Stderr)
return 1
}
exeName = args[argi]
argi++
case "-s":
if argi >= argc {
regTestUsage(verbName, os.Stderr, 1)
regTestUsage(verbName, os.Stderr)
return 1
}
temp, err := strconv.Atoi(args[argi])
if err != nil {
regTestUsage(verbName, os.Stderr, 1)
regTestUsage(verbName, os.Stderr)
return 1
}
firstNFailsToShow = temp
argi++
@ -88,7 +91,8 @@ func RegTestMain(args []string) int {
plainMode = true
default:
regTestUsage(verbName, os.Stderr, 1)
regTestUsage(verbName, os.Stderr)
return 1
}
}
casePaths := args[argi:]
@ -104,7 +108,11 @@ func RegTestMain(args []string) int {
firstNFailsToShow,
)
ok := regtester.Execute(casePaths)
ok, err := regtester.Execute(casePaths)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr %s: %v\n", verbName, err)
return 1
}
if !ok {
return 1

View file

@ -146,7 +146,7 @@ func (rt *RegTester) resetCounts() {
func (rt *RegTester) Execute(
casePaths []string,
) bool {
) (bool, error) {
// Don't let the current user's settings affect expected results
for _, name := range envVarsToUnset {
_ = os.Unsetenv(name)
@ -175,7 +175,9 @@ func (rt *RegTester) Execute(
}
for _, path := range casePaths {
rt.executeSinglePath(path)
if _, err := rt.executeSinglePath(path); err != nil {
return false, err
}
}
if len(rt.failCaseNames) > 0 && rt.firstNFailsToShow > 0 {
@ -215,12 +217,12 @@ func (rt *RegTester) Execute(
if !rt.plainMode {
fmt.Printf("%s overall\n", colorizer.MaybeColorizePass("PASS", true))
}
return true
return true, nil
}
if !rt.plainMode {
fmt.Printf("%s overall\n", colorizer.MaybeColorizeFail("FAIL", true))
}
return false
return false, nil
}
// Recursively invoked routine to process either a single .cmd file, or a
@ -228,15 +230,18 @@ func (rt *RegTester) Execute(
func (rt *RegTester) executeSinglePath(
path string,
) bool {
) (bool, error) {
handle, err := os.Stat(path)
if err != nil {
fmt.Printf("%s: %v\n", path, err)
return false
return false, nil
}
mode := handle.Mode()
if mode.IsDir() {
passed, hasCaseSubdirectories := rt.executeSingleDirectory(path)
passed, hasCaseSubdirectories, err := rt.executeSingleDirectory(path)
if err != nil {
return false, err
}
if hasCaseSubdirectories {
if passed {
rt.directoryPassCount++
@ -245,7 +250,7 @@ func (rt *RegTester) executeSinglePath(
rt.failDirNames = append(rt.failDirNames, path)
}
}
return passed
return passed, nil
} else if mode.IsRegular() {
basename := filepath.Base(path)
if basename == CmdName {
@ -256,21 +261,24 @@ func (rt *RegTester) executeSinglePath(
rt.caseFailCount++
rt.failCaseNames = append(rt.failCaseNames, path)
}
return passed
return passed, nil
}
return true // No .cmd files directly inside
return true, nil // No .cmd files directly inside
}
fmt.Printf("%s: neither directory nor regular file.\n", path)
return false // fall-through
return false, nil // fall-through
}
func (rt *RegTester) executeSingleDirectory(
dirName string,
) (bool, bool) {
) (bool, bool, error) {
passed := true
// TODO: comment
fileNames, hasCaseSubdirectories := rt.hasCaseSubdirectories(dirName)
fileNames, hasCaseSubdirectories, err := rt.hasCaseSubdirectories(dirName)
if err != nil {
return false, false, err
}
if !rt.plainMode {
if hasCaseSubdirectories && rt.verbosityLevel >= 2 {
@ -281,7 +289,10 @@ func (rt *RegTester) executeSingleDirectory(
for _, name := range fileNames {
path := dirName + "/" + name
ok := rt.executeSinglePath(path)
ok, err := rt.executeSinglePath(path)
if err != nil {
return false, false, err
}
if !ok {
passed = false
}
@ -309,7 +320,7 @@ func (rt *RegTester) executeSingleDirectory(
}
}
return passed, hasCaseSubdirectories
return passed, hasCaseSubdirectories, nil
}
// Sees if a directory contains a single test case. If so, we don't want to
@ -322,28 +333,26 @@ func (rt *RegTester) executeSingleDirectory(
func (rt *RegTester) hasCaseSubdirectories(
dirName string,
) ([]string, bool) {
) ([]string, bool, error) {
f, err := os.Open(dirName)
if err != nil {
fmt.Printf("%s: %v\n", dirName, err)
os.Exit(1)
return nil, false, err
}
defer func() { _ = f.Close() }()
names, err := f.Readdirnames(-1)
if err != nil {
fmt.Printf("%s: %v\n", dirName, err)
os.Exit(1)
return nil, false, err
}
sort.Strings(names)
for _, name := range names {
path := dirName + string(filepath.Separator) + name
if rt.isCaseDirectory(path) {
return names, true
return names, true, nil
}
}
return names, false
return names, false, nil
}
func (rt *RegTester) isCaseDirectory(

View file

@ -30,7 +30,7 @@ import (
"github.com/johnkerl/miller/v6/pkg/lib"
)
func replUsage(verbName string, o *os.File, exitCode int) {
func replUsage(verbName string, o *os.File) {
exeName := path.Base(os.Args[0])
fmt.Fprintf(o, "Usage: %s %s [options] {zero or more data-file names}\n", exeName, verbName)
@ -72,8 +72,6 @@ Or any --icsv, --ojson, etc. reader/writer options as for the main Miller comman
Any data-file names are opened just as if you had waited and typed :open {filenames}
at the Miller REPL prompt.
`)
os.Exit(exitCode)
}
// Here the args are the full Miller command line: if the latter was "mlr
@ -97,7 +95,8 @@ func ReplMain(args []string) int {
}
if args[argi] == "-h" || args[argi] == "--help" {
replUsage(replName, os.Stdout, 0)
replUsage(replName, os.Stdout)
return 0
} else if args[argi] == "-q" {
showStartupBanner = false
@ -124,14 +123,16 @@ func ReplMain(args []string) int {
} else if args[argi] == "--load" {
if argc-argi < 2 {
replUsage(replName, os.Stderr, 1)
replUsage(replName, os.Stderr)
return 1
}
options.DSLPreloadFileNames = append(options.DSLPreloadFileNames, args[argi+1])
argi += 2
} else if args[argi] == "--mload" {
if argc-argi < 2 {
replUsage(replName, os.Stderr, 1)
replUsage(replName, os.Stderr)
return 1
}
argi += 1
for argi < argc && args[argi] != "--" {
@ -153,7 +154,8 @@ func ReplMain(args []string) int {
} else if handled {
} else {
replUsage(replName, os.Stderr, 1)
replUsage(replName, os.Stderr)
return 1
}
}
@ -188,7 +190,7 @@ func ReplMain(args []string) int {
)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
os.Exit(1)
return 1
}
filenames := args[argi:]
@ -199,18 +201,18 @@ func ReplMain(args []string) int {
err = repl.handleSession(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr %s: %v\n", repl.replName, err)
os.Exit(1)
return 1
}
err = repl.bufferedRecordOutputStream.Flush()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr %s: %v\n", repl.replName, err)
os.Exit(1)
return 1
}
err = repl.closeBufferedOutputStream()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr %s: %v\n", repl.replName, err)
os.Exit(1)
return 1
}
return 0
}

View file

@ -20,7 +20,7 @@ import (
"github.com/johnkerl/miller/v6/pkg/lib"
)
func scriptUsage(verbName string, o *os.File, exitCode int) {
func scriptUsage(verbName string, o *os.File) {
exeName := path.Base(os.Args[0])
fmt.Fprintf(o, "Usage: %s %s [options] -f {script file} | -e {expression} [zero or more data-file names]\n", exeName, verbName)
fmt.Fprint(o,
@ -39,7 +39,6 @@ Or any --icsv, --ojson, etc. reader/writer options.
Data-file names (or stdin if none) are the record input stream.
`)
os.Exit(exitCode)
}
func ScriptMain(args []string) int {
@ -59,7 +58,8 @@ func ScriptMain(args []string) int {
}
if args[argi] == "-h" || args[argi] == "--help" {
scriptUsage(scriptName, os.Stdout, 0)
scriptUsage(scriptName, os.Stdout)
return 0
} else if args[argi] == "-w" {
doWarnings = true
argi++
@ -68,42 +68,46 @@ func ScriptMain(args []string) int {
argi++
} else if args[argi] == "-f" {
if argc-argi < 2 {
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
argi++
filename, err := cli.VerbGetStringArg("script", "-f", args, &argi, argc)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
os.Exit(1)
return 1
}
theseStrings, err := lib.LoadStringsFromFileOrDir(filename, ".mlr")
if err != nil {
fmt.Fprintf(os.Stderr, "mlr script: cannot load script from \"%s\": %v\n", filename, err)
os.Exit(1)
return 1
}
dslStrings = append(dslStrings, theseStrings...)
haveDSLStrings = true
} else if args[argi] == "-e" {
if argc-argi < 2 {
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
argi++
expr, err := cli.VerbGetStringArg("script", "-e", args, &argi, argc)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
os.Exit(1)
return 1
}
dslStrings = append(dslStrings, expr)
haveDSLStrings = true
} else if args[argi] == "--load" {
if argc-argi < 2 {
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
options.DSLPreloadFileNames = append(options.DSLPreloadFileNames, args[argi+1])
argi += 2
} else if args[argi] == "--mload" {
if argc-argi < 2 {
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
argi++
for argi < argc && args[argi] != "--" {
@ -122,13 +126,15 @@ func ScriptMain(args []string) int {
return 1
} else if handled {
} else {
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
}
if !haveDSLStrings {
fmt.Fprintf(os.Stderr, "mlr script: -f or -e is required\n")
scriptUsage(scriptName, os.Stderr, 1)
scriptUsage(scriptName, os.Stderr)
return 1
}
if err := cli.FinalizeReaderOptions(&options.ReaderOptions); err != nil {
@ -147,25 +153,25 @@ func ScriptMain(args []string) int {
scr, err := NewScript(options, doWarnings, strictMode, dslStrings)
if err != nil {
fmt.Fprintf(os.Stderr, "mlr script: %v\n", err)
os.Exit(1)
return 1
}
scr.openFiles(filenames)
err = scr.run()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr script: %v\n", err)
os.Exit(1)
return 1
}
err = scr.bufferedRecordOutputStream.Flush()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr script: %v\n", err)
os.Exit(1)
return 1
}
err = scr.closeBufferedOutputStream()
if err != nil {
fmt.Fprintf(os.Stderr, "mlr script: %v\n", err)
os.Exit(1)
return 1
}
return 0
}

View file

@ -57,19 +57,24 @@ func Dispatchable(arg string) bool {
return false
}
func Dispatch(args []string) {
// Dispatch runs the terminal named by args[0] and returns the process exit
// code for the caller to propagate (via lib.ExitRequest) up to the entrypoint
// layer.
func Dispatch(args []string) int {
if len(args) < 1 {
return
// Can't happen: the climain caller passes a non-empty terminal sequence.
fmt.Fprintf(os.Stderr, "mlr: internal coding error: empty terminal sequence.\n")
return 1
}
terminal := args[0]
for _, entry := range _TERMINAL_LOOKUP_TABLE {
if terminal == entry.name {
os.Exit(entry.main(args))
return entry.main(args)
}
}
fmt.Fprintf(os.Stderr, "mlr: terminal \"%s\" not found.\n", terminal)
os.Exit(1)
return 1
}
// terminalListMain is the handler for 'mlr terminal-list'.

View file

@ -65,7 +65,10 @@ func TestRegression(t *testing.T) {
firstNFailsToShow,
)
ok := regtester.Execute(casePaths)
ok, err := regtester.Execute(casePaths)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal()
}