250 lines
6.1 KiB
Bash
Executable File
250 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/bash
|
|
# shellcheck disable=SC2016
|
|
set -e
|
|
# export PATH="/usr/bin:/usr/local/bin" # safer, if you can
|
|
|
|
(( BASH_VERSINFO[0] < 4 )) && echo "Bash 4+ required." && exit 1
|
|
|
|
: "${PAGER:=more}"
|
|
: "${EDITOR:=vi}"
|
|
: "${HELP_BROWSER:=}"
|
|
|
|
EXE="${0##*/}"
|
|
|
|
declare -A help # associative arrays *require* declaration
|
|
|
|
help[main]='
|
|
# Bash Template Command
|
|
|
|
*This `README.md` is autogenerated.*
|
|
|
|
This is a GitHub template repo that will be copied instead of forked to
|
|
create a new Bash command with a command something like this:
|
|
|
|
```
|
|
gh repo create rwxrob/mycmd -p rwxrob/template-bash-command
|
|
```
|
|
|
|
This `command` inside can then be renamed and finished.
|
|
|
|
Obviously, not all of this is needed for many Bash scripts, but anything
|
|
with more than two subcommands will benefit from the builtin tab
|
|
completion, embedded Markdown help documentation support, and included
|
|
functions (`usage`, `_filter`, `_buffer`, `_have`, etc.)
|
|
|
|
## Naming Conventions
|
|
|
|
* Name repos containing single bash commands with `cmd-`
|
|
* Name template repos beginning with `template-`
|
|
* Start command functions with `command_` to be completed
|
|
* Start command functions with `command__` to not be completed
|
|
|
|
## Dependencies
|
|
|
|
Required:
|
|
|
|
* Bash 4+
|
|
|
|
Optional:
|
|
|
|
* `pandoc` - for rich help docs
|
|
|
|
## Justification
|
|
|
|
Bash is the dominate shell scripting language and the official default
|
|
Linux interactive shell, which reduces cognitive overhead; every command
|
|
line *is* a line of code that could be put into script as is. Bash
|
|
scripts are at the core of cloud, containers, and Kubernetes. Bash 4+
|
|
with its associative array support, powerful regular expressions, and
|
|
multiple ways of feeding data to loops easily covers the needs
|
|
previously requiring Python and Perl scripts. Bash scripts are also much
|
|
more powerful, safer, flexible, and performant than POSIX shell or Zsh.
|
|
|
|
## Caveats
|
|
|
|
* Use the official bash path: `#!/usr/bin/bash`
|
|
* Using `#!/usr/bin/env bash` introduces unnecessary risk
|
|
* Always set the acceptable `PATH` at beginning of script
|
|
* Always check script with [`shellcheck`] before releasing
|
|
* Always use `bc` for *any* floating point math
|
|
|
|
[`shellcheck`]: <https://www.shellcheck.net>
|
|
|
|
## Legal
|
|
|
|
Copyright 2021 Rob Muhlestein <rob@rwx.gg>
|
|
Released under Apache-2.0 License
|
|
Please mention <https://youtube.com/rwxrob>
|
|
|
|
'
|
|
|
|
help[foo]='Foos.'
|
|
|
|
command_foo() {
|
|
_filter "$@" && return $?
|
|
echo "would foo: $*"
|
|
}
|
|
|
|
help[bar]='Bars.'
|
|
|
|
command_bar() {
|
|
_buffer "$@" && return $?
|
|
echo "would bar: $*"
|
|
}
|
|
|
|
command__hidden() {
|
|
_filter "$@" && return $?
|
|
echo "would run _hidden: $*"
|
|
}
|
|
|
|
# ------------------ builtin commands and functions ------------------
|
|
# (https://github.com/rwxrob/template-bash-command)
|
|
|
|
help[usage]='Displays a summary of usage.'
|
|
|
|
command_usage() {
|
|
local -a cmds
|
|
for c in "${COMMANDS[@]}"; do
|
|
[[ ${c:0:1} =~ _ ]] && continue
|
|
cmds+=($c)
|
|
done
|
|
cmds="${cmds[*]}"
|
|
printf "usage: %s (%s)\n" "$EXE" "${cmds// /|}"
|
|
}
|
|
|
|
help[help]='
|
|
# Display Help Information
|
|
|
|
```
|
|
help [<command>]
|
|
```
|
|
|
|
Displays specific help information. If no argument is passed displays
|
|
general help information (main). Otherwise, the documentation for the
|
|
specific argument keyword is displayed, which usually corresponds to
|
|
a command name (but not necessarily). All documentation is written in
|
|
GitHub Flavored Markdown and will displayed as a web page if `pandoc`
|
|
and `$HELP_BROWSER` are detected, otherwise, just the Markdown is sent
|
|
to `$PAGER` (default: more).
|
|
|
|
Also see `readme` and `usage` commands.
|
|
'
|
|
|
|
command_help() {
|
|
local name="${1:-main}" title own body
|
|
title=$(_help_title "$name")
|
|
if [[ -z "$title" ]]; then
|
|
body="${help[$name]}"
|
|
title="$EXE $name"
|
|
[[ $name = main ]] && title="$EXE"
|
|
else
|
|
body="${help[$name]}"
|
|
local eol=$'\n'
|
|
body=${body#*$title}
|
|
fi
|
|
local file="/tmp/help-$EXE-$name.html"
|
|
if _have pandoc ; then
|
|
if _have "$HELP_BROWSER" && [[ -t 1 ]] ;then
|
|
pandoc -s --metadata title="$title" \
|
|
-o "$file" <<< "$body"
|
|
[[ -z "$2" ]] && cd /tmp && exec "$HELP_BROWSER" "$file"
|
|
return 0
|
|
fi
|
|
pandoc -s --metadata title="$title" \
|
|
-t plain <<< "$body" | "$PAGER"
|
|
return 0
|
|
fi
|
|
echo -e "$title\n\n$body" | "$PAGER"
|
|
}
|
|
|
|
help[readme]='
|
|
# Generate `README.md` File
|
|
|
|
```
|
|
command readme > README.md
|
|
```
|
|
|
|
The `readme` command will output the embedded help documentation in raw
|
|
GitHub Flavored Markdown suitable for use as a `README.md` file on
|
|
GitHub or similar hosting service. '
|
|
|
|
command_readme() {
|
|
_trim "${help[main]}"
|
|
local usage="$(command_usage)"
|
|
printf "\n## Commands\n\n"
|
|
local -a names=("${!help[@]}")
|
|
while IFS= read -r name; do
|
|
[[ $name = main ]] && continue
|
|
local body=$(_trim "${help[$name]}")
|
|
[[ $body =~ ^\# ]] || body="# The \`$name\` Command\n\n$body"
|
|
printf "##$body\n\n"
|
|
done < <(printf "%s\n" "${!help[@]}" | LC_COLLATE=C sort)
|
|
echo -e "----\n\n*Autogenerated $(date)*\n"
|
|
}
|
|
|
|
|
|
_help_title() {
|
|
_filter "$@" && return $?;
|
|
local name="$1"
|
|
while IFS= read -r line; do
|
|
[[ $line =~ ^[:space]*$ ]] && continue
|
|
[[ $line =~ ^#\ (.+) ]] && echo "${BASH_REMATCH[1]}" && return 0
|
|
return 1
|
|
done <<< "${help[$name]}"
|
|
}
|
|
|
|
_trim() {
|
|
local it="${1#"${1%%[![:space:]]*}"}"
|
|
echo -e "${it%"${it##*[![:space:]]}"}"
|
|
}
|
|
|
|
_have(){ type "$1" &>/dev/null; }
|
|
|
|
_filter(){
|
|
[[ -n "$1" ]] && return 1
|
|
while IFS= read -ra args; do
|
|
"${FUNCNAME[1]}" "${args[@]}"
|
|
done
|
|
}
|
|
|
|
_buffer() {
|
|
[[ -n "$1" ]] && return 1
|
|
"${FUNCNAME[1]}" "$(</dev/stdin)"
|
|
}
|
|
|
|
# --------------------- completion and delegation --------------------
|
|
# (better than . <(foo bloated_completion) in .bashrc)
|
|
|
|
while IFS= read -r line; do
|
|
[[ $line =~ ^declare\ -f\ command_ ]] || continue
|
|
COMMANDS+=( "${line##declare -f command_}" )
|
|
done < <(declare -F)
|
|
COMMANDS=($(LC_COLLATE=C sort < <(printf "%s\n" "${COMMANDS[@]}")))
|
|
|
|
if [[ -n $COMP_LINE ]]; then
|
|
line=${COMP_LINE#* }
|
|
for c in "${COMMANDS[@]}"; do
|
|
[[ ${c:0:${#line}} == "${line,,}" && ${c:0:1} != _ ]] && echo "$c"
|
|
done
|
|
exit
|
|
fi
|
|
|
|
for c in "${COMMANDS[@]}"; do
|
|
if [[ $c == "$EXE" ]]; then
|
|
"command_$EXE" "$@"
|
|
exit $?
|
|
fi
|
|
done
|
|
|
|
if [[ -n "$1" ]]; then
|
|
declare cmd="$1"; shift
|
|
for c in "${COMMANDS[@]}"; do
|
|
if [[ $c == "$cmd" ]]; then
|
|
"command_$cmd" "$@"
|
|
exit $?
|
|
fi
|
|
done
|
|
fi
|
|
|
|
command_usage "$@"
|