diff --git a/Makefile b/Makefile index 4fe53f20f..2c8b6a997 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ build: mlr: go build github.com/johnkerl/miller/cmd/mlr +# For interactive use, 'mlr regtest' offers more options and transparency. check: unit_test regression_test # Unit tests (small number) diff --git a/docs/src/manpage.txt b/docs/src/manpage.txt index 56201690b..08db5cd67 100644 --- a/docs/src/manpage.txt +++ b/docs/src/manpage.txt @@ -151,11 +151,10 @@ HELP OPTIONS mlr -F = mlr help usage-functions mlr -k = mlr help list-keywords mlr -K = mlr help usage-keywords - Lastly, 'mlr help ...' will search for your text '...' using the sources of + Lastly, 'mlr help ...' will search for your exact text '...' using the sources of 'mlr help flag', 'mlr help verb', 'mlr help function', and 'mlr help keyword'. - For things appearing in more than one place, e.g. 'sec2gmt' which is the name of a - verb as well as a function, use `mlr help verb sec2gmt' or `mlr help function sec2gmt' - to disambiguate. + Use 'mlr help find ...' for approximate (substring) matches, e.g. 'mlr help find map' + for all things with "map" in their names. VERB LIST altkv bar bootstrap cat check clean-whitespace count-distinct count @@ -2959,4 +2958,4 @@ SEE ALSO - 2021-11-15 MILLER(1) + 2021-11-18 MILLER(1) diff --git a/docs/src/online-help.md b/docs/src/online-help.md index cce2cdde2..4bea51ab4 100644 --- a/docs/src/online-help.md +++ b/docs/src/online-help.md @@ -90,18 +90,70 @@ Shorthands: mlr -F = mlr help usage-functions mlr -k = mlr help list-keywords mlr -K = mlr help usage-keywords -Lastly, 'mlr help ...' will search for your text '...' using the sources of +Lastly, 'mlr help ...' will search for your exact text '...' using the sources of 'mlr help flag', 'mlr help verb', 'mlr help function', and 'mlr help keyword'. -For things appearing in more than one place, e.g. 'sec2gmt' which is the name of a -verb as well as a function, use `mlr help verb sec2gmt' or `mlr help function sec2gmt' -to disambiguate. +Use 'mlr help find ...' for approximate (substring) matches, e.g. 'mlr help find map' +for all things with "map" in their names. +If you know the name of the thing you're looking for, use `mlr help`: +
-mlr help functions
+mlr help map
 
-No help found for "functions" -- please try 'mlr help topics'.
+map: declares an map-valued local variable in the current curly-braced scope.
+Type-checking happens at assignment: 'map b = 0' is an error. map b = {} is
+always OK. map b = a is OK or not depending on whether a is a map.
+
+ +To search by substring, use `mlr help find`: + +
+mlr help find gmt
+
+
+sec2gmtdate
+Usage: ../c/mlr sec2gmtdate {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT year-month-day timestamp; leaves non-numbers as-is.
+This is nothing more than a keystroke-saver for the sec2gmtdate function:
+  ../c/mlr sec2gmtdate time1,time2
+is the same as
+  ../c/mlr put '$time1=sec2gmtdate($time1);$time2=sec2gmtdate($time2)'
+sec2gmt
+Usage: mlr sec2gmt [options] {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT timestamp; leaves non-numbers as-is. This is nothing
+more than a keystroke-saver for the sec2gmt function:
+  mlr sec2gmt time1,time2
+is the same as
+  mlr put '$time1 = sec2gmt($time1); $time2 = sec2gmt($time2)'
+Options:
+-1 through -9: format the seconds using 1..9 decimal places, respectively.
+--millis Input numbers are treated as milliseconds since the epoch.
+--micros Input numbers are treated as microseconds since the epoch.
+--nanos  Input numbers are treated as nanoseconds since the epoch.
+-h|--help Show this message.
+gmt2localtime  (class=time #args=1,2) Convert from a GMT-time string to a local-time string. Consulting $TZ unless second argument is supplied.
+Examples:
+gmt2localtime("1999-12-31T22:00:00Z") = "2000-01-01 00:00:00" with TZ="Asia/Istanbul"
+gmt2localtime("1999-12-31T22:00:00Z", "Asia/Istanbul") = "2000-01-01 00:00:00"
+gmt2sec  (class=time #args=1) Parses GMT timestamp as integer seconds since the epoch.
+Example:
+gmt2sec("2001-02-03T04:05:06Z") = 981173106
+localtime2gmt  (class=time #args=1,2) Convert from a local-time string to a GMT-time string. Consults $TZ unless second argument is supplied.
+Examples:
+localtime2gmt("2000-01-01 00:00:00") = "1999-12-31T22:00:00Z" with TZ="Asia/Istanbul"
+localtime2gmt("2000-01-01 00:00:00", "Asia/Istanbul") = "1999-12-31T22:00:00Z"
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
+sec2gmtdate  (class=time #args=1) Formats seconds since epoch (integer part) as GMT timestamp with year-month-date. Leaves non-numbers as-is.
+Example:
+sec2gmtdate(1440768801.7) = "2015-08-28".
 
Etc. @@ -143,6 +195,7 @@ Options: mlr help verb sort
+sort
 Usage: mlr sort {flags}
 Sorts records primarily by the first specified field, secondarily by the second
 field, and so on.  (Any records not having all specified sort keys will appear
@@ -180,18 +233,9 @@ Given the name of a DSL function (from `mlr -f`) you can use `mlr help function`
 append  (class=collections #args=2) Appends second argument to end of first argument, which must be an array.
 
-
+
 mlr help function split
 
-
-No exact match for "split". Inexact matches:
-  splita
-  splitax
-  splitkv
-  splitkvx
-  splitnv
-  splitnvx
-
 mlr help function splita
diff --git a/docs/src/online-help.md.in b/docs/src/online-help.md.in
index e75ebf2dd..1b51c8ddb 100644
--- a/docs/src/online-help.md.in
+++ b/docs/src/online-help.md.in
@@ -14,8 +14,16 @@ GENMD-RUN-COMMAND
 mlr help topics
 GENMD-EOF
 
+If you know the name of the thing you're looking for, use `mlr help`:
+
 GENMD-RUN-COMMAND
-mlr help functions
+mlr help map
+GENMD-EOF
+
+To search by substring, use `mlr help find`:
+
+GENMD-RUN-COMMAND
+mlr help find gmt
 GENMD-EOF
 
 Etc.
diff --git a/internal/pkg/auxents/help/entry.go b/internal/pkg/auxents/help/entry.go
index 93f8ea9db..7e8c14597 100644
--- a/internal/pkg/auxents/help/entry.go
+++ b/internal/pkg/auxents/help/entry.go
@@ -19,7 +19,7 @@ import (
 
 // ================================================================
 type tZaryHandlerFunc func()
-type tUnaryHandlerFunc func(arg string)
+type tVarArgHandlerFunc func(args []string)
 
 type tHandlerLookupTable struct {
 	sections []tHandlerInfoSection
@@ -35,9 +35,9 @@ type tHandlerInfoSection struct {
 }
 
 type tHandlerInfo struct {
-	name             string
-	zaryHandlerFunc  tZaryHandlerFunc
-	unaryHandlerFunc tUnaryHandlerFunc
+	name              string
+	zaryHandlerFunc   tZaryHandlerFunc
+	varArgHandlerFunc tVarArgHandlerFunc
 }
 
 type tShorthandTable struct {
@@ -80,7 +80,7 @@ func init() {
 				handlerInfos: []tHandlerInfo{
 					{name: "list-verbs", zaryHandlerFunc: listVerbs},
 					{name: "usage-verbs", zaryHandlerFunc: usageVerbs},
-					{name: "verb", unaryHandlerFunc: helpForVerb},
+					{name: "verb", varArgHandlerFunc: helpForVerb},
 				},
 			},
 			{
@@ -88,10 +88,10 @@ func init() {
 				handlerInfos: []tHandlerInfo{
 					{name: "list-functions", zaryHandlerFunc: listFunctions},
 					{name: "list-function-classes", zaryHandlerFunc: listFunctionClasses},
-					{name: "list-functions-in-class", unaryHandlerFunc: listFunctionsInClass},
+					{name: "list-functions-in-class", varArgHandlerFunc: listFunctionsInClass},
 					{name: "usage-functions", zaryHandlerFunc: usageFunctions},
 					{name: "usage-functions-by-class", zaryHandlerFunc: usageFunctionsByClass},
-					{name: "function", unaryHandlerFunc: helpForFunction},
+					{name: "function", varArgHandlerFunc: helpForFunction},
 				},
 			},
 			{
@@ -99,7 +99,7 @@ func init() {
 				handlerInfos: []tHandlerInfo{
 					{name: "list-keywords", zaryHandlerFunc: listKeywords},
 					{name: "usage-keywords", zaryHandlerFunc: usageKeywords},
-					{name: "keyword", unaryHandlerFunc: helpForKeyword},
+					{name: "keyword", varArgHandlerFunc: helpForKeyword},
 				},
 			},
 			{
@@ -117,16 +117,16 @@ func init() {
 				handlerInfos: []tHandlerInfo{
 					{name: "flag-table-nil-check", zaryHandlerFunc: flagTableNilCheck},
 					{name: "list-flag-sections", zaryHandlerFunc: listFlagSections},
-					{name: "list-flags-for-section", unaryHandlerFunc: listFlagsForSection},
+					{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", unaryHandlerFunc: printInfoForSection},
-					{name: "show-headline-for-flag", unaryHandlerFunc: showHeadlineForFlag},
-					{name: "show-help-for-flag", unaryHandlerFunc: showHelpForFlag},
-					{name: "show-help-for-section", unaryHandlerFunc: showHelpForSection},
-					{name: "show-help-for-section-via-downdash", unaryHandlerFunc: showHelpForSectionViaDowndash},
+					{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},
 				},
 			},
 		},
@@ -158,7 +158,7 @@ func init() {
 				name: downdashSectionName,
 				// Make a function which passes in "csv-only-flags" etc. to the FLAG_TABLE.
 				zaryHandlerFunc: func() {
-					showHelpForSectionViaDowndash(downdashSectionName)
+					showHelpForSectionViaDowndash([]string{downdashSectionName})
 				},
 			}
 			handlerLookupTable.sections[i].handlerInfos = append(handlerLookupTable.sections[i].handlerInfos, entry)
@@ -193,6 +193,19 @@ func HelpMain(args []string) int {
 		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 {
@@ -206,26 +219,23 @@ func HelpMain(args []string) int {
 					info.zaryHandlerFunc()
 					return 0
 				}
-				if info.unaryHandlerFunc != nil {
+				if info.varArgHandlerFunc != nil {
 					if len(args) < 2 {
 						fmt.Printf("mlr help %s takes at least one required argument.\n", name)
 						return 0
 					}
-					for _, arg := range args[1:] {
-						info.unaryHandlerFunc(arg)
-					}
+					info.varArgHandlerFunc(args[1:])
 					return 0
 				}
 			}
 		}
 	}
 
-	if helpBySearch(name) {
+	// 'mlr help x' searches for all things (flags, transformers, functions, keywords) named "x".
+	if helpByExactSearch(args) {
 		return 0
 	}
 
-	// "mlr help something" where we do not recognize the something
-	fmt.Printf("No help found for \"%s\" -- please try 'mlr help topics'.\n", name)
 	return 0
 }
 
@@ -289,11 +299,10 @@ func listTopics() {
 	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 text '...' using the sources of\n")
+	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("For things appearing in more than one place, e.g. 'sec2gmt' which is the name of a\n")
-	fmt.Printf("verb as well as a function, use `mlr help verb sec2gmt' or `mlr help function sec2gmt'\n")
-	fmt.Printf("to disambiguate.\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")
 }
 
 // ----------------------------------------------------------------
@@ -482,51 +491,63 @@ func listFlagSections() {
 	cli.FLAG_TABLE.ListFlagSections()
 }
 
-func printInfoForSection(sectionName string) {
-	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 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(sectionName string) {
-	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)
+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(sectionName string) {
-	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)
+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(downdashSectionName string) {
-	if !cli.FLAG_TABLE.ShowHelpForSectionViaDowndash(downdashSectionName) {
-		fmt.Printf("mlr: flag-section \"%s\" not found.\n", downdashSectionName)
+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(flagName string) {
-	if !cli.FLAG_TABLE.ShowHeadlineForFlag(flagName) {
-		fmt.Printf("mlr: flag \"%s\" not found..\n", flagName)
+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(flagName string) {
-	if !cli.FLAG_TABLE.ShowHelpForFlag(flagName) {
-		fmt.Printf("mlr: flag \"%s\" not found..\n", flagName)
+func showHelpForFlag(flagNames []string) {
+	for _, flagName := range flagNames {
+		if !cli.FLAG_TABLE.ShowHelpForFlag(flagName) {
+			fmt.Printf("mlr: flag \"%s\" not found..\n", flagName)
+		}
 	}
 }
 
@@ -543,14 +564,13 @@ func listVerbsAsParagraph() {
 	transformers.ListVerbNamesAsParagraph()
 }
 
-func helpForVerb(arg string) {
-	transformerSetup := transformers.LookUp(arg)
-	if transformerSetup != nil {
-		transformerSetup.UsageFunc(os.Stdout, true, 0)
-	} else {
-		fmt.Printf(
-			"mlr: verb \"%s\" not found. Please use \"mlr help list-verbs\" for a list.\n",
-			arg)
+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)
+		}
 	}
 }
 
@@ -571,8 +591,10 @@ func listFunctionClasses() {
 	cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionClasses()
 }
 
-func listFunctionsInClass(class string) {
-	cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionsInClass(class)
+func listFunctionsInClass(classes []string) {
+	for _, class := range classes {
+		cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionsInClass(class)
+	}
 }
 
 func listFunctionsAsParagraph() {
@@ -591,45 +613,92 @@ func usageFunctionsByClass() {
 	cst.BuiltinFunctionManagerInstance.ListBuiltinFunctionUsagesByClass()
 }
 
-func helpForFunction(arg string) {
-	cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(arg, true)
+func helpForFunction(args []string) {
+	for _, arg := range args {
+		cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(arg)
+	}
 }
 
-// TODO: comment
-// xxx polymorphic looker-upper: try:
-// o flag
-// o verb
-// o function
-// o keyword
-// xxx note 'mlr help sort' finds verb before DSL function w/ same name ...
-// xxx 'mlr help verb sort' vs 'mlr help function sort'
-func helpBySearch(thing string) bool {
+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.ShowHelpForFlag(thing) {
-		return true
+	if cli.FLAG_TABLE.ShowHelpForFlagWithName(thing) {
+		found = true
 	}
 
 	// verb
-	transformerSetup := transformers.LookUp(thing)
-	if transformerSetup != nil {
-		transformerSetup.UsageFunc(os.Stdout, true, 0)
-		return true
+	if transformers.ShowHelpForTransformer(thing) {
+		found = true
 	}
 
 	// function
-	// to do: parameterize inexact-match printing ...
-	if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(thing, false) {
-		return true
+	if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(thing) {
+		found = true
 	}
 
 	// keyword
 	if cst.TryUsageForKeyword(thing) {
-		return true
+		found = true
 	}
 
-	// not found
-	return false
+	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\". Please try 'mlr help find %s' for approximate match.\n", thing, thing)
+		fmt.Printf("See also 'mlr help topics'.\n")
+	}
+	return found
 }
 
 // ----------------------------------------------------------------
@@ -649,8 +718,10 @@ func usageKeywords() {
 	cst.UsageKeywords()
 }
 
-func helpForKeyword(arg string) {
-	cst.UsageForKeyword(arg)
+func helpForKeyword(args []string) {
+	for _, arg := range args {
+		cst.UsageForKeyword(arg)
+	}
 }
 
 // ----------------------------------------------------------------
diff --git a/internal/pkg/auxents/regtest/entry.go b/internal/pkg/auxents/regtest/entry.go
index 6c509a005..74d5ce0e3 100644
--- a/internal/pkg/auxents/regtest/entry.go
+++ b/internal/pkg/auxents/regtest/entry.go
@@ -12,11 +12,13 @@ import (
 	"strings"
 )
 
+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, "If no directories/files are specified, the directory %s is used by default.\n", DefaultPath)
+	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")
 	fmt.Fprintf(o, "If foo.should-fail exists and is a file, the command is expected to exit non-zero back to\n")
@@ -94,6 +96,9 @@ func RegTestMain(args []string) int {
 		}
 	}
 	casePaths := args[argi:]
+	if len(casePaths) == 0 {
+		casePaths = []string{defaultPath}
+	}
 
 	regtester := NewRegTester(
 		exeName,
diff --git a/internal/pkg/auxents/regtest/regtester.go b/internal/pkg/auxents/regtest/regtester.go
index 73e2dbf1a..31cd1c150 100644
--- a/internal/pkg/auxents/regtest/regtester.go
+++ b/internal/pkg/auxents/regtest/regtester.go
@@ -69,7 +69,6 @@ import (
 	"github.com/johnkerl/miller/internal/pkg/lib"
 )
 
-const DefaultPath = "./cases"
 const CmdName = "cmd"
 const MlrName = "mlr"
 const EnvName = "env"
@@ -163,9 +162,7 @@ func (regtester *RegTester) Execute(
 
 	regtester.resetCounts()
 
-	if len(casePaths) == 0 {
-		casePaths = []string{DefaultPath}
-	}
+	lib.InternalCodingErrorIf(len(casePaths) == 0)
 
 	if !regtester.plainMode {
 		fmt.Println("REGRESSION TEST:")
diff --git a/internal/pkg/auxents/repl/verbs.go b/internal/pkg/auxents/repl/verbs.go
index a941846f3..e36ac24ac 100644
--- a/internal/pkg/auxents/repl/verbs.go
+++ b/internal/pkg/auxents/repl/verbs.go
@@ -70,6 +70,31 @@ func (repl *Repl) findHandler(verbName string) *handlerInfo {
 	return nil
 }
 
+func (repl *Repl) showUsageForHandler(verbName string) bool {
+	nonDSLHandler := repl.findHandler(verbName)
+	if nonDSLHandler != nil {
+		fmt.Println(colorizer.MaybeColorizeHelp(verbName, true))
+		nonDSLHandler.usageFunc(repl)
+		return true
+	} else {
+		return false
+	}
+}
+
+func (repl *Repl) showUsageForHandlerApproximate(searchString string) bool {
+	foundAny := false
+	for _, entry := range handlerLookupTable {
+		for _, entryVerbName := range entry.verbNames {
+			if strings.Contains(entryVerbName, searchString) {
+				fmt.Println(colorizer.MaybeColorizeHelp(entryVerbName, true))
+				entry.usageFunc(repl)
+				foundAny = true
+			}
+		}
+	}
+	return foundAny
+}
+
 // ----------------------------------------------------------------
 // Handles a single non-DSL statement like ':open foo.dat' or ':help'.
 func (repl *Repl) handleNonDSLLine(trimmedLine string) bool {
@@ -786,7 +811,7 @@ func usageHelp(repl *Repl) {
 	fmt.Println(":help prompt")
 	fmt.Println(":help function-names")
 	fmt.Println(":help function-details")
-	fmt.Println(":help {function name}, e.g. :help sec2gmt")
+	fmt.Println(":help find {substring}, e.g. :help find gmt or :help find map")
 	fmt.Println(":help {function name}, e.g. :help sec2gmt")
 }
 
@@ -797,6 +822,14 @@ func handleHelp(repl *Repl, args []string) bool {
 		return true
 	}
 
+	if len(args) >= 1 && args[0] == "find" {
+		args = args[1:]
+		for _, arg := range args {
+			handleHelpFindSingle(repl, arg)
+		}
+		return true
+	}
+
 	for _, arg := range args {
 		handleHelpSingle(repl, arg)
 	}
@@ -804,6 +837,26 @@ func handleHelp(repl *Repl, args []string) bool {
 	return true
 }
 
+func handleHelpFindSingle(repl *Repl, arg string) {
+	foundAny := false
+
+	if cst.TryUsageForKeywordApproximate(arg) {
+		foundAny = true
+	}
+
+	if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsageApproximate(arg) {
+		foundAny = true
+	}
+
+	if repl.showUsageForHandlerApproximate(arg) {
+		foundAny = true
+	}
+
+	if !foundAny {
+		fmt.Printf("No help available for %s. Try \":help find %s\" to search for matches\n", arg, arg)
+	}
+}
+
 func handleHelpSingle(repl *Repl, arg string) {
 	if arg == "intro" {
 		showREPLIntro(repl)
@@ -872,13 +925,11 @@ func handleHelpSingle(repl *Repl, arg string) {
 		return
 	}
 
-	if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(arg, true) {
+	if cst.BuiltinFunctionManagerInstance.TryListBuiltinFunctionUsage(arg) {
 		return
 	}
 
-	nonDSLHandler := repl.findHandler(arg)
-	if nonDSLHandler != nil {
-		nonDSLHandler.usageFunc(repl)
+	if repl.showUsageForHandler(arg) {
 		return
 	}
 
diff --git a/internal/pkg/cli/flag_types.go b/internal/pkg/cli/flag_types.go
index caeea95c6..4a285962c 100644
--- a/internal/pkg/cli/flag_types.go
+++ b/internal/pkg/cli/flag_types.go
@@ -258,9 +258,25 @@ func (ft *FlagTable) ShowHeadlineForFlag(flagName string) bool {
 // webdoc usage where the browser does dynamic line-wrapping, as the user
 // resizes the browser window.
 func (ft *FlagTable) ShowHelpForFlag(flagName string) bool {
+	return ft.showHelpForFlagMaybeWithName(flagName, false)
+}
+
+// ShowHelpForFlagWithName prints the flag's name colorized, then flag's
+// help-string all on one line.  This is for on-line help usage.
+func (ft *FlagTable) ShowHelpForFlagWithName(flagName string) bool {
+	return ft.showHelpForFlagMaybeWithName(flagName, true)
+}
+
+// showHelpForFlagMaybeWithName supports ShowHelpForFlag and ShowHelpForFlagWithName.
+// webdoc usage where the browser does dynamic line-wrapping, as the user
+// resizes the browser window.
+func (ft *FlagTable) showHelpForFlagMaybeWithName(flagName string, showName bool) bool {
 	for _, fs := range ft.sections {
 		for _, flag := range fs.flags {
 			if flag.Owns(flagName) {
+				if showName {
+					fmt.Println(colorizer.MaybeColorizeHelp(flagName, true))
+				}
 				fmt.Println(flag.GetHelpOneLine())
 				return true
 			}
@@ -269,6 +285,20 @@ func (ft *FlagTable) ShowHelpForFlag(flagName string) bool {
 	return false
 }
 
+// ShowHelpForFlagApproximateWithName is like ShowHelpForFlagWithName
+// but allows substring matches.  This is for on-line help usage.
+func (ft *FlagTable) ShowHelpForFlagApproximateWithName(searchString string) bool {
+	for _, fs := range ft.sections {
+		for _, flag := range fs.flags {
+			if flag.Matches(searchString) {
+				fmt.Println(colorizer.MaybeColorizeHelp(flag.name, true))
+				fmt.Println(flag.GetHelpOneLine())
+			}
+		}
+	}
+	return false
+}
+
 // Map "CSV-only flags" to "csv-only-flags" etc. for the benefit of per-section
 // help in `mlr help topics`.
 func (ft *FlagTable) GetDowndashSectionNames() []string {
@@ -361,14 +391,32 @@ func (fs *FlagSection) NilCheck() {
 // ================================================================
 // Flag methods
 
-// Owns determines whether this object handles a command-line flag such as "--foo".
+// Owns determines whether this object handles a command-line flag such as
+// "--foo".  This is used for command-line parsing, as well as for on-line help
+// with exact match on flag name.
 func (flag *Flag) Owns(input string) bool {
-	if input == flag.name {
+	if flag.name == input {
 		return true
 	}
 	if flag.altNames != nil {
 		for _, name := range flag.altNames {
-			if input == name {
+			if name == input {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// Matches is like Owns but is for substring matching, for on-line help with
+// approximate match on flag name.
+func (flag *Flag) Matches(input string) bool {
+	if strings.Contains(flag.name, input) {
+		return true
+	}
+	if flag.altNames != nil {
+		for _, name := range flag.altNames {
+			if strings.Contains(name, input) {
 				return true
 			}
 		}
diff --git a/internal/pkg/dsl/cst/builtin_function_manager.go b/internal/pkg/dsl/cst/builtin_function_manager.go
index cf42a7e84..d90eec326 100644
--- a/internal/pkg/dsl/cst/builtin_function_manager.go
+++ b/internal/pkg/dsl/cst/builtin_function_manager.go
@@ -1906,27 +1906,35 @@ func (manager *BuiltinFunctionManager) ListBuiltinFunctionUsagesByClass() {
 }
 
 func (manager *BuiltinFunctionManager) ListBuiltinFunctionUsage(functionName string) {
-	// xxx remove bool-arg code smell
-	if !manager.TryListBuiltinFunctionUsage(functionName, true) {
+	if !manager.TryListBuiltinFunctionUsage(functionName) {
 		fmt.Fprintf(os.Stderr, "Function \"%s\" not found.\n", functionName)
 	}
 }
 
 func (manager *BuiltinFunctionManager) TryListBuiltinFunctionUsage(
 	functionName string,
-	showApproximate bool,
 ) bool {
 	builtinFunctionInfo := manager.LookUp(functionName)
 	if builtinFunctionInfo == nil {
-		if showApproximate {
-			manager.listBuiltinFunctionUsageApproximate(functionName)
-		}
 		return false
 	}
 	manager.listBuiltinFunctionUsageExact(builtinFunctionInfo)
 	return true
 }
 
+func (manager *BuiltinFunctionManager) TryListBuiltinFunctionUsageApproximate(
+	searchString string,
+) bool {
+	found := false
+	for _, builtinFunctionInfo := range *manager.lookupTable {
+		if strings.Contains(builtinFunctionInfo.name, searchString) {
+			manager.showSingleUsage(&builtinFunctionInfo)
+			found = true
+		}
+	}
+	return found
+}
+
 func (manager *BuiltinFunctionManager) listBuiltinFunctionUsageExact(
 	builtinFunctionInfo *BuiltinFunctionInfo,
 ) {
@@ -1955,10 +1963,9 @@ func (manager *BuiltinFunctionManager) showSingleUsage(
 	}
 }
 
-func (manager *BuiltinFunctionManager) listBuiltinFunctionUsageApproximate(
+func (manager *BuiltinFunctionManager) ListBuiltinFunctionUsageApproximate(
 	text string,
-) {
-	fmt.Printf("No exact match for \"%s\". Inexact matches:\n", text)
+) bool {
 	found := false
 	for _, builtinFunctionInfo := range *manager.lookupTable {
 		if strings.Contains(builtinFunctionInfo.name, text) {
@@ -1966,9 +1973,7 @@ func (manager *BuiltinFunctionManager) listBuiltinFunctionUsageApproximate(
 			found = true
 		}
 	}
-	if !found {
-		fmt.Println("None found.")
-	}
+	return found
 }
 
 func describeNargs(info *BuiltinFunctionInfo) string {
diff --git a/internal/pkg/dsl/cst/keyword_usage.go b/internal/pkg/dsl/cst/keyword_usage.go
index 807c8527d..0972cbce8 100644
--- a/internal/pkg/dsl/cst/keyword_usage.go
+++ b/internal/pkg/dsl/cst/keyword_usage.go
@@ -2,6 +2,7 @@ package cst
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/johnkerl/miller/internal/pkg/colorizer"
 	"github.com/johnkerl/miller/internal/pkg/lib"
@@ -102,6 +103,18 @@ func TryUsageForKeyword(name string) bool {
 	return false
 }
 
+func TryUsageForKeywordApproximate(searchString string) bool {
+	foundAny := false
+	for _, entry := range KEYWORD_USAGE_TABLE {
+		if strings.Contains(entry.name, searchString) {
+			fmt.Printf("%s: ", colorizer.MaybeColorizeHelp(entry.name, true))
+			entry.usageFunc()
+			foundAny = true
+		}
+	}
+	return foundAny
+}
+
 // ----------------------------------------------------------------
 func ListKeywordsVertically() {
 	for _, entry := range KEYWORD_USAGE_TABLE {
diff --git a/internal/pkg/transformers/aaa_transformer_table.go b/internal/pkg/transformers/aaa_transformer_table.go
index 3c2a0acaf..ed6c0a84d 100644
--- a/internal/pkg/transformers/aaa_transformer_table.go
+++ b/internal/pkg/transformers/aaa_transformer_table.go
@@ -3,6 +3,7 @@ package transformers
 import (
 	"fmt"
 	"os"
+	"strings"
 
 	"github.com/johnkerl/miller/internal/pkg/colorizer"
 	"github.com/johnkerl/miller/internal/pkg/lib"
@@ -71,7 +72,28 @@ var TRANSFORMER_LOOKUP_TABLE = []TransformerSetup{
 	UnsparsifySetup,
 }
 
-// ----------------------------------------------------------------
+func ShowHelpForTransformer(verb string) bool {
+	transformerSetup := LookUp(verb)
+	if transformerSetup != nil {
+		fmt.Println(colorizer.MaybeColorizeHelp(transformerSetup.Verb, true))
+		transformerSetup.UsageFunc(os.Stdout, false, 0)
+		return true
+	}
+	return false
+}
+
+func ShowHelpForTransformerApproximate(searchString string) bool {
+	found := false
+	for _, transformerSetup := range TRANSFORMER_LOOKUP_TABLE {
+		if strings.Contains(transformerSetup.Verb, searchString) {
+			fmt.Println(colorizer.MaybeColorizeHelp(transformerSetup.Verb, true))
+			transformerSetup.UsageFunc(os.Stdout, false, 0)
+			found = true
+		}
+	}
+	return found
+}
+
 func LookUp(verb string) *TransformerSetup {
 	for _, transformerSetup := range TRANSFORMER_LOOKUP_TABLE {
 		if transformerSetup.Verb == verb {
@@ -81,14 +103,12 @@ func LookUp(verb string) *TransformerSetup {
 	return nil
 }
 
-// ----------------------------------------------------------------
 func ListVerbNamesVertically() {
 	for _, transformerSetup := range TRANSFORMER_LOOKUP_TABLE {
 		fmt.Printf("%s\n", transformerSetup.Verb)
 	}
 }
 
-// ----------------------------------------------------------------
 func ListVerbNamesAsParagraph() {
 	verbNames := make([]string, len(TRANSFORMER_LOOKUP_TABLE))
 
diff --git a/man/manpage.txt b/man/manpage.txt
index 56201690b..08db5cd67 100644
--- a/man/manpage.txt
+++ b/man/manpage.txt
@@ -151,11 +151,10 @@ HELP OPTIONS
          mlr -F = mlr help usage-functions
          mlr -k = mlr help list-keywords
          mlr -K = mlr help usage-keywords
-       Lastly, 'mlr help ...' will search for your text '...' using the sources of
+       Lastly, 'mlr help ...' will search for your exact text '...' using the sources of
        'mlr help flag', 'mlr help verb', 'mlr help function', and 'mlr help keyword'.
-       For things appearing in more than one place, e.g. 'sec2gmt' which is the name of a
-       verb as well as a function, use `mlr help verb sec2gmt' or `mlr help function sec2gmt'
-       to disambiguate.
+       Use 'mlr help find ...' for approximate (substring) matches, e.g. 'mlr help find map'
+       for all things with "map" in their names.
 
 VERB LIST
        altkv bar bootstrap cat check clean-whitespace count-distinct count
@@ -2959,4 +2958,4 @@ SEE ALSO
 
 
 
-                                  2021-11-15                         MILLER(1)
+                                  2021-11-18                         MILLER(1)
diff --git a/man/mlr.1 b/man/mlr.1
index ca61710c5..315f86366 100644
--- a/man/mlr.1
+++ b/man/mlr.1
@@ -2,12 +2,12 @@
 .\"     Title: mlr
 .\"    Author: [see the "AUTHOR" section]
 .\" Generator: ./mkman.rb
-.\"      Date: 2021-11-15
+.\"      Date: 2021-11-18
 .\"    Manual: \ \&
 .\"    Source: \ \&
 .\"  Language: English
 .\"
-.TH "MILLER" "1" "2021-11-15" "\ \&" "\ \&"
+.TH "MILLER" "1" "2021-11-18" "\ \&" "\ \&"
 .\" -----------------------------------------------------------------
 .\" * Portability definitions
 .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -186,11 +186,10 @@ Shorthands:
   mlr -F = mlr help usage-functions
   mlr -k = mlr help list-keywords
   mlr -K = mlr help usage-keywords
-Lastly, 'mlr help ...' will search for your text '...' using the sources of
+Lastly, 'mlr help ...' will search for your exact text '...' using the sources of
 \(cqmlr help flag', 'mlr help verb', 'mlr help function', and 'mlr help keyword'.
-For things appearing in more than one place, e.g. 'sec2gmt' which is the name of a
-verb as well as a function, use `mlr help verb sec2gmt' or `mlr help function sec2gmt'
-to disambiguate.
+Use 'mlr help find ...' for approximate (substring) matches, e.g. 'mlr help find map'
+for all things with "map" in their names.
 .fi
 .if n \{\
 .RE
diff --git a/test/cases/help/0001/cmd b/test/cases/help/0001/cmd
new file mode 100644
index 000000000..4c30096cb
--- /dev/null
+++ b/test/cases/help/0001/cmd
@@ -0,0 +1 @@
+mlr help cos
diff --git a/test/cases/help/0001/experr b/test/cases/help/0001/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0001/expout b/test/cases/help/0001/expout
new file mode 100644
index 000000000..59ee1e4fb
--- /dev/null
+++ b/test/cases/help/0001/expout
@@ -0,0 +1 @@
+cos  (class=math #args=1) Trigonometric cosine.
diff --git a/test/cases/help/0002/cmd b/test/cases/help/0002/cmd
new file mode 100644
index 000000000..02371dc8f
--- /dev/null
+++ b/test/cases/help/0002/cmd
@@ -0,0 +1 @@
+mlr help cos sin
diff --git a/test/cases/help/0002/experr b/test/cases/help/0002/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0002/expout b/test/cases/help/0002/expout
new file mode 100644
index 000000000..1529b7199
--- /dev/null
+++ b/test/cases/help/0002/expout
@@ -0,0 +1,2 @@
+cos  (class=math #args=1) Trigonometric cosine.
+sin  (class=math #args=1) Trigonometric sine.
diff --git a/test/cases/help/0003/cmd b/test/cases/help/0003/cmd
new file mode 100644
index 000000000..938fd5ac4
--- /dev/null
+++ b/test/cases/help/0003/cmd
@@ -0,0 +1 @@
+mlr help nonesuch
diff --git a/test/cases/help/0003/experr b/test/cases/help/0003/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0003/expout b/test/cases/help/0003/expout
new file mode 100644
index 000000000..b39493a58
--- /dev/null
+++ b/test/cases/help/0003/expout
@@ -0,0 +1,2 @@
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/help/0004/cmd b/test/cases/help/0004/cmd
new file mode 100644
index 000000000..8f4d5fef8
--- /dev/null
+++ b/test/cases/help/0004/cmd
@@ -0,0 +1 @@
+mlr help nonesuch sin
diff --git a/test/cases/help/0004/experr b/test/cases/help/0004/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0004/expout b/test/cases/help/0004/expout
new file mode 100644
index 000000000..2957a47dd
--- /dev/null
+++ b/test/cases/help/0004/expout
@@ -0,0 +1,3 @@
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
+sin  (class=math #args=1) Trigonometric sine.
diff --git a/test/cases/help/0005/cmd b/test/cases/help/0005/cmd
new file mode 100644
index 000000000..5dacd53eb
--- /dev/null
+++ b/test/cases/help/0005/cmd
@@ -0,0 +1 @@
+mlr help cos nonesuch
diff --git a/test/cases/help/0005/experr b/test/cases/help/0005/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0005/expout b/test/cases/help/0005/expout
new file mode 100644
index 000000000..706bbda4c
--- /dev/null
+++ b/test/cases/help/0005/expout
@@ -0,0 +1,3 @@
+cos  (class=math #args=1) Trigonometric cosine.
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/help/0006/cmd b/test/cases/help/0006/cmd
new file mode 100644
index 000000000..5ab70547a
--- /dev/null
+++ b/test/cases/help/0006/cmd
@@ -0,0 +1 @@
+mlr help find cos
diff --git a/test/cases/help/0006/experr b/test/cases/help/0006/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0006/expout b/test/cases/help/0006/expout
new file mode 100644
index 000000000..7e39509e9
--- /dev/null
+++ b/test/cases/help/0006/expout
@@ -0,0 +1,4 @@
+acos  (class=math #args=1) Inverse trigonometric cosine.
+acosh  (class=math #args=1) Inverse hyperbolic cosine.
+cos  (class=math #args=1) Trigonometric cosine.
+cosh  (class=math #args=1) Hyperbolic cosine.
diff --git a/test/cases/help/0007/cmd b/test/cases/help/0007/cmd
new file mode 100644
index 000000000..9f8308c45
--- /dev/null
+++ b/test/cases/help/0007/cmd
@@ -0,0 +1 @@
+mlr help find cos sin
diff --git a/test/cases/help/0007/experr b/test/cases/help/0007/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0007/expout b/test/cases/help/0007/expout
new file mode 100644
index 000000000..c6c0b22c8
--- /dev/null
+++ b/test/cases/help/0007/expout
@@ -0,0 +1,8 @@
+acos  (class=math #args=1) Inverse trigonometric cosine.
+acosh  (class=math #args=1) Inverse hyperbolic cosine.
+cos  (class=math #args=1) Trigonometric cosine.
+cosh  (class=math #args=1) Hyperbolic cosine.
+asin  (class=math #args=1) Inverse trigonometric sine.
+asinh  (class=math #args=1) Inverse hyperbolic sine.
+sin  (class=math #args=1) Trigonometric sine.
+sinh  (class=math #args=1) Hyperbolic sine.
diff --git a/test/cases/help/0008/cmd b/test/cases/help/0008/cmd
new file mode 100644
index 000000000..21289ba04
--- /dev/null
+++ b/test/cases/help/0008/cmd
@@ -0,0 +1 @@
+mlr help find nonesuch
diff --git a/test/cases/help/0008/experr b/test/cases/help/0008/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0008/expout b/test/cases/help/0008/expout
new file mode 100644
index 000000000..b39493a58
--- /dev/null
+++ b/test/cases/help/0008/expout
@@ -0,0 +1,2 @@
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/help/0009/cmd b/test/cases/help/0009/cmd
new file mode 100644
index 000000000..fd26d1f11
--- /dev/null
+++ b/test/cases/help/0009/cmd
@@ -0,0 +1 @@
+mlr help find nonesuch sin
diff --git a/test/cases/help/0009/experr b/test/cases/help/0009/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0009/expout b/test/cases/help/0009/expout
new file mode 100644
index 000000000..1e09eb9ec
--- /dev/null
+++ b/test/cases/help/0009/expout
@@ -0,0 +1,6 @@
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
+asin  (class=math #args=1) Inverse trigonometric sine.
+asinh  (class=math #args=1) Inverse hyperbolic sine.
+sin  (class=math #args=1) Trigonometric sine.
+sinh  (class=math #args=1) Hyperbolic sine.
diff --git a/test/cases/help/0010/cmd b/test/cases/help/0010/cmd
new file mode 100644
index 000000000..5dacd53eb
--- /dev/null
+++ b/test/cases/help/0010/cmd
@@ -0,0 +1 @@
+mlr help cos nonesuch
diff --git a/test/cases/help/0010/experr b/test/cases/help/0010/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0010/expout b/test/cases/help/0010/expout
new file mode 100644
index 000000000..706bbda4c
--- /dev/null
+++ b/test/cases/help/0010/expout
@@ -0,0 +1,3 @@
+cos  (class=math #args=1) Trigonometric cosine.
+No help found for "nonesuch". Please try 'mlr help find nonesuch' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/help/0011/cmd b/test/cases/help/0011/cmd
new file mode 100644
index 000000000..79e4e3cdc
--- /dev/null
+++ b/test/cases/help/0011/cmd
@@ -0,0 +1 @@
+mlr help for
diff --git a/test/cases/help/0011/experr b/test/cases/help/0011/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0011/expout b/test/cases/help/0011/expout
new file mode 100644
index 000000000..9f5c6b71f
--- /dev/null
+++ b/test/cases/help/0011/expout
@@ -0,0 +1,15 @@
+for: defines a for-loop using one of three styles. The body statements must
+be wrapped in curly braces.
+For-loop over stream record:
+
+  Example:  'for (k, v in $*) { ... }'
+
+For-loop over out-of-stream variables:
+
+  Example: 'for (k, v in @counts) { ... }'
+  Example: 'for ((k1, k2), v in @counts) { ... }'
+  Example: 'for ((k1, k2, k3), v in @*) { ... }'
+
+C-style for-loop:
+
+  Example:  'for (var i = 0, var b = 1; i < 10; i += 1, b *= 2) { ... }'
diff --git a/test/cases/help/0012/cmd b/test/cases/help/0012/cmd
new file mode 100644
index 000000000..814997851
--- /dev/null
+++ b/test/cases/help/0012/cmd
@@ -0,0 +1 @@
+mlr help find for
diff --git a/test/cases/help/0012/experr b/test/cases/help/0012/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0012/expout b/test/cases/help/0012/expout
new file mode 100644
index 000000000..245e2ab33
--- /dev/null
+++ b/test/cases/help/0012/expout
@@ -0,0 +1,45 @@
+format-values
+Usage: mlr format-values [options]
+Applies format strings to all field values, depending on autodetected type.
+* If a field value is detected to be integer, applies integer format.
+* Else, if a field value is detected to be float, applies float format.
+* Else, applies string format.
+
+Note: this is a low-keystroke way to apply formatting to many fields. To get
+finer control, please see the fmtnum function within the mlr put DSL.
+
+Note: this verb lets you apply arbitrary format strings, which can produce
+undefined behavior and/or program crashes.  See your system's "man printf".
+
+Options:
+-i {integer format} Defaults to "%d".
+                    Examples: "%06lld", "%08llx".
+                    Note that Miller integers are long long so you must use
+                    formats which apply to long long, e.g. with ll in them.
+                    Undefined behavior results otherwise.
+-f {float format}   Defaults to "%f".
+                    Examples: "%8.3lf", "%.6le".
+                    Note that Miller floats are double-precision so you must
+                    use formats which apply to double, e.g. with l[efg] in them.
+                    Undefined behavior results otherwise.
+-s {string format}  Defaults to "%s".
+                    Examples: "_%s", "%08s".
+                    Note that you must use formats which apply to string, e.g.
+                    with s in them. Undefined behavior results otherwise.
+-n                  Coerce field values autodetected as int to float, and then
+                    apply the float format.
+for: defines a for-loop using one of three styles. The body statements must
+be wrapped in curly braces.
+For-loop over stream record:
+
+  Example:  'for (k, v in $*) { ... }'
+
+For-loop over out-of-stream variables:
+
+  Example: 'for (k, v in @counts) { ... }'
+  Example: 'for ((k1, k2), v in @counts) { ... }'
+  Example: 'for ((k1, k2, k3), v in @*) { ... }'
+
+C-style for-loop:
+
+  Example:  'for (var i = 0, var b = 1; i < 10; i += 1, b *= 2) { ... }'
diff --git a/test/cases/help/0013/cmd b/test/cases/help/0013/cmd
new file mode 100644
index 000000000..b3cc86b0b
--- /dev/null
+++ b/test/cases/help/0013/cmd
@@ -0,0 +1 @@
+mlr help sec2
diff --git a/test/cases/help/0013/experr b/test/cases/help/0013/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0013/expout b/test/cases/help/0013/expout
new file mode 100644
index 000000000..39d075259
--- /dev/null
+++ b/test/cases/help/0013/expout
@@ -0,0 +1,2 @@
+No help found for "sec2". Please try 'mlr help find sec2' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/help/0014/cmd b/test/cases/help/0014/cmd
new file mode 100644
index 000000000..990a00af1
--- /dev/null
+++ b/test/cases/help/0014/cmd
@@ -0,0 +1 @@
+mlr help find sec2
diff --git a/test/cases/help/0014/experr b/test/cases/help/0014/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0014/expout b/test/cases/help/0014/expout
new file mode 100644
index 000000000..97977a3db
--- /dev/null
+++ b/test/cases/help/0014/expout
@@ -0,0 +1,44 @@
+sec2gmtdate
+Usage: ../c/mlr sec2gmtdate {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT year-month-day timestamp; leaves non-numbers as-is.
+This is nothing more than a keystroke-saver for the sec2gmtdate function:
+  ../c/mlr sec2gmtdate time1,time2
+is the same as
+  ../c/mlr put '$time1=sec2gmtdate($time1);$time2=sec2gmtdate($time2)'
+sec2gmt
+Usage: mlr sec2gmt [options] {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT timestamp; leaves non-numbers as-is. This is nothing
+more than a keystroke-saver for the sec2gmt function:
+  mlr sec2gmt time1,time2
+is the same as
+  mlr put '$time1 = sec2gmt($time1); $time2 = sec2gmt($time2)'
+Options:
+-1 through -9: format the seconds using 1..9 decimal places, respectively.
+--millis Input numbers are treated as milliseconds since the epoch.
+--micros Input numbers are treated as microseconds since the epoch.
+--nanos  Input numbers are treated as nanoseconds since the epoch.
+-h|--help Show this message.
+fsec2dhms  (class=time #args=1) Formats floating-point seconds as in fsec2dhms(500000.25) = "5d18h53m20.250000s"
+fsec2hms  (class=time #args=1) Formats floating-point seconds as in fsec2hms(5000.25) = "01:23:20.250000"
+sec2dhms  (class=time #args=1) Formats integer seconds as in sec2dhms(500000) = "5d18h53m20s"
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
+sec2gmtdate  (class=time #args=1) Formats seconds since epoch (integer part) as GMT timestamp with year-month-date. Leaves non-numbers as-is.
+Example:
+sec2gmtdate(1440768801.7) = "2015-08-28".
+sec2hms  (class=time #args=1) Formats integer seconds as in sec2hms(5000) = "01:23:20"
+sec2localdate  (class=time #args=1,2) Formats seconds since epoch (integer part) as local timestamp with year-month-date. Leaves non-numbers as-is. Consults $TZ environment variable unless second argument is supplied.
+Examples:
+sec2localdate(1440768801.7) = "2015-08-28" with TZ="Asia/Istanbul"
+sec2localdate(1440768801.7, "Asia/Istanbul") = "2015-08-28"
+sec2localtime  (class=time #args=1,2,3) Formats seconds since epoch (integer part) as local timestamp. Consults $TZ environment variable unless third argument is supplied. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part
+Examples:
+sec2localtime(1234567890)           = "2009-02-14 01:31:30"        with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456)    = "2009-02-14 01:31:30"        with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456, 6) = "2009-02-14 01:31:30.123456" with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456, 6, "Asia/Istanbul") = "2009-02-14 01:31:30.123456"
diff --git a/test/cases/help/0015/cmd b/test/cases/help/0015/cmd
new file mode 100644
index 000000000..dd88ca8b5
--- /dev/null
+++ b/test/cases/help/0015/cmd
@@ -0,0 +1 @@
+mlr help sec2gmt
diff --git a/test/cases/help/0015/experr b/test/cases/help/0015/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0015/expout b/test/cases/help/0015/expout
new file mode 100644
index 000000000..cf4bafab7
--- /dev/null
+++ b/test/cases/help/0015/expout
@@ -0,0 +1,19 @@
+sec2gmt
+Usage: mlr sec2gmt [options] {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT timestamp; leaves non-numbers as-is. This is nothing
+more than a keystroke-saver for the sec2gmt function:
+  mlr sec2gmt time1,time2
+is the same as
+  mlr put '$time1 = sec2gmt($time1); $time2 = sec2gmt($time2)'
+Options:
+-1 through -9: format the seconds using 1..9 decimal places, respectively.
+--millis Input numbers are treated as milliseconds since the epoch.
+--micros Input numbers are treated as microseconds since the epoch.
+--nanos  Input numbers are treated as nanoseconds since the epoch.
+-h|--help Show this message.
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
diff --git a/test/cases/help/0016/cmd b/test/cases/help/0016/cmd
new file mode 100644
index 000000000..2fa6ad66c
--- /dev/null
+++ b/test/cases/help/0016/cmd
@@ -0,0 +1 @@
+mlr help find sec2gmt
diff --git a/test/cases/help/0016/experr b/test/cases/help/0016/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0016/expout b/test/cases/help/0016/expout
new file mode 100644
index 000000000..87aa76721
--- /dev/null
+++ b/test/cases/help/0016/expout
@@ -0,0 +1,30 @@
+sec2gmtdate
+Usage: ../c/mlr sec2gmtdate {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT year-month-day timestamp; leaves non-numbers as-is.
+This is nothing more than a keystroke-saver for the sec2gmtdate function:
+  ../c/mlr sec2gmtdate time1,time2
+is the same as
+  ../c/mlr put '$time1=sec2gmtdate($time1);$time2=sec2gmtdate($time2)'
+sec2gmt
+Usage: mlr sec2gmt [options] {comma-separated list of field names}
+Replaces a numeric field representing seconds since the epoch with the
+corresponding GMT timestamp; leaves non-numbers as-is. This is nothing
+more than a keystroke-saver for the sec2gmt function:
+  mlr sec2gmt time1,time2
+is the same as
+  mlr put '$time1 = sec2gmt($time1); $time2 = sec2gmt($time2)'
+Options:
+-1 through -9: format the seconds using 1..9 decimal places, respectively.
+--millis Input numbers are treated as milliseconds since the epoch.
+--micros Input numbers are treated as microseconds since the epoch.
+--nanos  Input numbers are treated as nanoseconds since the epoch.
+-h|--help Show this message.
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
+sec2gmtdate  (class=time #args=1) Formats seconds since epoch (integer part) as GMT timestamp with year-month-date. Leaves non-numbers as-is.
+Example:
+sec2gmtdate(1440768801.7) = "2015-08-28".
diff --git a/test/cases/help/0017/cmd b/test/cases/help/0017/cmd
new file mode 100644
index 000000000..a525d1fb4
--- /dev/null
+++ b/test/cases/help/0017/cmd
@@ -0,0 +1 @@
+mlr help --csv
diff --git a/test/cases/help/0017/experr b/test/cases/help/0017/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0017/expout b/test/cases/help/0017/expout
new file mode 100644
index 000000000..9c3ab866f
--- /dev/null
+++ b/test/cases/help/0017/expout
@@ -0,0 +1,2 @@
+--csv
+Use CSV format for input and output data.
diff --git a/test/cases/help/0018/cmd b/test/cases/help/0018/cmd
new file mode 100644
index 000000000..0da8741c9
--- /dev/null
+++ b/test/cases/help/0018/cmd
@@ -0,0 +1 @@
+mlr help find --csv
diff --git a/test/cases/help/0018/experr b/test/cases/help/0018/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/help/0018/expout b/test/cases/help/0018/expout
new file mode 100644
index 000000000..560401525
--- /dev/null
+++ b/test/cases/help/0018/expout
@@ -0,0 +1,6 @@
+--csv
+Use CSV format for input and output data.
+--csvlite
+Use CSV-lite format for input and output data.
+No help found for "--csv". Please try 'mlr help find --csv' for approximate match.
+See also 'mlr help topics'.
diff --git a/test/cases/repl-help/0001/cmd b/test/cases/repl-help/0001/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0001/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0001/experr b/test/cases/repl-help/0001/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0001/expout b/test/cases/repl-help/0001/expout
new file mode 100644
index 000000000..59ee1e4fb
--- /dev/null
+++ b/test/cases/repl-help/0001/expout
@@ -0,0 +1 @@
+cos  (class=math #args=1) Trigonometric cosine.
diff --git a/test/cases/repl-help/0001/input b/test/cases/repl-help/0001/input
new file mode 100644
index 000000000..11118bb2e
--- /dev/null
+++ b/test/cases/repl-help/0001/input
@@ -0,0 +1 @@
+:help cos
diff --git a/test/cases/repl-help/0002/cmd b/test/cases/repl-help/0002/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0002/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0002/experr b/test/cases/repl-help/0002/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0002/expout b/test/cases/repl-help/0002/expout
new file mode 100644
index 000000000..1529b7199
--- /dev/null
+++ b/test/cases/repl-help/0002/expout
@@ -0,0 +1,2 @@
+cos  (class=math #args=1) Trigonometric cosine.
+sin  (class=math #args=1) Trigonometric sine.
diff --git a/test/cases/repl-help/0002/input b/test/cases/repl-help/0002/input
new file mode 100644
index 000000000..fe91e6745
--- /dev/null
+++ b/test/cases/repl-help/0002/input
@@ -0,0 +1 @@
+:help cos sin
diff --git a/test/cases/repl-help/0003/cmd b/test/cases/repl-help/0003/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0003/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0003/experr b/test/cases/repl-help/0003/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0003/expout b/test/cases/repl-help/0003/expout
new file mode 100644
index 000000000..6c499ebae
--- /dev/null
+++ b/test/cases/repl-help/0003/expout
@@ -0,0 +1 @@
+No help available for nonesuch
diff --git a/test/cases/repl-help/0003/input b/test/cases/repl-help/0003/input
new file mode 100644
index 000000000..07b3f318d
--- /dev/null
+++ b/test/cases/repl-help/0003/input
@@ -0,0 +1 @@
+:help nonesuch
diff --git a/test/cases/repl-help/0004/cmd b/test/cases/repl-help/0004/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0004/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0004/experr b/test/cases/repl-help/0004/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0004/expout b/test/cases/repl-help/0004/expout
new file mode 100644
index 000000000..3519be25c
--- /dev/null
+++ b/test/cases/repl-help/0004/expout
@@ -0,0 +1,2 @@
+No help available for nonesuch
+sin  (class=math #args=1) Trigonometric sine.
diff --git a/test/cases/repl-help/0004/input b/test/cases/repl-help/0004/input
new file mode 100644
index 000000000..7bd372580
--- /dev/null
+++ b/test/cases/repl-help/0004/input
@@ -0,0 +1 @@
+:help nonesuch sin
diff --git a/test/cases/repl-help/0005/cmd b/test/cases/repl-help/0005/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0005/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0005/experr b/test/cases/repl-help/0005/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0005/expout b/test/cases/repl-help/0005/expout
new file mode 100644
index 000000000..8369d3d44
--- /dev/null
+++ b/test/cases/repl-help/0005/expout
@@ -0,0 +1,2 @@
+cos  (class=math #args=1) Trigonometric cosine.
+No help available for nonesuch
diff --git a/test/cases/repl-help/0005/input b/test/cases/repl-help/0005/input
new file mode 100644
index 000000000..44f267d0b
--- /dev/null
+++ b/test/cases/repl-help/0005/input
@@ -0,0 +1 @@
+:help cos nonesuch
diff --git a/test/cases/repl-help/0006/cmd b/test/cases/repl-help/0006/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0006/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0006/experr b/test/cases/repl-help/0006/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0006/expout b/test/cases/repl-help/0006/expout
new file mode 100644
index 000000000..7e39509e9
--- /dev/null
+++ b/test/cases/repl-help/0006/expout
@@ -0,0 +1,4 @@
+acos  (class=math #args=1) Inverse trigonometric cosine.
+acosh  (class=math #args=1) Inverse hyperbolic cosine.
+cos  (class=math #args=1) Trigonometric cosine.
+cosh  (class=math #args=1) Hyperbolic cosine.
diff --git a/test/cases/repl-help/0006/input b/test/cases/repl-help/0006/input
new file mode 100644
index 000000000..40e82b097
--- /dev/null
+++ b/test/cases/repl-help/0006/input
@@ -0,0 +1 @@
+:help find cos
diff --git a/test/cases/repl-help/0007/cmd b/test/cases/repl-help/0007/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0007/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0007/experr b/test/cases/repl-help/0007/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0007/expout b/test/cases/repl-help/0007/expout
new file mode 100644
index 000000000..c6c0b22c8
--- /dev/null
+++ b/test/cases/repl-help/0007/expout
@@ -0,0 +1,8 @@
+acos  (class=math #args=1) Inverse trigonometric cosine.
+acosh  (class=math #args=1) Inverse hyperbolic cosine.
+cos  (class=math #args=1) Trigonometric cosine.
+cosh  (class=math #args=1) Hyperbolic cosine.
+asin  (class=math #args=1) Inverse trigonometric sine.
+asinh  (class=math #args=1) Inverse hyperbolic sine.
+sin  (class=math #args=1) Trigonometric sine.
+sinh  (class=math #args=1) Hyperbolic sine.
diff --git a/test/cases/repl-help/0007/input b/test/cases/repl-help/0007/input
new file mode 100644
index 000000000..1bc0dae93
--- /dev/null
+++ b/test/cases/repl-help/0007/input
@@ -0,0 +1 @@
+:help find cos sin
diff --git a/test/cases/repl-help/0008/cmd b/test/cases/repl-help/0008/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0008/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0008/experr b/test/cases/repl-help/0008/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0008/expout b/test/cases/repl-help/0008/expout
new file mode 100644
index 000000000..61bce8170
--- /dev/null
+++ b/test/cases/repl-help/0008/expout
@@ -0,0 +1 @@
+No help available for nonesuch. Try ":help find nonesuch" to search for matches
diff --git a/test/cases/repl-help/0008/input b/test/cases/repl-help/0008/input
new file mode 100644
index 000000000..6758fb508
--- /dev/null
+++ b/test/cases/repl-help/0008/input
@@ -0,0 +1 @@
+:help find nonesuch
diff --git a/test/cases/repl-help/0009/cmd b/test/cases/repl-help/0009/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0009/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0009/experr b/test/cases/repl-help/0009/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0009/expout b/test/cases/repl-help/0009/expout
new file mode 100644
index 000000000..1a1a7ecd6
--- /dev/null
+++ b/test/cases/repl-help/0009/expout
@@ -0,0 +1,5 @@
+No help available for nonesuch. Try ":help find nonesuch" to search for matches
+asin  (class=math #args=1) Inverse trigonometric sine.
+asinh  (class=math #args=1) Inverse hyperbolic sine.
+sin  (class=math #args=1) Trigonometric sine.
+sinh  (class=math #args=1) Hyperbolic sine.
diff --git a/test/cases/repl-help/0009/input b/test/cases/repl-help/0009/input
new file mode 100644
index 000000000..2c1ceade8
--- /dev/null
+++ b/test/cases/repl-help/0009/input
@@ -0,0 +1 @@
+:help find nonesuch sin
diff --git a/test/cases/repl-help/0010/cmd b/test/cases/repl-help/0010/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0010/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0010/experr b/test/cases/repl-help/0010/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0010/expout b/test/cases/repl-help/0010/expout
new file mode 100644
index 000000000..cf7c24ab1
--- /dev/null
+++ b/test/cases/repl-help/0010/expout
@@ -0,0 +1,5 @@
+acos  (class=math #args=1) Inverse trigonometric cosine.
+acosh  (class=math #args=1) Inverse hyperbolic cosine.
+cos  (class=math #args=1) Trigonometric cosine.
+cosh  (class=math #args=1) Hyperbolic cosine.
+No help available for nonesuch. Try ":help find nonesuch" to search for matches
diff --git a/test/cases/repl-help/0010/input b/test/cases/repl-help/0010/input
new file mode 100644
index 000000000..4735fd0e8
--- /dev/null
+++ b/test/cases/repl-help/0010/input
@@ -0,0 +1 @@
+:help find cos nonesuch
diff --git a/test/cases/repl-help/0011/cmd b/test/cases/repl-help/0011/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0011/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0011/experr b/test/cases/repl-help/0011/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0011/expout b/test/cases/repl-help/0011/expout
new file mode 100644
index 000000000..9f5c6b71f
--- /dev/null
+++ b/test/cases/repl-help/0011/expout
@@ -0,0 +1,15 @@
+for: defines a for-loop using one of three styles. The body statements must
+be wrapped in curly braces.
+For-loop over stream record:
+
+  Example:  'for (k, v in $*) { ... }'
+
+For-loop over out-of-stream variables:
+
+  Example: 'for (k, v in @counts) { ... }'
+  Example: 'for ((k1, k2), v in @counts) { ... }'
+  Example: 'for ((k1, k2, k3), v in @*) { ... }'
+
+C-style for-loop:
+
+  Example:  'for (var i = 0, var b = 1; i < 10; i += 1, b *= 2) { ... }'
diff --git a/test/cases/repl-help/0011/input b/test/cases/repl-help/0011/input
new file mode 100644
index 000000000..3b352ca73
--- /dev/null
+++ b/test/cases/repl-help/0011/input
@@ -0,0 +1 @@
+:help for
diff --git a/test/cases/repl-help/0012/cmd b/test/cases/repl-help/0012/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0012/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0012/experr b/test/cases/repl-help/0012/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0012/expout b/test/cases/repl-help/0012/expout
new file mode 100644
index 000000000..9f5c6b71f
--- /dev/null
+++ b/test/cases/repl-help/0012/expout
@@ -0,0 +1,15 @@
+for: defines a for-loop using one of three styles. The body statements must
+be wrapped in curly braces.
+For-loop over stream record:
+
+  Example:  'for (k, v in $*) { ... }'
+
+For-loop over out-of-stream variables:
+
+  Example: 'for (k, v in @counts) { ... }'
+  Example: 'for ((k1, k2), v in @counts) { ... }'
+  Example: 'for ((k1, k2, k3), v in @*) { ... }'
+
+C-style for-loop:
+
+  Example:  'for (var i = 0, var b = 1; i < 10; i += 1, b *= 2) { ... }'
diff --git a/test/cases/repl-help/0012/input b/test/cases/repl-help/0012/input
new file mode 100644
index 000000000..57c25123e
--- /dev/null
+++ b/test/cases/repl-help/0012/input
@@ -0,0 +1 @@
+:help find for
diff --git a/test/cases/repl-help/0013/cmd b/test/cases/repl-help/0013/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0013/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0013/experr b/test/cases/repl-help/0013/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0013/expout b/test/cases/repl-help/0013/expout
new file mode 100644
index 000000000..4f922d64b
--- /dev/null
+++ b/test/cases/repl-help/0013/expout
@@ -0,0 +1 @@
+No help available for sec2
diff --git a/test/cases/repl-help/0013/input b/test/cases/repl-help/0013/input
new file mode 100644
index 000000000..94ea514c3
--- /dev/null
+++ b/test/cases/repl-help/0013/input
@@ -0,0 +1 @@
+:help sec2
diff --git a/test/cases/repl-help/0014/cmd b/test/cases/repl-help/0014/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0014/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0014/experr b/test/cases/repl-help/0014/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0014/expout b/test/cases/repl-help/0014/expout
new file mode 100644
index 000000000..a710a4f26
--- /dev/null
+++ b/test/cases/repl-help/0014/expout
@@ -0,0 +1,22 @@
+fsec2dhms  (class=time #args=1) Formats floating-point seconds as in fsec2dhms(500000.25) = "5d18h53m20.250000s"
+fsec2hms  (class=time #args=1) Formats floating-point seconds as in fsec2hms(5000.25) = "01:23:20.250000"
+sec2dhms  (class=time #args=1) Formats integer seconds as in sec2dhms(500000) = "5d18h53m20s"
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
+sec2gmtdate  (class=time #args=1) Formats seconds since epoch (integer part) as GMT timestamp with year-month-date. Leaves non-numbers as-is.
+Example:
+sec2gmtdate(1440768801.7) = "2015-08-28".
+sec2hms  (class=time #args=1) Formats integer seconds as in sec2hms(5000) = "01:23:20"
+sec2localdate  (class=time #args=1,2) Formats seconds since epoch (integer part) as local timestamp with year-month-date. Leaves non-numbers as-is. Consults $TZ environment variable unless second argument is supplied.
+Examples:
+sec2localdate(1440768801.7) = "2015-08-28" with TZ="Asia/Istanbul"
+sec2localdate(1440768801.7, "Asia/Istanbul") = "2015-08-28"
+sec2localtime  (class=time #args=1,2,3) Formats seconds since epoch (integer part) as local timestamp. Consults $TZ environment variable unless third argument is supplied. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part
+Examples:
+sec2localtime(1234567890)           = "2009-02-14 01:31:30"        with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456)    = "2009-02-14 01:31:30"        with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456, 6) = "2009-02-14 01:31:30.123456" with TZ="Asia/Istanbul"
+sec2localtime(1234567890.123456, 6, "Asia/Istanbul") = "2009-02-14 01:31:30.123456"
diff --git a/test/cases/repl-help/0014/input b/test/cases/repl-help/0014/input
new file mode 100644
index 000000000..dee6765ad
--- /dev/null
+++ b/test/cases/repl-help/0014/input
@@ -0,0 +1 @@
+:help find sec2
diff --git a/test/cases/repl-help/0015/cmd b/test/cases/repl-help/0015/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0015/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0015/experr b/test/cases/repl-help/0015/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0015/expout b/test/cases/repl-help/0015/expout
new file mode 100644
index 000000000..e0b453374
--- /dev/null
+++ b/test/cases/repl-help/0015/expout
@@ -0,0 +1,5 @@
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
diff --git a/test/cases/repl-help/0015/input b/test/cases/repl-help/0015/input
new file mode 100644
index 000000000..c1b7fa9de
--- /dev/null
+++ b/test/cases/repl-help/0015/input
@@ -0,0 +1 @@
+:help sec2gmt
diff --git a/test/cases/repl-help/0016/cmd b/test/cases/repl-help/0016/cmd
new file mode 100644
index 000000000..f17dba06d
--- /dev/null
+++ b/test/cases/repl-help/0016/cmd
@@ -0,0 +1 @@
+mlr repl < ./${CASEDIR}/input
diff --git a/test/cases/repl-help/0016/experr b/test/cases/repl-help/0016/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/repl-help/0016/expout b/test/cases/repl-help/0016/expout
new file mode 100644
index 000000000..ad1e26c87
--- /dev/null
+++ b/test/cases/repl-help/0016/expout
@@ -0,0 +1,8 @@
+sec2gmt  (class=time #args=1,2) Formats seconds since epoch as GMT timestamp. Leaves non-numbers as-is. With second integer argument n, includes n decimal places for the seconds part.
+Examples:
+sec2gmt(1234567890)           = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456)    = "2009-02-13T23:31:30Z"
+sec2gmt(1234567890.123456, 6) = "2009-02-13T23:31:30.123456Z"
+sec2gmtdate  (class=time #args=1) Formats seconds since epoch (integer part) as GMT timestamp with year-month-date. Leaves non-numbers as-is.
+Example:
+sec2gmtdate(1440768801.7) = "2015-08-28".
diff --git a/test/cases/repl-help/0016/input b/test/cases/repl-help/0016/input
new file mode 100644
index 000000000..ce7f831e8
--- /dev/null
+++ b/test/cases/repl-help/0016/input
@@ -0,0 +1 @@
+:help find sec2gmt
diff --git a/todo.txt b/todo.txt
index f6031e43f..bb42991e3 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,7 +1,6 @@
 ================================================================
 PUNCHDOWN LIST
 
-* conda issue
 * blockers:
   - keep checking issues
   - verslink old relnotes
@@ -12,7 +11,7 @@ PUNCHDOWN LIST
   - csv irs lf/crlf ignores -- ? already is so?
   - `mlr put` -> coverart
   - 0b1011 olh/webdoc
-  - help approx-match even if exact exists (e.g. 'map')
+
 * release ordering?
   conda
   brew macports chocolatey
@@ -46,12 +45,7 @@ PUNCHDOWN LIST
       strptime("1970-01-01T00:00:00.Z", "%Y-%m-%dT%H:%M:%SZ")
       (error)
 
-* olh
-  o kw olh for func and funct both
-  o better help-searching:
-    ? mlr help search foo -- including flag-search
-    ? mlr -f foo
-    ? mlr -F foo == mlr help function foo
+* kw olh for func and funct both
 
 * pre-release:
   o check issues