mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
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:
parent
314b26cfaa
commit
2f3e54d1f6
13 changed files with 195 additions and 133 deletions
|
|
@ -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'.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue