miller/pkg/terminals/help/entry.go
Adam Lesperance 085e831668
The package version must match the major tag version (#1654)
* Update package version

* Update makefile targets

* Update readme packages

* Remaining old packages via rg/sd
2024-09-20 12:10:11 -04:00

855 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
} else {
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 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":""
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"
+---------------------+
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"
+---------------------+
`)
}
// ----------------------------------------------------------------
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()
}