Instantly share code, notes, and snippets.
Created
July 29, 2019 22:26
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save andsens/9a8c414a0c3d1332157d0a66a433ef50 to your computer and use it in GitHub Desktop.
Demonstration of docopt.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
DOC="Naval Fate. | |
Usage: | |
naval_fate.sh ship <name> move <x> <y> [--speed=<kn>] | |
naval_fate.sh ship shoot <x> <y> | |
Options: | |
--speed=<kn> Speed in knots [default: 10]. | |
--moored Moored (anchored) mine. | |
--drifting Drifting mine." | |
naval_fate() { | |
eval "$(docopt "$@")" | |
$ship && $move && printf "The %s is now moving to %d,%d at %d knots.\n" "$_name_" "$_x_" "$_y_" "$__speed" | |
$ship && $shoot && printf "You shoot at %d,%d. It's a hit!\n" "$_x_" "$_y_" | |
return 0 | |
} | |
naval_fate "$@" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
DOC="Naval Fate. | |
Usage: | |
naval_fate.sh ship <name> move <x> <y> [--speed=<kn>] | |
naval_fate.sh ship shoot <x> <y> | |
Options: | |
--speed=<kn> Speed in knots [default: 10]. | |
--moored Moored (anchored) mine. | |
--drifting Drifting mine." | |
# docopt parser below, refresh this parser with `docopt.sh naval_fate.sh` | |
# shellcheck disable=2016,2154,1075 | |
docopt() { parse() { if ${DOCOPT_DOC_CHECK:-true}; then local doc_hash | |
doc_hash=$(printf "%s" "$DOC" | shasum -a 256) | |
if [[ ${doc_hash:0:5} != "$digest" ]]; then | |
stderr "The current usage doc (${doc_hash:0:5}) does not match \ | |
what the parser was generated with (${digest}) | |
Run \`docopt.sh\` to refresh the parser."; _return 70; fi; fi; local root_idx=$1 | |
shift; argv=("$@"); parsed_params=(); parsed_values=(); left=(); testdepth=0 | |
local arg; while [[ ${#argv[@]} -gt 0 ]]; do if [[ ${argv[0]} = "--" ]]; then | |
for arg in "${argv[@]}"; do parsed_params+=('a'); parsed_values+=("$arg"); done | |
break; elif [[ ${argv[0]} = --* ]]; then parse_long | |
elif [[ ${argv[0]} = -* && ${argv[0]} != "-" ]]; then parse_shorts | |
elif ${DOCOPT_OPTIONS_FIRST:-false}; then for arg in "${argv[@]}"; do | |
parsed_params+=('a'); parsed_values+=("$arg"); done; break; else | |
parsed_params+=('a'); parsed_values+=("${argv[0]}"); argv=("${argv[@]:1}"); fi | |
done; local idx; if ${DOCOPT_ADD_HELP:-true}; then | |
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue | |
if [[ ${shorts[$idx]} = "-h" || ${longs[$idx]} = "--help" ]]; then | |
stdout "$trimmed_doc"; _return 0; fi; done; fi | |
if [[ ${DOCOPT_PROGRAM_VERSION:-false} != 'false' ]]; then | |
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue | |
if [[ ${longs[$idx]} = "--version" ]]; then stdout "$DOCOPT_PROGRAM_VERSION" | |
_return 0; fi; done; fi; local i=0; while [[ $i -lt ${#parsed_params[@]} ]]; do | |
left+=("$i"); ((i++)) || true; done | |
if ! required "$root_idx" || [ ${#left[@]} -gt 0 ]; then error; fi; return 0; } | |
parse_shorts() { local token=${argv[0]}; local value; argv=("${argv[@]:1}") | |
[[ $token = -* && $token != --* ]] || _return 88; local remaining=${token#-} | |
while [[ -n $remaining ]]; do local short="-${remaining:0:1}" | |
remaining="${remaining:1}"; local i=0; local similar=(); local match=false | |
for o in "${shorts[@]}"; do if [[ $o = "$short" ]]; then similar+=("$short") | |
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done | |
if [[ ${#similar[@]} -gt 1 ]]; then | |
error "${short} is specified ambiguously ${#similar[@]} times" | |
elif [[ ${#similar[@]} -lt 1 ]]; then match=${#shorts[@]}; value=true | |
shorts+=("$short"); longs+=(''); argcounts+=(0); else value=false | |
if [[ ${argcounts[$match]} -ne 0 ]]; then if [[ $remaining = '' ]]; then | |
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then | |
error "${short} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}") | |
else value=$remaining; remaining=''; fi; fi; if [[ $value = false ]]; then | |
value=true; fi; fi; parsed_params+=("$match"); parsed_values+=("$value"); done | |
}; parse_long() { local token=${argv[0]}; local long=${token%%=*} | |
local value=${token#*=}; local argcount; argv=("${argv[@]:1}") | |
[[ $token = --* ]] || _return 88; if [[ $token = *=* ]]; then eq='='; else eq='' | |
value=false; fi; local i=0; local similar=(); local match=false | |
for o in "${longs[@]}"; do if [[ $o = "$long" ]]; then similar+=("$long") | |
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done | |
if [[ $match = false ]]; then i=0; for o in "${longs[@]}"; do | |
if [[ $o = $long* ]]; then similar+=("$long"); [[ $match = false ]] && match=$i | |
fi; ((i++)) || true; done; fi; if [[ ${#similar[@]} -gt 1 ]]; then | |
error "${long} is not a unique prefix: ${similar[*]}?" | |
elif [[ ${#similar[@]} -lt 1 ]]; then | |
[[ $eq = '=' ]] && argcount=1 || argcount=0; match=${#shorts[@]} | |
[[ $argcount -eq 0 ]] && value=true; shorts+=(''); longs+=("$long") | |
argcounts+=("$argcount"); else if [[ ${argcounts[$match]} -eq 0 ]]; then | |
if [[ $value != false ]]; then | |
error "${longs[$match]} must not have an argument"; fi | |
elif [[ $value = false ]]; then | |
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then | |
error "${long} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}") | |
fi; if [[ $value = false ]]; then value=true; fi; fi; parsed_params+=("$match") | |
parsed_values+=("$value"); }; required() { local initial_left=("${left[@]}") | |
local node_idx; ((testdepth++)) || true; for node_idx in "$@"; do | |
if ! "node_$node_idx"; then left=("${initial_left[@]}"); ((testdepth--)) || true | |
return 1; fi; done; if [[ $((--testdepth)) -eq 0 ]]; then | |
left=("${initial_left[@]}"); for node_idx in "$@"; do "node_$node_idx"; done; fi | |
return 0; }; either() { local initial_left=("${left[@]}"); local best_match_idx | |
local match_count; local node_idx; ((testdepth++)) || true | |
for node_idx in "$@"; do if "node_$node_idx"; then | |
if [[ -z $match_count || ${#left[@]} -lt $match_count ]]; then | |
best_match_idx=$node_idx; match_count=${#left[@]}; fi; fi | |
left=("${initial_left[@]}"); done; ((testdepth--)) || true | |
if [[ -n $best_match_idx ]]; then "node_$best_match_idx"; return 0; fi | |
left=("${initial_left[@]}"); return 1; }; optional() { local node_idx | |
for node_idx in "$@"; do "node_$node_idx"; done; return 0; }; _command() { | |
local i; local name=${2:-$1}; for i in "${!left[@]}"; do local l=${left[$i]} | |
if [[ ${parsed_params[$l]} = 'a' ]]; then | |
if [[ ${parsed_values[$l]} != "$name" ]]; then return 1; fi | |
left=("${left[@]:0:$i}" "${left[@]:((i+1))}") | |
[[ $testdepth -gt 0 ]] && return 0; if [[ $3 = true ]]; then | |
eval "((var_$1++)) || true"; else eval "var_$1=true"; fi; return 0; fi; done | |
return 1; }; value() { local i; for i in "${!left[@]}"; do local l=${left[$i]} | |
if [[ ${parsed_params[$l]} = "$2" ]]; then | |
left=("${left[@]:0:$i}" "${left[@]:((i+1))}") | |
[[ $testdepth -gt 0 ]] && return 0; local value | |
value=$(printf -- "%q" "${parsed_values[$l]}"); if [[ $3 = true ]]; then | |
eval "var_$1+=($value)"; else eval "var_$1=$value"; fi; return 0; fi; done | |
return 1; }; stdout() { printf -- "cat <<'EOM'\n%s\nEOM\n" "$1"; }; stderr() { | |
printf -- "cat <<'EOM' >&2\n%s\nEOM\n" "$1"; }; error() { | |
[[ -n $1 ]] && stderr "$1"; stderr "$usage"; _return 1; }; _return() { | |
printf -- "exit %d\n" "$1"; exit "$1"; }; set -e; trimmed_doc=${DOC:0:237} | |
usage=${DOC:13:97}; digest=6780a; shorts=(''); longs=(--speed); argcounts=(1) | |
node_0(){ value __speed 0; }; node_1(){ value _name_ a; }; node_2(){ value _x_ a | |
}; node_3(){ value _y_ a; }; node_4(){ _command ship; }; node_5(){ _command move | |
}; node_6(){ _command shoot; }; node_13(){ optional 0; }; node_14(){ | |
required 4 1 5 2 3 13; }; node_19(){ required 4 6 2 3; }; node_20(){ | |
either 14 19; }; node_21(){ required 20; }; cat <<<' docopt_exit() { | |
[[ -n $1 ]] && printf "%s\n" "$1" >&2; printf "%s\n" "${DOC:13:97}" >&2; exit 1 | |
}'; unset var___speed var__name_ var__x_ var__y_ var_ship var_move var_shoot | |
parse 21 "$@"; local prefix=${DOCOPT_PREFIX:-''}; local docopt_decl=1 | |
[[ $BASH_VERSION =~ ^4.3 ]] && docopt_decl=2; unset "${prefix}__speed" \ | |
"${prefix}_name_" "${prefix}_x_" "${prefix}_y_" "${prefix}ship" \ | |
"${prefix}move" "${prefix}shoot"; eval "${prefix}"'__speed=${var___speed:-10}' | |
eval "${prefix}"'_name_=${var__name_:-}'; eval "${prefix}"'_x_=${var__x_:-}' | |
eval "${prefix}"'_y_=${var__y_:-}'; eval "${prefix}"'ship=${var_ship:-false}' | |
eval "${prefix}"'move=${var_move:-false}' | |
eval "${prefix}"'shoot=${var_shoot:-false}'; local docopt_i=0 | |
for ((docopt_i=0;docopt_i<docopt_decl;docopt_i++)); do | |
declare -p "${prefix}__speed" "${prefix}_name_" "${prefix}_x_" "${prefix}_y_" \ | |
"${prefix}ship" "${prefix}move" "${prefix}shoot"; done; } | |
# docopt parser above, complete command for generating this parser is `docopt.sh naval_fate.sh` | |
naval_fate() { | |
eval "$(docopt "$@")" | |
$ship && $move && printf "The %s is now moving to %d,%d at %d knots.\n" "$_name_" "$_x_" "$_y_" "$__speed" | |
$ship && $shoot && printf "You shoot at %d,%d. It's a hit!\n" "$_x_" "$_y_" | |
return 0 | |
} | |
naval_fate "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment