diff --git a/README.md b/README.md index 4e9adf85f..84afce541 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,10 @@ See also [building from source](https://miller.readthedocs.io/en/latest/build.ht # Build status -[![Go-port multi-platform build status](https://github.com/johnkerl/miller/actions/workflows/go.yml/badge.svg)](https://github.com/johnkerl/miller/actions) +[![Multi-platform build status](https://github.com/johnkerl/miller/actions/workflows/go.yml/badge.svg)](https://github.com/johnkerl/miller/actions) +[![CodeQL status](https://github.com/johnkerl/miller/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/johnkerl/miller/actions) +[![Codespell status](https://github.com/johnkerl/miller/actions/workflows/codespell.yml/badge.svg)](https://github.com/johnkerl/miller/actions) +[![Release status](https://github.com/johnkerl/miller/actions/workflows/release.yml/badge.svg)](https://github.com/johnkerl/miller/actions) # Building from source diff --git a/cmd/experiments/colors/main.go b/cmd/experiments/colors/main.go new file mode 100644 index 000000000..305fa6062 --- /dev/null +++ b/cmd/experiments/colors/main.go @@ -0,0 +1,43 @@ +// This is the entry point for the mlr executable. +package main + +import ( + "fmt" + "github.com/johnkerl/miller/internal/pkg/colorizer" +) + +const boldString = "\u001b[1m" +const underlineString = "\u001b[4m" +const reversedString = "\u001b[7m" +const redString = "\u001b[1;31m" +const blueString = "\u001b[1;34m" +const defaultString = "\u001b[0m" + +func main() { + fmt.Printf("Hello, world!\n") + + fmt.Printf("1. before %s during %s after\n", "", "") + fmt.Printf("4. before %s during %s after\n", boldString, defaultString) + fmt.Printf("2. before %s during %s after\n", redString, defaultString) + fmt.Printf("3. before %s during %s after\n", blueString, defaultString) + fmt.Printf("5. before %s during %s after\n", redString+boldString, defaultString) + fmt.Printf("7. before %s during %s after\n", blueString, defaultString) + fmt.Printf("8. before %s during %s after\n", boldString, defaultString) + fmt.Printf("9. before %s during %s after\n", boldString, defaultString) + fmt.Println() + + names := []string{ + "plain", + "red", + "bold", + "bold-red", + "red-bold", + "blue-underline", + "208", + "reversed-208", + } + for _, name := range names { + colorizer.SetKeyColor(name) + fmt.Printf("testing: [%s]\n", colorizer.MaybeColorizeKey(name, true)) + } +} diff --git a/docs/src/manpage.md b/docs/src/manpage.md index 9492fe5b1..e7327a694 100644 --- a/docs/src/manpage.md +++ b/docs/src/manpage.md @@ -621,8 +621,11 @@ OUTPUT-COLORIZATION FLAGS If environment-variable settings and command-line flags are both provided, the latter take precedence. - Please do mlr `--list-color-codes` to see the available color codes (like 170), - and `mlr --list-color-names` to see available names (like `orchid`). + Colors can be specified using names such as "red" or "orchid": please see + `mlr --list-color-names` to see available names. They can also be specified using + numbers in the range 0..255, like 170: please see `mlr --list-color-codes`. + You can also use "bold", "underline", and/or "reverse". Additionally, combinations of + those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc. --always-color or -C Instructs Miller to colorize output even when it normally would not. Useful for piping output to `less @@ -3131,5 +3134,5 @@ SEE ALSO - 2022-01-27 MILLER(1) + 2022-01-30 MILLER(1) diff --git a/docs/src/manpage.txt b/docs/src/manpage.txt index bdba32306..456700526 100644 --- a/docs/src/manpage.txt +++ b/docs/src/manpage.txt @@ -600,8 +600,11 @@ OUTPUT-COLORIZATION FLAGS If environment-variable settings and command-line flags are both provided, the latter take precedence. - Please do mlr `--list-color-codes` to see the available color codes (like 170), - and `mlr --list-color-names` to see available names (like `orchid`). + Colors can be specified using names such as "red" or "orchid": please see + `mlr --list-color-names` to see available names. They can also be specified using + numbers in the range 0..255, like 170: please see `mlr --list-color-codes`. + You can also use "bold", "underline", and/or "reverse". Additionally, combinations of + those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc. --always-color or -C Instructs Miller to colorize output even when it normally would not. Useful for piping output to `less @@ -3110,4 +3113,4 @@ SEE ALSO - 2022-01-27 MILLER(1) + 2022-01-30 MILLER(1) diff --git a/docs/src/output-colorization.md b/docs/src/output-colorization.md index b7147b3d6..4b824a54e 100644 --- a/docs/src/output-colorization.md +++ b/docs/src/output-colorization.md @@ -16,7 +16,11 @@ Quick links: # Output colorization -As of version 6.0.0, Miller supports output-colorization. Here are examples using side-by-side black-background and white-background terminals: +As of version 6.0.0, Miller supports output colorization. This includes colors +per se, as well as bold, underline, and/or reverse. As of version 6.1.0, the +default is bold+underline for keys -- this works well as a default regardless +of your terminal's color settings -- with further customization options as +described below: ![pix/colorization.png](pix/colorization.png) @@ -37,8 +41,6 @@ As of version 6.0.0, Miller supports output-colorization. Here are examples usin * NIDX output format (plain text, Unix-style, with implicitly positionally indexed fields) is not colored. This is because in other formats, keys are one color and values are another. For NIDX, all output is values. -* The default colors were chosen since they look OK with white or black terminal background, and are differentiable with common varieties of human color vision. - ## Mechanisms for colorization * Miller uses ANSI escape sequences only. This does not work on Windows except on Cygwin. diff --git a/docs/src/output-colorization.md.in b/docs/src/output-colorization.md.in index 9ce149604..95baa76ab 100644 --- a/docs/src/output-colorization.md.in +++ b/docs/src/output-colorization.md.in @@ -1,6 +1,10 @@ # Output colorization -As of version 6.0.0, Miller supports output-colorization. Here are examples using side-by-side black-background and white-background terminals: +As of version 6.0.0, Miller supports output colorization. This includes colors +per se, as well as bold, underline, and/or reverse. As of version 6.1.0, the +default is bold+underline for keys -- this works well as a default regardless +of your terminal's color settings -- with further customization options as +described below: ![pix/colorization.png](pix/colorization.png) @@ -21,8 +25,6 @@ As of version 6.0.0, Miller supports output-colorization. Here are examples usin * NIDX output format (plain text, Unix-style, with implicitly positionally indexed fields) is not colored. This is because in other formats, keys are one color and values are another. For NIDX, all output is values. -* The default colors were chosen since they look OK with white or black terminal background, and are differentiable with common varieties of human color vision. - ## Mechanisms for colorization * Miller uses ANSI escape sequences only. This does not work on Windows except on Cygwin. diff --git a/docs/src/pix/colorization.png b/docs/src/pix/colorization.png index 0909e7bd7..68f67fd81 100644 Binary files a/docs/src/pix/colorization.png and b/docs/src/pix/colorization.png differ diff --git a/docs/src/pix/colorization.png.000 b/docs/src/pix/colorization.png.000 deleted file mode 100644 index 005d8edeb..000000000 Binary files a/docs/src/pix/colorization.png.000 and /dev/null differ diff --git a/docs/src/pix/colorization2.png.000 b/docs/src/pix/colorization2.png.000 deleted file mode 100644 index 935793a60..000000000 Binary files a/docs/src/pix/colorization2.png.000 and /dev/null differ diff --git a/docs/src/reference-main-flag-list.md b/docs/src/reference-main-flag-list.md index 91766b37e..a32a6615b 100644 --- a/docs/src/reference-main-flag-list.md +++ b/docs/src/reference-main-flag-list.md @@ -327,8 +327,11 @@ How you can control colorization: If environment-variable settings and command-line flags are both provided, the latter take precedence. -Please do mlr `--list-color-codes` to see the available color codes (like 170), -and `mlr --list-color-names` to see available names (like `orchid`). +Colors can be specified using names such as "red" or "orchid": please see +`mlr --list-color-names` to see available names. They can also be specified using +numbers in the range 0..255, like 170: please see `mlr --list-color-codes`. +You can also use "bold", "underline", and/or "reverse". Additionally, combinations of +those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc. **Flags:** diff --git a/docs/src/scripting.md b/docs/src/scripting.md index aa67d8db0..29cac3fb7 100644 --- a/docs/src/scripting.md +++ b/docs/src/scripting.md @@ -65,7 +65,7 @@ Key points here: Then you can do
-example-shell-script example.csv
+./example-shell-script example.csv
 
 shape    count count_fraction
@@ -75,7 +75,7 @@ circle   3     0.3
 
-cat example.csv | example-shell-script
+cat example.csv | ./example-shell-script
 
 shape    count count_fraction
@@ -85,7 +85,7 @@ circle   3     0.3
 
-example-shell-script --ojson example.csv
+./example-shell-script --ojson example.csv
 
 [
@@ -108,7 +108,7 @@ circle   3     0.3
 
-example-shell-script --ojson then filter '$count == 3' example.csv
+./example-shell-script --ojson then filter '$count == 3' example.csv
 
 [
@@ -132,7 +132,7 @@ etc.
 Here instead of putting `#!/bin/bash` on the first line, we can put `mlr` directly:
 
 
-cat example-mlr-s-script
+cat ./example-mlr-s-script
 
 #!/usr/bin/env mlr -s
@@ -153,7 +153,7 @@ Points:
 Then you can do
 
 
-example-mlr-s-script example.csv
+./example-mlr-s-script example.csv
 
 shape    count count_fraction
@@ -163,7 +163,7 @@ circle   3     0.3
 
-cat example.csv | example-mlr-s-script
+cat example.csv | ./example-mlr-s-script
 
 shape    count count_fraction
@@ -173,7 +173,7 @@ circle   3     0.3
 
-example-mlr-s-script --ojson example.csv
+./example-mlr-s-script --ojson example.csv
 
 [
@@ -196,7 +196,7 @@ circle   3     0.3
 
-example-mlr-s-script --ojson then filter '$count == 3' example.csv
+./example-mlr-s-script --ojson then filter '$count == 3' example.csv
 
 [
diff --git a/docs/src/scripting.md.in b/docs/src/scripting.md.in
index 6ab7933ec..0e4afc9ac 100644
--- a/docs/src/scripting.md.in
+++ b/docs/src/scripting.md.in
@@ -35,19 +35,19 @@ Key points here:
 Then you can do
 
 GENMD-RUN-COMMAND
-example-shell-script example.csv
+./example-shell-script example.csv
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-cat example.csv | example-shell-script
+cat example.csv | ./example-shell-script
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-example-shell-script --ojson example.csv
+./example-shell-script --ojson example.csv
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-example-shell-script --ojson then filter '$count == 3' example.csv
+./example-shell-script --ojson then filter '$count == 3' example.csv
 GENMD-EOF
 
 etc.
@@ -57,7 +57,7 @@ etc.
 Here instead of putting `#!/bin/bash` on the first line, we can put `mlr` directly:
 
 GENMD-RUN-COMMAND
-cat example-mlr-s-script
+cat ./example-mlr-s-script
 GENMD-EOF
 
 Points:
@@ -71,19 +71,19 @@ Points:
 Then you can do
 
 GENMD-RUN-COMMAND
-example-mlr-s-script example.csv
+./example-mlr-s-script example.csv
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-cat example.csv | example-mlr-s-script
+cat example.csv | ./example-mlr-s-script
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-example-mlr-s-script --ojson example.csv
+./example-mlr-s-script --ojson example.csv
 GENMD-EOF
 
 GENMD-RUN-COMMAND
-example-mlr-s-script --ojson then filter '$count == 3' example.csv
+./example-mlr-s-script --ojson then filter '$count == 3' example.csv
 GENMD-EOF
 
 ## Miller scripts on Windows
diff --git a/go.mod b/go.mod
index 8f7e1f5e9..cd3efaa2a 100644
--- a/go.mod
+++ b/go.mod
@@ -10,10 +10,14 @@ module github.com/johnkerl/miller
 // * go build   github.com/johnkerl/miller/cmd/mlr
 // * go install github.com/johnkerl/miller/cmd/mlr
 
+// go get github.com/johnkerl/lumin@v1.0.0
+// Local development:
+// replace github.com/johnkerl/lumin => /Users/kerl/git/johnkerl/lumin
+
 go 1.15
 
 require (
-	github.com/goccmack/gocc v0.0.0-20211213154817-7ea699349eca // indirect
+	github.com/johnkerl/lumin v1.0.0 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 	github.com/lestrrat-go/strftime v1.0.5
 	github.com/mattn/go-isatty v0.0.14
diff --git a/go.sum b/go.sum
index 0ddbd2245..b00489b38 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/goccmack/gocc v0.0.0-20211213154817-7ea699349eca h1:NuA6w6b01Ojdig+4K1l9p4Pp3unlv4owphbOiENm8m4=
-github.com/goccmack/gocc v0.0.0-20211213154817-7ea699349eca/go.mod h1:c4Mb67Mg9+pl6OlxvnFBUiiQOSlXfh0QukINLl54OD0=
+github.com/johnkerl/lumin v1.0.0 h1:CV34cHZOJ92Y02RbQ0rd4gA0C06Qck9q8blOyaPoWpU=
+github.com/johnkerl/lumin v1.0.0/go.mod h1:eLf5AdQOaLvzZ2zVy4REr/DSeEwG+CZreHwNLICqv9E=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
@@ -20,38 +20,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
diff --git a/internal/pkg/cli/option_parse.go b/internal/pkg/cli/option_parse.go
index b9ed6d944..900728369 100644
--- a/internal/pkg/cli/option_parse.go
+++ b/internal/pkg/cli/option_parse.go
@@ -2452,8 +2452,11 @@ How you can control colorization:
 If environment-variable settings and command-line flags are both provided, the
 latter take precedence.
 
-Please do mlr ` + "`--list-color-codes`" + ` to see the available color codes (like 170),
-and ` + "`mlr --list-color-names`" + ` to see available names (like ` + "`orchid`" + `).
+Colors can be specified using names such as "red" or "orchid": please see
+` + "`mlr --list-color-names`" + ` to see available names. They can also be specified using
+numbers in the range 0..255, like 170: please see ` + "`mlr --list-color-codes`" + `.
+You can also use "bold", "underline", and/or "reverse". Additionally, combinations of
+those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc.
 `)
 }
 
diff --git a/internal/pkg/colorizer/colorizer.go b/internal/pkg/colorizer/colorizer.go
index d04eb7934..2438aa949 100644
--- a/internal/pkg/colorizer/colorizer.go
+++ b/internal/pkg/colorizer/colorizer.go
@@ -9,10 +9,9 @@
 package colorizer
 
 import (
-	"fmt"
 	"os"
-	"strconv"
 
+	lumin "github.com/johnkerl/lumin/pkg/colors"
 	"github.com/mattn/go-isatty"
 )
 
@@ -35,51 +34,51 @@ func SetColorization(arg TOutputColorization) {
 
 // For command-line flags like --pass-color 208 etc
 func SetKeyColor(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		keyColorString = makeColorString(code)
+		keyColorString = escape
 	}
 	return ok
 }
 func SetValueColor(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		valueColorString = makeColorString(code)
+		valueColorString = escape
 	}
 	return ok
 }
 func SetPassColor(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		passColorString = makeColorString(code)
+		passColorString = escape
 	}
 	return ok
 }
 func SetFailColor(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		failColorString = makeColorString(code)
+		failColorString = escape
 	}
 	return ok
 }
 func SetREPLPS1Color(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		replPS1ColorString = makeColorString(code)
+		replPS1ColorString = escape
 	}
 	return ok
 }
 func SetREPLPS2Color(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		replPS2ColorString = makeColorString(code)
+		replPS2ColorString = escape
 	}
 	return ok
 }
 func SetHelpColor(name string) bool {
-	code, ok := makeColorCodeFromName(name)
+	escape, ok := lumin.MakeANSIEscapesFromName(name)
 	if ok {
-		helpColorString = makeColorString(code)
+		helpColorString = escape
 	}
 	return ok
 }
@@ -116,61 +115,81 @@ func MaybeColorizeHelp(text string, outputIsStdout bool) string {
 // ListColorCodes shows codes in the range 0..255.
 // For --list-color-codes command-line flag.
 func ListColorCodes() {
-	fmt.Println("Available color codes:")
-	for i := 0; i <= 255; i++ {
-		fmt.Printf("%s%3d%s", makeColorString(i), i, defaultColorString)
-		if i%16 < 15 {
-			fmt.Print(" ")
-		} else {
-			fmt.Print("\n")
-		}
-	}
+	lumin.ListColorCodes()
 }
 
 // ListColorNames shows names for codes in the range 0..255.
 // For --list-color-names command-line flag.
 func ListColorNames() {
-	fmt.Println("Available color names:")
-	for _, pair := range namesAndCodes {
-		fmt.Printf(
-			"%s%-20s%s %d\n",
-			makeColorString(pair.code), pair.name, defaultColorString, pair.code,
-		)
+	lumin.ListColorNames()
+}
+
+// ================================================================
+// Internal implementation
+
+func maybeColorize(text string, colorString string, outputIsStdout bool) string {
+	if outputIsStdout && stdoutIsATTY {
+		if colorization == ColorizeOutputNever {
+			return text
+		} else {
+			return colorize(text, colorString)
+		}
+	} else {
+		if colorization == ColorizeOutputAlways {
+			return colorize(text, colorString)
+		} else {
+			return text
+		}
 	}
 }
 
-// makeColorCodeFromName looks up a named code, if available: e.g. "orchid"
-// maps to 170.
-func makeColorCodeFromName(name string) (int, bool) {
-	// Things like "170"
-	code, err := strconv.Atoi(name)
-	if err == nil {
-		return code, true
-	}
+func colorize(text string, colorString string) string {
+	return colorString + text + defaultColorString
+}
 
-	// Things like "orchid"
-	for _, pair := range namesAndCodes {
-		if pair.name == name {
-			return pair.code, true
+// GetColorization is for the CSV writer
+func GetColorization(outputIsStdout bool, isKey bool) (string, string) {
+	if outputIsStdout && stdoutIsATTY {
+		if colorization == ColorizeOutputNever {
+			return "", ""
+		} else {
+			if isKey {
+				return keyColorString, defaultColorString
+			} else {
+				return valueColorString, defaultColorString
+			}
+		}
+	} else {
+		if colorization == ColorizeOutputAlways {
+			if isKey {
+				return keyColorString, defaultColorString
+			} else {
+				return valueColorString, defaultColorString
+			}
+		} else {
+			return "", ""
 		}
 	}
-	return -1, false
 }
 
 // ================================================================
 // Internal implementation
 
 // Default ANSI color codes
-var keyColorString = make256ColorString(208)  // orange
-var valueColorString = make256ColorString(33) // blue
-var passColorString = make16ColorString(10)   // bold green
-var failColorString = make16ColorString(9)    // bold red
-var replPS1ColorString = make16ColorString(9) // bold red
-var replPS2ColorString = make16ColorString(1) // red
-var helpColorString = make16ColorString(9)    // bold red
+// 6.0.0:
+// var keyColorString = lumin.MakeANSIEscapesFromNameUnconditionally("orange")
+// var valueColorString = lumin.MakeANSIEscapesFromNameUnconditionally("blue")
+// 6.1.0:
+var keyColorString = lumin.MakeANSIEscapesFromNameUnconditionally("bold-underline")
+var valueColorString = lumin.MakeANSIEscapesFromNameUnconditionally("plain")
+var passColorString = lumin.MakeANSIEscapesFromNameUnconditionally("bold-lime")
+var failColorString = lumin.MakeANSIEscapesFromNameUnconditionally("bold-red")
+var replPS1ColorString = lumin.MakeANSIEscapesFromNameUnconditionally("bold-red")
+var replPS2ColorString = lumin.MakeANSIEscapesFromNameUnconditionally("red")
+var helpColorString = lumin.MakeANSIEscapesFromNameUnconditionally("bold-red")
 
 // Used to switch back to default color
-var defaultColorString = "\033[0m"
+var defaultColorString = "\u001b[0m"
 
 // Default: colorize if writing to stdout and if stdout is a TTY
 var colorization TOutputColorization = ColorizeOutputIfTTY
@@ -218,6 +237,15 @@ func init() {
 	}
 }
 
+func makeColorStringFromEnv(envName string) (string, bool) {
+	envValue := os.Getenv(envName)
+	if envValue == "" {
+		return "", false
+	}
+
+	return lumin.MakeANSIEscapesFromName(envValue)
+}
+
 func getStdoutIsATTY() bool {
 	// Don't try ANSI color on Windows (except Cygwin)
 	if os.Getenv("TERM") == "" {
@@ -231,366 +259,3 @@ func getStdoutIsATTY() bool {
 	}
 	return false
 }
-
-// make16ColorString constructs an ANSI-16-color-mode escape sequence
-func make16ColorString(i int) string {
-	i &= 15
-	boldBit := (i >> 3) & 1
-	colorBits := i & 7
-	return fmt.Sprintf("\033[%d;%dm", boldBit, 30+colorBits)
-}
-
-// make256ColorString constructs an ANSI-256-color-mode escape sequence
-func make256ColorString(i int) string {
-	i &= 255
-	return fmt.Sprintf("\033[1;38;5;%dm", i&255)
-}
-
-// makeColorString constructs an ANSI-16-color-mode escape sequence if arg is
-// in 0..15, else ANSI-256-color-mode escape sequence
-func makeColorString(i int) string {
-	if 0 <= i && i <= 15 {
-		return make16ColorString(i)
-	} else {
-		return make256ColorString(i)
-	}
-}
-
-func makeColorStringFromEnv(envName string) (string, bool) {
-	envValue := os.Getenv(envName)
-	if envValue == "" {
-		return "", false
-	}
-	code, ok := makeColorCodeFromName(envValue)
-	if !ok {
-		return "", false // TODO: return error?
-	}
-	if code < 0 {
-		return "", false // TODO: return error?
-	}
-	code &= 255
-	return makeColorString(code), true
-}
-
-func maybeColorize(text string, colorString string, outputIsStdout bool) string {
-	if outputIsStdout && stdoutIsATTY {
-		if colorization == ColorizeOutputNever {
-			return text
-		} else {
-			return colorize(text, colorString)
-		}
-	} else {
-		if colorization == ColorizeOutputAlways {
-			return colorize(text, colorString)
-		} else {
-			return text
-		}
-	}
-}
-
-func colorize(text string, colorString string) string {
-	return colorString + text + defaultColorString
-}
-
-// GetColorization is for the CSV writer
-func GetColorization(outputIsStdout bool, isKey bool) (string, string) {
-	if outputIsStdout && stdoutIsATTY {
-		if colorization == ColorizeOutputNever {
-			return "", ""
-		} else {
-			if isKey {
-				return keyColorString, defaultColorString
-			} else {
-				return valueColorString, defaultColorString
-			}
-		}
-	} else {
-		if colorization == ColorizeOutputAlways {
-			if isKey {
-				return keyColorString, defaultColorString
-			} else {
-				return valueColorString, defaultColorString
-			}
-		} else {
-			return "", ""
-		}
-	}
-}
-
-// ----------------------------------------------------------------
-// Name-to-code lookup table.
-//
-// Source: https://jonasjacek.github.io/colors/
-//
-// This is intentionally an array, not a map.
-//
-// Map would be more efficient for lookup but maps do not preserve
-// insertion-ordering in Go -- so, doing 'list colors' would print them in a
-// random order. To do nice 'list colors' we'd have to put them in this kind of
-// array and sort anyway. Also, lookups are done exactly once at program start
-// -- so, it's more efficient to make this an array with lookup by search, than
-// to populate a hash-map (computing hashcodes) for a use-once hash lookup.
-
-type tNameAndCode struct {
-	name string
-	code int
-}
-
-var namesAndCodes = []tNameAndCode{
-	{"aqua", 14},
-	{"aquamarine1", 122},
-	// {"aquamarine1", 86}, // duplicate
-	{"aquamarine3", 79},
-	{"black", 0},
-	{"blue", 12},
-	{"blue1", 21},
-	{"blue3", 19},
-	// {"blue3", 20}, // duplicate
-	{"blueviolet", 57},
-	{"cadetblue", 72},
-	// {"cadetblue", 73}, // duplicate
-	{"chartreuse1", 118},
-	{"chartreuse2", 112},
-	// {"chartreuse2", 82}, // duplicate
-	{"chartreuse3", 70},
-	// {"chartreuse3", 76}, // duplicate
-	{"chartreuse4", 64},
-	{"cornflowerblue", 69},
-	{"cornsilk1", 230},
-	{"cyan1", 51},
-	{"cyan2", 50},
-	{"cyan3", 43},
-	{"darkblue", 18},
-	{"darkcyan", 36},
-	{"darkgoldenrod", 136},
-	{"darkgreen", 22},
-	{"darkkhaki", 143},
-	{"darkmagenta", 90},
-	// {"darkmagenta", 91}, // duplicate
-	{"darkolivegreen1", 191},
-	// {"darkolivegreen1", 192}, // duplicate
-	{"darkolivegreen2", 155},
-	{"darkolivegreen3", 107},
-	// {"darkolivegreen3", 113}, // duplicate
-	// {"darkolivegreen3", 149}, // duplicate
-	{"darkorange", 208},
-	{"darkorange3", 130},
-	// {"darkorange3", 166}, // duplicate
-	{"darkred", 52},
-	// {"darkred", 88}, // duplicate
-	{"darkseagreen", 108},
-	{"darkseagreen1", 158},
-	// {"darkseagreen1", 193}, // duplicate
-	{"darkseagreen2", 151},
-	// {"darkseagreen2", 157}, // duplicate
-	{"darkseagreen3", 115},
-	// {"darkseagreen3", 150}, // duplicate
-	{"darkseagreen4", 65},
-	// {"darkseagreen4", 71}, // duplicate
-	{"darkslategray1", 123},
-	{"darkslategray2", 87},
-	{"darkslategray3", 116},
-	{"darkturquoise", 44},
-	{"darkviolet", 128},
-	// {"darkviolet", 92}, // duplicate
-	{"deeppink1", 198},
-	// {"deeppink1", 199}, // duplicate
-	{"deeppink2", 197},
-	{"deeppink3", 161},
-	// {"deeppink3", 162}, // duplicate
-	{"deeppink4", 125},
-	// {"deeppink4", 53}, // duplicate
-	// {"deeppink4", 89}, // duplicate
-	{"deepskyblue1", 39},
-	{"deepskyblue2", 38},
-	{"deepskyblue3", 31},
-	// {"deepskyblue3", 32}, // duplicate
-	{"deepskyblue4", 23},
-	// {"deepskyblue4", 24}, // duplicate
-	// {"deepskyblue4", 25}, // duplicate
-	{"dodgerblue1", 33},
-	{"dodgerblue2", 27},
-	{"dodgerblue3", 26},
-	{"fuchsia", 13},
-	{"gold1", 220},
-	{"gold3", 142},
-	// {"gold3", 178}, // duplicate
-	{"green", 2},
-	{"green1", 46},
-	{"green3", 34},
-	// {"green3", 40}, // duplicate
-	{"green4", 28},
-	{"greenyellow", 154},
-	{"grey", 8},
-	{"grey0", 16},
-	{"grey100", 231},
-	{"grey11", 234},
-	{"grey15", 235},
-	{"grey19", 236},
-	{"grey23", 237},
-	{"grey27", 238},
-	{"grey3", 232},
-	{"grey30", 239},
-	{"grey35", 240},
-	{"grey37", 59},
-	{"grey39", 241},
-	{"grey42", 242},
-	{"grey46", 243},
-	{"grey50", 244},
-	{"grey53", 102},
-	{"grey54", 245},
-	{"grey58", 246},
-	{"grey62", 247},
-	{"grey63", 139},
-	{"grey66", 248},
-	{"grey69", 145},
-	{"grey7", 233},
-	{"grey70", 249},
-	{"grey74", 250},
-	{"grey78", 251},
-	{"grey82", 252},
-	{"grey84", 188},
-	{"grey85", 253},
-	{"grey89", 254},
-	{"grey93", 255},
-	{"honeydew2", 194},
-	{"hotpink", 205},
-	// {"hotpink", 206}, // duplicate
-	{"hotpink2", 169},
-	{"hotpink3", 132},
-	// {"hotpink3", 168}, // duplicate
-	{"indianred", 131},
-	// {"indianred", 167}, // duplicate
-	{"indianred1", 203},
-	// {"indianred1", 204}, // duplicate
-	{"khaki1", 228},
-	{"khaki3", 185},
-	{"lightcoral", 210},
-	{"lightcyan1", 195},
-	{"lightcyan3", 152},
-	{"lightgoldenrod1", 227},
-	{"lightgoldenrod2", 186},
-	// {"lightgoldenrod2", 221}, // duplicate
-	// {"lightgoldenrod2", 222}, // duplicate
-	{"lightgoldenrod3", 179},
-	{"lightgreen", 119},
-	// {"lightgreen", 120}, // duplicate
-	{"lightpink1", 217},
-	{"lightpink3", 174},
-	{"lightpink4", 95},
-	{"lightsalmon1", 216},
-	{"lightsalmon3", 137},
-	// {"lightsalmon3", 173}, // duplicate
-	{"lightseagreen", 37},
-	{"lightskyblue1", 153},
-	{"lightskyblue3", 109},
-	// {"lightskyblue3", 110}, // duplicate
-	{"lightslateblue", 105},
-	{"lightslategrey", 103},
-	{"lightsteelblue", 147},
-	{"lightsteelblue1", 189},
-	{"lightsteelblue3", 146},
-	{"lightyellow3", 187},
-	{"lime", 10},
-	{"magenta1", 201},
-	{"magenta2", 165},
-	//{"magenta2", 200}, // duplicate
-	{"magenta3", 127},
-	// {"magenta3", 163}, // duplicate
-	// {"magenta3", 164}, // duplicate
-	{"maroon", 1},
-	{"mediumorchid", 134},
-	{"mediumorchid1", 171},
-	// {"mediumorchid1", 207}, // duplicate
-	{"mediumorchid3", 133},
-	{"mediumpurple", 104},
-	{"mediumpurple1", 141},
-	{"mediumpurple2", 135},
-	// {"mediumpurple2", 140}, // duplicate
-	{"mediumpurple3", 97},
-	// {"mediumpurple3", 98}, // duplicate
-	{"mediumpurple4", 60},
-	{"mediumspringgreen", 49},
-	{"mediumturquoise", 80},
-	{"mediumvioletred", 126},
-	{"mistyrose1", 224},
-	{"mistyrose3", 181},
-	{"navajowhite1", 223},
-	{"navajowhite3", 144},
-	{"navy", 4},
-	{"navyblue", 17},
-	{"olive", 3},
-	{"orange1", 214},
-	{"orange3", 172},
-	{"orange4", 58},
-	// {"orange4", 94}, // duplicate
-	{"orangered1", 202},
-	{"orchid", 170},
-	{"orchid1", 213},
-	{"orchid2", 212},
-	{"palegreen1", 121},
-	// {"palegreen1", 156}, // duplicate
-	{"palegreen3", 114},
-	// {"palegreen3", 77}, // duplicate
-	{"paleturquoise1", 159},
-	{"paleturquoise4", 66},
-	{"palevioletred1", 211},
-	{"pink1", 218},
-	{"pink3", 175},
-	{"plum1", 219},
-	{"plum2", 183},
-	{"plum3", 176},
-	{"plum4", 96},
-	{"purple", 129},
-	//{"purple", 5}, // duplicate
-	//{"purple", 93}, // duplicate
-	{"purple3", 56},
-	{"purple4", 54},
-	//{"purple4", 55}, // duplicate
-	{"red", 9},
-	{"red1", 196},
-	{"red3", 124},
-	//{"red3", 160}, // duplicate
-	{"rosybrown", 138},
-	{"royalblue1", 63},
-	{"salmon1", 209},
-	{"sandybrown", 215},
-	{"seagreen1", 84},
-	//{"seagreen1", 85}, // duplicate
-	{"seagreen2", 83},
-	{"seagreen3", 78},
-	{"silver", 7},
-	{"skyblue1", 117},
-	{"skyblue2", 111},
-	{"skyblue3", 74},
-	{"slateblue1", 99},
-	{"slateblue3", 61},
-	//{"slateblue3", 62}, // duplicate
-	{"springgreen1", 48},
-	{"springgreen2", 42},
-	//{"springgreen2", 47}, // duplicate
-	{"springgreen3", 35},
-	//{"springgreen3", 41}, // duplicate
-	{"springgreen4", 29},
-	{"steelblue", 67},
-	{"steelblue1", 75},
-	//{"steelblue1", 81}, // duplicate
-	{"steelblue3", 68},
-	{"tan", 180},
-	{"teal", 6},
-	{"thistle1", 225},
-	{"thistle3", 182},
-	{"turquoise2", 45},
-	{"turquoise4", 30},
-	{"violet", 177},
-	{"wheat1", 229},
-	{"wheat4", 101},
-	{"white", 15},
-	{"yellow", 11},
-	{"yellow1", 226},
-	{"yellow2", 190},
-	{"yellow3", 148},
-	//{"yellow3", 184}, // duplicate
-	{"yellow4", 100},
-	//{"yellow4", 106}, // duplicate
-}
diff --git a/man/manpage.txt b/man/manpage.txt
index bdba32306..456700526 100644
--- a/man/manpage.txt
+++ b/man/manpage.txt
@@ -600,8 +600,11 @@ OUTPUT-COLORIZATION FLAGS
        If environment-variable settings and command-line flags are both provided, the
        latter take precedence.
 
-       Please do mlr `--list-color-codes` to see the available color codes (like 170),
-       and `mlr --list-color-names` to see available names (like `orchid`).
+       Colors can be specified using names such as "red" or "orchid": please see
+       `mlr --list-color-names` to see available names. They can also be specified using
+       numbers in the range 0..255, like 170: please see `mlr --list-color-codes`.
+       You can also use "bold", "underline", and/or "reverse". Additionally, combinations of
+       those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc.
 
        --always-color or -C     Instructs Miller to colorize output even when it
                                 normally would not. Useful for piping output to `less
@@ -3110,4 +3113,4 @@ SEE ALSO
 
 
 
-                                  2022-01-27                         MILLER(1)
+                                  2022-01-30                         MILLER(1)
diff --git a/man/mlr.1 b/man/mlr.1
index 57011f3e4..52cd18b35 100644
--- a/man/mlr.1
+++ b/man/mlr.1
@@ -2,12 +2,12 @@
 .\"     Title: mlr
 .\"    Author: [see the "AUTHOR" section]
 .\" Generator: ./mkman.rb
-.\"      Date: 2022-01-27
+.\"      Date: 2022-01-30
 .\"    Manual: \ \&
 .\"    Source: \ \&
 .\"  Language: English
 .\"
-.TH "MILLER" "1" "2022-01-27" "\ \&" "\ \&"
+.TH "MILLER" "1" "2022-01-30" "\ \&" "\ \&"
 .\" -----------------------------------------------------------------
 .\" * Portability definitions
 .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -719,8 +719,11 @@ How you can control colorization:
 If environment-variable settings and command-line flags are both provided, the
 latter take precedence.
 
-Please do mlr `--list-color-codes` to see the available color codes (like 170),
-and `mlr --list-color-names` to see available names (like `orchid`).
+Colors can be specified using names such as "red" or "orchid": please see
+`mlr --list-color-names` to see available names. They can also be specified using
+numbers in the range 0..255, like 170: please see `mlr --list-color-codes`.
+You can also use "bold", "underline", and/or "reverse". Additionally, combinations of
+those can be joined with a "-", like "red-bold", "bold-170", "bold-underline", etc.
 
 --always-color or -C     Instructs Miller to colorize output even when it
                          normally would not. Useful for piping output to `less
diff --git a/todo.txt b/todo.txt
index 5354f298a..7dc1b0e45 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,21 +1,46 @@
 ===============================================================
 RELEASES
 
-* follow ...
 * plan 6.1.0
   ? w/ natural sort order
   ? strptime
-  ? split
-  ? fmt/unfmt/regex doc
   ? datediff et al.
+  ? mlr join --left-fields a,b,c
+  ? rank
+  ? ?foo and ??foo @ repl help
+  o default colors; bold/underline/reverse
+  o fmt/unfmt/regex doc
+  o FAQ/examples reorg
+  k array concat
+  k format/unformat
+  k split
+  k slwin & shift-lead
+* plan 6.2.0
+  k 0o.. octal literals in the DSL
+  ? YAML
+-> more badges -- ?
 
 ================================================================
 FEATURES
 
+----------------------------------------------------------------
+* colors
+  o lumin import
+    k lumin verstag
+    - lumin needs bold/underline/reversed -> help; plus x-y-z syntax
+    k wup for local go.mod override
+  o RT w/ -C
+  o olh/webdocs x several
+
+----------------------------------------------------------------
+* natural sort order
+   https://github.com/facette/natsort
+
 ----------------------------------------------------------------
 example pages as feature catch-up:
 * example page for format/scan options
   o format/unformat
+    x=unformat(1,2); x; string(x); is_error(x)
   o strmatch
   o =~
 * separate examples section from (new) FAQs section
@@ -55,6 +80,8 @@ k better print-interpolate with {} etc
 
 ----------------------------------------------------------------
 ! CSV default unsparsify ... note this must trust the *first* record .......
+* CSV: acceptance somehow for \" in place of ""
+  o & more flex more generally
 
 ----------------------------------------------------------------
 ! quoted NIDX
@@ -114,6 +141,7 @@ k better print-interpolate with {} etc
 UX
 
 * ?xyz and ??xyz in repl, for :help and :help find respectively
+? NF?
 
 ! bnf fix for '[[' ']]' etc -- make it a nesting of singles. since otherwise no '[[3,4]]' literals :(
 ! broadly rethink os.Exit, especially as affecting mlr repl
@@ -147,6 +175,33 @@ DOC
 ! link to SE table ...
   https://github.com/johnkerl/miller/discussions/609#discussioncomment-1115715
 
+* put -f link in scrpting.html
+* no --load in .mlrrc b/c
+  https://github.com/johnkerl/miller/security/advisories/GHSA-mw2v-4q78-j2cw
+  (system() in begin{})
+  but suggest alias
+
+* surface readme-versions.md @ install & tell them how to get current
+  o in particular, older distros won't auto-update
+
+* how-to-contribute @ rmd
+* more clear miller docs == head
+
+* go version & why not generics (not all distros w/ newer go compilers)
+
+* document the fill-empty verb in
+  https://miller.readthedocs.io/en/latest/reference-main-null-data/#rules-for-null-handling
+
+* mlr R numpy/scipy/pandas ref.
+* ffmts page <-> R read.table read.csv ...
+* https://github.com/agarrharr/awesome-cli-apps#data-manipulation
+
+* "Miller, if I understand correctly, was born for those who already frequented
+  the command line, awk, for users with at least average data skills. While for
+  me, a basic user, it made me discover how it gives the possibility of
+  analyzing and transforming data, even complex ones, with a very low learning
+  curve."
+
 * document cloudthings, e.g.
   o go.yml
   o codespell.yml
@@ -273,10 +328,14 @@ INFO
 
 i https://en.wikipedia.org/wiki/Delimiter#Delimiter_collision
 
+i https://framework.frictionlessdata.io/docs/tutorials/working-with-cli/
+
 i https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/
 
 i go tool nm -size mlr | sort -nrk 2
 
+i https://go.dev/play/p/hrB77U3d0S3?v=gotip
+
 * godoc notes:
   o go get golang.org/x/tools/cmd/godoc
   o dev mode: