mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-20 18:10:07 +00:00
* Default to "cat" verb when none is supplied (#2029) Invocations like 'mlr --j2y' or 'mlr --c2p' previously failed with "no verb supplied", forcing users to type the trailing 'cat' explicitly for pure format conversions. Default the verb to 'cat' in that case. Bare 'mlr' with no flags, no verb, and no files still prints the main usage banner. This handles flag-only invocations (e.g. 'mlr --c2j < input.csv' or 'mlr --c2j --from input.csv'). File names without a preceding verb are still parsed as verb candidates and continue to error if not found; that broader change is out of scope here. * Use ${MLR} substitution for bare-mlr regression case (#2029) The regtester only substitutes the mlr executable when the cmd starts with "mlr " (with a trailing space). The bare-mlr usage-banner test had a cmd of just "mlr", so on CI -- which invokes regtest with a relative path like 'test/../mlr' -- the test shelled out to a literal 'mlr' that isn't on PATH and failed with exit 127. Switch the cmd to ${MLR} (the regtester's explicit substitution token) so the case runs the right binary in any invocation context.
865 lines
25 KiB
Go
865 lines
25 KiB
Go
// Online help
|
|
|
|
package help
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
|
|
"github.com/johnkerl/miller/v6/pkg/auxents"
|
|
"github.com/johnkerl/miller/v6/pkg/bifs"
|
|
"github.com/johnkerl/miller/v6/pkg/cli"
|
|
"github.com/johnkerl/miller/v6/pkg/dsl/cst"
|
|
"github.com/johnkerl/miller/v6/pkg/lib"
|
|
"github.com/johnkerl/miller/v6/pkg/mlrval"
|
|
"github.com/johnkerl/miller/v6/pkg/runtime"
|
|
"github.com/johnkerl/miller/v6/pkg/transformers"
|
|
)
|
|
|
|
type tZaryHandlerFunc func()
|
|
type tVarArgHandlerFunc func(args []string)
|
|
|
|
type tHandlerLookupTable struct {
|
|
sections []tHandlerInfoSection
|
|
}
|
|
|
|
type tHandlerInfoSection struct {
|
|
name string
|
|
handlerInfos []tHandlerInfo
|
|
|
|
// Some handlers are used only for webdoc/manpage autogen and needn't
|
|
// clutter up the on-line help experience for the interactive user
|
|
internal bool
|
|
}
|
|
|
|
type tHandlerInfo struct {
|
|
name string
|
|
zaryHandlerFunc tZaryHandlerFunc
|
|
varArgHandlerFunc tVarArgHandlerFunc
|
|
}
|
|
|
|
type tShorthandTable struct {
|
|
shorthandInfos []tShorthandInfo
|
|
}
|
|
|
|
type tShorthandInfo struct {
|
|
shorthand string
|
|
longhand string
|
|
}
|
|
|
|
// We get a Golang "initialization loop" if this is defined statically. So, we
|
|
// use a "package init" function.
|
|
var handlerLookupTable = tHandlerLookupTable{}
|
|
var shorthandLookupTable = tShorthandTable{}
|
|
|
|
func init() {
|
|
// For things like 'mlr help foo', invoked through the terminals framework
|
|
// which goes through our HelpMain().
|
|
handlerLookupTable = tHandlerLookupTable{
|
|
sections: []tHandlerInfoSection{
|
|
{
|
|
name: "Essentials",
|
|
handlerInfos: []tHandlerInfo{
|
|
{name: "topics", zaryHandlerFunc: listTopics},
|
|
{name: "basic-examples", zaryHandlerFunc: helpBasicExamples},
|
|
{name: "file-formats", zaryHandlerFunc: helpFileFormats},
|
|
},
|
|
},
|
|
{
|
|
name: "Flags",
|
|
handlerInfos: []tHandlerInfo{
|
|
{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
|
|
},
|
|
},
|
|
{
|
|
name: "Verbs",
|
|
handlerInfos: []tHandlerInfo{
|
|
{name: "list-verbs", zaryHandlerFunc: listVerbs},
|
|
{name: "usage-verbs", zaryHandlerFunc: usageVerbs},
|
|
{name: "verb", varArgHandlerFunc: helpForVerb},
|
|
},
|
|
},
|
|
{
|
|
name: "Functions",
|
|
handlerInfos: []tHandlerInfo{
|
|
{name: "list-functions", zaryHandlerFunc: listFunctions},
|
|
{name: "list-function-classes", zaryHandlerFunc: listFunctionClasses},
|
|
{name: "list-functions-in-class", varArgHandlerFunc: listFunctionsInClass},
|
|
{name: "usage-functions", zaryHandlerFunc: usageFunctions},
|
|
{name: "usage-functions-by-class", zaryHandlerFunc: usageFunctionsByClass},
|
|
{name: "function", varArgHandlerFunc: helpForFunction},
|
|
},
|
|
},
|
|
{
|
|
name: "Keywords",
|
|
handlerInfos: []tHandlerInfo{
|
|
{name: "list-keywords", zaryHandlerFunc: listKeywords},
|
|
{name: "usage-keywords", zaryHandlerFunc: usageKeywords},
|
|
{name: "keyword", varArgHandlerFunc: helpForKeyword},
|
|
},
|
|
},
|
|
{
|
|
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},
|
|
{name: "type-arithmetic-info-extended", zaryHandlerFunc: helpTypeArithmeticInfoExtended},
|
|
},
|
|
},
|
|
{
|
|
name: "Internal/docgen",
|
|
internal: true,
|
|
handlerInfos: []tHandlerInfo{
|
|
{name: "flag-table-nil-check", zaryHandlerFunc: flagTableNilCheck},
|
|
{name: "list-flag-sections", zaryHandlerFunc: listFlagSections},
|
|
{name: "list-flags-for-section", varArgHandlerFunc: listFlagsForSection},
|
|
{name: "list-functions-as-paragraph", zaryHandlerFunc: listFunctionsAsParagraph},
|
|
{name: "list-functions-as-table", zaryHandlerFunc: listFunctionsAsTable},
|
|
{name: "list-keywords-as-paragraph", zaryHandlerFunc: listKeywordsAsParagraph},
|
|
{name: "list-verbs-as-paragraph", zaryHandlerFunc: listVerbsAsParagraph},
|
|
{name: "print-info-for-section", varArgHandlerFunc: printInfoForSection},
|
|
{name: "show-headline-for-flag", varArgHandlerFunc: showHeadlineForFlag},
|
|
{name: "show-help-for-flag", varArgHandlerFunc: showHelpForFlag},
|
|
{name: "show-help-for-section", varArgHandlerFunc: showHelpForSection},
|
|
{name: "show-help-for-section-via-downdash", varArgHandlerFunc: showHelpForSectionViaDowndash},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// This is a wee bit clever. The rest of the topics in the table have names
|
|
// manually keyed in. But we want to produce `mlr help csv-only-flags` for
|
|
// flag-section named "CSV-only flags", etc. Here we can't key in the names
|
|
// since we want to compute them dynamically from cli.FLAG_TABLE which is
|
|
// Miller's wqy of tracking command-line flags.
|
|
|
|
// For this file's topic-lookup table, find and extend the section called "Flags".
|
|
for i, section := range handlerLookupTable.sections {
|
|
if section.name != "Flags" {
|
|
continue
|
|
}
|
|
|
|
// Ask the flags table for a list of flag-section names, downcased and
|
|
// with spaces replaced with dashes -- "downdashed" -- making the
|
|
// punctuation/casing style for online help.
|
|
downdashSectionNames := cli.FLAG_TABLE.GetDowndashSectionNames()
|
|
// Note: `j, _` rather than `_, downdashSectionName` since the latter
|
|
// is a data copy while the former allows us to do a reference. The
|
|
// former won't produce correct lookup-table data.
|
|
for j := range downdashSectionNames {
|
|
downdashSectionName := downdashSectionNames[j]
|
|
// Patch a new entry into the "Flags" section of our lookup table.
|
|
entry := tHandlerInfo{
|
|
name: downdashSectionName,
|
|
// Make a function which passes in "csv-only-flags" etc. to the FLAG_TABLE.
|
|
zaryHandlerFunc: func() {
|
|
showHelpForSectionViaDowndash([]string{downdashSectionName})
|
|
},
|
|
}
|
|
handlerLookupTable.sections[i].handlerInfos = append(handlerLookupTable.sections[i].handlerInfos, entry)
|
|
}
|
|
}
|
|
|
|
// For things like 'mlr -f', invoked through the CLI parser which does not
|
|
// go through our HelpMain().
|
|
shorthandLookupTable = tShorthandTable{
|
|
shorthandInfos: []tShorthandInfo{
|
|
{shorthand: "-g", longhand: "flags"},
|
|
{shorthand: "-l", longhand: "list-verbs"},
|
|
{shorthand: "-L", longhand: "usage-verbs"},
|
|
{shorthand: "-f", longhand: "list-functions"},
|
|
{shorthand: "-F", longhand: "usage-functions"},
|
|
{shorthand: "-k", longhand: "list-keywords"},
|
|
{shorthand: "-K", longhand: "usage-keywords"},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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[1:]
|
|
|
|
// "mlr help" and nothing else
|
|
if len(args) == 0 {
|
|
handleDefault()
|
|
return 0
|
|
}
|
|
|
|
// 'mlr help find x' searches for all things (flags, transformers,
|
|
// functions, keywords) with an "x" in their name, as a substring.
|
|
if args[0] == "find" {
|
|
args = args[1:]
|
|
if len(args) > 0 {
|
|
helpByApproximateSearch(args)
|
|
return 0
|
|
}
|
|
fmt.Printf("mlr help find: need one or more things to search for.\n")
|
|
return 1
|
|
}
|
|
|
|
// "mlr help something" where we recognize the something
|
|
name := args[0]
|
|
for _, section := range handlerLookupTable.sections {
|
|
for _, info := range section.handlerInfos {
|
|
if info.name == name {
|
|
if info.zaryHandlerFunc != nil {
|
|
if len(args) != 1 {
|
|
fmt.Printf("mlr help %s takes no additional argument.\n", name)
|
|
return 0
|
|
}
|
|
info.zaryHandlerFunc()
|
|
return 0
|
|
}
|
|
if info.varArgHandlerFunc != nil {
|
|
if len(args) < 2 {
|
|
fmt.Printf("mlr help %s takes at least one required argument.\n", name)
|
|
return 0
|
|
}
|
|
info.varArgHandlerFunc(args[1:])
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 'mlr help x' searches for all things (flags, transformers, functions, keywords) named "x".
|
|
if helpByExactSearch(args) {
|
|
return 0
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func MainUsage(o *os.File) {
|
|
fmt.Fprintf(o,
|
|
`Usage: mlr [flags] [verb] [verb-dependent options ...] {zero or more file names}
|
|
|
|
If no verb is given, "cat" is used, which is handy for format conversions, e.g.
|
|
mlr --c2j < example.csv
|
|
|
|
If zero file names are provided, standard input is read, e.g.
|
|
mlr --csv sort -f shape example.csv
|
|
|
|
Output of one verb may be chained as input to another using "then", e.g.
|
|
mlr --csv stats1 -a min,mean,max -f quantity then sort -f color example.csv
|
|
|
|
Please see 'mlr help topics' for more information.
|
|
`)
|
|
fmt.Fprintf(o, "Please also see %s\n", lib.DOC_URL)
|
|
}
|
|
|
|
// For things like 'mlr -F', invoked through the CLI parser which does not
|
|
// go through our HelpMain().
|
|
func ParseTerminalUsage(arg string) bool {
|
|
if arg == "-h" || arg == "--help" {
|
|
handleDefault()
|
|
return true
|
|
}
|
|
// "mlr -l" is shorthand for "mlr help list-verbs", etc.
|
|
for _, sinfo := range shorthandLookupTable.shorthandInfos {
|
|
if sinfo.shorthand == arg {
|
|
for _, section := range handlerLookupTable.sections {
|
|
for _, info := range section.handlerInfos {
|
|
if info.name == sinfo.longhand {
|
|
info.zaryHandlerFunc()
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func handleDefault() {
|
|
MainUsage(os.Stdout)
|
|
}
|
|
|
|
func listTopics() {
|
|
fmt.Println("Type 'mlr help {topic}' for any of the following:")
|
|
for _, section := range handlerLookupTable.sections {
|
|
if !section.internal {
|
|
fmt.Printf("%s:\n", section.name)
|
|
for _, info := range section.handlerInfos {
|
|
fmt.Printf(" mlr help %s\n", info.name)
|
|
}
|
|
}
|
|
}
|
|
fmt.Println("Shorthands:")
|
|
for _, info := range shorthandLookupTable.shorthandInfos {
|
|
fmt.Printf(" mlr %s = mlr help %s\n", info.shorthand, info.longhand)
|
|
}
|
|
fmt.Printf("Lastly, 'mlr help ...' will search for your exact text '...' using the sources of\n")
|
|
fmt.Printf("'mlr help flag', 'mlr help verb', 'mlr help function', and 'mlr help keyword'.\n")
|
|
fmt.Printf("Use 'mlr help find ...' for approximate (substring) matches, e.g. 'mlr help find map'\n")
|
|
fmt.Printf("for all things with \"map\" in their names.\n")
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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.
|
|
`)
|
|
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.
|
|
}
|
|
|
|
func helpBasicExamples() {
|
|
fmt.Print(
|
|
`mlr --icsv --opprint cat example.csv
|
|
mlr --icsv --opprint sort -f shape example.csv
|
|
mlr --icsv --opprint sort -f shape -nr index example.csv
|
|
mlr --icsv --opprint cut -f flag,shape example.csv
|
|
mlr --csv filter '$color == "red"' example.csv
|
|
mlr --icsv --ojson put '$ratio = $quantity / $rate' example.csv
|
|
mlr --icsv --opprint --from example.csv sort -nr index then cut -f shape,quantity
|
|
`)
|
|
}
|
|
|
|
func helpFileFormats() {
|
|
fmt.Printf(
|
|
`CSV/CSV-lite: comma-separated values with separate header line
|
|
TSV: same but with tabs in places of commas
|
|
+---------------------+
|
|
| apple,bat,cog |
|
|
| 1,2,3 | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| 4,5,6 | Record 2: "apple":"4", "bat":"5", "cog":"6"
|
|
+---------------------+
|
|
|
|
JSON (array of objects):
|
|
+---------------------+
|
|
| [ |
|
|
| { |
|
|
| "apple": 1, | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| "bat": 2, |
|
|
| "cog": 3 |
|
|
| }, |
|
|
| { |
|
|
| "dish": { | Record 2: "dish.egg":"7",
|
|
| "egg": 7, | "dish.flint":"8", "garlic":""
|
|
| "flint": 8 |
|
|
| }, |
|
|
| "garlic": "" |
|
|
| } |
|
|
| ] |
|
|
+---------------------+
|
|
|
|
JSON Lines (sequence of one-line objects):
|
|
+------------------------------------------------+
|
|
| {"apple": 1, "bat": 2, "cog": 3} |
|
|
| {"dish": {"egg": 7, "flint": 8}, "garlic": ""} |
|
|
+------------------------------------------------+
|
|
Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
Record 2: "dish:egg":"7", "dish:flint":"8", "garlic":""
|
|
|
|
YAML (single document = object or array of objects; multiple documents with ---):
|
|
+---------------------+
|
|
| apple: 1 |
|
|
| bat: 2 | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| cog: 3 |
|
|
| --- |
|
|
| dish: 7 | Record 2: "dish":"7", "egg":"8"
|
|
| egg: 8 |
|
|
+---------------------+
|
|
|
|
PPRINT: pretty-printed tabular
|
|
+---------------------+
|
|
| apple bat cog |
|
|
| 1 2 3 | Record 1: "apple:"1", "bat":"2", "cog":"3"
|
|
| 4 5 6 | Record 2: "apple":"4", "bat":"5", "cog":"6"
|
|
+---------------------+
|
|
|
|
Markdown tabular:
|
|
+-----------------------+
|
|
| | apple | bat | cog | |
|
|
| | --- | --- | --- | |
|
|
| | 1 | 2 | 3 | | Record 1: "apple:"1", "bat":"2", "cog":"3"
|
|
| | 4 | 5 | 6 | | Record 2: "apple":"4", "bat":"5", "cog":"6"
|
|
+-----------------------+
|
|
|
|
XTAB: pretty-printed transposed tabular
|
|
+---------------------+
|
|
| apple 1 | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| bat 2 |
|
|
| cog 3 |
|
|
| |
|
|
| dish 7 | Record 2: "dish":"7", "egg":"8"
|
|
| egg 8 |
|
|
+---------------------+
|
|
|
|
DKVP: delimited key-value pairs (Miller default format)
|
|
+---------------------+
|
|
| apple=1,bat=2,cog=3 | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| dish=7,egg=8,flint | Record 2: "dish":"7", "egg":"8", "3":"flint"
|
|
+---------------------+
|
|
|
|
DKVPX: delimited key-value pairs with CSV-style quoting
|
|
+----------------------------------+
|
|
| apple=1,bat=2,cog=3 | Record 1: "apple":"1", "bat":"2", "cog":"3"
|
|
| "x,y"="a,b,c",z=3 | Record 2: "x,y":"a,b,c", "z":"3"
|
|
+----------------------------------+
|
|
|
|
NIDX: implicitly numerically indexed (Unix-toolkit style)
|
|
+---------------------+
|
|
| the quick brown | Record 1: "1":"the", "2":"quick", "3":"brown"
|
|
| fox jumped | Record 2: "1":"fox", "2":"jumped"
|
|
+---------------------+
|
|
|
|
DCF: Debian control file format
|
|
+------------+
|
|
| apple: 1 |
|
|
| bat: 2 |
|
|
| cog: 3 |
|
|
| |
|
|
| dish: 7 |
|
|
| egg: 8 |
|
|
| 3: flint |
|
|
+------------+
|
|
`)
|
|
}
|
|
|
|
func helpMlrrc() {
|
|
fmt.Print(
|
|
`You can set up personal defaults via a $HOME/.mlrrc and/or ./.mlrrc.
|
|
For example, if you usually process CSV, then you can put "--csv" in your .mlrrc file
|
|
and that will be the default input/output format unless otherwise specified on the command line.
|
|
|
|
The .mlrrc file format is one "--flag" or "--option value" per line, with the leading "--" optional.
|
|
Hash-style comments and blank lines are ignored.
|
|
|
|
Sample .mlrrc:
|
|
# Input and output formats are CSV by default (unless otherwise specified
|
|
# on the mlr command line):
|
|
csv
|
|
# These are no-ops for CSV, but when I do use JSON output, I want these
|
|
# pretty-printing options to be used:
|
|
jvstack
|
|
jlistwrap
|
|
|
|
How to specify location of .mlrrc:
|
|
* If $MLRRC is set:
|
|
o If its value is "__none__" then no .mlrrc files are processed.
|
|
o Otherwise, its value (as a filename) is loaded and processed. If there are syntax
|
|
errors, they abort mlr with a usage message (as if you had mistyped something on the
|
|
command line). If the file can't be loaded at all, though, it is silently skipped.
|
|
o Any .mlrrc in your home directory or current directory is ignored whenever $MLRRC is
|
|
set in the environment.
|
|
* Otherwise:
|
|
o If $HOME/.mlrrc exists, it's then processed as above.
|
|
o If ./.mlrrc exists, it's then also processed as above.
|
|
(I.e. current-directory .mlrrc defaults are stacked over home-directory .mlrrc defaults.)
|
|
* The command-line flag "--norc" can be used to suppress loading the .mlrrc file even when other
|
|
conditions are met.
|
|
See also:
|
|
https://miller.readthedocs.io/en/latest/customization.html
|
|
`)
|
|
}
|
|
|
|
func helpOutputColorization() {
|
|
cli.OutputColorizationPrintInfo()
|
|
}
|
|
|
|
func helpTypeArithmeticInfo() {
|
|
helpTypeArithmeticInfoAux(false)
|
|
}
|
|
|
|
func helpTypeArithmeticInfoExtended() {
|
|
helpTypeArithmeticInfoAux(true)
|
|
}
|
|
|
|
func helpTypeArithmeticInfoAux(extended bool) {
|
|
mlrvals := []*mlrval.Mlrval{
|
|
mlrval.FromInt(1),
|
|
mlrval.FromFloat(2.5),
|
|
mlrval.FromBool(true),
|
|
mlrval.VOID,
|
|
mlrval.ABSENT,
|
|
mlrval.FromAnonymousError(),
|
|
}
|
|
|
|
n := len(mlrvals)
|
|
|
|
for i := -2; i < n; i++ {
|
|
if i == -2 {
|
|
fmt.Printf("%-10s |", "(+)")
|
|
} else if i == -1 {
|
|
fmt.Printf("%-10s +", "------")
|
|
} else if mlrvals[i].IsVoid() {
|
|
fmt.Printf("%-10s |", "(empty)")
|
|
} else {
|
|
fmt.Printf("%-10s |", mlrvals[i].String())
|
|
}
|
|
for j := 0; j < n; j++ {
|
|
if i == -2 {
|
|
if mlrvals[j].IsVoid() {
|
|
fmt.Printf("%-10s", "(empty)")
|
|
} else {
|
|
fmt.Printf(" %-10s", mlrvals[j].String())
|
|
}
|
|
} else if i == -1 {
|
|
fmt.Printf(" %-10s", "------")
|
|
} else {
|
|
sum := bifs.BIF_plus_binary(mlrvals[i], mlrvals[j])
|
|
if sum.IsVoid() {
|
|
fmt.Printf(" %-10s", "(empty)")
|
|
} else {
|
|
fmt.Printf(" %-10s", sum.String())
|
|
}
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
if !extended {
|
|
return
|
|
}
|
|
|
|
mlrvals = []*mlrval.Mlrval{
|
|
mlrval.FromBool(true),
|
|
mlrval.FromBool(false),
|
|
mlrval.FromInt(3),
|
|
mlrval.VOID,
|
|
mlrval.ABSENT,
|
|
mlrval.FromAnonymousError(),
|
|
}
|
|
|
|
n = len(mlrvals)
|
|
|
|
state := runtime.NewEmptyState(cli.DefaultOptions(), false)
|
|
|
|
descs := []string{"(&&)", "(||)"}
|
|
for k, desc := range descs {
|
|
|
|
fmt.Println()
|
|
for i := -2; i < n; i++ {
|
|
if i == -2 {
|
|
fmt.Printf("%-10s |", desc)
|
|
} else if i == -1 {
|
|
fmt.Printf("%-10s +", "------")
|
|
} else if mlrvals[i].IsVoid() {
|
|
fmt.Printf("%-10s |", "(empty)")
|
|
} else {
|
|
fmt.Printf("%-10s |", mlrvals[i].String())
|
|
}
|
|
for j := 0; j < n; j++ {
|
|
if i == -2 {
|
|
if mlrvals[j].IsVoid() {
|
|
fmt.Printf("%-10s", "(empty)")
|
|
} else {
|
|
fmt.Printf(" %-10s", mlrvals[j].String())
|
|
}
|
|
} else if i == -1 {
|
|
fmt.Printf(" %-10s", "------")
|
|
} else {
|
|
|
|
inode := cst.BuildMlrvalLiteralNode(mlrvals[i])
|
|
jnode := cst.BuildMlrvalLiteralNode(mlrvals[j])
|
|
|
|
var binary_node cst.IEvaluable
|
|
if k == 0 {
|
|
binary_node = cst.BuildLogicalANDOperatorNode(inode, jnode)
|
|
} else {
|
|
binary_node = cst.BuildLogicalOROperatorNode(inode, jnode)
|
|
}
|
|
|
|
output := binary_node.Evaluate(state)
|
|
|
|
if output.IsVoid() {
|
|
fmt.Printf(" %-10s", "(empty)")
|
|
} else {
|
|
fmt.Printf(" %-10s", output.String())
|
|
}
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
}
|
|
|
|
// listFlagSections et al. are for webdoc/manpage autogen in the miller/docs
|
|
// 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
|
|
// scripts) control their own looping and formatting.
|
|
|
|
func listFlagSections() {
|
|
cli.FLAG_TABLE.ListFlagSections()
|
|
}
|
|
|
|
func printInfoForSection(sectionNames []string) {
|
|
for _, sectionName := range sectionNames {
|
|
if !cli.FLAG_TABLE.PrintInfoForSection(sectionName) {
|
|
fmt.Printf(
|
|
"mlr: flag-section \"%s\" not found. Please use \"mlr help list-flag-sections\" for a list.\n",
|
|
sectionName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func listFlagsForSection(sectionNames []string) {
|
|
for _, sectionName := range sectionNames {
|
|
if !cli.FLAG_TABLE.ListFlagsForSection(sectionName) {
|
|
fmt.Printf(
|
|
"mlr: flag-section \"%s\" not found. Please use \"mlr help list-flag-sections\" for a list.\n",
|
|
sectionName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// For manpage autogen: just produce text
|
|
func showHelpForSection(sectionNames []string) {
|
|
for _, sectionName := range sectionNames {
|
|
if !cli.FLAG_TABLE.ShowHelpForSection(sectionName) {
|
|
fmt.Printf(
|
|
"mlr: flag-section \"%s\" not found. Please use \"mlr help list-flag-sections\" for a list.\n",
|
|
sectionName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// For on-the-fly `mlr help foo-bar-flags` where `Foo-bar flags` is the name of
|
|
// a section in the FLAG_TABLE. See the func-init block at the top of this
|
|
// file.
|
|
func showHelpForSectionViaDowndash(downdashSectionNames []string) {
|
|
for _, downdashSectionName := range downdashSectionNames {
|
|
if !cli.FLAG_TABLE.ShowHelpForSectionViaDowndash(downdashSectionName) {
|
|
fmt.Printf("mlr: flag-section \"%s\" not found.\n", downdashSectionName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// For webdocs autogen: we want the headline separately so we can backtick it.
|
|
func showHeadlineForFlag(flagNames []string) {
|
|
for _, flagName := range flagNames {
|
|
if !cli.FLAG_TABLE.ShowHeadlineForFlag(flagName) {
|
|
fmt.Printf("mlr: flag \"%s\" not found..\n", flagName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// For webdocs autogen
|
|
func showHelpForFlag(flagNames []string) {
|
|
for _, flagName := range flagNames {
|
|
if !cli.FLAG_TABLE.ShowHelpForFlag(flagName) {
|
|
fmt.Printf("mlr: flag \"%s\" not found..\n", flagName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func listVerbs() {
|
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
transformers.ListVerbNamesAsParagraph()
|
|
} else {
|
|
transformers.ListVerbNamesVertically()
|
|
}
|
|
}
|
|
|
|
func listVerbsAsParagraph() {
|
|
transformers.ListVerbNamesAsParagraph()
|
|
}
|
|
|
|
func helpForVerb(args []string) {
|
|
for _, arg := range args {
|
|
if !transformers.ShowHelpForTransformer(arg) {
|
|
fmt.Printf(
|
|
"mlr: verb \"%s\" not found. Please use \"mlr help list-verbs\" for a list.\n",
|
|
arg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func usageVerbs() {
|
|
transformers.UsageVerbs()
|
|
}
|
|
|
|
func listFunctions() {
|
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionNamesAsParagraph()
|
|
} else {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionNamesVertically()
|
|
}
|
|
}
|
|
|
|
func listFunctionClasses() {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionClasses()
|
|
}
|
|
|
|
func listFunctionsInClass(classes []string) {
|
|
for _, class := range classes {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionsInClass(class)
|
|
}
|
|
}
|
|
|
|
func listFunctionsAsParagraph() {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionNamesAsParagraph()
|
|
}
|
|
|
|
func listFunctionsAsTable() {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionsAsTable()
|
|
}
|
|
|
|
func usageFunctions() {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionUsages()
|
|
}
|
|
|
|
func usageFunctionsByClass() {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionUsagesByClass()
|
|
}
|
|
|
|
func helpForFunction(args []string) {
|
|
for _, arg := range args {
|
|
cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionUsage(arg)
|
|
}
|
|
}
|
|
|
|
func helpByExactSearch(things []string) bool {
|
|
foundAny := false
|
|
for _, thing := range things {
|
|
foundThisOne := helpByExactSearchOne(thing)
|
|
foundAny = foundAny || foundThisOne
|
|
if !foundThisOne {
|
|
fmt.Printf("No help found for \"%s\". Please try 'mlr help find %s' for approximate match.\n", thing, thing)
|
|
fmt.Printf("See also 'mlr help topics'.\n")
|
|
}
|
|
}
|
|
|
|
return foundAny
|
|
}
|
|
|
|
// We need to look various places, e.g. "sec2gmt" is the name of a verb as well
|
|
// as a DSL function.
|
|
func helpByExactSearchOne(thing string) bool {
|
|
found := false
|
|
|
|
// flag
|
|
if cli.FLAG_TABLE.ShowHelpForFlagWithName(thing) {
|
|
found = true
|
|
}
|
|
|
|
// verb
|
|
if transformers.ShowHelpForTransformer(thing) {
|
|
found = true
|
|
}
|
|
|
|
// function
|
|
if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(thing) {
|
|
found = true
|
|
}
|
|
|
|
// keyword
|
|
if cst.TryUsageForKeyword(thing) {
|
|
found = true
|
|
}
|
|
|
|
return found
|
|
}
|
|
|
|
func helpByApproximateSearch(things []string) bool {
|
|
foundAny := false
|
|
for _, thing := range things {
|
|
foundThisOne := helpByApproximateSearchOne(thing)
|
|
foundAny = foundAny || foundThisOne
|
|
}
|
|
|
|
return foundAny
|
|
}
|
|
|
|
func helpByApproximateSearchOne(thing string) bool {
|
|
found := false
|
|
|
|
// flag
|
|
if cli.FLAG_TABLE.ShowHelpForFlagApproximateWithName(thing) {
|
|
found = true
|
|
}
|
|
|
|
// verb
|
|
if transformers.ShowHelpForTransformerApproximate(thing) {
|
|
found = true
|
|
}
|
|
|
|
// function
|
|
if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsageApproximate(thing) {
|
|
found = true
|
|
}
|
|
|
|
// keyword
|
|
if cst.TryUsageForKeywordApproximate(thing) {
|
|
found = true
|
|
}
|
|
|
|
if !found {
|
|
fmt.Printf("No help found for \"%s\". See also 'mlr help topics'.\n", thing)
|
|
}
|
|
return found
|
|
}
|
|
|
|
func listKeywords() {
|
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
cst.ListKeywordsAsParagraph()
|
|
} else {
|
|
cst.ListKeywordsVertically()
|
|
}
|
|
}
|
|
|
|
func listKeywordsAsParagraph() {
|
|
cst.ListKeywordsAsParagraph()
|
|
}
|
|
|
|
func usageKeywords() {
|
|
cst.UsageKeywords()
|
|
}
|
|
|
|
func helpForKeyword(args []string) {
|
|
for _, arg := range args {
|
|
cst.UsageForKeyword(arg)
|
|
}
|
|
}
|
|
|
|
// flagTableNilCheckflagTableNilCheck is invoked by an internal-only
|
|
// command-handler. It's intended to be invoked from a regression-test context.
|
|
// It makes sure (at build time) that the flags-table isn't missing help strings
|
|
// for any flags, etc.
|
|
func flagTableNilCheck() {
|
|
cli.FLAG_TABLE.NilCheck()
|
|
}
|