Skip to content

Commit

Permalink
Add zk config alias, resolves feature request in issue zk-org#253.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrebauer committed Dec 25, 2024
1 parent aa2e4ec commit e4e853b
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 2 deletions.
9 changes: 7 additions & 2 deletions docs/config/config-alias.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ' ' $@"
Expand Down Expand Up @@ -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`.
140 changes: 140 additions & 0 deletions internal/cli/cmd/config.go
Original file line number Diff line number Diff line change
@@ -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`,
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."`
Expand Down
88 changes: 88 additions & 0 deletions tests/cmd-config-alias.tesh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
$ cd config-alias

# Print help for `zk config`.
$ zk config --help
>Usage: zk config <command>
>
>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 <PATH> 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 <PATH> 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
31 changes: 31 additions & 0 deletions tests/fixtures/config-alias/.zk/config.toml
Original file line number Diff line number Diff line change
@@ -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"'

0 comments on commit e4e853b

Please sign in to comment.