Commands#
Registering commands, subcommands, and dispatchers.
The experiment script skeleton#
The minimal top-and-tail every Knit experiment script needs.
APIs: knit, KNIT_SCRIPT_NAME
A Knit experiment is an ordinary Bash script that sources knit.sh near
the top and hands control to Knit’s dispatcher at the very bottom:
source knit.sh
Everything between those two lines registers commands. The final line dispatches the script’s arguments to whichever command the user named:
knit "$@"
Inside any command you can read KNIT_SCRIPT_NAME — the base name of the
script that sourced knit.sh — to build messages that name the experiment
(for example "Run ./${KNIT_SCRIPT_NAME} bootstrap first.").
Register a command#
Declare a command with knit_register, a body function, and knit_done.
APIs: knit_register, knit_done, knit_empty
Every command is declared with knit_register <name> <function> <description>,
followed by any parameter declarations, and closed with knit_done. The named
function is the command’s body. In the @ shorthand shown below, @command
takes only <name> <description> — it finds the body from the function
defined just beneath the declaration — and @done closes it:
@command "hello" "Print a greeting."
_hello() {
echo "Hello World"
}
@done
# @empty registers a command with no behavior --- handy for a stub or a
# grouping parent that only carries subcommands.
@command "todo" "Planned command, not implemented yet."
@empty
@done
knit_register "hello" _hello "Print a greeting."
_hello() {
echo "Hello World"
}
knit_done
# @empty registers a command with no behavior --- handy for a stub or a
# grouping parent that only carries subcommands.
knit_register "todo" knit_empty "Planned command, not implemented yet."
knit_done
Use knit_empty as the body when a command does nothing yet (a stub) or when
it exists only to group subcommands under it; the shorthand writes this as an
@empty marker line.
Set the program description#
Give the experiment a one-line description shown in top-level –help.
APIs: knit_set_program_description
Call knit_set_program_description once, after sourcing knit.sh, to set the
line shown at the top of the experiment’s --help:
knit_set_program_description "Demonstrate command registration and nesting."
Nest subcommands#
Group commands under a parent using colon-nested names.
APIs: knit_register, knit_empty, knit_with_subcommand_title
Colons in a command name nest it under a parent, so say:hello is invoked as
say hello. Register the parent with knit_empty (it only groups its
children) and, optionally, rename the section they appear under in --help with
knit_with_subcommand_title:
# A parent command that only groups subcommands: register it with @empty and
# (optionally) rename the section its children appear under in --help.
@command "say" "Say something."
@empty
@with_subcommand_title "Greetings"
@done
# Subcommands: colons in the name nest them under the parent, so they are invoked
# as "say hello" and "say goodbye".
@command "say:hello" "Say hello."
_say_hello() {
echo "Hello"
}
@done
@command "say:goodbye" "Say goodbye."
_say_goodbye() {
echo "Goodbye"
}
@done
# A parent command that only groups subcommands: register it with @empty and
# (optionally) rename the section its children appear under in --help.
knit_register "say" knit_empty "Say something."
knit_with_subcommand_title "Greetings"
knit_done
# Subcommands: colons in the name nest them under the parent, so they are invoked
# as "say hello" and "say goodbye".
knit_register "say:hello" _say_hello "Say hello."
_say_hello() {
echo "Hello"
}
knit_done
knit_register "say:goodbye" _say_goodbye "Say goodbye."
_say_goodbye() {
echo "Goodbye"
}
knit_done
Nesting can go deeper (say:hello:loudly → say hello loudly); each parent
level is registered the same way.
Add a dispatcher command#
Forward the trailing arguments to a target the command resolves itself.
APIs: knit_with_dispatch, knit_extra_index
A dispatcher takes a target after -- and forwards the remaining arguments to
it. Knit’s own setup, submit, and run are dispatchers — each takes a
setup, job, or app name after -- and runs it.
knit_with_dispatch marks the command as a dispatcher (which changes its
--help usage line to cmd [OPTIONS] -- <target> [OPTIONS] and allows the
trailing arguments), and the body reads them with knit_extra_index:
# A dispatcher forwards everything after "--" to a target it looks up itself.
# @with_dispatch changes the --help usage line to "tool [OPTIONS] -- <tool>"
# and allows the trailing arguments; the body reads them with knit_extra_index.
@command "tool" "Run a named tool with trailing arguments."
@with_dispatch "tool" "The tool name and its arguments (after --)."
_run_tool() {
local args=("$@") extra_index extra
extra_index=$(knit_extra_index "${args[@]}")
extra=("${args[@]:extra_index}")
printf 'running: %s\n' "${extra[*]}"
}
@done
# A dispatcher forwards everything after "--" to a target it looks up itself.
# @with_dispatch changes the --help usage line to "tool [OPTIONS] -- <tool>"
# and allows the trailing arguments; the body reads them with knit_extra_index.
knit_register "tool" _run_tool "Run a named tool with trailing arguments."
knit_with_dispatch "tool" "The tool name and its arguments (after --)."
_run_tool() {
local args=("$@") extra_index extra
extra_index=$(knit_extra_index "${args[@]}")
extra=("${args[@]:extra_index}")
printf 'running: %s\n' "${extra[*]}"
}
knit_done
knit_extra_index returns the index of the first argument after --, so
"${args[@]:extra_index}" is the forwarded target and its arguments.
Hide or gate a command#
Control a command’s visibility in –help and whether it may run.
APIs: knit_hidden, knit_hidden_if, knit_usable_if, knit_usable_before_bootstrap
knit_hidden removes a command from --help while leaving it fully
invokable; knit_usable_if blocks a command from running unless a predicate
holds, showing its description as the error otherwise:
# Hide a command from --help. It stays fully invokable.
@command "internal" "Internal helper."
@hidden
_internal_cmd() {
echo "internal"
}
@done
# Refuse to run a command unless a predicate holds. The predicate receives the
# command name and returns 0 (usable) or non-zero (blocked); its description is
# shown as the error when the command is invoked while blocked.
_danger_allowed() {
[[ "${ALLOW_DANGER:-}" == "1" ]]
}
@command "danger" "Perform a dangerous operation."
@usable_if _danger_allowed "Set ALLOW_DANGER=1 to enable this command."
_danger_cmd() {
echo "danger performed"
}
@done
# Hide a command from --help. It stays fully invokable.
knit_register "internal" _internal_cmd "Internal helper."
knit_hidden
_internal_cmd() {
echo "internal"
}
knit_done
# Refuse to run a command unless a predicate holds. The predicate receives the
# command name and returns 0 (usable) or non-zero (blocked); its description is
# shown as the error when the command is invoked while blocked.
_danger_allowed() {
[[ "${ALLOW_DANGER:-}" == "1" ]]
}
knit_register "danger" _danger_cmd "Perform a dangerous operation."
knit_usable_if _danger_allowed "Set ALLOW_DANGER=1 to enable this command."
_danger_cmd() {
echo "danger performed"
}
knit_done
Related decorators, all applied between knit_register and knit_done:
knit_hidden_if <predicate>— hide from--helponly when the predicate returns 0 (dynamic; the command stays invokable). Mutually exclusive withknit_hidden.knit_usable_before_bootstrap— allow the command to run (and appear in--help) on a fresh checkout beforebootstraphas created.knit/. Such a command may not declare a table or a--whenconstraint.
Each predicate is the name of a shell function that receives the command name and returns 0 or non-zero.
Highlight a command in –help#
Bold a command’s name in –help when a predicate holds.
APIs: knit_highlight_if
knit_highlight_if <predicate> bolds a command’s name in its parent’s
--help whenever the predicate returns 0 and the output is a terminal. It is
purely cosmetic: it never affects whether the command runs, its provenance, or how
describe sees it.
# Bold a command's name in --help when a predicate holds and output is a
# terminal. Purely cosmetic: it never affects whether the command runs.
_is_featured() {
true
}
@command "featured" "A command highlighted in --help."
@highlight_if _is_featured
_featured_cmd() {
echo "featured"
}
@done
# Bold a command's name in --help when a predicate holds and output is a
# terminal. Purely cosmetic: it never affects whether the command runs.
_is_featured() {
true
}
knit_register "featured" _featured_cmd "A command highlighted in --help."
knit_highlight_if _is_featured
_featured_cmd() {
echo "featured"
}
knit_done
The predicate is the name of a shell function that receives the command name and returns 0 (highlight) or non-zero (plain). It is repeatable — the name is highlighted if any registered predicate returns 0.
This is the mechanism that bolds the builtin bootstrap command in the root
--help while the experiment has not been bootstrapped yet (and leaves it plain
afterwards). The aim is to visually steer the user toward the next relevant
command.