-
Notifications
You must be signed in to change notification settings - Fork 1
Technical Details
grind is a script derived from devcli CLI (https://github.com/mavcunha/devcli) but with some changes.
grind itself is quite simple it has knowledge on how to load and execute commands, show documentations and some general purpose functions. It is more important to understand some conventions around the subcommands definitions than actually understand the grind script itself.
grind uses a model similar to some other tools like brew, vagrant and etc.
grind SUBCMD [OPTIONS]
Where a SUBCMD is an action the script will execute and OPTIONS are changes that related to that SUBCMD. As an example, grind update refers to run the definitions, while grind update --dry will alter the update to be in dry-run mode.
Each subcommand needs to accept at least two OPTIONS, named noop and -h|--help. The noop is required so grind can load the command and not execute its function right away with the goal of just showing the documentation. Here's a template of a basic command:
SUBCOMMAND_DESC="Subcommands one-liner help"
SUBCOMMAND_HELP=$(cat <<EOH
Usage ${MAIN_COMMAND} ${SUBCOMMAND}
A short description of this SUBCMD
EOH
)
case ${1} in
noop) ;;
-h|--help) _help ;;
*)
# Your default functionality here
;;
esac
grind loads its SUBCMDs and libraries from the grind.d directory. Libraries differ from SUBCMDs by name, libraries have a prefix of _, example _macos-defaults is a library and run is a SUBCMD. Both are loaded by grind using the source function of zsh and on demand.
When grind SUBCMD is run, grind will source grind.d/SUBCMD this brings every function and variable defined in SUBCMD to the current execution namespace.
The way source works in zsh actually trigger the execution of the script loaded. This is made obvious by this line in grind:
. "${SUBCOMMANDS_DIR}/${SUBCOMMAND}" ${OPTIONS}A SUBCMD is loaded and given the optional action as a parameter. Inside the SUBCMD we handle the OPTIONS with a case statement. In fact, anything after the SUBCMD is passed as an OPTION, like: grind show defs/test will call show and $1 will be defs/test.
The choices around ACTIONS and SUBCMD are made to provide what is thought to be "intuitive" or "practical" for a CLI. This structure is flexible but has it's limitations, like a FLAG can't be present before a SUBCMD doing so will cause grind to search for the FLAG as it was a SUBCMD, example:
$ GRIND_DEBUG=true grind run --force
debug: check if grind is on PATH
debug: user configurations from: /Users/mvaltas/Projects/grind/conf/grind.conf
debug: got argument '--force'
debug: arguments '--force run'
debug: checking if '/Users/mvaltas/Projects/grind/grind.d/--force' exists
'--force' not found
The upside is that FLAGS are isolated between grind, SUBCMD and ACTION allowing the same flag be used in different contexts.
A SUBCMD has to provide at least two things, its documentation, and some behavior. The convention for the documentation uses two bash variables SUBCOMMAND_DESC and SUBCOMMAND_HELP. Here's a hello world SUBCMD:
SUBCOMMAND_DESC="Hello World"
SUBCOMMAND_HELP=$(cat <<EOH
Usage ${MAIN_COMMAND} ${SUBCOMMAND} NAME
Will say 'Hello, NAME!' if NAME is omitted
'Hello World!' will be printed on screen.
EOH
)
case ${1} in
noop) ;;
-h|--help) _help ;;
hello)
name=${1:-World}
echo "Hello ${name}!"
;;
esacSaving this file in grind.d/greet will instantly make the SUBCMD available, like:
$ grind
Subcommands available ('grind SUBCOMMAND' for usage):
bootstrap: Bootstrap machine
conf: Grind's configuration
greet: Hello World
run: Run an arbitrary definition
update: Update machine commands
And we can invoke as:
# without a name parameter
$ grind greet
Hello World!
# with a name parameter
$ grind greet Marco
Hello Marco!
As you probably guessed SUBCOMMAND_DESC is used by grind to output a short description of the SUBCMD and SUBCOMMAND_HELP is a more detailed help, in our example:
$ grind greet -h
Showing 'grind greet' available actions
Usage grind greet
Will say 'Hello, NAME!' if NAME is omitted
'Hello World!' will be printed on screen.
This is all you need for creating SUBCMDs in general.
These are internal details of grind as a general CLI, some global variables, and functions available for any SUBCMD.
These are defined inside grind and will be on any definition namespace, be careful to avoid conflicts and/or overwriting them.
This is the name of the script itself, for grind is just "grind", renaming the file will cause this to change and any reference to it too.
Is defined as the directory where the grind script is, for example, if /home/myuser/grind is the path to grind then /home/myuser is ${ROOT_DIR}.
Points to the grind.d directory.
These are available for any API and definition. Its uses should be on API's but definitions also inherit these.
This function should be used, extensively, for providing debug messages, its output will be visible if GRIND_DEBUG=true is defined. SUBCMD should use it to notify details of execution and messages for troubleshooting.
These are the colors available for different kinds of outputs from grind. Note, these do not add a linebreak after the output so you should to yourself, like in_cyan "This is a message\n".
function in_red() { _color 1 "${1}"; } # use for failures
function in_green() { _color 2 "${1}"; } # use for successes
function in_yellow() { _color 3 "${1}"; } # use for warnings / attention
function in_magenta() { _color 5 "${1}"; } # use for debug messages
function in_cyan() { _color 6 "${1}"; } # use for main actions / progressThis is a shortcut for stopping the execution on errors, it will print "ERROR: MSG" where MSG is the argument given to the function and then it will exit 1.
Will print in yellow WARN: MSG where MSG is the argument given to the function.
It's the prefered way to load a library. It will source the file prefixing with _ from the SUBCOMMANDS_DIR and log.
use "somelibrary"Will cause grind to source grind.d/_somelibrary
This is a "special" library that grind will load by default if the library exists. It aims to provide some more functions and global variables for all execution environments and to avoid defining everything inside the grind script itself.