From e4e853b6562d9ae186b649dde2825fe61629c6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bauer?= Date: Wed, 25 Dec 2024 23:36:31 +0100 Subject: [PATCH 1/3] Add `zk config alias`, resolves feature request in issue #253. --- docs/config/config-alias.md | 9 +- internal/cli/cmd/config.go | 140 ++++++++++++++++++++ main.go | 1 + tests/cmd-config-alias.tesh | 88 ++++++++++++ tests/fixtures/config-alias/.zk/config.toml | 31 +++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 internal/cli/cmd/config.go create mode 100644 tests/cmd-config-alias.tesh create mode 100644 tests/fixtures/config-alias/.zk/config.toml diff --git a/docs/config/config-alias.md b/docs/config/config-alias.md index d6582201..44809154 100644 --- a/docs/config/config-alias.md +++ b/docs/config/config-alias.md @@ -99,8 +99,8 @@ conf = '$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml"' Use this alias to send a list of space-separated file paths matching the given [filtering criteria](../notes/note-filtering.md) to another program. See -[send notes for processing by other programs](../tips/external-processing.md) for more -details. +[send notes for processing by other programs](../tips/external-processing.md) +for more details. ```toml paths = "zk list --quiet --format \"'{{path}}'\" --delimiter ' ' $@" @@ -235,3 +235,8 @@ cp = 'mkdir -p "$1" && zk list --quiet --format path --delimiter0 ${@:2} | xargs ``` Usage: `zk cp output/ --created-after 'last two weeks'` + +## Listing tags + +You can list all the aliases found in your configuration file using +`zk config alias`. diff --git a/internal/cli/cmd/config.go b/internal/cli/cmd/config.go new file mode 100644 index 00000000..9d400524 --- /dev/null +++ b/internal/cli/cmd/config.go @@ -0,0 +1,140 @@ +package cmd + +import ( + "fmt" + "io" + "os" + "sort" + + "github.com/zk-org/zk/internal/cli" + "github.com/zk-org/zk/internal/util/errors" + "github.com/zk-org/zk/internal/util/strings" +) + +// Config lists configuration setting +type Config struct { + Alias AliasList `cmd group:"cmd" default:"withargs" help:"List all the aliases."` +} + +// AliasList lists all the aliases. +type AliasList struct { + Format string `group:format short:f placeholder:TEMPLATE help:"Pretty print the list using a custom template or one of the predefined formats: short, full, json, jsonl."` + Header string `group:format help:"Arbitrary text printed at the start of the list."` + Footer string `group:format default:\n help:"Arbitrary text printed at the end of the list."` + Delimiter string "group:format short:d default:\n help:\"Print tags delimited by the given separator.\"" + Delimiter0 bool "group:format short:0 name:delimiter0 help:\"Print tags delimited by ASCII NUL characters. This is useful when used in conjunction with `xargs -0`.\"" + NoPager bool `group:format short:P help:"Do not pipe output into a pager."` + Quiet bool `group:format short:q help:"Do not print the total number of tags found."` +} + +func (cmd *AliasList) Run(container *cli.Container) error { + cmd.Header = strings.ExpandWhitespaceLiterals(cmd.Header) + cmd.Footer = strings.ExpandWhitespaceLiterals(cmd.Footer) + cmd.Delimiter = strings.ExpandWhitespaceLiterals(cmd.Delimiter) + + if cmd.Delimiter0 { + if cmd.Delimiter != "\n" { + return errors.New("--delimiter and --delimiter0 can't be used together") + } + if cmd.Header != "" { + return errors.New("--footer and --delimiter0 can't be used together") + } + if cmd.Footer != "\n" { + return errors.New("--footer and --delimiter0 can't be used together") + } + + cmd.Delimiter = "\x00" + cmd.Footer = "\x00" + } + + if cmd.Format == "json" || cmd.Format == "jsonl" { + if cmd.Header != "" { + return errors.New("--header can't be used with JSON format") + } + if cmd.Footer != "\n" { + return errors.New("--footer can't be used with JSON format") + } + if cmd.Delimiter != "\n" { + return errors.New("--delimiter can't be used with JSON format") + } + + switch cmd.Format { + case "json": + cmd.Delimiter = "," + cmd.Header = "[" + cmd.Footer = "]\n" + + case "jsonl": + // > The last character in the file may be a line separator, and it + // > will be treated the same as if there was no line separator + // > present. + // > https://jsonlines.org/ + cmd.Footer = "\n" + } + } + + var aliases = container.Config.Aliases + count := len(aliases) + keys := make([]string, count) + i := 0 + for k := range aliases { + keys[i] = k + i++ + } + sort.Strings(keys) + + format := cmd.aliasTemplate() + + var err = container.Paginate(cmd.NoPager, func(out io.Writer) error { + if cmd.Header != "" { + fmt.Fprint(out, cmd.Header) + } + for i, alias := range keys { + + if i > 0 { + fmt.Fprint(out, cmd.Delimiter) + } + if cmd.Format == "" || cmd.Format == "short" { + fmt.Fprintf(out, format, alias) + } else { + fmt.Fprintf(out, format, alias, aliases[alias]) + } + + i += 1 + } + if cmd.Footer != "" { + fmt.Fprint(out, cmd.Footer) + } + return nil + }) + + if err == nil && !cmd.Quiet { + if count > 1 { + fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, "aliases") + } else { + fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, "alias") + } + } + return err +} + +func (cmd *AliasList) aliasTemplate() string { + format := cmd.Format + if format == "" { + format = "short" + } + + templ, ok := defaultAliasFormats[format] + if !ok { + templ = strings.ExpandWhitespaceLiterals(format) + } + + return templ +} + +var defaultAliasFormats = map[string]string{ + "json": `{"%s":"%s"}`, + "jsonl": `{"%s":"%s"}`, + "short": `%s`, + "full": `%12s %s`, +} diff --git a/main.go b/main.go index 3f2ccac4..88a9fa0a 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ var Build = "dev" var root struct { Init cmd.Init `cmd group:"zk" help:"Create a new notebook in the given directory."` Index cmd.Index `cmd group:"zk" help:"Index the notes to be searchable."` + Config cmd.Config `cmd group:"zk" help:"List configuration parameters."` New cmd.New `cmd group:"notes" help:"Create a new note in the given notebook directory."` List cmd.List `cmd group:"notes" help:"List notes matching the given criteria."` diff --git a/tests/cmd-config-alias.tesh b/tests/cmd-config-alias.tesh new file mode 100644 index 00000000..f46145d5 --- /dev/null +++ b/tests/cmd-config-alias.tesh @@ -0,0 +1,88 @@ +$ cd config-alias + +# Print help for `zk config`. +$ zk config --help +>Usage: zk config +> +>List configuration parameters. +> +>Commands: +> config alias List all the aliases. +> +>Flags: +> -h, --help Show context-sensitive help. +> --notebook-dir=PATH Turn off notebook auto-discovery and set manually +> the notebook where commands are run. +> -W, --working-dir=PATH Run as if zk was started in instead of the +> current working directory. +> --no-input Never prompt or ask for confirmation. + +# Print help for `zk config alias`. +$ zk config alias --help +>Usage: zk config alias +> +>List all the aliases. +> +>Flags: +> -h, --help Show context-sensitive help. +> --notebook-dir=PATH Turn off notebook auto-discovery and set manually +> the notebook where commands are run. +> -W, --working-dir=PATH Run as if zk was started in instead of the +> current working directory. +> --no-input Never prompt or ask for confirmation. +> +>Formatting +> -f, --format=TEMPLATE Pretty print the list using a custom template or one +> of the predefined formats: short, full, json, jsonl. +> --header=STRING Arbitrary text printed at the start of the list. +> --footer="\\n" Arbitrary text printed at the end of the list. +> -d, --delimiter="\n" Print tags delimited by the given separator. +> -0, --delimiter0 Print tags delimited by ASCII NUL characters. This is +> useful when used in conjunction with `xargs -0`. +> -P, --no-pager Do not pipe output into a pager. +> -q, --quiet Do not print the total number of tags found. + +# Print short list. +$ zk config alias +>conf +>editlast +>hist +>list +>ls +>lucky +>path +>recent +2> +2>Found 8 aliases + +# Print full list. +$ zk config alias --format=full +> conf $EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml" +> editlast zk edit --limit 1 --sort modified- $@ +> hist zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch -- +> list zk list --quiet $@ +> ls zk list $@ +> lucky zk list --quiet --format full --sort random --limit 1 +> path zk list --quiet --format \{{path}} --delimiter , $@ +> recent zk edit --sort created- --created-after 'last two weeks' --interactive +2> +2>Found 8 aliases + +# Print json. +$ zk config alias --format=json +>[{"conf":"$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml""},{"editlast":"zk edit --limit 1 --sort modified- $@"},{"hist":"zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --"},{"list":"zk list --quiet $@"},{"ls":"zk list $@"},{"lucky":"zk list --quiet --format full --sort random --limit 1"},{"path":"zk list --quiet --format \{{path}} --delimiter , $@"},{"recent":"zk edit --sort created- --created-after 'last two weeks' --interactive"}] +2> +2>Found 8 aliases + +# Print jsonl. +$ zk config alias --format=jsonl +>{"conf":"$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml""} +>{"editlast":"zk edit --limit 1 --sort modified- $@"} +>{"hist":"zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --"} +>{"list":"zk list --quiet $@"} +>{"ls":"zk list $@"} +>{"lucky":"zk list --quiet --format full --sort random --limit 1"} +>{"path":"zk list --quiet --format \{{path}} --delimiter , $@"} +>{"recent":"zk edit --sort created- --created-after 'last two weeks' --interactive"} +2> +2>Found 8 aliases diff --git a/tests/fixtures/config-alias/.zk/config.toml b/tests/fixtures/config-alias/.zk/config.toml new file mode 100644 index 00000000..246e0584 --- /dev/null +++ b/tests/fixtures/config-alias/.zk/config.toml @@ -0,0 +1,31 @@ +[alias] +# Here are a few aliases to get you started. + +# Shortcut to a command. +ls = "zk list $@" + +# Default flags for an existing command. +list = "zk list --quiet $@" + +# Edit the last modified note. +editlast = "zk edit --limit 1 --sort modified- $@" + +# Edit the notes selected interactively among the notes created the last two weeks. +# This alias doesn't take any argument, so we don't use $@. +recent = "zk edit --sort created- --created-after 'last two weeks' --interactive" + +# Print paths separated with colons for the notes found with the given +# arguments. This can be useful to expand a complex search query into a flag +# taking only paths. For example: +# zk list --link-to "`zk path -m potatoe`" +path = "zk list --quiet --format {{path}} --delimiter , $@" + +# Show a random note. +lucky = "zk list --quiet --format full --sort random --limit 1" + +# Returns the Git history for the notes found with the given arguments. +# Note the use of a pipe and the location of $@. +hist = "zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --" + +# Edit this configuration file. +conf = '$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml"' From 55745b9dfa73c914ff72cde4ddf1f4ec0b33b1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bauer?= Date: Fri, 3 Jan 2025 21:27:56 +0100 Subject: [PATCH 2/3] Restructure `zk config`, so that `zk config --list OBJECT` list configuration objects like aliases, filters or extras. --- docs/config/config-alias.md | 4 +- docs/config/config-extra.md | 13 +++-- docs/config/config-filter.md | 5 ++ internal/cli/cmd/config.go | 46 ++++++++-------- ...config-alias.tesh => cmd-config-list.tesh} | 52 +++++++++---------- .../.zk/config.toml | 7 +++ 6 files changed, 73 insertions(+), 54 deletions(-) rename tests/{cmd-config-alias.tesh => cmd-config-list.tesh} (77%) rename tests/fixtures/{config-alias => config-list}/.zk/config.toml (92%) diff --git a/docs/config/config-alias.md b/docs/config/config-alias.md index 44809154..f51af7d0 100644 --- a/docs/config/config-alias.md +++ b/docs/config/config-alias.md @@ -236,7 +236,7 @@ cp = 'mkdir -p "$1" && zk list --quiet --format path --delimiter0 ${@:2} | xargs Usage: `zk cp output/ --created-after 'last two weeks'` -## Listing tags +## Listing aliases You can list all the aliases found in your configuration file using -`zk config alias`. +`zk config --list aliases`. diff --git a/docs/config/config-extra.md b/docs/config/config-extra.md index fe95df9f..837c326c 100644 --- a/docs/config/config-extra.md +++ b/docs/config/config-extra.md @@ -4,8 +4,8 @@ [creating new notes](../notes/note-creation.md), for example: - expanding custom metadata (author, subject, etc.) -- modifying a [template](../notes/template.md)'s output dynamically depending on the - value of an extra variable +- modifying a [template](../notes/template.md)'s output dynamically depending on + the value of an extra variable ## Static extra variables @@ -36,8 +36,8 @@ $ zk new --extra show-header=1,author=Thomas ## Using extra variables in templates After declaring extra variables, you can expand them inside the -[template used when creating new notes](../notes/template-creation.md), using the usual -[Handlebars syntax](../notes/template.md). +[template used when creating new notes](../notes/template-creation.md), using +the usual [Handlebars syntax](../notes/template.md). ```markdown # {{title}} @@ -46,3 +46,8 @@ Written by {{extra.author}}. {{#if extra.show-header}} Behold, the mighty dynamic header! {{/if}} ``` + +## Listing extras + +You can list all the extras found in your configuration file using +`zk config --list extras`. diff --git a/docs/config/config-filter.md b/docs/config/config-filter.md index 73f3a03e..2c288337 100644 --- a/docs/config/config-filter.md +++ b/docs/config/config-filter.md @@ -59,3 +59,8 @@ $ zk list --sort created journal # With the filter $ zk list journal ``` + +## Listing filters + +You can list all the filters found in your configuration file using +`zk config --list filters`. diff --git a/internal/cli/cmd/config.go b/internal/cli/cmd/config.go index 9d400524..d45b8a25 100644 --- a/internal/cli/cmd/config.go +++ b/internal/cli/cmd/config.go @@ -11,13 +11,9 @@ import ( "github.com/zk-org/zk/internal/util/strings" ) -// Config lists configuration setting -type Config struct { - Alias AliasList `cmd group:"cmd" default:"withargs" help:"List all the aliases."` -} - // AliasList lists all the aliases. -type AliasList struct { +type Config struct { + List string `short:l placeholder:OBJECT help:"list configuration objects. Possible ojects are aliases, filters or extras."` Format string `group:format short:f placeholder:TEMPLATE help:"Pretty print the list using a custom template or one of the predefined formats: short, full, json, jsonl."` Header string `group:format help:"Arbitrary text printed at the start of the list."` Footer string `group:format default:\n help:"Arbitrary text printed at the end of the list."` @@ -27,7 +23,7 @@ type AliasList struct { Quiet bool `group:format short:q help:"Do not print the total number of tags found."` } -func (cmd *AliasList) Run(container *cli.Container) error { +func (cmd *Config) Run(container *cli.Container) error { cmd.Header = strings.ExpandWhitespaceLiterals(cmd.Header) cmd.Footer = strings.ExpandWhitespaceLiterals(cmd.Footer) cmd.Delimiter = strings.ExpandWhitespaceLiterals(cmd.Delimiter) @@ -73,31 +69,41 @@ func (cmd *AliasList) Run(container *cli.Container) error { } } - var aliases = container.Config.Aliases - count := len(aliases) + var objects = make(map[string]string) + + switch cmd.List { + case "filters": + objects = container.Config.Filters + case "aliases": + objects = container.Config.Aliases + case "extras": + objects = container.Config.Extra + } + + count := len(objects) keys := make([]string, count) i := 0 - for k := range aliases { + for k := range objects { keys[i] = k i++ } sort.Strings(keys) - format := cmd.aliasTemplate() + format := cmd.mapTemplate() var err = container.Paginate(cmd.NoPager, func(out io.Writer) error { if cmd.Header != "" { fmt.Fprint(out, cmd.Header) } - for i, alias := range keys { + for i, o := range keys { if i > 0 { fmt.Fprint(out, cmd.Delimiter) } if cmd.Format == "" || cmd.Format == "short" { - fmt.Fprintf(out, format, alias) + fmt.Fprintf(out, format, o) } else { - fmt.Fprintf(out, format, alias, aliases[alias]) + fmt.Fprintf(out, format, o, objects[o]) } i += 1 @@ -109,22 +115,18 @@ func (cmd *AliasList) Run(container *cli.Container) error { }) if err == nil && !cmd.Quiet { - if count > 1 { - fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, "aliases") - } else { - fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, "alias") - } + fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, cmd.List) } return err } -func (cmd *AliasList) aliasTemplate() string { +func (cmd *Config) mapTemplate() string { format := cmd.Format if format == "" { format = "short" } - templ, ok := defaultAliasFormats[format] + templ, ok := defaultMapFormats[format] if !ok { templ = strings.ExpandWhitespaceLiterals(format) } @@ -132,7 +134,7 @@ func (cmd *AliasList) aliasTemplate() string { return templ } -var defaultAliasFormats = map[string]string{ +var defaultMapFormats = map[string]string{ "json": `{"%s":"%s"}`, "jsonl": `{"%s":"%s"}`, "short": `%s`, diff --git a/tests/cmd-config-alias.tesh b/tests/cmd-config-list.tesh similarity index 77% rename from tests/cmd-config-alias.tesh rename to tests/cmd-config-list.tesh index f46145d5..a1f2e86e 100644 --- a/tests/cmd-config-alias.tesh +++ b/tests/cmd-config-list.tesh @@ -1,14 +1,11 @@ -$ cd config-alias +$ cd config-list # Print help for `zk config`. $ zk config --help ->Usage: zk config +>Usage: zk config > >List configuration parameters. > ->Commands: -> config alias List all the aliases. -> >Flags: > -h, --help Show context-sensitive help. > --notebook-dir=PATH Turn off notebook auto-discovery and set manually @@ -16,20 +13,9 @@ $ zk config --help > -W, --working-dir=PATH Run as if zk was started in instead of the > current working directory. > --no-input Never prompt or ask for confirmation. - -# Print help for `zk config alias`. -$ zk config alias --help ->Usage: zk config alias > ->List all the aliases. -> ->Flags: -> -h, --help Show context-sensitive help. -> --notebook-dir=PATH Turn off notebook auto-discovery and set manually -> the notebook where commands are run. -> -W, --working-dir=PATH Run as if zk was started in instead of the -> current working directory. -> --no-input Never prompt or ask for confirmation. +> -l, --list=OBJECT list configuration objects. Possible ojects are +> aliases, filters or extras. > >Formatting > -f, --format=TEMPLATE Pretty print the list using a custom template or one @@ -42,8 +28,8 @@ $ zk config alias --help > -P, --no-pager Do not pipe output into a pager. > -q, --quiet Do not print the total number of tags found. -# Print short list. -$ zk config alias +# Print short aliases list. +$ zk config --list aliases >conf >editlast >hist @@ -55,8 +41,8 @@ $ zk config alias 2> 2>Found 8 aliases -# Print full list. -$ zk config alias --format=full +# Print full aliases list. +$ zk config --list aliases --format=full > conf $EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml" > editlast zk edit --limit 1 --sort modified- $@ > hist zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch -- @@ -68,14 +54,14 @@ $ zk config alias --format=full 2> 2>Found 8 aliases -# Print json. -$ zk config alias --format=json +# Print aliases as json. +$ zk config --list aliases --format=json >[{"conf":"$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml""},{"editlast":"zk edit --limit 1 --sort modified- $@"},{"hist":"zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --"},{"list":"zk list --quiet $@"},{"ls":"zk list $@"},{"lucky":"zk list --quiet --format full --sort random --limit 1"},{"path":"zk list --quiet --format \{{path}} --delimiter , $@"},{"recent":"zk edit --sort created- --created-after 'last two weeks' --interactive"}] 2> 2>Found 8 aliases -# Print jsonl. -$ zk config alias --format=jsonl +# Print aliases as jsonl. +$ zk config --list aliases --format=jsonl >{"conf":"$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml""} >{"editlast":"zk edit --limit 1 --sort modified- $@"} >{"hist":"zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --patch --"} @@ -86,3 +72,17 @@ $ zk config alias --format=jsonl >{"recent":"zk edit --sort created- --created-after 'last two weeks' --interactive"} 2> 2>Found 8 aliases + +# Print full filters list. +$ zk config --list filters --format=full +> journal --sort created journal +2> +2>Found 1 filters + +# Print full extras list. +$ zk config --list extras --format=full +> author Mickaël +> visibility public +2> +2>Found 2 extras + diff --git a/tests/fixtures/config-alias/.zk/config.toml b/tests/fixtures/config-list/.zk/config.toml similarity index 92% rename from tests/fixtures/config-alias/.zk/config.toml rename to tests/fixtures/config-list/.zk/config.toml index 246e0584..7098b18b 100644 --- a/tests/fixtures/config-alias/.zk/config.toml +++ b/tests/fixtures/config-list/.zk/config.toml @@ -29,3 +29,10 @@ hist = "zk list --format path --delimiter0 --quiet $@ | xargs -t -0 git log --pa # Edit this configuration file. conf = '$EDITOR "$ZK_NOTEBOOK_DIR/.zk/config.toml"' + +[filter] +journal = "--sort created journal" + +[extra] +visibility = "public" +author = "Mickaël" \ No newline at end of file From fb2bc406aa8b5fb127b61983172eff68944f68c8 Mon Sep 17 00:00:00 2001 From: tjex Date: Sat, 11 Jan 2025 20:48:00 +0100 Subject: [PATCH 3/3] add default case to config list cmd --- internal/cli/cmd/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/cli/cmd/config.go b/internal/cli/cmd/config.go index d45b8a25..8bad68da 100644 --- a/internal/cli/cmd/config.go +++ b/internal/cli/cmd/config.go @@ -13,7 +13,7 @@ import ( // AliasList lists all the aliases. type Config struct { - List string `short:l placeholder:OBJECT help:"list configuration objects. Possible ojects are aliases, filters or extras."` + List string `short:l placeholder:OBJECT help:"List configuration objects. Listable ojects are: aliases, filters and extras."` Format string `group:format short:f placeholder:TEMPLATE help:"Pretty print the list using a custom template or one of the predefined formats: short, full, json, jsonl."` Header string `group:format help:"Arbitrary text printed at the start of the list."` Footer string `group:format default:\n help:"Arbitrary text printed at the end of the list."` @@ -78,6 +78,9 @@ func (cmd *Config) Run(container *cli.Container) error { objects = container.Config.Aliases case "extras": objects = container.Config.Extra + default: + fmt.Print("Listable config objects are: filters, aliases and extras.\n") + os.Exit(1) } count := len(objects)