diff --git a/barg b/barg new file mode 100644 index 0000000..deac21c --- /dev/null +++ b/barg @@ -0,0 +1,372 @@ +# barg - Bash argument parser + tiny CLI framework +# Bash 5+ + +# --- helpers ----------------------------------------------------------------- +join() { + local out="" sep="" + for x in "$@"; do [[ -n "$x" ]] && { + out+="${sep}${x}" + sep="." + }; done + printf '%s' "$out" +} + +# --- tokenizer --------------------------------------------------------------- +tokenize_argv() { + local prog=$1 + shift + TOKENS=() + TOKENS+=("root::${prog}") + local -a args=("$@") + while ((${#args[@]})); do + local t=${args[0]} + args=("${args[@]:1}") + if [[ "$t" == "--" ]]; then + TOKENS+=("rest::${args[*]}") + break + fi + if [[ "$t" == --* ]]; then + local kv=${t#--} + local key=${kv%%=*} + TOKENS+=("narg::${key}") + [[ "$kv" == *"="* ]] && TOKENS+=("value::${kv#*=}") + continue + fi + if [[ "$t" == -* ]]; then + local sv=${t#-} + local flags=${sv%%=*} + local val= + [[ "$sv" == *"="* ]] && val=${sv#*=} + local i ch + for ((i = 0; i < ${#flags}; i++)); do + ch=${flags:i:1} + TOKENS+=("narg::${ch}") + done + [[ -n "$val" ]] && TOKENS+=("value::${val}") + continue + fi + TOKENS+=("unk::${t}") + done +} + +# --- schema + parse ---------------------------------------------------------- +declare -A S A + +_generate_schema() { + S=() + A=() + local spec_name=$1 + local -n SPEC_REF="$spec_name" + local _id=100 + local -a pos=() cmds=() + local root_added_help=0 + + for entry in "${SPEC_REF[@]}"; do + IFS=';' read -r -a parts <<<"$entry" + case "${parts[0]}" in + command) + local id=$_id + ((_id++)) + IFS=',' read -r -a aliases <<<"${parts[1]}" + S["$id.entryType"]="command" + S["$id.name"]="${aliases[0]}" + S["$id.help"]="${parts[2]}" + S["$id.args"]="" + + if ((${#cmds[@]} == 0)); then + A["cmd::root"]=$id + else + local last=$((${#cmds[@]} - 1)) + for alias in "${aliases[@]}"; do A["$(join "${cmds[$last]}" "cmd::${alias}")"]=$id; done + fi + + cmds+=("$id") + pos+=(0) + + # inject -h/--help only on root + if ((root_added_help == 0)); then + root_added_help=1 + local hid=$_id + ((_id++)) + S["$hid.entryType"]="argument" + S["$hid.name"]="help" + S["$hid.dest"]="help" + S["$hid.required"]="false" + S["$hid.type"]="flag" + S["$hid.help"]="Show help" + local cmdArgsKey + cmdArgsKey="$(join "${cmds[0]}" "args")" + S["$cmdArgsKey"]="${S[$cmdArgsKey]:+${S[$cmdArgsKey]},}$hid" + A["$(join "${cmds[0]}" "narg::help")"]=$hid + A["$(join "${cmds[0]}" "narg::h")"]=$hid + fi + ;; + end) + if ((${#cmds[@]})); then cmds=("${cmds[@]:0:${#cmds[@]}-1}"); fi + if ((${#pos[@]})); then pos=("${pos[@]:0:${#pos[@]}-1}"); fi + ;; + argument) + local id=$_id + ((_id++)) + IFS=',' read -r -a aliases <<<"${parts[1]}" + local name="${aliases[0]}" + local dest="$name" required="false" atype="positional" value="" help="" + local i kv k v + for ((i = 2; i < ${#parts[@]}; i++)); do + kv=${parts[i]} + if [[ "$kv" == *":"* ]]; then + k=${kv%%:*} + v=${kv#*:} + else + k=$kv + v="true" + fi + case "$k" in + dest) dest="$v" ;; + required) required="$v" ;; + type) atype="$v" ;; + help) help="$v" ;; + default) value="$v" ;; + repeatable) S["$id.repeatable"]="$v" ;; # not implemented + esac + done + + S["$id.entryType"]="argument" + S["$id.name"]="$name" + S["$id.dest"]="$dest" + S["$id.required"]="$required" + S["$id.type"]="$atype" + [[ -n "$help" ]] && S["$id.help"]="$help" + [[ -n "$value" ]] && S["$id.value"]="$value" + + local last=$((${#cmds[@]} - 1)) + local cmdArgsKey + cmdArgsKey="$(join "${cmds[$last]}" "args")" + S["$cmdArgsKey"]="${S[$cmdArgsKey]:+${S[$cmdArgsKey]},}$id" + + case "$atype" in + positional) + A["$(join "${cmds[$last]}" "pos::${pos[$last]}")"]=$id + pos[$last]=$((pos[$last] + 1)) + ;; + rest) A["$(join "${cmds[$last]}" "rest")"]=$id ;; + option | flag) + for alias in "${aliases[@]}"; do A["$(join "${cmds[$last]}" "narg::${alias}")"]=$id; done + ;; + esac + ;; + *) + printf 'Error: Invalid entry type: "%s"\n' "${parts[0]}" >&2 + return 1 + ;; + esac + done +} + +_parse_tokens() { + # outputs: COMMANDS[], VALUES[] + local -a cmds=() pos=() + COMMANDS=() + declare -gA VALUES=() + + while ((${#TOKENS[@]})); do + local tagged=${TOKENS[0]} + TOKENS=("${TOKENS[@]:1}") + local tokenTag=${tagged%%::*} + local token=${tagged#*::} + local found=0 + + local start_level + if ((${#cmds[@]})); then start_level=$((${#cmds[@]} - 1)); else start_level=0; fi + + local level + for ((level = start_level; level >= 0; level--)); do + local entryId="" + case "$tokenTag" in + root) entryId="${A["cmd::root"]}" ;; + rest) ((${#cmds[@]})) && entryId="${A["$(join "${cmds[$level]}" "rest")"]}" ;; + narg) ((${#cmds[@]})) && entryId="${A["$(join "${cmds[$level]}" "$tagged")"]}" ;; + unk) + if ((${#cmds[@]})); then + local id="${A["$(join "${cmds[$level]}" "cmd::${token}")"]}" + if [[ -z "$id" && $level -eq $((${#pos[@]} - 1)) ]]; then + id="${A["$(join "${cmds[$level]}" "pos::${pos[$level]}")"]}" + [[ -n "$id" ]] && pos[$level]=$((pos[$level] + 1)) + fi + entryId="$id" + fi + ;; + esac + [[ -z "$entryId" ]] && continue + + local entryType=${S["$entryId.entryType"]} + if [[ "$entryType" == "command" ]]; then + cmds+=("$entryId") + pos+=(0) + [[ "$tokenTag" == "root" ]] && S["$entryId.name"]="$token" + else + local atype=${S["$entryId.type"]} + local val="" + case "$atype" in + rest | positional) val="$token" ;; + flag) val="true" ;; + option) + if ((${#TOKENS[@]} == 0)); then + printf 'Error: No value for option "%s"\n' "$token" >&2 + return 1 + fi + local nTagged=${TOKENS[0]} + local nTag=${nTagged%%::*} + local nTok=${nTagged#*::} + if [[ "$nTag" != "value" && "$nTag" != "unk" ]]; then + printf 'Error: Expected option value, got "%s" (%s)\n' "$nTok" "$nTag" >&2 + else + TOKENS=("${TOKENS[@]:1}") + val="$nTok" + fi + ;; + esac + S["$entryId.value"]="$val" + fi + + found=1 + break + done + + ((found == 0)) && printf 'Invalid argument: "%s" (%s). Skipping...\n' "${tagged#*::}" "$tokenTag" + done + + # finalize + local cmdId + for cmdId in "${cmds[@]}"; do + local cmdName=${S["$cmdId.name"]} + COMMANDS+=("$cmdName") + local cmdArgs=${S["$cmdId.args"]} + [[ -z "$cmdArgs" ]] && continue + IFS=',' read -r -a argIds <<<"$cmdArgs" + local argId + for argId in "${argIds[@]}"; do + local name=${S["$argId.name"]} + local dest=${S["$argId.dest"]} + local value=${S["$argId.value"]} + local required=${S["$argId.required"]} + if [[ "$required" == "true" && -z "$value" ]]; then + printf 'Error: Argument "%s" is required for "%s"\n' "$name" "$cmdName" >&2 + return 1 + fi + if [[ -n "${VALUES[$dest]+x}" ]]; then VALUES["${cmdName}_${dest}"]="$value"; else VALUES["$dest"]="$value"; fi + done + done +} + +# --- usage printer ----------------------------------------------------------- +_barg_aliases_for() { # -> prints "-x --xyz " list + local cmdId=$1 argId=$2 k v a out=() + for k in "${!A[@]}"; do + [[ $k == "$cmdId.narg::"* ]] || continue + v=${A[$k]} # ← fixed bracket + [[ "$v" != "$argId" ]] && continue + a=${k##*::} + if ((${#a} > 1)); then out+=("--$a"); else out+=("-$a"); fi + done + printf '%s ' "${out[@]}" +} + +_barg_children_of() { # -> "idnamehelp" + local cmdId=$1 k childId + declare -A seen=() + for k in "${!A[@]}"; do + [[ $k == "$cmdId.cmd::"* ]] || continue + childId=${A[$k]} + [[ -n "${seen[$childId]}" ]] && continue + seen[$childId]=1 + printf '%s\t%s\t%s\n' "$childId" "${S["$childId.name"]}" "${S["$childId.help"]}" + done +} + +_barg_usage_for() { # [] + local rootId=$1 showId=${2:-$1} + local prog=${S["$rootId.name"]} + printf 'Usage: %s [args]\n' "$prog" + printf '\nCommands:\n' + local name help + while IFS=$'\t' read -r _ name help; do + printf ' %-12s %s\n' "$name" "$help" + done < <(_barg_children_of "$rootId") + + printf '\nArgs for "%s":\n' "${S["$showId.name"]}" + local args=${S["$showId.args"]} + [[ -z "$args" ]] && { + printf ' (none)\n' + return + } + IFS=',' read -r -a argIds <<<"$args" + local aid atype req nm dest desc aliases + for aid in "${argIds[@]}"; do + atype=${S["$aid.type"]} + req=${S["$aid.required"]} + nm=${S["$aid.name"]} + dest=${S["$aid.dest"]} + desc=${S["$aid.help"]} + aliases="$(_barg_aliases_for "$showId" "$aid")" + case "$atype" in + option) printf ' %-16s -- %s (dest=%s)%s\n' "$aliases" "${desc:-option}" "$dest" "$([[ $req == true ]] && echo ' [required]')" ;; + flag) printf ' %-16s -- %s (flag)\n' "$aliases" "${desc:-flag}" ;; + positional) printf ' %-16s -- %s (positional dest=%s)%s\n' "$nm" "${desc:-positional}" "$dest" "$([[ $req == true ]] && echo ' [required]')" ;; + rest) printf ' %-16s -- %s (-- terminator)\n' "$nm" "${desc:-rest}" ;; + esac + done +} + +# --- public API -------------------------------------------------------------- +usage() { + local rootId=${A["cmd::root"]} + _barg_usage_for "$rootId" "$rootId" +} + +barg::dispatch() { + local spec_name=$1 + shift + local prog=${BARG_PROG:-$(basename "$0")} + tokenize_argv "$prog" "$@" + _generate_schema "$spec_name" || return 1 + _parse_tokens || return 1 + + # Help or no subcommand -> usage of deepest reachable context + if [[ "${VALUES[help]}" == "true" || ${#COMMANDS[@]} -le 1 ]]; then + local rootId=${A["cmd::root"]} + local showId=$rootId parent=$rootId i + for ((i = 1; i < ${#COMMANDS[@]}; i++)); do + local name=${COMMANDS[i]} + local key + key="$(join "$parent" "cmd::${name}")" + local child=${A[$key]} + [[ -n "$child" ]] && { + showId=$child + parent=$child + } + done + _barg_usage_for "$rootId" "$showId" + return 0 + fi + + # Export parsed values as plain vars + local k + for k in "${!VALUES[@]}"; do + local var=${k//[^a-zA-Z0-9_]/_} + printf -v "$var" '%s' "${VALUES[$k]}" + done + + # Call deepest command function + local last=$((${#COMMANDS[@]} - 1)) + local target=${COMMANDS[$last]} + local fn="cmd_${target}" + if [[ $(type -t "$fn") == "function" ]]; then + "$fn" + else + printf 'Error: command handler not found: %s\n' "$fn" >&2 + local rootId=${A["cmd::root"]} + _barg_usage_for "$rootId" "$rootId" + return 1 + fi +} diff --git a/barg.sh b/barg.sh deleted file mode 100644 index 4ff65a0..0000000 --- a/barg.sh +++ /dev/null @@ -1,379 +0,0 @@ -#!/usr/bin/env bash -# Bash 5+ - -# TODO (NOT IMPLEMENTED): -# - repeatable attribute - -# ---------- helpers ---------- -join() { - local out="" sep="" x - for x in "$@"; do - [[ -n "$x" ]] || continue - out+="${sep}${x}" - sep="." - done - printf '%s' "$out" -} - -# ---------- tokenizer ---------- -tokenize() { - local input=$1 - TOKENS=() - read -r -a args <<<"$input" - local root=${args[0]} - TOKENS+=("root::${root}") - args=("${args[@]:1}") - - while ((${#args[@]})); do - local token=${args[0]} - args=("${args[@]:1}") - - if [[ "$token" == "--" ]]; then - TOKENS+=("rest::${args[*]}") - break - fi - - if [[ "$token" == --* ]]; then - local kv=${token#--} - local key=${kv%%=*} - TOKENS+=("narg::${key}") - if [[ "$kv" == *"="* ]]; then - local value=${kv#*=} - TOKENS+=("value::${value}") - fi - continue - fi - - if [[ "$token" == -* ]]; then - local sv=${token#-} - local flags=${sv%%=*} - local value= - if [[ "$sv" == *"="* ]]; then value=${sv#*=}; fi - local i ch - for ((i = 0; i < ${#flags}; i++)); do - ch=${flags:i:1} - TOKENS+=("narg::${ch}") - done - [[ -n "$value" ]] && TOKENS+=("value::${value}") - continue - fi - - TOKENS+=("unk::${token}") - done -} - -# ---------- parser ---------- -declare -A S A - -generate_schema() { - S=() - A=() - local spec_name=$1 - local -n SPEC_REF="$spec_name" - - local _id=100 - local level=-1 - local -a pos=() - local -a cmds=() - - for entry in "${SPEC_REF[@]}"; do - IFS=';' read -r -a parts <<<"$entry" - local type=${parts[0]} - case "$type" in - command) - local id=$_id - _id=$((_id + 1)) - IFS=',' read -r -a aliases <<<"${parts[1]}" - local help="${parts[2]}" - S["$id.entryType"]="command" - S["$id.name"]="${aliases[0]}" - S["$id.help"]="$help" - S["$id.args"]="" - - if ((${#cmds[@]} == 0)); then - A["cmd::root"]=$id - else - local last=$((${#cmds[@]} - 1)) - for alias in "${aliases[@]}"; do - A["$(join "${cmds[$last]}" "cmd::${alias}")"]=$id - done - fi - - level=$((level + 1)) - cmds+=("$id") - pos+=(0) - ;; - end) - level=$((level - 1)) - if ((${#cmds[@]} > 0)); then cmds=("${cmds[@]:0:${#cmds[@]}-1}"); fi - if ((${#pos[@]} > 0)); then pos=("${pos[@]:0:${#pos[@]}-1}"); fi - ;; - argument) - local id=$_id - _id=$((_id + 1)) - IFS=',' read -r -a aliases <<<"${parts[1]}" - local name="${aliases[0]}" - - local dest="$name" - local required="false" - local atype="positional" - local value="" - local help="" - local i kv k v - for ((i = 2; i < ${#parts[@]}; i++)); do - kv=${parts[i]} - if [[ "$kv" == *":"* ]]; then - k=${kv%%:*} - v=${kv#*:} - else - k=$kv - v="true" - fi - case "$k" in - dest) dest="$v" ;; - required) required="$v" ;; - type) atype="$v" ;; - help) help="$v" ;; - default) value="$v" ;; - repeatable) S["$id.repeatable"]="$v" ;; - esac - done - - S["$id.entryType"]="argument" - S["$id.name"]="$name" - S["$id.dest"]="$dest" - S["$id.required"]="$required" - S["$id.type"]="$atype" - [[ -n "$help" ]] && S["$id.help"]="$help" - [[ -n "$value" ]] && S["$id.value"]="$value" - - local last=$((${#cmds[@]} - 1)) - local cmdArgsKey - cmdArgsKey="$(join "${cmds[$last]}" "args")" - if [[ -z "${S[$cmdArgsKey]}" ]]; then - S["$cmdArgsKey"]="$id" - else - S["$cmdArgsKey"]+=",${id}" - fi - - case "$atype" in - positional) - A["$(join "${cmds[$last]}" "pos::${pos[$last]}")"]=$id - pos[$last]=$((pos[$last] + 1)) - ;; - rest) - A["$(join "${cmds[$last]}" "rest")"]=$id - ;; - option | flag) - for alias in "${aliases[@]}"; do - A["$(join "${cmds[$last]}" "narg::${alias}")"]=$id - done - ;; - esac - ;; - *) - printf 'Error: Invalid entry type: "%s"\n' "$type" >&2 - return 1 - ;; - esac - done -} - -print_result() { - local -a COMMANDS=("${!1}") - declare -n VALUES_REF="$2" - - printf 'commands:' - local c - for c in "${COMMANDS[@]}"; do printf ' %s' "$c"; done - printf '\n' - printf 'values:\n' - local k v - for k in "${!VALUES_REF[@]}"; do - v=${VALUES_REF[$k]} - if [[ "$v" == *" "* ]]; then - printf ' %s="%s"\n' "$k" "$v" - else - printf ' %s=%s\n' "$k" "$v" - fi - done -} - -parse_input() { - local spec_name=$1 - local input=$2 - tokenize "$input" - generate_schema "$spec_name" || return 1 - - local -a cmds=() - local -a pos=() - local tagged token tokenTag - - while ((${#TOKENS[@]})); do - tagged=${TOKENS[0]} - TOKENS=("${TOKENS[@]:1}") - tokenTag=${tagged%%::*} - token=${tagged#*::} - - local found=0 - local level start_level entryId id - start_level=$((${#cmds[@]} ? ${#cmds[@]} - 1 : 0)) - - for ((level = start_level; level >= 0; level--)); do - entryId="" - case "$tokenTag" in - root) - entryId="${A["cmd::root"]}" - ;; - rest) - ((${#cmds[@]})) || { - entryId="" - break - } - entryId="${A["$(join "${cmds[$level]}" "rest")"]}" - ;; - narg) - ((${#cmds[@]})) || { - entryId="" - break - } - entryId="${A["$(join "${cmds[$level]}" "$tagged")"]}" - ;; - unk) - ((${#cmds[@]})) || { entryId=""; } # cannot be positional before a command - if [[ -z "$entryId" && ${#cmds[@]} -gt 0 ]]; then - id="${A["$(join "${cmds[$level]}" "cmd::${token}")"]}" - if [[ -z "$id" && $level -eq $((${#pos[@]} - 1)) ]]; then - id="${A["$(join "${cmds[$level]}" "pos::${pos[$level]}")"]}" - [[ -n "$id" ]] && pos[$level]=$((pos[$level] + 1)) - fi - entryId="$id" - fi - ;; - esac - - [[ -z "$entryId" ]] && continue - - local entryType=${S["$entryId.entryType"]} - case "$entryType" in - command) - cmds+=("$entryId") - pos+=(0) - if [[ "$tokenTag" == "root" ]]; then - S["$entryId.name"]="$token" - fi - ;; - argument) - local atype=${S["$entryId.type"]} - local val="" - case "$atype" in - rest | positional) val="$token" ;; - flag) val="true" ;; - option) - if ((${#TOKENS[@]} == 0)); then - printf 'Error: No value provided for option argument: "%s"\n' "$token" >&2 - return 1 - fi - local nTagged=${TOKENS[0]} - local nTag=${nTagged%%::*} - local nTok=${nTagged#*::} - if [[ "$nTag" != "value" && "$nTag" != "unk" ]]; then - printf 'Error: Expected option argument, but got: "%s" (%s). Skipping...\n' "$nTok" "$nTag" >&2 - else - TOKENS=("${TOKENS[@]:1}") - val="$nTok" - fi - ;; - esac - S["$entryId.value"]="$val" - ;; - esac - - found=1 - break - done - - if ((found == 0)); then - printf 'Invalid argument: "%s" (%s). Skipping...\n' "$token" "$tokenTag" - fi - done - - # finalize - local -a COMMANDS=() - declare -A VALUES=() - - local cmdId cmdName cmdArgs argId name dest value required - for cmdId in "${cmds[@]}"; do - cmdName=${S["$cmdId.name"]} - COMMANDS+=("$cmdName") - cmdArgs=${S["$cmdId.args"]} - [[ -z "$cmdArgs" ]] && continue - IFS=',' read -r -a argIds <<<"$cmdArgs" - for argId in "${argIds[@]}"; do - name=${S["$argId.name"]} - dest=${S["$argId.dest"]} - value=${S["$argId.value"]} - required=${S["$argId.required"]} - - if [[ "$required" == "true" && -z "$value" ]]; then - printf 'Error: Argument "%s" is required in command "%s"\n' "$name" "$cmdName" >&2 - return 1 - fi - if [[ -n "${VALUES[$dest]+x}" ]]; then - VALUES["${cmdName}_${dest}"]="$value" - else - VALUES["$dest"]="$value" - fi - done - done - - print_result COMMANDS[@] VALUES -} - -parse_input_wrap() { - local spec_name=$1 - local input=$2 - parse_input "$spec_name" "$input" || printf 'Error: Failed to parse input\n' -} - -# ---------- spec and tests ---------- -SPEC=( - "command;barg;Barg - Bash Argument Parser" - "argument;global;type:flag" - "command;build;Build a dev container" - "argument;from;type:option;dest:fromName;help:Name of the base container" - "argument;name;type:positional;required;help:Name of the container" - "argument;nametwo;help:Name of the container" - "argument;image,i;type:option;required;dest:imageName;help:Base image" - "argument;include,I;type:option;repeatable;dest:includePaths;help:Include paths" - "argument;verbose,v;type:flag;repeatable;default:0;help:Increase verbosity (-v, -vv, ...)" - "argument;quiet,q;type:flag;default:false;help:Suppress all output" - "argument;cmd;type:rest;help:Command and args to run inside the container" - "argument;name;dest:nameOpt;type:option;help:Building container name" - "command;container;Container building" - "argument;dev;type:flag;help:Is dev or not" - "argument;name;required;help:Building container name" - "argument;cmd;type:rest;dest:cmdRest;help:Command and args to run inside the container" - "end" - "end" - "command;stop;Stop a dev container" - "argument;name;type:positional;required;help:Name of the container" - "argument;kill,k;type:flag;default:false;help:Force kill the container" - "end" - "end" -) - -INPUTS=( - "dev build -i myimage buildtwo --from base-dev --image node:18 --include src --include test -v -v -q mycontainer -- npm run start" - "dev build buildname container -i myimage --dev --name webapp externalContainer" - "dev --global build --image debian mybox container --dev --name webapp externalContainer -- echo hi" - "dev stop mycontainer --kill" - "dev stop mycontainer --kill -- not specified one" - "dev build --from alpine --image node:20 mybox -- echo hi" - "dev build --aaaaa --from alpine --image node:20 --bbbbb orb mybox ccccc -- echo hi" -) - -for input in "${INPUTS[@]}"; do - printf "\n\n------------------------\n\n> %s\n" "$input" - parse_input_wrap SPEC "$input" -done diff --git a/sample.sh b/sample.sh new file mode 100755 index 0000000..44f4ab3 --- /dev/null +++ b/sample.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# sample.sh +# shellcheck disable=SC1091 + +source "$(dirname "$0")/barg" || { + echo "barg not found" >&2 + exit 1 +} + +SPEC=( + "command;dev;Dev tool" + "argument;global;type:flag;help:Global toggle" + "command;build;Build a dev container" + "argument;from;type:option;dest:fromName;help:Base container" + "argument;name;type:positional;required;help:Container name" + "argument;image,i;type:option;required;dest:imageName;help:Base image" + "argument;verbose,v;type:flag;default:false;help:Verbose output" + "argument;cmd;type:rest;help:Command to run" + "end" + "command;stop;Stop a dev container" + "argument;name;type:positional;required;help:Container name" + "argument;kill,k;type:flag;default:false;help:Force kill" + "end" + "end" +) + +cmd_build() { + echo "cmd_build:" + echo " fromName = ${fromName}" + echo " name = ${name}" + echo " imageName = ${imageName}" + echo " verbose = ${verbose}" + echo " cmd = ${cmd}" +} + +cmd_stop() { + echo "cmd_stop:" + echo " name = ${name}" + echo " kill = ${kill}" +} + +barg::dispatch SPEC "$@"