Compare commits
No commits in common. "main" and "v0.4.1" have entirely different histories.
273
README.md
273
README.md
|
@ -1,12 +1,4 @@
|
||||||
# Bash Template Command
|
# Bash Command Template
|
||||||
|
|
||||||
*I no longer use this using [Bonzai][] instead. But it does have some good bash tricks to keep around including self-completion.*
|
|
||||||
|
|
||||||
[Bonzai]: <https://github.com/rwxrob/bonzai>
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
*This `README.md` is autogenerated.*
|
|
||||||
|
|
||||||
This is a GitHub template repo that will be copied instead of forked to
|
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:
|
create a new Bash command with a command something like this:
|
||||||
|
@ -15,104 +7,68 @@ create a new Bash command with a command something like this:
|
||||||
gh repo create rwxrob/mycmd -p rwxrob/template-bash-command
|
gh repo create rwxrob/mycmd -p rwxrob/template-bash-command
|
||||||
```
|
```
|
||||||
|
|
||||||
This `cmd` 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.)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
It's usually easiest to install by cloning the repo and adding the full
|
||||||
|
path to this repo to your `PATH`. That way you can keep up with updates.
|
||||||
|
|
||||||
## Naming Conventions
|
## Naming Conventions
|
||||||
|
|
||||||
* Name repos containing single bash commands with `cmd-`
|
* Name repos containing single bash commands with `cmd-`
|
||||||
* Name template repos beginning with `template-`
|
* Name template repos beginning with `template-`
|
||||||
* Start command functions with `x.` to be completed
|
* Start command functions with `command_` to be completed
|
||||||
* Name `CONF` accessors with `x.` and full path
|
* Start command functions with `command__` to not be completed
|
||||||
* Use dot (`.`) pathing in `CONF` key names
|
|
||||||
|
|
||||||
Think of `x` as in "executable" command.
|
## Usage
|
||||||
|
|
||||||
> ⚠️
|
```
|
||||||
> Note that all versions of Vim current have a bug that does not allow
|
command
|
||||||
> dot (`.`) to be included in the function name even though it is
|
command usage
|
||||||
> explicitly allowed by bash. So you may have to add `.` to your
|
command help [<cmd>]
|
||||||
> `/usr/share/vim/vim82/syntax/sh.vim` file anywhere a function name
|
command foo [<arg>]
|
||||||
> expression is defined. I have yet to isolate it out and override it in
|
command bar [<arg>]
|
||||||
> my own `.vimrc`. It is a minimal edit.
|
```
|
||||||
|
|
||||||
## Builtins and Utilities
|
## Commands
|
||||||
|
|
||||||
A number of builtin and frequently used utility functions have been
|
| Command | Summary |
|
||||||
included for convenience. These save developers from adding other
|
| :-: | - |
|
||||||
moronic things like `sed` and `awk` subprocesses. Obviously, not all of
|
| [usage] | Display single line summary of all command usage |
|
||||||
this is needed for many Bash scripts. Just remove what you do not need
|
| [help] | Display help information |
|
||||||
or want.
|
| [foo] | Do the foo |
|
||||||
|
| [bar] | Do the bar |
|
||||||
|
|
||||||
### `_initialize`
|
[usage]: #usage
|
||||||
|
[help]: #help
|
||||||
|
[foo]: #foo
|
||||||
|
[bar]: #bar
|
||||||
|
|
||||||
The `_initialize` function is meant to contain initialization code and
|
### `usage`
|
||||||
be placed at the beginning of the script to be found easily even though
|
|
||||||
it is called at the bottom of the script (as bash requires). It is
|
|
||||||
passed the arguments that are passed to the script itself. This function can be omitted.
|
|
||||||
|
|
||||||
### `_alternatives`
|
The `usage` command displays a summary of usage.
|
||||||
|
|
||||||
The `_alternatives` function (usually placed after `_initialize`
|
### `help`
|
||||||
provides a hook for dealing with alternative arguments to those that
|
|
||||||
identify commands (`x.*`). If the first argument to the script does not
|
|
||||||
match a command function then this function will be called before the
|
|
||||||
default `x.usage` command allowing shortcuts and other argument
|
|
||||||
alternatives and intelligent sensing of what command function is wanted
|
|
||||||
by simply examining the argument list. This can be useful when you wish
|
|
||||||
to provide shortcuts for longer commands but do not want to clutter the
|
|
||||||
command usage and completion list. For example, `zet dex.titles` could
|
|
||||||
be trapped in `_alternatives` to call `zet titles`.
|
|
||||||
|
|
||||||
### `_have`
|
The help command prints 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 CommonMark (Markdown) and will displayed as Web page if
|
||||||
|
`pandoc` and `$HELP_BROWSER` are detected, otherwise, just the Markdown is
|
||||||
|
sent to `$PAGER` (default: `more`).
|
||||||
|
|
||||||
Returns true (0) if the first argument exists as an executable in the
|
### `foo`
|
||||||
current `PATH`. Otherwise, return false (1).
|
|
||||||
|
|
||||||
### `_checkdep`
|
The `foo` command foos.
|
||||||
|
|
||||||
Checks that the first argument exists as an executable in the current
|
### `bar`
|
||||||
`PATH`. If so, returns true (0). If not, prints a generic error message
|
|
||||||
in English and returns false (1). The "progressive enhancement" design
|
|
||||||
principle requires minimal functionality using what is available and
|
|
||||||
progressively upgrading based on what is detected.
|
|
||||||
|
|
||||||
### `_newest`
|
The `bar` command bars.
|
||||||
|
|
||||||
Uses `ls` to return the newest file or directory in the specified
|
|
||||||
directory.
|
|
||||||
|
|
||||||
### `_trim`
|
|
||||||
|
|
||||||
Removes all whitespace (`[:space:]`) from the beginning and ending
|
|
||||||
of a string without invoking a subprocess.
|
|
||||||
|
|
||||||
### `_filter`
|
|
||||||
|
|
||||||
Reads the first argument or each line of standard input passing
|
|
||||||
each individually as the first argument to the calling function one at
|
|
||||||
a time. The UNIX philosophy requires all commands be filters whenever possible.
|
|
||||||
|
|
||||||
### `_buffer`
|
|
||||||
|
|
||||||
Reads the first argument or all lines of standard input and then
|
|
||||||
passes them to the calling function as the first argument. The UNIX philosophy requires all command be filters whenever possible.
|
|
||||||
|
|
||||||
### `_reduce`
|
|
||||||
|
|
||||||
Takes the name of an array and a bash extended regular expression
|
|
||||||
and prints only the array entries that match, one to a line suitable for
|
|
||||||
converting back into an array with `IFS=$'\n'` or just as an in-memory
|
|
||||||
`grep` replacement.
|
|
||||||
|
|
||||||
### `_jsonstr`
|
|
||||||
|
|
||||||
Encodes first argument or all standard input into a single line of JSON text. This function depends on the `jq` command.
|
|
||||||
|
|
||||||
### `_urlencode`
|
|
||||||
|
|
||||||
Encodes the first argument or all standard input using standard URL
|
|
||||||
encoding suitable for passing to `curl` or whatever. This function has
|
|
||||||
no external dependencies.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
@ -123,11 +79,10 @@ Required:
|
||||||
Optional:
|
Optional:
|
||||||
|
|
||||||
* `pandoc` - for rich help docs
|
* `pandoc` - for rich help docs
|
||||||
* `jq` - for `json` and anything that uses it
|
|
||||||
|
|
||||||
## Justification
|
## Justification
|
||||||
|
|
||||||
Bash is the dominant shell scripting language and the official default
|
Bash is the dominate shell scripting language and the official default
|
||||||
Linux interactive shell, which reduces cognitive overhead; every command
|
Linux interactive shell, which reduces cognitive overhead; every command
|
||||||
line *is* a line of code that could be put into script as is. Bash
|
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+
|
scripts are at the core of cloud, containers, and Kubernetes. Bash 4+
|
||||||
|
@ -136,147 +91,19 @@ multiple ways of feeding data to loops easily covers the needs
|
||||||
previously requiring Python and Perl scripts. Bash scripts are also much
|
previously requiring Python and Perl scripts. Bash scripts are also much
|
||||||
more powerful, safer, flexible, and performant than POSIX shell or Zsh.
|
more powerful, safer, flexible, and performant than POSIX shell or Zsh.
|
||||||
|
|
||||||
## Guidelines
|
## Caveats
|
||||||
|
|
||||||
* Write GitHub Flavored Markdown only
|
* Use the official bash path: `#!/usr/bin/bash`
|
||||||
* Use present tense ("outputs" over "will output")
|
|
||||||
* Prefer term "output" and "display" over ~~print~~
|
|
||||||
* Follow the [naming conventions](#naming-conventions)
|
|
||||||
* Use the official bash path: `#!/bin/bash`
|
|
||||||
* Use of `#!/usr/bin/bash` is outdated
|
|
||||||
* Using `#!/usr/bin/env bash` introduces unnecessary risk
|
* Using `#!/usr/bin/env bash` introduces unnecessary risk
|
||||||
* Explicitly export `PATH` in script when possible
|
* Always set the acceptable `PATH` at beginning of script
|
||||||
* Always check script with [`shellcheck`] before releasing
|
* Always check script with [`shellcheck`] before releasing
|
||||||
* Always use `bc` for *any* floating point math
|
* Always use `bc` for *any* floating point math
|
||||||
|
|
||||||
[`shellcheck`]: <https://www.shellcheck.net>
|
[`shellcheck`]: <https://www.shellcheck.net>
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
This script is expected to be installed for a specific user and only
|
|
||||||
ever run by that user. No additional security vetting for running as an
|
|
||||||
untrusted user has been done.
|
|
||||||
|
|
||||||
## Legal
|
## Legal
|
||||||
|
|
||||||
Copyright 2021 Rob Muhlestein <rob@rwx.gg>
|
Copyright 2021 Rob Muhlestein <rob@rwx.gg>
|
||||||
Released under Apache-2.0 License
|
Released under Apache-2.0 License
|
||||||
Please mention rwxrob.tv
|
Please mention <https://youtube.com/rwxrob>
|
||||||
|
|
||||||
## The `bar` Command
|
|
||||||
|
|
||||||
```
|
|
||||||
cmd bar
|
|
||||||
```
|
|
||||||
|
|
||||||
Bar the things.
|
|
||||||
|
|
||||||
## The `config` Command
|
|
||||||
|
|
||||||
```
|
|
||||||
cmd config
|
|
||||||
cmd config KEY
|
|
||||||
cmd config KEY VALUE
|
|
||||||
cmd config KEY ""
|
|
||||||
cmd config keys
|
|
||||||
cmd config val[ues]
|
|
||||||
cmd config dir[ectory]
|
|
||||||
cmd config path [file]
|
|
||||||
cmd config edit [file]
|
|
||||||
cmd config del[ete]
|
|
||||||
```
|
|
||||||
|
|
||||||
The `config` command is for reading, writing, and displaying standard
|
|
||||||
open desktop configuration properties. Pass an empty string to delete
|
|
||||||
a property.
|
|
||||||
|
|
||||||
### Arguments
|
|
||||||
|
|
||||||
With no arguments outputs all the currently cached configuration
|
|
||||||
settings.
|
|
||||||
|
|
||||||
With a single KEY argument fetches the value for that key and outputs
|
|
||||||
it unless it is one of the following special (reserved) key names:
|
|
||||||
|
|
||||||
* `dir*` full path to config directory
|
|
||||||
* `path` full path to specific config file (default: `values`)
|
|
||||||
* `edit` opens config file in editor (default: `editor` or `$EDITOR)
|
|
||||||
* `keys` output the configuration keys, one per line
|
|
||||||
* `val*` output the configuration values, one per line
|
|
||||||
* `del*` if key argument then delete a specific key, otherwise prompt
|
|
||||||
|
|
||||||
With more than one argument the remaining arguments after the KEY will
|
|
||||||
be combined into the VALUE and written to a `values` file in the
|
|
||||||
configuration directory.
|
|
||||||
|
|
||||||
### Configuration Directory
|
|
||||||
|
|
||||||
The configuration directory path relies on the following environment
|
|
||||||
variables:
|
|
||||||
|
|
||||||
* `EXE` - defaults to name of currently running command (cmd)
|
|
||||||
* `HOME` - checked for `$HOME/.config/$EXE/values`
|
|
||||||
* `XDG_CONFIG_HOME` - overrides `$HOME/.config`
|
|
||||||
* `CONFIG_DIR` - full path to directory containing `values` file
|
|
||||||
|
|
||||||
The `CONFIG_DIR` always takes priority over anything else if set, but is
|
|
||||||
never implied. If the directory does not exist it will be created the
|
|
||||||
first time a value is set.
|
|
||||||
|
|
||||||
### Configuration `values` File Format
|
|
||||||
|
|
||||||
The file (which is almost always located at
|
|
||||||
`~/.config/cmd/values`) uses the simplest possible format to
|
|
||||||
facilitate standard UNIX parsing and filtering with any number of
|
|
||||||
existing tools (and no `jq` dependency).
|
|
||||||
|
|
||||||
* One KEY=VALUE per line
|
|
||||||
* KEYs may be anything but the equal sign (`=`)
|
|
||||||
* VALUEs may be anything but line returns must be escaped
|
|
||||||
|
|
||||||
Note that this is *not* the same as Java properties and other similar
|
|
||||||
format. It is designed for ultimate simplicity, efficiency, and
|
|
||||||
portability.
|
|
||||||
|
|
||||||
## The `foo` Command
|
|
||||||
|
|
||||||
Foos things.
|
|
||||||
|
|
||||||
## The `help` Command
|
|
||||||
|
|
||||||
```
|
|
||||||
cmd 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.
|
|
||||||
|
|
||||||
## Generate `README.md` File
|
|
||||||
|
|
||||||
```
|
|
||||||
cmd 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.
|
|
||||||
|
|
||||||
## The `some.config.setting` Command
|
|
||||||
|
|
||||||
Get and set `some.config.setting`.
|
|
||||||
|
|
||||||
## The `usage` Command
|
|
||||||
|
|
||||||
Displays a summary of usage.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
*Autogenerated Sat Dec 18 11:13:17 AM EST 2021*
|
|
||||||
|
|
||||||
|
|
610
cmd
610
cmd
|
@ -1,610 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# shellcheck disable=SC2016
|
|
||||||
set -e
|
|
||||||
# export PATH="/bin:/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
|
|
||||||
declare -A CONF
|
|
||||||
|
|
||||||
# declare black=$'\e[30m'
|
|
||||||
# declare red=$'\e[31m'
|
|
||||||
# declare green=$'\e[32m'
|
|
||||||
# declare yellow=$'\e[33m'
|
|
||||||
# declare blue=$'\e[34m'
|
|
||||||
# declare magenta=$'\e[35m'
|
|
||||||
# declare cyan=$'\e[36m'
|
|
||||||
# declare white=$'\e[37m'
|
|
||||||
# declare reset=$'\e[0m'
|
|
||||||
|
|
||||||
_initialize() {
|
|
||||||
: # put initialization code here
|
|
||||||
}
|
|
||||||
|
|
||||||
_alternatives() {
|
|
||||||
# put alternative argument possibilities here
|
|
||||||
if [[ $CMD = f ]];then
|
|
||||||
x.foo "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
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 `cmd` inside can then be renamed and finished.
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
* Name repos containing single bash commands with `cmd-`
|
|
||||||
* Name template repos beginning with `template-`
|
|
||||||
* Start command functions with `x.` to be completed
|
|
||||||
* Name `CONF` accessors with `x.` and full path
|
|
||||||
* Use dot (`.`) pathing in `CONF` key names
|
|
||||||
|
|
||||||
Think of `x` as in "executable" command.
|
|
||||||
|
|
||||||
> ⚠️
|
|
||||||
> Note that all versions of Vim current have a bug that does not allow
|
|
||||||
> dot (`.`) to be included in the function name even though it is
|
|
||||||
> explicitly allowed by bash. So you may have to add `.` to your
|
|
||||||
> `/usr/share/vim/vim82/syntax/sh.vim` file anywhere a function name
|
|
||||||
> expression is defined. I have yet to isolate it out and override it in
|
|
||||||
> my own `.vimrc`. It is a minimal edit.
|
|
||||||
|
|
||||||
## Builtins and Utilities
|
|
||||||
|
|
||||||
A number of builtin and frequently used utility functions have been
|
|
||||||
included for convenience. These save developers from adding other
|
|
||||||
moronic things like `sed` and `awk` subprocesses. Obviously, not all of
|
|
||||||
this is needed for many Bash scripts. Just remove what you do not need
|
|
||||||
or want.
|
|
||||||
|
|
||||||
### `_initialize`
|
|
||||||
|
|
||||||
The `_initialize` function is meant to contain initialization code and
|
|
||||||
be placed at the beginning of the script to be found easily even though
|
|
||||||
it is called at the bottom of the script (as bash requires). It is
|
|
||||||
passed the arguments that are passed to the script itself. This function can be omitted.
|
|
||||||
|
|
||||||
### `_alternatives`
|
|
||||||
|
|
||||||
The `_alternatives` function (usually placed after `_initialize`
|
|
||||||
provides a hook for dealing with alternative arguments to those that
|
|
||||||
identify commands (`x.*`). If the first argument to the script does not
|
|
||||||
match a command function then this function will be called before the
|
|
||||||
default `x.usage` command allowing shortcuts and other argument
|
|
||||||
alternatives and intelligent sensing of what command function is wanted
|
|
||||||
by simply examining the argument list. This can be useful when you wish
|
|
||||||
to provide shortcuts for longer commands but do not want to clutter the
|
|
||||||
command usage and completion list. For example, `zet dex.titles` could
|
|
||||||
be trapped in `_alternatives` to call `zet titles`.
|
|
||||||
|
|
||||||
### `_have`
|
|
||||||
|
|
||||||
Returns true (0) if the first argument exists as an executable in the
|
|
||||||
current `PATH`. Otherwise, return false (1).
|
|
||||||
|
|
||||||
### `_checkdep`
|
|
||||||
|
|
||||||
Checks that the first argument exists as an executable in the current
|
|
||||||
`PATH`. If so, returns true (0). If not, prints a generic error message
|
|
||||||
in English and returns false (1). The "progressive enhancement" design
|
|
||||||
principle requires minimal functionality using what is available and
|
|
||||||
progressively upgrading based on what is detected.
|
|
||||||
|
|
||||||
### `_newest`
|
|
||||||
|
|
||||||
Uses `ls` to return the newest file or directory in the specified
|
|
||||||
directory.
|
|
||||||
|
|
||||||
### `_trim`
|
|
||||||
|
|
||||||
Removes all whitespace (`[:space:]`) from the beginning and ending
|
|
||||||
of a string without invoking a subprocess.
|
|
||||||
|
|
||||||
### `_filter`
|
|
||||||
|
|
||||||
Reads the first argument or each line of standard input passing
|
|
||||||
each individually as the first argument to the calling function one at
|
|
||||||
a time. The UNIX philosophy requires all commands be filters whenever possible.
|
|
||||||
|
|
||||||
### `_buffer`
|
|
||||||
|
|
||||||
Reads the first argument or all lines of standard input and then
|
|
||||||
passes them to the calling function as the first argument. The UNIX philosophy requires all command be filters whenever possible.
|
|
||||||
|
|
||||||
### `_reduce`
|
|
||||||
|
|
||||||
Takes the name of an array and a bash extended regular expression
|
|
||||||
and prints only the array entries that match, one to a line suitable for
|
|
||||||
converting back into an array with `IFS=$'"'\\\n'"'` or just as an in-memory
|
|
||||||
`grep` replacement.
|
|
||||||
|
|
||||||
### `_jsonstr`
|
|
||||||
|
|
||||||
Encodes first argument or all standard input into a single line of JSON text. This function depends on the `jq` command.
|
|
||||||
|
|
||||||
### `_urlencode`
|
|
||||||
|
|
||||||
Encodes the first argument or all standard input using standard URL
|
|
||||||
encoding suitable for passing to `curl` or whatever. This function has
|
|
||||||
no external dependencies.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
Required:
|
|
||||||
|
|
||||||
* Bash 4+
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
|
|
||||||
* `pandoc` - for rich help docs
|
|
||||||
* `jq` - for `json` and anything that uses it
|
|
||||||
|
|
||||||
## Justification
|
|
||||||
|
|
||||||
Bash is the dominant 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.
|
|
||||||
|
|
||||||
## Guidelines
|
|
||||||
|
|
||||||
* Write GitHub Flavored Markdown only
|
|
||||||
* Use present tense ("outputs" over "will output")
|
|
||||||
* Prefer term "output" and "display" over ~~print~~
|
|
||||||
* Follow the [naming conventions](#naming-conventions)
|
|
||||||
* Use the official bash path: `#!/bin/bash`
|
|
||||||
* Use of `#!/usr/bin/bash` is outdated
|
|
||||||
* Using `#!/usr/bin/env bash` introduces unnecessary risk
|
|
||||||
* Explicitly export `PATH` in script when possible
|
|
||||||
* Always check script with [`shellcheck`] before releasing
|
|
||||||
* Always use `bc` for *any* floating point math
|
|
||||||
|
|
||||||
[`shellcheck`]: <https://www.shellcheck.net>
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
This script is expected to be installed for a specific user and only
|
|
||||||
ever run by that user. No additional security vetting for running as an
|
|
||||||
untrusted user has been done.
|
|
||||||
|
|
||||||
## Legal
|
|
||||||
|
|
||||||
Copyright 2021 Rob Muhlestein <rob@rwx.gg>
|
|
||||||
Released under Apache-2.0 License
|
|
||||||
Please mention rwxrob.tv'
|
|
||||||
|
|
||||||
HELP[foo]='Foos things.'
|
|
||||||
|
|
||||||
x.foo () {
|
|
||||||
_filter "$@" && return $?
|
|
||||||
echo "would foo: $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
HELP[bar]='
|
|
||||||
|
|
||||||
```
|
|
||||||
'"$EXE"' bar
|
|
||||||
```
|
|
||||||
|
|
||||||
Bar the things.'
|
|
||||||
|
|
||||||
x.bar() {
|
|
||||||
_buffer "$@" && return $?
|
|
||||||
echo "would bar: $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
HELP[some.config.setting]='Get and set `some.config.setting`.'
|
|
||||||
|
|
||||||
x.some.config.setting() {
|
|
||||||
x.config some.config.setting "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
############################## BOILERPLATE ###########################
|
|
||||||
## Everything from here to the end of file can be snipped and updated
|
|
||||||
## with latest from https://github.com/rwxrob/template-bash-command.
|
|
||||||
|
|
||||||
# ------------------------------- usage ------------------------------
|
|
||||||
|
|
||||||
HELP[usage]='
|
|
||||||
|
|
||||||
```
|
|
||||||
'"$EXE"' usage
|
|
||||||
```
|
|
||||||
|
|
||||||
Display all possible commands. Note that this is usually easier by
|
|
||||||
simply using tab completion instead.'
|
|
||||||
|
|
||||||
x.usage() {
|
|
||||||
local -a cmds
|
|
||||||
for c in "${COMMANDS[@]}"; do
|
|
||||||
[[ ${c:0:1} =~ _ ]] && continue
|
|
||||||
cmds+=("$c")
|
|
||||||
done
|
|
||||||
local IFS='|'
|
|
||||||
printf "usage: %s (%s)\n" "$EXE" "${cmds[*]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------- help -------------------------------
|
|
||||||
|
|
||||||
HELP[help]='
|
|
||||||
|
|
||||||
```
|
|
||||||
'"$EXE"' 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.
|
|
||||||
'
|
|
||||||
|
|
||||||
x.help() {
|
|
||||||
local name="${1:-main}" title body file
|
|
||||||
title=$(_help_title "$name") || true
|
|
||||||
if [[ -z "$title" ]]; then
|
|
||||||
title="$EXE $name"
|
|
||||||
[[ $name == main ]] && title="$EXE"
|
|
||||||
fi
|
|
||||||
if [[ $name == main ]]; then
|
|
||||||
body=$(x.readme)
|
|
||||||
body=${body#*$title}
|
|
||||||
else
|
|
||||||
body="${HELP[$name]}"
|
|
||||||
fi
|
|
||||||
file="/tmp/help-$EXE-$name.html"
|
|
||||||
if _have pandoc ; then
|
|
||||||
if _have "$HELP_BROWSER" && [[ -t 1 ]] ;then
|
|
||||||
pandoc -f gfm -s --metadata title="$title" \
|
|
||||||
-o "$file" <<< "$body"
|
|
||||||
[[ -z "$2" ]] && cd /tmp && exec "$HELP_BROWSER" "$file"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
pandoc -f gfm -s --metadata title="$title" \
|
|
||||||
-t plain <<< "$body" | "$PAGER"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo -e "$title\n\n$body" | "$PAGER"
|
|
||||||
}
|
|
||||||
|
|
||||||
_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]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------ readme ------------------------------
|
|
||||||
|
|
||||||
HELP[readme]='
|
|
||||||
## Generate `README.md` File
|
|
||||||
|
|
||||||
```
|
|
||||||
'"$EXE"' 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.'
|
|
||||||
|
|
||||||
x.readme() {
|
|
||||||
_trim "${HELP[main]}"
|
|
||||||
echo
|
|
||||||
while IFS= read -r name; do
|
|
||||||
[[ $name = main ]] && continue
|
|
||||||
body=$(_trim "${HELP[$name]}")
|
|
||||||
[[ $body =~ ^\# ]] || body="## The \`$name\` Command"$'\n\n'$body
|
|
||||||
printf "%s\n\n" "$body"
|
|
||||||
done < <(printf "%s\n" "${!HELP[@]}" | LC_COLLATE=C sort)
|
|
||||||
echo -e "----\n\n*Autogenerated $(date)*\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------------ config ------------------------------
|
|
||||||
|
|
||||||
HELP[config]='
|
|
||||||
|
|
||||||
```
|
|
||||||
'"$EXE"' config
|
|
||||||
'"$EXE"' config KEY
|
|
||||||
'"$EXE"' config.set KEY VALUE
|
|
||||||
'"$EXE"' config.set KEY ""
|
|
||||||
'"$EXE"' config.keys
|
|
||||||
'"$EXE"' config.values
|
|
||||||
'"$EXE"' config.directory
|
|
||||||
'"$EXE"' config.path [file]
|
|
||||||
'"$EXE"' config.edit [file]
|
|
||||||
'"$EXE"' config.delete
|
|
||||||
'"$EXE"' config.read
|
|
||||||
'"$EXE"' config.write
|
|
||||||
'"$EXE"' config.dump
|
|
||||||
```
|
|
||||||
|
|
||||||
The `config` command is for reading, writing, and displaying standard
|
|
||||||
open desktop configuration properties.
|
|
||||||
|
|
||||||
### Arguments
|
|
||||||
|
|
||||||
With no arguments calls `dump` and outputs all the currently cached
|
|
||||||
configuration settings.
|
|
||||||
|
|
||||||
With a single KEY argument fetches the value for that key and outputs
|
|
||||||
it unless it is one of the following special (reserved) key names:
|
|
||||||
|
|
||||||
* `directory` full path to config directory
|
|
||||||
* `path` full path to specific config file (default: `values`)
|
|
||||||
* `edit` opens config file in editor (default: `editor` or `$EDITOR)
|
|
||||||
* `keys` output the configuration keys, one per line
|
|
||||||
* `values` output the configuration values, one per line
|
|
||||||
* `delete` if key argument then delete a specific key, otherwise prompt
|
|
||||||
* `read` reads the configuration file into CONF associative array
|
|
||||||
* `write` write the CONF associative array to the configuration file
|
|
||||||
* `dump` write the flattened CONF associative array to standard output
|
|
||||||
|
|
||||||
With more than one argument the remaining arguments after the KEY will
|
|
||||||
be combined into the VALUE and written to a `values` file in the
|
|
||||||
configuration directory.
|
|
||||||
|
|
||||||
### Configuration Directory
|
|
||||||
|
|
||||||
The configuration directory path relies on the following environment
|
|
||||||
variables:
|
|
||||||
|
|
||||||
* `EXE` - defaults to name of currently running command ('"$EXE"')
|
|
||||||
* `HOME` - checked for `$HOME/.config/$EXE/values`
|
|
||||||
* `XDG_CONFIG_HOME` - overrides `$HOME/.config`
|
|
||||||
* `CONFIG_DIR` - full path to directory containing `values` file
|
|
||||||
|
|
||||||
The `CONFIG_DIR` always takes priority over anything else if set, but is
|
|
||||||
never implied. If the directory does not exist it will be created the
|
|
||||||
first time a value is set.
|
|
||||||
|
|
||||||
### Configuration `values` File Format
|
|
||||||
|
|
||||||
The file (which is almost always located at
|
|
||||||
`~/.config/'"$EXE"'/values`) uses the simplest possible format to
|
|
||||||
facilitate standard UNIX parsing and filtering with any number of
|
|
||||||
existing tools (and no `jq` dependency).
|
|
||||||
|
|
||||||
* One KEY=VALUE per line
|
|
||||||
* KEYs may be anything but the equal sign (`=`)
|
|
||||||
* VALUEs may be anything but line returns must be escaped
|
|
||||||
|
|
||||||
Note that, although similar, this is *not* the same as Java properties
|
|
||||||
and other similar format. It is designed for ultimate simplicity,
|
|
||||||
efficiency, and portability.'
|
|
||||||
|
|
||||||
x.config() {
|
|
||||||
case $# in
|
|
||||||
0) x.config.dump ;;
|
|
||||||
1) x.config.get "$@" ;;
|
|
||||||
*) x.config.set "$@" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.edit() {
|
|
||||||
: "${CONF[editor]:="${EDITOR:=vi}"}"
|
|
||||||
exec "${CONF[editor]}" "$(x.config.path "${1:-values}")"
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.delete() {
|
|
||||||
if [[ -z "$1" ]];then
|
|
||||||
select key in "${!CONF[@]}"; do
|
|
||||||
x.config.delete "$key"
|
|
||||||
return $?
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
x.config.set "$1" ''
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.keys() { printf "%s\n" "${!CONF[@]}"; }
|
|
||||||
|
|
||||||
x.config.values() { printf "%s\n" "${CONF[@]}"; }
|
|
||||||
|
|
||||||
x.config.dir() {
|
|
||||||
local dir="$HOME/.config/$EXE"
|
|
||||||
[[ -n "$XDG_CONFIG_HOME" ]] && dir="$XDG_CONFIG_HOME/$EXE"
|
|
||||||
[[ -n "$CONFIG_DIR" ]] && dir="$CONFIG_DIR"
|
|
||||||
[[ -n "$1" ]] && echo "$dir/$1" && return 0
|
|
||||||
printf "%s" "$dir"
|
|
||||||
[[ -t 1 ]] && echo
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.path() {
|
|
||||||
local file=${1:-values}
|
|
||||||
printf "%s/%s" "$(x.config.dir)" "$file"
|
|
||||||
[[ -t 1 ]] && echo
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.set() {
|
|
||||||
local key="$1"; shift; local val="$*"
|
|
||||||
val="${val//$'\n'/\\n}"
|
|
||||||
CONF["$key"]="$val"
|
|
||||||
x.config.write
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.get() {
|
|
||||||
printf "%s" "${CONF[$1]}"
|
|
||||||
[[ -t 1 ]] && echo
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.read() {
|
|
||||||
local values="$(x.config.path)"
|
|
||||||
[[ -r "$values" ]] || return 0
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ $line =~ ^([^=]+)=(.+)$ ]] || continue
|
|
||||||
CONF["${BASH_REMATCH[1]}"]="${BASH_REMATCH[2]}"
|
|
||||||
done < "$values"
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.write() {
|
|
||||||
local dir="$(x.config.dir)"
|
|
||||||
mkdir -p "$dir"
|
|
||||||
x.config.dump > "$dir/values"
|
|
||||||
}
|
|
||||||
|
|
||||||
x.config.dump() {
|
|
||||||
(( ${#CONF[@]} == 0 )) && return 0
|
|
||||||
paste -d=\
|
|
||||||
<(printf "%s\n" "${!CONF[@]}") \
|
|
||||||
<(printf "%s\n" "${CONF[@]}") \
|
|
||||||
| sort
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------- utilities ----------------------------
|
|
||||||
|
|
||||||
_jsonstr() {
|
|
||||||
_checkdep jq || return $?
|
|
||||||
_buffer "$@" && return $?
|
|
||||||
jq -MRsc <<< "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
_urlencode() {
|
|
||||||
_buffer "$@" && return $?
|
|
||||||
local string="$1"
|
|
||||||
local strlen=${#string}
|
|
||||||
local encoded=""
|
|
||||||
local pos c o
|
|
||||||
for ((pos = 0; pos < strlen; pos++)); do
|
|
||||||
c=${string:$pos:1}
|
|
||||||
case "$c" in
|
|
||||||
[-_.~a-zA-Z0-9]) o="$c" ;;
|
|
||||||
*) printf -v o '%%%02x' "'$c'" ;;
|
|
||||||
esac
|
|
||||||
encoded+="$o"
|
|
||||||
done
|
|
||||||
echo "$encoded"
|
|
||||||
}
|
|
||||||
|
|
||||||
_reduce() {
|
|
||||||
local -n name="${1:?"name of array required"}"
|
|
||||||
while IFS= read -r key; do
|
|
||||||
[[ $key =~ $2 ]] && echo "$key"
|
|
||||||
done < <(printf "%s\n" "${name[@]}")
|
|
||||||
}
|
|
||||||
|
|
||||||
_newest() {
|
|
||||||
IFS=$'\n'
|
|
||||||
mapfile -t f < <(ls -1 --color=never -trd "${1:-.}"/* 2>/dev/null)
|
|
||||||
[[ ${#f} > 0 ]] && echo "${f[-1]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
_trim() {
|
|
||||||
local it="${1#"${1%%[![:space:]]*}"}"
|
|
||||||
echo -e "${it%"${it##*[![:space:]]}"}"
|
|
||||||
}
|
|
||||||
|
|
||||||
_join() { local IFS="$1"; shift; echo "$*"; }
|
|
||||||
|
|
||||||
_have(){ type "$1" &>/dev/null; }
|
|
||||||
|
|
||||||
_checkdep() {
|
|
||||||
_have "$1" && return 0
|
|
||||||
echo "'$EXE' depends on '$1' for this, but not found"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_filter(){
|
|
||||||
[[ -n "$1" ]] && return 1
|
|
||||||
while IFS= read -ra args; do
|
|
||||||
"${FUNCNAME[1]}" "${args[@]}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffer() {
|
|
||||||
[[ -n "$1" ]] && return 1
|
|
||||||
"${FUNCNAME[1]}" "$(</dev/stdin)"
|
|
||||||
}
|
|
||||||
|
|
||||||
_prompt() {
|
|
||||||
local key="$1" def="$2" regx="$3" value first=yes
|
|
||||||
shift 3
|
|
||||||
local text="${*:-Enter value for %s [%s]: }"
|
|
||||||
[[ -z "$key" ]] && echo "Missing prompt key" >&2 && return 1
|
|
||||||
[[ -z "$regx" ]] && echo "Missing valid regx" >&2 && return 1
|
|
||||||
while [[ ! $value =~ $regx ]];do
|
|
||||||
printf "$text" "$key" "$def" >&2
|
|
||||||
IFS= read -r value
|
|
||||||
[[ -z "$value" ]] && value="$def"
|
|
||||||
[[ $value =~ ^\ +$ ]] && value=""
|
|
||||||
[[ -n "$first" ]] && unset first && continue
|
|
||||||
echo "Must match /$regx/" >&2
|
|
||||||
done
|
|
||||||
_trim "$value"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------- completion and delegation --------------------
|
|
||||||
# `complete -C foo foo` > `source <(foo bloated_completion)`
|
|
||||||
|
|
||||||
x.config.read
|
|
||||||
_have _initialize && _initialize "$@"
|
|
||||||
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ $line =~ ^declare\ -f\ x\. ]] || continue
|
|
||||||
COMMANDS+=( "${line##declare -f x.}" )
|
|
||||||
done < <(declare -F)
|
|
||||||
mapfile -t 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,,}" ]] && echo "$c"
|
|
||||||
done
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
for c in "${COMMANDS[@]}"; do
|
|
||||||
if [[ $c == "$EXE" ]]; then
|
|
||||||
"x.$EXE" "$@"
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "$1" ]]; then
|
|
||||||
declare CMD="$1"; shift
|
|
||||||
for c in "${COMMANDS[@]}"; do
|
|
||||||
declare cmd=$(command -v "x.$c")
|
|
||||||
if [[ $c == "$CMD" && -n "$cmd" ]]; then
|
|
||||||
"x.$CMD" "$@"
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if _have _alternatives; then
|
|
||||||
_alternatives "$@"
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
x.usage "$@"
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
set -e
|
||||||
|
export PATH="/usr/bin:/usr/local/bin"
|
||||||
|
|
||||||
|
# Copyright 2021 Rob Muhlestein <rob@rwx.gg>
|
||||||
|
# Released under Apache-2.0 license
|
||||||
|
# Please mention https://youtube.com/rwxrob
|
||||||
|
|
||||||
|
(( 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]='
|
||||||
|
This snippet contains the scaffolding for Bash tab completion using
|
||||||
|
the complete -C foo foo variation which allows scripts to complete
|
||||||
|
themselves (rather than having another script somewhere to manage). To
|
||||||
|
use it simply add a function with the additional command and add the
|
||||||
|
name of it to the commands array declaration at the top of the script.
|
||||||
|
Then add complete -C foo foo (or something like it) to your bashrc.
|
||||||
|
Begin functions with x_ to allow useful command names to be used that
|
||||||
|
would otherwise conflict with existing system and bash keywords. Begin
|
||||||
|
functions with x__ when you do not want them to appear with tab
|
||||||
|
completion, but still want them to be avaiable, just hidden.
|
||||||
|
'
|
||||||
|
|
||||||
|
help[foo]='The `foo` command foos.'
|
||||||
|
|
||||||
|
command_foo() {
|
||||||
|
_filter "$@" && return $?
|
||||||
|
echo "would foo: $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
help[bar]='The `bar` command bars.'
|
||||||
|
|
||||||
|
command_bar() {
|
||||||
|
_buffer "$@" && return $?
|
||||||
|
echo "would bar: $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
command__hidden() {
|
||||||
|
_filter "$@" && return $?
|
||||||
|
echo "would run _hidden: $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
help[usage]='The `usage` command 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]='
|
||||||
|
The `help` command prints 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 CommonMark (Markdown) and will displayed as Web page if
|
||||||
|
`pandoc` and `$HELP_BROWSER` are detected, otherwise, just the Markdown is
|
||||||
|
sent to `$PAGER` (default: more).'
|
||||||
|
|
||||||
|
command_help() {
|
||||||
|
local name="$1"
|
||||||
|
if [[ -z "$name" ]];then
|
||||||
|
for c in "${COMMANDS[@]}";do
|
||||||
|
[[ ${c:0:1} = _ ]] && continue;
|
||||||
|
command_help "$c" buildonly
|
||||||
|
done
|
||||||
|
command_help main
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local title="$EXE $name"
|
||||||
|
[[ $name = main ]] && title="$EXE"
|
||||||
|
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" <<< "${help[$name]}"
|
||||||
|
[[ -z "$2" ]] && cd /tmp && exec "$HELP_BROWSER" "$file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
pandoc -s --metadata title="$title" \
|
||||||
|
-t plain <<< "${help[$name]}" | "$PAGER"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "${help[$name]}" | "$PAGER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --------------------- completion and delegation --------------------
|
||||||
|
|
||||||
|
_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)"
|
||||||
|
}
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ $line =~ ^declare\ -f\ command_ ]] || continue
|
||||||
|
COMMANDS+=( "${line##declare -f command_}" )
|
||||||
|
done < <(declare -F)
|
||||||
|
|
||||||
|
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 "$@"
|
Loading…
Reference in New Issue