clk

A very opinionated framework to ease the creation of command line interfaces

View on GitHub

An option is an optional parameter that is given a value. A flag is an optional parameter that is a boolean. An argument is a positional parameter that you must give.

A:kind-of-animal:$(clk_format_choice duck whale cat dog):A kind of animal:{"default": "duck", "nargs": 1}
O:--sound-of-animal:str:The sound the animal makes
O:--repeat:int:How many times to repeat the message:{"default": 0}
F:--shout:Print the message of the animal in capital case
if clk_given sound-of-animal
then
    msg="$(clk_value kind-of-animal) does $(clk_value sound-of-animal)"
else
    msg="I don't know what sound ${CLK___KIND_OF_ANIMAL} makes"
fi

if clk_true shout
then
    echo "${msg}"|tr '[:lower:]' '[:upper:]'
else
    echo "${msg}"
fi

for i in $(seq 1 "${CLK___REPEAT}")
do
    echo "${msg}"
done

Rather than opening my editor, --no-open says where it put the command, so that I can write the whole of it right away.

cat <<"EOH" > "$(clk command create bash animal --no-open)"
#!/usr/bin/env bash
  set -eu

source "_clk.sh"

clk_usage () {
    cat<<EOF
$0

This command shows something
--
A:kind-of-animal:$(clk_format_choice duck whale cat dog):A kind of animal:{"default": "duck", "nargs": 1}
O:--sound-of-animal:str:The sound the animal makes
O:--repeat:int:How many times to repeat the message:{"default": 0}
F:--shout:Print the message of the animal in capital case
EOF
}

clk_help_handler "$@"

if clk_given sound-of-animal
then
    msg="$(clk_value kind-of-animal) does $(clk_value sound-of-animal)"
else
    msg="I don't know what sound ${CLK___KIND_OF_ANIMAL} makes"
fi

if clk_true shout
then
    echo "${msg}"|tr '[:lower:]' '[:upper:]'
else
    echo "${msg}"
fi

for i in $(seq 1 "${CLK___REPEAT}")
do
    echo "${msg}"
done

EOH

We can see the help of those parameters in the help of the command.

clk animal --help | grep -- 'A kind of animal'
clk animal --help | grep -- '--sound-of-animal'
clk animal --help | grep -- '--repeat'
clk animal --help | grep -- '--shout'
[duck|whale|cat|dog]  A kind of animal  [default: duck]
--sound-of-animal TEXT  The sound the animal makes
--repeat INTEGER        How many times to repeat the message  [default: 0]
--shout                 Print the message of the animal in capital case

Passing --help-all additionally reveals the automatic options every clk command carries, whichever way it was written.

clk animal --help-all | grep -- '--in-project / --no-in-project'
--in-project / --no-in-project  Run the command in the project directory  [default: no-in-project]

completing against files

Sometimes, your command takes a file as input. For instance, let’s say you want a command that counts the words in a document.

You want pressing <TAB> on the argument to suggest files from the current directory, just like cat or ls would. To get this, use the file type.

A:document:file:The document to count words in
cat <<"EOH" > "$(clk command create bash wordcount --no-open)"
#!/usr/bin/env bash
  set -eu

source "_clk.sh"

clk_usage () {
    cat<<EOF
$0

Count the words in a document
--
A:document:file:The document to count words in
EOF
}

clk_help_handler "$@"

wc -w < "$(clk_value document)"

EOH
clk wordcount --help | grep DOCUMENT
Usage: clk wordcount [OPTIONS] [DOCUMENT]
  [DOCUMENT]  The document to count words in

Let’s try it.

echo "one two three four five" > testfile.txt
clk wordcount testfile.txt
5

And completion suggests files from the current directory.

clk wordcount te<TAB>
./testfile.txt

The file type works the same way for options. For instance, if you had written O:--document:file:The document to count words in instead, pressing <TAB> after --document would also suggest files.

All of that needs your shell to know about clk. clk completion show prints the code that teaches it.

clk completion show | head -6
_clk_completion() {
    local IFS=$'\n'
    local response

    response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD _CLK_COMPLETE=bash_complete $1)

clk completion install writes it in ~/.bash_completion, where your shell reads it when it starts.

clk completion install

Type the name in the wrong case though, and nothing comes back.

clk wordcount TE<TAB>

Install the completion telling it to ignore the case.

clk completion --case-insensitive install

The same key now finds the file.

clk wordcount TE<TAB>
./testfile.txt

passing a URL to a file argument

Sometimes, a command argument declared with the file type may also accept a URL. For instance, a command that installs a package might accept either a local file path or a URL to download from.

A:package:file:Package to install
echo "$(clk_value package)"

When passing a URL, the value should be preserved as-is, without being resolved as a file path.

clk showpackage https://example.com/path/to/package.apk
https://example.com/path/to/package.apk

giving the defaults in json

A command I wrote a while ago gives the defaults of its option and of its flag after a colon.

O:--times:int:How many times to greet:1
F:--loud/--quiet:Greet in capital case:True

clk still reads them, and tells me how to write them now.

clk greet
deprecated: In greet, O:--times:int:How many times to greet:1 gives its default after a colon. Give it in the json that ends the line instead, like O:name:type:help:{"default": "value"}
deprecated: In greet, F:--loud/--quiet:Greet in capital case:True gives its default after a colon. Give it in the json that ends the line instead, like F:name:help:{"default": true}
HELLO

A default that holds colons, like a url, would not fit after a colon anyway. The json that ends the line takes any default.

clk command create bash greet --force --description "Greet someone" \
    --option '--times:int:How many times to greet:{"default": 1}' \
    --flag '--loud/--quiet:Greet in capital case:{"default": true}' \
    --body '
msg=hello
if clk_true loud
then
    msg=HELLO
fi
for i in $(seq 1 "${CLK___TIMES}")
do
    echo "${msg}"
done
'
clk greet --times 2
HELLO
HELLO

When I forget the help of an option, clk tells me what it expected.

clk command create bash typo --option '--times:int'
clk typo
warning: When loading command typo at path ./clk-root/bin/typo: Expected format in typo is O:name:type:help[:{someextrajsondata}], got O:--times:int
error: Found the command typo in the resolver external but could not load it.
warning: Failed to get the command typo: Expected format in typo is O:name:type:help[:{someextrajsondata}], got O:--times:int
error: clk.typo could not be loaded. Re run with clk --develop to see the stacktrace or clk --debug-on-command-load-error to debug the load error

The same goes for a flag.

clk command create bash typo --force --flag '--loud'
clk typo
warning: When loading command typo at path ./clk-root/bin/typo: Expected format in typo is F:name:help[:{someextrajsondata}], got F:--loud
error: Found the command typo in the resolver external but could not load it.
warning: Failed to get the command typo: Expected format in typo is F:name:help[:{someextrajsondata}], got F:--loud
error: clk.typo could not be loaded. Re run with clk --develop to see the stacktrace or clk --debug-on-command-load-error to debug the load error