Technical Details - mvaltas/grind GitHub Wiki
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.
Command Line Interface (CLI)
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.
SUBCMD
implementation
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
SUBCMD
and libraries load and execution
grind
loads its SUBCMD
s and libraries from the grind.d
directory. Libraries differ from SUBCMD
s 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.
SUBCMD
Execution of a 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.
SUBCMD
A short 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}!"
;;
esac
Saving 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 SUBCMD
s in general.
grind
s core
These are internal details of grind
as a general CLI, some global variables, and functions available for any SUBCMD
.
Global Variables
These are defined inside grind
and will be on any definition namespace, be careful to avoid conflicts and/or overwriting them.
${MAIN_COMMAND}
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.
${ROOT_DIR}
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}
.
${SUBCOMMANDS_DIR}
Points to the grind.d
directory.
Global Functions
These are available for any API and definition. Its uses should be on API's but definitions also inherit these.
log
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.
in_cyan
and other colors
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 / progress
error
This 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
.
warn
Will print in yellow WARN: MSG
where MSG
is the argument given to the function.
use
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
_environment
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.