Skip to content

Commit

Permalink
Let mlr help take pre-flags, such as --always-color (#1292)
Browse files Browse the repository at this point in the history
* Let `mlr help` take pre-flags, such as `--always-color`

* Better on-line help for auxents and terminals

* Support `mlr help flag --foo`
  • Loading branch information
johnkerl authored May 15, 2023
1 parent d16310e commit 60b1f1b
Show file tree
Hide file tree
Showing 28 changed files with 231 additions and 115 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
7 changes: 7 additions & 0 deletions docs/src/glossary.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 2 additions & 17 deletions internal/pkg/auxents/auxents.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,10 +31,6 @@ func init() {
{"lecat", lecatMain},
{"termcvt", termcvtMain},
{"unhex", unhexMain},
{"help", help.HelpMain},
{"regtest", regtest.RegTestMain},
{"repl", repl.ReplMain},
{"version", showVersion},
}
}

Expand Down Expand Up @@ -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
}
4 changes: 1 addition & 3 deletions internal/pkg/auxents/doc.go
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions internal/pkg/cli/flag_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
43 changes: 31 additions & 12 deletions internal/pkg/climain/mlrcli_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -99,21 +100,23 @@ 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.
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)

Expand Down Expand Up @@ -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)

Expand All @@ -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.
Expand Down Expand Up @@ -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,
) (
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions internal/pkg/lib/getoptify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions internal/pkg/terminals/doc.go
Original file line number Diff line number Diff line change
@@ -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
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
}
Expand All @@ -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.
}

// ----------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package regtest
import (
"fmt"
"os"
"path"
"strconv"
"strings"
)
Expand All @@ -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")
Expand All @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 60b1f1b

Please sign in to comment.