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 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.

Execution of a SUBCMD

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 short SUBCMD

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 SUBCMDs in general.

grinds 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.