Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
4 changed files with 38 additions and 22 deletions
Showing only changes of commit 5b2bb7e681 - Show all commits
+3 -3
View File
@@ -441,14 +441,14 @@ import (
"github.com/larvit/fejkdata/data" "github.com/larvit/fejkdata/data"
) )
//go:embed *.json *.tsv //go:embed *.json
var mydata embed.FS var mydata embed.FS
func main() { os.Exit(cli.Main(append(data.Modules(), mydata)...)) } func main() { os.Exit(cli.Main(append(data.Modules(), mydata)...)) }
``` ```
It takes `fejkdata`'s flags and exit codes, and names itself by the file it runs from in Add `*.tsv` to the embed where your data holds a table. The CLI takes `fejkdata`'s flags
its help and errors. `--no-shipped-data` leaves out the modules passed to `Main`, and and exit codes, and names itself by the file it runs from in its help and errors. `--no-shipped-data` leaves out the modules passed to `Main`, and
`--version` prints your module's version beside the fejkdata version it is built with. `--version` prints your module's version beside the fejkdata version it is built with.
`fejkdata`'s own `main` is `os.Exit(cli.Main(data.Modules()...))`. `fejkdata`'s own `main` is `os.Exit(cli.Main(data.Modules()...))`.
+30 -18
View File
@@ -22,16 +22,16 @@ import (
"github.com/larvit/fejkdata" "github.com/larvit/fejkdata"
) )
const usage = `Usage: <template> | fejkdata [flags] const usage = `Usage: <template> | COMMAND [flags]
fejkdata --list | --help | --version COMMAND --list | --help | --version
fejkdata reads one template from stdin and renders it: COMMAND reads one template from stdin and renders it:
echo 'name: {/sv_SE.person.last}' | fejkdata echo 'name: {/sv_SE.person.last}' | COMMAND
echo '{"format":"{x}","x":["bosse","lina"]}' | fejkdata echo '{"format":"{x}","x":["bosse","lina"]}' | COMMAND
fejkdata < template.txt COMMAND < template.txt
fejkdata <<'EOF' COMMAND <<'EOF'
born {date(1990-01-01,2010-12-31,'2006-01-02')} born {date(1990-01-01,2010-12-31,'2006-01-02')}
EOF EOF
@@ -42,7 +42,7 @@ path into one ({/sv_SE.person.last}), a table's row by key or name
({/misc.territory[SE]}), and .. steps up from a row to the row it links to ({/misc.territory[SE]}), and .. steps up from a row to the row it links to
({/geo.SE.locality..municipality.name}). ({/geo.SE.locality..municipality.name}).
Under --format text, the default, what you send is what renders, and fejkdata adds Under --format text, the default, what you send is what renders, and COMMAND adds
nothing: a format string keeps every byte, so echo's newline ends each render, and nothing: a format string keeps every byte, so echo's newline ends each render, and
printf '%s' sends none. -n joins renders with --separator, empty by default. A JSON printf '%s' sends none. -n joins renders with --separator, empty by default. A JSON
object, array or string drops the whitespace around it, and null alone renders nothing; object, array or string drops the whitespace around it, and null alone renders nothing;
@@ -569,7 +569,13 @@ func Main(modules ...fs.FS) int {
if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 { if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
stdin = nil stdin = nil
} }
name := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") name := filepath.Base(os.Args[0])
if ext := filepath.Ext(name); strings.EqualFold(ext, ".exe") {
name = strings.TrimSuffix(name, ext)
}
if name == "." || name == "" {
name = "fejkdata"
}
return run(name, modules, os.Args[1:], stdin, os.Stdout, os.Stderr) return run(name, modules, os.Args[1:], stdin, os.Stdout, os.Stderr)
} }
@@ -581,7 +587,7 @@ func run(name string, modules []fs.FS, args []string, stdin io.Reader, stdout, s
return misuse(stderr, name, err) return misuse(stderr, name, err)
} }
if in.help { if in.help {
io.WriteString(stdout, strings.ReplaceAll(usage, "fejkdata", name)) io.WriteString(stdout, strings.ReplaceAll(usage, "COMMAND", name))
return 0 return 0
} }
if in.version { if in.version {
@@ -600,8 +606,7 @@ func run(name string, modules []fs.FS, args []string, stdin io.Reader, stdout, s
} }
f, err := fejkdata.New(in.options(modules)...) f, err := fejkdata.New(in.options(modules)...)
if err != nil { if err != nil {
fmt.Fprintln(stderr, err) return fail(stderr, name, err)
return 1
} }
if in.list { if in.list {
for _, p := range f.List() { for _, p := range f.List() {
@@ -639,7 +644,7 @@ func fail(stderr io.Writer, name string, err error) int {
if errors.As(err, &te) { if errors.As(err, &te) {
return misuse(stderr, name, te.error) return misuse(stderr, name, te.error)
} }
fmt.Fprintln(stderr, err) report(stderr, name, err)
if errors.Is(err, fejkdata.ErrNoColumns) { if errors.Is(err, fejkdata.ErrNoColumns) {
fmt.Fprintln(stderr, "wrap that JSON in single quotes, which keep a shell from expanding its braces") fmt.Fprintln(stderr, "wrap that JSON in single quotes, which keep a shell from expanding its braces")
} }
@@ -647,11 +652,16 @@ func fail(stderr io.Writer, name string, err error) int {
} }
func misuse(stderr io.Writer, name string, err error) int { func misuse(stderr io.Writer, name string, err error) int {
// A library error opens with the library's name, so the prefix is not doubled. report(stderr, name, err)
fmt.Fprintf(stderr, "%s: %s\ntry '%s --help'\n", name, strings.TrimPrefix(err.Error(), "fejkdata: "), name) fmt.Fprintf(stderr, "try '%s --help'\n", name)
return 2 return 2
} }
// report writes err under the command name, in place of the library's own.
func report(stderr io.Writer, name string, err error) {
fmt.Fprintf(stderr, "%s: %s\n", name, strings.TrimPrefix(err.Error(), "fejkdata: "))
}
// fejkdataModule is the path of the module holding the library; the root package's path is // fejkdataModule is the path of the module holding the library; the root package's path is
// the module's. // the module's.
var fejkdataModule = reflect.TypeOf((*fejkdata.Generator)(nil)).Elem().PkgPath() var fejkdataModule = reflect.TypeOf((*fejkdata.Generator)(nil)).Elem().PkgPath()
@@ -662,13 +672,15 @@ func versionLine(name string, info *debug.BuildInfo) string {
if info == nil { if info == nil {
return "fejkdata devel" return "fejkdata devel"
} }
line := "fejkdata " + moduleVersion(info.Main) if info.Main.Path == fejkdataModule {
return "fejkdata " + moduleVersion(info.Main)
}
for _, m := range info.Deps { for _, m := range info.Deps {
if m.Path == fejkdataModule { if m.Path == fejkdataModule {
line = name + " " + moduleVersion(info.Main) + ", fejkdata " + moduleVersion(*m) return name + " " + moduleVersion(info.Main) + ", fejkdata " + moduleVersion(*m)
} }
} }
return line return name + " " + moduleVersion(info.Main) + ", fejkdata devel"
} }
// moduleVersion is the version m is built at: the replacement's where a replace directive // moduleVersion is the version m is built at: the replacement's where a replace directive
+4
View File
@@ -473,6 +473,10 @@ func TestRunNamesItselfByTheCommandName(t *testing.T) {
if code := run("mycli", shipped, nil, nil, &out, &errb); code != 2 || !strings.Contains(errb.String(), "| mycli\n") { if code := run("mycli", shipped, nil, nil, &out, &errb); code != 2 || !strings.Contains(errb.String(), "| mycli\n") {
t.Errorf("mycli with nothing piped = %d, %q, want a pipe into mycli", code, errb.String()) t.Errorf("mycli with nothing piped = %d, %q, want a pipe into mycli", code, errb.String())
} }
errb.Reset()
if code := run("mycli", shipped, []string{"-d", "/nope"}, strings.NewReader("{/sv_SE.person}"), &out, &errb); code != 1 || !strings.HasPrefix(errb.String(), "mycli: ") {
t.Errorf("mycli -d /nope = %d, %q, want a load error under mycli", code, errb.String())
}
if code := run("mycli", shipped, []string{"--help"}, nil, &out, &errb); code != 0 || !strings.HasPrefix(out.String(), "Usage: <template> | mycli [flags]\n") || strings.Contains(out.String(), "fejkdata") { if code := run("mycli", shipped, []string{"--help"}, nil, &out, &errb); code != 0 || !strings.HasPrefix(out.String(), "Usage: <template> | mycli [flags]\n") || strings.Contains(out.String(), "fejkdata") {
t.Errorf("mycli --help = %d, %q, want mycli named throughout", code, out.String()) t.Errorf("mycli --help = %d, %q, want mycli named throughout", code, out.String())
} }
+1 -1
View File
@@ -366,7 +366,7 @@ GitHub has no `tags/{tag}` endpoint and answers 404 whether or not the tag exist
### 58. Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go. ### 58. Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go.
GoReleaser attaches the binaries to the release the tag workflow publishes, and builds deb and rpm packages, an Alpine APK and an AUR package beside Homebrew and Scoop. A checkout build prints `devel` for `--version`; the binaries carry the stamped tag. v0.1.0 ships first, so it can be tested before it is packaged. GoReleaser attaches the binaries to the release the tag workflow publishes, and builds deb and rpm packages, an Alpine APK and an AUR package beside Homebrew and Scoop. The binaries carry the stamped tag, which `--version` prints. v0.1.0 ships first, so it can be tested before it is packaged.
### 206. Add `zh_CN` with its `geo/CN` tree. ### 206. Add `zh_CN` with its `geo/CN` tree.