diff --git a/Makefile b/Makefile index b28168b339..b5d7a67605 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ bench-input:build # See ./regression_test.go for information on how to get more details # for debugging. TL;DR is for CI jobs, we have 'go test -v'; for # interactive use, instead of 'go test -v' simply use 'mlr regtest -# -vvv' or 'mlr regtest -s 20'. See also internal/pkg/auxents/regtest. +# -vvv' or 'mlr regtest -s 20'. See also internal/pkg/terminals/regtest. regression-test: build go test -v regression_test.go diff --git a/README-dev.md b/README-dev.md index 4ae06c81fd..715dee06ee 100644 --- a/README-dev.md +++ b/README-dev.md @@ -98,7 +98,7 @@ So, in broad overview, the key packages are: * This package defines the grammar for Miller's domain-specific language (DSL) for the Miller `put` and `filter` verbs. And, GOCC is a joy to use. :) * It is used on the terms of its open-source license. * [golang.org/x/term](https://pkg.go.dev/golang.org/x/term): - * Just a one-line Miller callsite for is-a-terminal checking for the [Miller REPL](./internal/pkg/auxents/repl/README.md). + * Just a one-line Miller callsite for is-a-terminal checking for the [Miller REPL](./internal/pkg/terminals/repl/README.md). * It is used on the terms of its open-source license. * See also [./go.mod](go.mod). Setup: * `go get github.com/goccmack/gocc` diff --git a/docs/src/glossary.md.in b/docs/src/glossary.md.in index eb9580caf9..7e03b7d11b 100644 --- a/docs/src/glossary.md.in +++ b/docs/src/glossary.md.in @@ -753,6 +753,13 @@ can split it into several files, one for each distinct `id`. See the [section on tee statements](reference-dsl-output-statements.md#tee-statements) for an example. +## terminals + +These include `mlr help`, `mlr regtest`, `mlr repl`, and `mlr version`. They +aren't verbs but they can be preceded by various command-line flags. They're in +contrast to [auxents](#auxents) which are effectively standalone programs +packaged with Miller. + ## terminator Used in two senses: diff --git a/internal/pkg/auxents/auxents.go b/internal/pkg/auxents/auxents.go index b9cc3f64fb..ec4e1299ae 100644 --- a/internal/pkg/auxents/auxents.go +++ b/internal/pkg/auxents/auxents.go @@ -8,12 +8,6 @@ package auxents import ( "fmt" "os" - "runtime" - - "github.com/johnkerl/miller/internal/pkg/auxents/help" - "github.com/johnkerl/miller/internal/pkg/auxents/regtest" - "github.com/johnkerl/miller/internal/pkg/auxents/repl" - "github.com/johnkerl/miller/internal/pkg/version" ) // tAuxMain is a function-pointer type for the entrypoint handler for a given auxent, @@ -37,10 +31,6 @@ func init() { {"lecat", lecatMain}, {"termcvt", termcvtMain}, {"unhex", unhexMain}, - {"help", help.HelpMain}, - {"regtest", regtest.RegTestMain}, - {"repl", repl.ReplMain}, - {"version", showVersion}, } } @@ -70,15 +60,10 @@ func auxListMain(args []string) int { // ShowAuxEntries is a symbol is exported for 'mlr --help'. func ShowAuxEntries(o *os.File) { - fmt.Fprintf(o, "Available subcommands:\n") + fmt.Fprintf(o, "Available entries:\n") for _, entry := range _AUX_LOOKUP_TABLE { - fmt.Fprintf(o, " %s\n", entry.name) + fmt.Fprintf(o, " mlr %s\n", entry.name) } fmt.Fprintf(o, "For more information, please invoke mlr {subcommand} --help.\n") } - -func showVersion(args []string) int { - fmt.Printf("mlr version %s for %s/%s/%s\n", version.STRING, runtime.GOOS, runtime.GOARCH, runtime.Version()) - return 0 -} diff --git a/internal/pkg/auxents/doc.go b/internal/pkg/auxents/doc.go index e61a9caac3..75f5d05f50 100644 --- a/internal/pkg/auxents/doc.go +++ b/internal/pkg/auxents/doc.go @@ -1,5 +1,3 @@ // Package auxents implements little helper-tools embedded within Miller, such -// as mlr hex and mlr termcvt. It also implements mlr help (on-line help), mlr -// regtest (for regressio-testing), and mlr repl (Miller's read-evaluate-print -// loop). +// as `mlr hex` and `mlr termcvt`. package auxents diff --git a/internal/pkg/cli/flag_types.go b/internal/pkg/cli/flag_types.go index e4b1cf99a2..e8ffabe0ce 100644 --- a/internal/pkg/cli/flag_types.go +++ b/internal/pkg/cli/flag_types.go @@ -316,8 +316,8 @@ func (ft *FlagTable) GetDowndashSectionNames() []string { // since in Go you needn't specify all struct initializers, so for example a // Flag struct-initializer which doesn't say `help: "..."` will have empty help // string. This nil-checking doesn't need to be done on every Miller -// invocation, but rather, only at build time. The `mlr help` auxent has an -// entry point wherein a regression-test case can do `mlr help nil-check` and +// invocation, but rather, only at build time. The `mlr help` terminal has an +// entrypoint wherein a regression-test case can do `mlr help nil-check` and // make this function exits cleanly. func (ft *FlagTable) NilCheck() { lib.InternalCodingErrorWithMessageIf(ft.sections == nil, "Nil table sections") diff --git a/internal/pkg/climain/mlrcli_parse.go b/internal/pkg/climain/mlrcli_parse.go index a05c2315bb..31e72408f3 100644 --- a/internal/pkg/climain/mlrcli_parse.go +++ b/internal/pkg/climain/mlrcli_parse.go @@ -74,10 +74,11 @@ import ( "fmt" "os" - "github.com/johnkerl/miller/internal/pkg/auxents/help" "github.com/johnkerl/miller/internal/pkg/cli" "github.com/johnkerl/miller/internal/pkg/lib" "github.com/johnkerl/miller/internal/pkg/mlrval" + "github.com/johnkerl/miller/internal/pkg/terminals" + "github.com/johnkerl/miller/internal/pkg/terminals/help" "github.com/johnkerl/miller/internal/pkg/transformers" "github.com/johnkerl/miller/internal/pkg/version" ) @@ -99,10 +100,10 @@ func ParseCommandLine( } // Pass one as described at the top of this file. - flagSequences, verbSequences, dataFileNames := parseCommandLinePassOne(args) + flagSequences, terminalSequence, verbSequences, dataFileNames := parseCommandLinePassOne(args) // Pass two as described at the top of this file. - return parseCommandLinePassTwo(flagSequences, verbSequences, dataFileNames) + return parseCommandLinePassTwo(flagSequences, terminalSequence, verbSequences, dataFileNames) } // parseCommandLinePassOne is as described at the top of this file. @@ -110,10 +111,12 @@ func parseCommandLinePassOne( args []string, ) ( flagSequences [][]string, + terminalSequence []string, verbSequences [][]string, dataFileNames []string, ) { flagSequences = make([][]string, 0) + terminalSequence = nil verbSequences = make([][]string, 0) dataFileNames = make([]string, 0) @@ -143,7 +146,7 @@ func parseCommandLinePassOne( os.Exit(0) } else if help.ParseTerminalUsage(args[argi]) { // Exiting flag: handle it immediately. - // Most help is in the 'mlr help' auxent but there are a few + // Most help is in the 'mlr help' terminal but there are a few // shorthands like 'mlr -h' and 'mlr -F'. os.Exit(0) @@ -165,6 +168,12 @@ func parseCommandLinePassOne( os.Exit(1) } + } else if onFirst && terminals.Dispatchable(args[argi]) { + // mlr help, mlr regtest, etc -- _everything_ on the command line after this + // will be handled by that terminal + terminalSequence = args[argi:] + break + } else if onFirst || args[argi] == "then" || args[argi] == "+" { // The first verb in the then-chain can *optionally* be preceded by // 'then'. The others one *must* be. @@ -212,22 +221,26 @@ func parseCommandLinePassOne( } } - for ; argi < argc; argi++ { - dataFileNames = append(dataFileNames, args[argi]) - } + if terminalSequence == nil { - if len(verbSequences) == 0 { - fmt.Fprintf(os.Stderr, "%s: no verb supplied.\n", "mlr") - help.MainUsage(os.Stderr) - os.Exit(1) + for ; argi < argc; argi++ { + dataFileNames = append(dataFileNames, args[argi]) + } + + if len(verbSequences) == 0 { + fmt.Fprintf(os.Stderr, "%s: no verb supplied.\n", "mlr") + help.MainUsage(os.Stderr) + os.Exit(1) + } } - return flagSequences, verbSequences, dataFileNames + return flagSequences, terminalSequence, verbSequences, dataFileNames } // parseCommandLinePassTwo is as described at the top of this file. func parseCommandLinePassTwo( flagSequences [][]string, + terminalSequence []string, verbSequences [][]string, dataFileNames []string, ) ( @@ -300,6 +313,12 @@ 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") + } + // Now process the verb-sequences from pass one, with options-struct set up // and finalized. for i, verbSequence := range verbSequences { diff --git a/internal/pkg/lib/getoptify.go b/internal/pkg/lib/getoptify.go index aa2a576e63..1979742af2 100644 --- a/internal/pkg/lib/getoptify.go +++ b/internal/pkg/lib/getoptify.go @@ -9,9 +9,9 @@ import ( // is a keystroke-saver for the user. // // This is OK to do here globally since Miller is quite consistent (in main, -// verbs, and auxents) that multi-character options start with two dashes, e.g. -// "--csv". (The sole exception is the sort verb's -nf/-nr which are handled -// specially there.) +// verbs, auxents, and terminals) that multi-character options start with two +// dashes, e.g. "--csv". (The sole exception is the sort verb's -nf/-nr which +// are handled specially there.) // // Additionally, we split "--foo=bar" into "--foo" and "bar". func Getoptify(inargs []string) []string { diff --git a/internal/pkg/terminals/doc.go b/internal/pkg/terminals/doc.go new file mode 100644 index 0000000000..7c54fb6a66 --- /dev/null +++ b/internal/pkg/terminals/doc.go @@ -0,0 +1,3 @@ +// Package terminals implements `mlr help` (on-line help), `mlr regtest` (for regressio-testing), +// `mlr repl` (Miller's read-evaluate-print loop), and `mlr version`. +package terminals diff --git a/internal/pkg/auxents/help/doc.go b/internal/pkg/terminals/help/doc.go similarity index 100% rename from internal/pkg/auxents/help/doc.go rename to internal/pkg/terminals/help/doc.go diff --git a/internal/pkg/auxents/help/entry.go b/internal/pkg/terminals/help/entry.go similarity index 95% rename from internal/pkg/auxents/help/entry.go rename to internal/pkg/terminals/help/entry.go index 61284d1a0b..5fe7c9a9f7 100644 --- a/internal/pkg/auxents/help/entry.go +++ b/internal/pkg/terminals/help/entry.go @@ -10,6 +10,7 @@ import ( "github.com/mattn/go-isatty" + "github.com/johnkerl/miller/internal/pkg/auxents" "github.com/johnkerl/miller/internal/pkg/bifs" "github.com/johnkerl/miller/internal/pkg/cli" "github.com/johnkerl/miller/internal/pkg/dsl/cst" @@ -56,7 +57,7 @@ var handlerLookupTable = tHandlerLookupTable{} var shorthandLookupTable = tShorthandTable{} func init() { - // For things like 'mlr help foo', invoked through the auxent framework + // For things like 'mlr help foo', invoked through the terminals framework // which goes through our HelpMain(). handlerLookupTable = tHandlerLookupTable{ sections: []tHandlerInfoSection{ @@ -71,7 +72,8 @@ func init() { { name: "Flags", handlerInfos: []tHandlerInfo{ - {name: "flags", zaryHandlerFunc: showFlagHelp}, + {name: "flags", zaryHandlerFunc: showFlagsHelp}, + {name: "flag", varArgHandlerFunc: helpForFlag}, {name: "list-separator-aliases", zaryHandlerFunc: listSeparatorAliases}, {name: "list-separator-regex-aliases", zaryHandlerFunc: listSeparatorRegexAliases}, // Per-section entries will be computed and installed below @@ -108,6 +110,7 @@ func init() { name: "Other", handlerInfos: []tHandlerInfo{ {name: "auxents", zaryHandlerFunc: helpAuxents}, + {name: "terminals", zaryHandlerFunc: helpTerminals}, {name: "mlrrc", zaryHandlerFunc: helpMlrrc}, {name: "output-colorization", zaryHandlerFunc: helpOutputColorization}, {name: "type-arithmetic-info", zaryHandlerFunc: helpTypeArithmeticInfo}, @@ -183,11 +186,12 @@ func init() { } // ================================================================ -// For things like 'mlr help foo', invoked through the auxent framework which -// goes through our HelpMain(). Here, the args are the full Miller command -// line: "mlr help foo bar". +// For things like 'mlr help foo', invoked through the terminals framework which +// goes through our HelpMain(). Here, the args are the terminal part of the full +// Miller command line: if the latter was "mlr --some-flag help foo bar" then +// the former is "help foo bar". func HelpMain(args []string) int { - args = args[2:] + args = args[1:] // "mlr help" and nothing else if len(args) == 0 { @@ -308,10 +312,22 @@ func listTopics() { } // ---------------------------------------------------------------- -func showFlagHelp() { +func showFlagsHelp() { cli.FLAG_TABLE.ShowHelp() } +func helpForFlag(args []string) { + for i, arg := range args { + if i > 0 { + fmt.Println() + } + fmt.Printf("%s:\n", arg) + if !cli.FLAG_TABLE.ShowHelpForFlag(arg) { + fmt.Println("Not found.") + } + } +} + func listSeparatorAliases() { cli.ListSeparatorAliasesForOnlineHelp() } @@ -320,14 +336,17 @@ func listSeparatorRegexAliases() { cli.ListSeparatorRegexAliasesForOnlineHelp() } -// ---------------------------------------------------------------- func helpAuxents() { fmt.Print(`Miller has a few otherwise-standalone executables packaged within it. They do not participate in any other parts of Miller. -Please "mlr aux-list" for more information. `) - // imports github.com/johnkerl/miller/internal/pkg/auxents: import cycle not allowed - // auxents.ShowAuxEntries(o) + fmt.Println() + auxents.ShowAuxEntries(os.Stdout) +} + +func helpTerminals() { + fmt.Println("Terminals include mlr help, the regression-test entry point mlr regtest, and the REPL mlr repl.") + // We can't invoke the terminal-lister since that would create a cyclic package reference. } // ---------------------------------------------------------------- @@ -498,7 +517,7 @@ func helpTypeArithmeticInfo() { // ---------------------------------------------------------------- // listFlagSections et al. are for webdoc/manpage autogen in the miller/docs -// and miller/man subdirectories. Unlike showFlagHelp where all looping over +// and miller/man subdirectories. Unlike showFlagsHelp where all looping over // the flags table, its sections, and flags within each section is done within // this Go program, by contrast the following few methods expose the hierarchy // to standard output, letting the calling programs (nominally Ruby autogen diff --git a/internal/pkg/auxents/regtest/README.md b/internal/pkg/terminals/regtest/README.md similarity index 100% rename from internal/pkg/auxents/regtest/README.md rename to internal/pkg/terminals/regtest/README.md diff --git a/internal/pkg/auxents/regtest/doc.go b/internal/pkg/terminals/regtest/doc.go similarity index 100% rename from internal/pkg/auxents/regtest/doc.go rename to internal/pkg/terminals/regtest/doc.go diff --git a/internal/pkg/auxents/regtest/entry.go b/internal/pkg/terminals/regtest/entry.go similarity index 94% rename from internal/pkg/auxents/regtest/entry.go rename to internal/pkg/terminals/regtest/entry.go index ef07a51a78..b59c443c93 100644 --- a/internal/pkg/auxents/regtest/entry.go +++ b/internal/pkg/terminals/regtest/entry.go @@ -7,7 +7,6 @@ package regtest import ( "fmt" "os" - "path" "strconv" "strings" ) @@ -16,8 +15,7 @@ const defaultPath = "./test/cases" // ================================================================ func regTestUsage(verbName string, o *os.File, exitCode int) { - exeName := path.Base(os.Args[0]) - fmt.Fprintf(o, "Usage: %s %s [options] [one or more directories/files]\n", exeName, verbName) + 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") fmt.Fprintf(o, "with foo.expout and foo.experr files having expected stdout and stderr, respectively.\n") @@ -41,10 +39,10 @@ func regTestUsage(verbName string, o *os.File, exitCode int) { // Here the args are the full Miller command line: "mlr regtest --foo bar". func RegTestMain(args []string) int { - exeName := args[0] - verbName := args[1] + exeName := os.Args[0] + verbName := args[0] argc := len(args) - argi := 2 + argi := 1 verbosityLevel := 0 doPopulate := false plainMode := false diff --git a/internal/pkg/auxents/regtest/invoker.go b/internal/pkg/terminals/regtest/invoker.go similarity index 100% rename from internal/pkg/auxents/regtest/invoker.go rename to internal/pkg/terminals/regtest/invoker.go diff --git a/internal/pkg/auxents/regtest/regtester.go b/internal/pkg/terminals/regtest/regtester.go similarity index 100% rename from internal/pkg/auxents/regtest/regtester.go rename to internal/pkg/terminals/regtest/regtester.go diff --git a/internal/pkg/auxents/repl/README.md b/internal/pkg/terminals/repl/README.md similarity index 100% rename from internal/pkg/auxents/repl/README.md rename to internal/pkg/terminals/repl/README.md diff --git a/internal/pkg/auxents/repl/doc.go b/internal/pkg/terminals/repl/doc.go similarity index 100% rename from internal/pkg/auxents/repl/doc.go rename to internal/pkg/terminals/repl/doc.go diff --git a/internal/pkg/auxents/repl/dsl.go b/internal/pkg/terminals/repl/dsl.go similarity index 100% rename from internal/pkg/auxents/repl/dsl.go rename to internal/pkg/terminals/repl/dsl.go diff --git a/internal/pkg/auxents/repl/entry.go b/internal/pkg/terminals/repl/entry.go similarity index 94% rename from internal/pkg/auxents/repl/entry.go rename to internal/pkg/terminals/repl/entry.go index 8cab4a99a2..28f873fd71 100644 --- a/internal/pkg/auxents/repl/entry.go +++ b/internal/pkg/terminals/repl/entry.go @@ -77,12 +77,13 @@ at the Miller REPL prompt. os.Exit(exitCode) } -// Here the args are the full Miller command line: "mlr repl foo bar". +// Here the args are the full Miller command line: if the latter was "mlr +// --some-flag repl foo bar" then the former is "repl foo bar". func ReplMain(args []string) int { - exeName := path.Base(args[0]) - replName := args[1] + exeName := os.Args[0] + replName := args[0] argc := len(args) - argi := 2 + argi := 1 showStartupBanner := true showPrompts := true @@ -184,14 +185,14 @@ func ReplMain(args []string) int { err = repl.handleSession(os.Stdin) if err != nil { - fmt.Fprintf(os.Stderr, "%s %s: %v", repl.exeName, repl.replName, err) + fmt.Fprintf(os.Stderr, "mlr %s: %v", repl.replName, err) os.Exit(1) } repl.bufferedRecordOutputStream.Flush() err = repl.closeBufferedOutputStream() if err != nil { - fmt.Fprintf(os.Stderr, "%s %s: %v", repl.exeName, repl.replName, err) + fmt.Fprintf(os.Stderr, "mlr %s: %v", repl.replName, err) os.Exit(1) } return 0 diff --git a/internal/pkg/auxents/repl/prompt.go b/internal/pkg/terminals/repl/prompt.go similarity index 100% rename from internal/pkg/auxents/repl/prompt.go rename to internal/pkg/terminals/repl/prompt.go diff --git a/internal/pkg/auxents/repl/session.go b/internal/pkg/terminals/repl/session.go similarity index 100% rename from internal/pkg/auxents/repl/session.go rename to internal/pkg/terminals/repl/session.go diff --git a/internal/pkg/auxents/repl/types.go b/internal/pkg/terminals/repl/types.go similarity index 100% rename from internal/pkg/auxents/repl/types.go rename to internal/pkg/terminals/repl/types.go diff --git a/internal/pkg/auxents/repl/verbs.go b/internal/pkg/terminals/repl/verbs.go similarity index 97% rename from internal/pkg/auxents/repl/verbs.go rename to internal/pkg/terminals/repl/verbs.go index a02aeb2c62..a47e93d53c 100644 --- a/internal/pkg/auxents/repl/verbs.go +++ b/internal/pkg/terminals/repl/verbs.go @@ -181,8 +181,8 @@ func handleLoad(repl *Repl, args []string) bool { // ---------------------------------------------------------------- func usageOpen(repl *Repl) { fmt.Printf( - ":open {one or more data-file names in the format specified by %s %s}.\n", - repl.exeName, repl.replName, + ":open {one or more data-file names in the format specified by mlr %s}.\n", + repl.replName, ) fmt.Print( `Then you can type :read to load the next record. Then any interactive @@ -216,14 +216,14 @@ func openFilesPreCheck(repl *Repl, args []string) bool { for _, arg := range args { fileInfo, err := os.Stat(arg) if err != nil { - fmt.Printf("%s %s: could not open \"%s\"\n", - repl.exeName, repl.replName, arg, + fmt.Printf("mlr %s: could not open \"%s\"\n", + repl.replName, arg, ) return false } if fileInfo.IsDir() { - fmt.Printf("%s %s: \"%s\" is a directory.\n", - repl.exeName, repl.replName, arg, + fmt.Printf("mlr %s: \"%s\" is a directory.\n", + repl.replName, arg, ) return false } @@ -271,8 +271,8 @@ func handleReopen(repl *Repl, args []string) bool { func usageRead(repl *Repl) { fmt.Println(":read with no arguments.") fmt.Printf( - "Reads in the next record from file(s) listed by :open, or by %s %s.\n", - repl.exeName, repl.replName, + "Reads in the next record from file(s) listed by :open, or by mlr %s.\n", + repl.replName, ) fmt.Println("Then you can operate on it with interactive statements, or :main, and you can") fmt.Println("write it out using :write.") @@ -340,8 +340,8 @@ func handleContext(repl *Repl, args []string) bool { func usageSkip(repl *Repl) { fmt.Println(":skip {n} to read n records without invoking :main statements or printing the records.") fmt.Printf( - "Reads in the next record from file(s) listed by :open, or by %s %s.\n", - repl.exeName, repl.replName, + "Reads in the next record from file(s) listed by :open, or by mlr %s.\n", + repl.replName, ) fmt.Println("Then you can operate on it with interactive statements, or :main, and you can") fmt.Println("write it out using :write.") @@ -389,8 +389,8 @@ func handleSkip(repl *Repl, args []string) bool { func usageProcess(repl *Repl) { fmt.Println(":process {n} to read n records, invoking :main statements on them, and printing the records.") fmt.Printf( - "Reads in the next record from file(s) listed by :open, or by %s %s.\n", - repl.exeName, repl.replName, + "Reads in the next record from file(s) listed by :open, or by mlr %s.\n", + repl.replName, ) fmt.Println("Then you can operate on it with interactive statements, or :main, and you can") fmt.Println("write it out using :write.") @@ -615,8 +615,8 @@ func skipOrProcessRecord( func usageWrite(repl *Repl) { fmt.Println(":write with no arguments.") fmt.Println("Sends the current record (maybe modified by statements you enter)") - fmt.Printf("to standard output, with format as specified by %s %s.\n", - repl.exeName, repl.replName) + fmt.Printf("to standard output, with format as specified by mlr %s.\n", + repl.replName) } func handleWrite(repl *Repl, args []string) bool { if len(args) != 1 { @@ -683,8 +683,8 @@ func handleRedirectWrite(repl *Repl, args []string) bool { ) if err != nil { fmt.Printf( - "%s %s: couldn't open \"%s\" for write.\n", - repl.exeName, repl.replName, filename, + "mlr %s: couldn't open \"%s\" for write.\n", + repl.replName, filename, ) } fmt.Printf("Redirecting record output to \"%s\"\n", filename) @@ -714,8 +714,8 @@ func handleRedirectAppend(repl *Repl, args []string) bool { ) if err != nil { fmt.Printf( - "%s %s: couldn't open \"%s\" for write.\n", - repl.exeName, repl.replName, filename, + "mlr %s: couldn't open \"%s\" for write.\n", + repl.replName, filename, ) } fmt.Printf("Redirecting record output to \"%s\"\n", filename) @@ -1022,8 +1022,7 @@ to record-processing using the put/filter DSL (domain-specific language).`) * Define user-defined functions/subroutines using func and subr. * Specify statements to be executed on each record -- which are anything outside of begin/end/func/subr. * Example: - %s --icsv --ojson put 'begin {print "HELLO"} $z = $x + $y; end {print "GOODBYE"}`, - repl.exeName) + mlr --icsv --ojson put 'begin {print "HELLO"} $z = $x + $y; end {print "GOODBYE"}`) fmt.Println() fmt.Println() diff --git a/internal/pkg/terminals/terminals.go b/internal/pkg/terminals/terminals.go new file mode 100644 index 0000000000..91f0fecf44 --- /dev/null +++ b/internal/pkg/terminals/terminals.go @@ -0,0 +1,87 @@ +// ================================================================ +// Support for Miller regression testing. Originally bash scripts; ported to Go +// for ease of Windows-native testing. +// ================================================================ + +package terminals + +import ( + "fmt" + "os" + "runtime" + + "github.com/johnkerl/miller/internal/pkg/terminals/help" + "github.com/johnkerl/miller/internal/pkg/terminals/regtest" + "github.com/johnkerl/miller/internal/pkg/terminals/repl" + "github.com/johnkerl/miller/internal/pkg/version" +) + +// tTerminalMain is a function-pointer type for the entrypoint handler for a given terminal, +// such as 'help' or 'regtest'. +type tTerminalMain func(args []string) int + +type tTerminalLookupEntry struct { + name string + main tTerminalMain +} + +// _TERMINAL_LOOKUP_TABLE is the lookup table for terminals. We get a Golang +// "initialization loop" if this is defined statically. So, we use a "package +// init" function. +var _TERMINAL_LOOKUP_TABLE = []tTerminalLookupEntry{} + +func init() { + _TERMINAL_LOOKUP_TABLE = []tTerminalLookupEntry{ + {"terminal-list", terminalListMain}, + {"help", help.HelpMain}, + {"regtest", regtest.RegTestMain}, + {"repl", repl.ReplMain}, + {"version", showVersion}, + } +} + +func Dispatchable(arg string) bool { + for _, entry := range _TERMINAL_LOOKUP_TABLE { + if arg == entry.name { + return true + } + } + return false +} + +func Dispatch(args []string) { + if len(args) < 1 { + return + } + terminal := args[0] + + for _, entry := range _TERMINAL_LOOKUP_TABLE { + if terminal == entry.name { + os.Exit(entry.main(args)) + } + } + fmt.Fprintf(os.Stderr, "mlr: terminal \"%s\" not found.\n", terminal) + os.Exit(1) +} + +// terminalListMain is the handler for 'mlr terminal-list'. +func terminalListMain(args []string) int { + ShowTerminalEntries(os.Stdout) + return 0 +} + +// ShowTerminalEntries is a symbol is exported for 'mlr --help'. +func ShowTerminalEntries(o *os.File) { + fmt.Fprintf(o, "Available subcommands:\n") + for _, entry := range _TERMINAL_LOOKUP_TABLE { + fmt.Fprintf(o, " %s\n", entry.name) + } + + fmt.Fprintf(o, "For more information, please invoke mlr {subcommand} --help.\n") +} + +func showVersion(args []string) int { + fmt.Printf("mlr version %s for %s/%s/%s\n", + version.STRING, runtime.GOOS, runtime.GOARCH, runtime.Version()) + return 0 +} diff --git a/internal/pkg/transformers/sort.go b/internal/pkg/transformers/sort.go index 5f559ec698..429314ddd0 100644 --- a/internal/pkg/transformers/sort.go +++ b/internal/pkg/transformers/sort.go @@ -196,8 +196,8 @@ func transformerSortParseCLI( // expanded to "-x -y -z" while "--xyz" is left intact. This is OK // to do globally (before any verb such as this one sees the // command line) since Miller is quite consistent (in main, verbs, - // and auxents) that multi-character options start with two dashes, - // e.g. "--csv" ... + // auxents, and terminals) that multi-character options start with + // two dashes, e.g. "--csv" ... // // ... with the sole exception being -nf/-nr, right here. This goes // back to the very start of Miller, and we don't want to break the diff --git a/regression_test.go b/regression_test.go index 6d3af8adec..1e819f2f8d 100644 --- a/regression_test.go +++ b/regression_test.go @@ -5,13 +5,13 @@ import ( "os" "testing" - "github.com/johnkerl/miller/internal/pkg/auxents/regtest" + "github.com/johnkerl/miller/internal/pkg/terminals/regtest" ) // TestRegression is a familiar entry point for regression testing. Miller // regression tests are more flexibly invoked via 'mlr regtest'. However here // is a standard location so people can get at them via 'go test'. Please see -// (as of this writing) internal/pkg/auxents/regtest for the Miller regtest package. +// (as of this writing) internal/pkg/terminals/regtest for the Miller regtest package. func TestRegression(t *testing.T) { // How much detail to show? There are thousands of cases, organized into a // few hundred top-level directories under ./test/cases. diff --git a/test/cases/repl/0005/expout b/test/cases/repl/0005/expout index 17584a8128..c5e40f229c 100644 --- a/test/cases/repl/0005/expout +++ b/test/cases/repl/0005/expout @@ -4,50 +4,50 @@ FILENAME="test/input/medium.dkvp",FILENUM=1,NR=10,FNR=10 "a": "pan", "b": "wye", "i": 10, - "x": 0.5026260055412137, - "y": 0.9526183602969864 + "x": 0.50262601, + "y": 0.95261836 } -a=pan,b=pan,i=11,x=0.7930488423451967,y=0.6505816637259333 -a=zee,b=pan,i=12,x=0.3676141320555616,y=0.23614420670296965 -a=eks,b=pan,i=13,x=0.4915175580479536,y=0.7709126592971468 -a=eks,b=zee,i=14,x=0.5207382318405251,y=0.34141681118811673 -a=eks,b=pan,i=15,x=0.07155556372719507,y=0.3596137145616235 -a=pan,b=pan,i=16,x=0.5736853980681922,y=0.7554169353781729 -a=zee,b=eks,i=17,x=0.29081949506712723,y=0.054478717073354166 -a=hat,b=zee,i=18,x=0.05727869223575699,y=0.13343527626645157 -a=zee,b=pan,i=19,x=0.43144132839222604,y=0.8442204830496998 -a=eks,b=wye,i=20,x=0.38245149780530685,y=0.4730652428100751 +a=pan,b=pan,i=11,x=0.79304884,y=0.65058166 +a=zee,b=pan,i=12,x=0.36761413,y=0.23614421 +a=eks,b=pan,i=13,x=0.49151756,y=0.77091266 +a=eks,b=zee,i=14,x=0.52073823,y=0.34141681 +a=eks,b=pan,i=15,x=0.07155556,y=0.35961371 +a=pan,b=pan,i=16,x=0.57368540,y=0.75541694 +a=zee,b=eks,i=17,x=0.29081950,y=0.05447872 +a=hat,b=zee,i=18,x=0.05727869,y=0.13343528 +a=zee,b=pan,i=19,x=0.43144133,y=0.84422048 +a=eks,b=wye,i=20,x=0.38245150,y=0.47306524 FILENAME="test/input/medium.dkvp",FILENUM=1,NR=20,FNR=20 { "a": "eks", "b": "wye", "i": 20, - "x": 0.38245149780530685, - "y": 0.4730652428100751 + "x": 0.38245150, + "y": 0.47306524 } FILENAME="test/input/medium.dkvp",FILENUM=1,NR=30,FNR=30 { "a": "zee", "b": "pan", "i": 30, - "x": 0.7514525581952406, - "y": 0.5869901584461873 + "x": 0.75145256, + "y": 0.58699016 } -a=eks,b=pan,i=31,x=0.5701563454882129,y=0.822178550628332 -a=wye,b=wye,i=32,x=0.5112738997714514,y=0.33107563648490446 -a=hat,b=pan,i=33,x=0.09303747345405,y=0.18404048980291832 -a=hat,b=wye,i=34,x=0.49061881414751796,y=0.4721354893261027 -a=zee,b=zee,i=35,x=0.12565828887119346,y=0.1936186210637818 -a=pan,b=hat,i=36,x=0.9436794292675323,y=0.9111218711707734 -a=wye,b=pan,i=37,x=0.056537252420374995,y=0.5450112991603523 -a=pan,b=zee,i=38,x=0.508250895522605,y=0.12919877119684198 -a=hat,b=pan,i=39,x=0.4591095934684415,y=0.6526252088703994 -a=pan,b=hat,i=40,x=0.30302850095892986,y=0.2884018711352886 +a=eks,b=pan,i=31,x=0.57015635,y=0.82217855 +a=wye,b=wye,i=32,x=0.51127390,y=0.33107564 +a=hat,b=pan,i=33,x=0.09303747,y=0.18404049 +a=hat,b=wye,i=34,x=0.49061881,y=0.47213549 +a=zee,b=zee,i=35,x=0.12565829,y=0.19361862 +a=pan,b=hat,i=36,x=0.94367943,y=0.91112187 +a=wye,b=pan,i=37,x=0.05653725,y=0.54501130 +a=pan,b=zee,i=38,x=0.50825090,y=0.12919877 +a=hat,b=pan,i=39,x=0.45910959,y=0.65262521 +a=pan,b=hat,i=40,x=0.30302850,y=0.28840187 FILENAME="test/input/medium.dkvp",FILENUM=1,NR=40,FNR=40 { "a": "pan", "b": "hat", "i": 40, - "x": 0.30302850095892986, - "y": 0.2884018711352886 + "x": 0.30302850, + "y": 0.28840187 }