From 4cd42cd7535f6ef3263cd24074f9d2aa36c5d126 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 13:00:35 -0700 Subject: [PATCH 01/21] Introduce a multi-value option syntax. --- scripts/lib/bashy-core/arg-processor.sh | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index a5936d1..5686478 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -564,6 +564,9 @@ function _argproc_define-value-taking-arg { eval 'function '"${handlerName}"' { if (( $# < 1 )); then '"${ifNoValue}"' + elif (( $# > 1 )); then + error-msg "Too many values for '"${desc}"'." + return 1 fi '"${handlerBody}"' }' @@ -915,7 +918,7 @@ function _argproc_set-arg-description { # read. function _argproc_statements-from-args { local argError=0 - local arg handler name value + local arg handler name value values # This is used for required-argument checking. _argproc_statements+=($'local _argproc_receivedArgNames=\'\'') @@ -931,7 +934,7 @@ function _argproc_statements-from-args { # Non-option argument. break elif [[ ${arg} =~ ^--([-a-zA-Z0-9]+)(=.*)?$ ]]; then - # Long-form argument. + # Long-form no- or single-value option. name="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}" handler="_argproc:long-${name}" @@ -944,8 +947,24 @@ function _argproc_statements-from-args { # `:1` to drop the `=` from the start of `value`. _argproc_statements+=("${handler} $(_argproc_quote "${value:1}")") fi + elif [[ ${arg} =~ ^--([-a-zA-Z0-9]+)\[(.*)\]$ ]]; then + # Long-form multi-value option. + name="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + handler="_argproc:long-${name}" + if ! declare -F "${handler}" >/dev/null; then + error-msg "Unknown option: --${name}" + argError=1 + else + # Parse the value into elements. + eval 2>/dev/null "values=(${value})" || { + error-msg "Invalid multi-value syntax for option --${name}:" + error-msg " ${value}" + } + _argproc_statements+=("${handler} $(_argproc_quote "${values[@]}")") + fi elif [[ $arg =~ ^-([a-zA-Z0-9]+)$ ]]; then - # Short-form argument. + # Short-form option. arg="${BASH_REMATCH[1]}" while [[ ${arg} =~ ^(.)(.*)$ ]]; do name="${BASH_REMATCH[1]}" From 889f6f9422cdc114c531cf87dc8bcfddf4b83dee Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 13:25:56 -0700 Subject: [PATCH 02/21] Start to document `arg-processor`. --- README.md | 4 +++ doc/arg-processor.md | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 doc/arg-processor.md diff --git a/README.md b/README.md index f6b18b0..296ef84 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ The 1.0 release is stable but not recommended for use. The 2.* series of releases isn't yet stable. You can still expect breaking changes with each release. Expect a stable release sometime in 2024. +## Documentation + +* [Argument Processing](./doc/arg-processor.md) + ## To use included commands 1. Copy the `scripts` directory from this project (or its contents) to a diff --git a/doc/arg-processor.md b/doc/arg-processor.md new file mode 100644 index 0000000..a143282 --- /dev/null +++ b/doc/arg-processor.md @@ -0,0 +1,63 @@ +The `arg-processor` argument processing system. +=============================================== + +Bashy-lib includes a full-featured argument processing system, which accepts an +argument/option syntax which hews closely to modern Posix(ish) de facto +standards, while innovating just a tad in order to help with robustness and +expressivity. + +## Recognized syntax + +As one might expect, the top-level syntax is that a set of valid arguments +consists of zero or more named options followed by zero or more positional +arguments. + +Short options consist of a single dash (`-`) followed by a single letter, and in +this system such options are not allowed to accept values. + +Long options consist of two dashes (`--`) followed by a series of alphanumerics +and more dashes. For options that accept one value (or more), the option name +can be followed by an equal sign (`=`) and the option value. For options that +accept _more_ than one value, the option can be followed by an open square +bracket (`[`) and then a series of strings in shell syntax (including unquoted +words if there are no special characters) and then a final `]`. This multi-value +form will also work for options that don't allow values or allow only one +(though there are probably few reasons to favor the form in those cases). + +The helper function `values` is a convenient way to safely pass multiple values +without having to worry about quoting hygiene. (That is, the helper handles it +for you.) + +Special cases: +* A single dash (`-`) is interpreted as a non-option argument. +* A negative number, that is a dash followed by one or more digits, is + interpreted as a non-option argument. +* An argument consisting of just two dashes (`--`) is taken to indicate the + end of _option_ parsing, with all subsequent arguments taken to be positional + even if they (seem to) have valid option syntax. +* If an option is a toggle, then: + * Prefixing its long name with `no-` turns it off. + * Specifying a value of `0` or `1` sets it to on or off explicitly. + +Examples: + +```bash +my-cmd -h # Short option. +my-cmd --help # Long option, no value. +my-cmd --size=27 # Long option, with value. +my-cmd --colors['red green blue'] # Long option, multi-value. +my-cmd --no-florp # Long option, turning off a toggle. +my-cmd --florp=1 # Long option, setting a toggle explicitly. +my-cmd some-argument # One positional argument. +my-cmd - # One positional argument. +my-cmd -34 # One positional argument. +my-cmd -- --foo # One positional argument, literally `--foo`. + +# Passing arbitrary strings safely to a multi-value option. +my-cmd --strings["$(values "${arrayOfStrings[@]}")"] +``` + +## Declaring options + +TODO! Coming soon! In the mean time, see the source of all the included commands +and tests for a rich set of examples. From 7d62243e58ae73d750a4a8a26ed919486bd516f1 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 13:32:23 -0700 Subject: [PATCH 03/21] Split out the docs. --- README.md | 200 +---------------------------------------- doc/developer-guide.md | 148 ++++++++++++++++++++++++++++++ doc/user-guide.md | 55 ++++++++++++ 3 files changed, 205 insertions(+), 198 deletions(-) create mode 100644 doc/developer-guide.md create mode 100644 doc/user-guide.md diff --git a/README.md b/README.md index 296ef84..3bae7b0 100644 --- a/README.md +++ b/README.md @@ -29,201 +29,5 @@ changes with each release. Expect a stable release sometime in 2024. ## Documentation * [Argument Processing](./doc/arg-processor.md) - -## To use included commands - -1. Copy the `scripts` directory from this project (or its contents) to a - directory that is part of your `$PATH`. You need to include the directory - `lib/bashy-core`, but the other directories under `lib` are all optional. - -2. To use a Bashy-lib command from the command line, call `ubik` and pass it - the (possibly-hierarchical) name of the command, e.g.: - - ``` - $ ubik timey secs now - 1685569338 - $ - ``` - - Just saying `ubik` will list all the available commands. Every command - responds to `--help` with a decently-detailed description. - -2. To call a Bashy-lib command from one of your own scripts, either: - - * Recommended: Include the top-level `_init.sh` from your script, and then - call `lib` with the (possibly-hierarchical) command, e.g.: - - ```bash - . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" - - ... - nowSecs="$(lib timey secs now)" - ... - ``` - - * Not recommended: Just use `ubik` as above. (Not recommended because it is - less efficient, in that it does extra stuff to be a nice interactive - command.) - -### Setup for even better interactive use - -The top-level script named `ubik` (so named for historical reasons) is a -general dispatcher, which can be called like `ubik `, where -`` is any command defined in the `lib` next to the `ubik` script. - -You can of course set your `$PATH` to include the `scripts` directory in -question, however it's often useful to be able to switch from project to project -without having to reset the `$PATH`. To that end, you can include the contents -of the file `scripts/lib/bashy-core/ubik-interactive.sh` in your interactive -setup, e.g. by using `. .../ubik-interactive.sh` or by just pasting its -contents directly (it is self-contained). - -`ubik-interactive.sh` defines a shell function called `ubik`. This function -searches up the directory hierarchy from the CWD for an `ubik` script to run (in -a `bin` or `scripts` directory), and if it finds one it will run it with -whatever arguments you passed to the function. - -## To extend with your own library - -### Basic directory layout - -``` -project-base-directory/ - scripts/ - _init.sh -- boilerplate init file - top-level-script - top-level-script - ubik -- general library caller (copy from this project) - lib/ - _init.sh -- boilerplate (mostly) init file - bashy-core/ -- copy of directory from this project - other-lib/ -- copy of other library (from this project or elsewhere) - my-project/ - _init.sh -- boilerplate init file - _prereqs -- unit-specific prerequisites checker (optional) - _setup.sh -- unit-specific setup (optional) - project-script - project-script - project-subcommand-dir/ - _init.sh -- boilerplate init file - _run -- default subcommand script (optional) - subcommand-script - subcommand-script - subsub-dir/ - _run -- default subcommand script (optional) - subcommand-script - project-subcommand-dir/ - _init.sh -- boilerplate init file - _run -- default subcommand script (optional) - subcommand-script - subcommand-script -``` - -### TLDR - -* Copy the `scripts` directory of this project. - -* Put your scripts in one of two places: - * A unit (sub-library) directory for your project in `scripts/lib`. - * Directly in `scripts` (if they don't need to be called by other scripts). - -* Put a non-executable file called `_init.sh` in every directory where a script - lives. This file is included by your scripts and serves to link them up to the - main library. - -* Put the following line at the top of every script: - - ```bash - . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" - ``` - -### Detailed Instructions - -1. Pick a name for your project's "script library" directory, typically at the - top level of your project. `scripts` and `bin` are good choices. - - The rest of these instructions assume you picked `scripts`, for ease of - exposition. Adjust accordingly. - -2. Pick a symbolic name for your project / product, e.g. that can (and will) be - used as a directory name. - - The rest of these instructions assume you named your project `my-project`. - Adjust accordingly. - -3. Copy the items from the `scripts` directory in _this_ project, that you are - interested in using, into `scripts` in your project. At a minimum, you need - to include the `lib/bashy-core` and `lib/_init.sh`. The files directly in - `scripts` (`_init.sh` and `ubik`) are needed if you want to expose library - scripts "publicly" in `scripts` (at least, in the standard way supported by - this project). - - **Note:** `lib/_init.sh` file will need to be adjusted if `scripts` is not - directly under your project's base directory. - -4. Make a directory for your own script sub-library, `scripts/lib/my-project`. - -5. Create a file called `scripts/lib/my-project/_init.sh`, to hook up - your project's script sub-library to `bashy-core`. The file should just - contain this: - - ```bash - . "${BASH_SOURCE[0]%/lib/*}/lib/_init.sh" || return "$?" - ``` - -6. Create one or more scripts in `scripts/lib/my-project`, or directly in - `scripts`. At the top of each script, include the following: - - ```bash - . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" - ``` - - **Note:** Scripts directly in `scripts` should generally not be called from - other scripts. See below about "exposing" a script in your unit for direct - "public" calling. - -7. Create one or more subcommand directories in `scripts/lib/my-project`. Add - an `_init.sh` file to it (same as in step 5), and one or more scripts or - subcommand directories (same as step 6 or this step). - -8. To expose a script in a unit for direct "public" usage, create a script with - the same name as the script in the top-level `scripts` directory, with the - following contents, which causes it to call through to the unit script: - - ```bash - #!/bin/bash - - . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" - lib "$(this-cmd-name)" "$@" - ``` - -**Note:** The files named with a `.sh` suffix are _not_ supposed to be marked -executable (`chmod +x ...`). These are _include_ files. - -### To add per-subproject subcommand-script - -Sometimes it makes sense to define subprojects as separate directories within -a project. In such cases, you may still want to have those subprojects' scripts -be available in the top level project for interactive or script-available use -(or both). Bashy-lib makes this arrangement possible via symlinking either per -se or via "reified link" files (a text file containing the path, which may be -preferable because of source control concerns). *Relative* reified links are -taken to use the main project base directory as the base directory of the link. - -For example, let's say you have a directory `my-subproject` under your main -project, which has its own `scripts` directory. Using symlinks per se: - -```bash -$ cd my-project/scripts/lib/my-project # Your project's main scripts. -$ ln -s ../../../my-subproject/scripts my-subproject -``` - -Or using a reified link file: - -```bash -$ cd my-project -$ echo 'my-subproject/scripts' > scripts/lib/my-subproject.link -``` - -With that, you can now say `ubik my-subproject ...` or (in scripts) `lib -my-subproject ...`. +* [Developer Guide](./doc/developer-guide.md) +* [User Guide](./doc/user-guide.md) diff --git a/doc/developer-guide.md b/doc/developer-guide.md new file mode 100644 index 0000000..c83e1a0 --- /dev/null +++ b/doc/developer-guide.md @@ -0,0 +1,148 @@ +Bashy-Lib Developer Guide +========================= + +This is a rough guide for adding and integrating new units (sub-libraries) to a +base Bashy-lib installation. + +## TLDR + +* Copy the `scripts` directory of this project. + +* Put your scripts in one of two places: + * A unit (sub-library) directory for your project in `scripts/lib`. + * Directly in `scripts` (if they don't need to be called by other scripts). + +* Put a non-executable file called `_init.sh` in every directory where a script + lives. This file is included by your scripts and serves to link them up to the + main library. + +* Put the following line at the top of every script: + + ```bash + . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" + ``` + +## Basic directory layout + +``` +project-base-directory/ + scripts/ + _init.sh -- boilerplate init file + top-level-script + top-level-script + ubik -- general library caller (copy from this project) + lib/ + _init.sh -- boilerplate (mostly) init file + bashy-core/ -- copy of directory from this project + other-lib/ -- copy of other library (from this project or elsewhere) + my-project/ + _init.sh -- boilerplate init file + _prereqs -- unit-specific prerequisites checker (optional) + _setup.sh -- unit-specific setup (optional) + project-script + project-script + project-subcommand-dir/ + _init.sh -- boilerplate init file + _run -- default subcommand script (optional) + subcommand-script + subcommand-script + subsub-dir/ + _run -- default subcommand script (optional) + subcommand-script + project-subcommand-dir/ + _init.sh -- boilerplate init file + _run -- default subcommand script (optional) + subcommand-script + subcommand-script +``` + +## Detailed Instructions + +1. Pick a name for your project's "script library" directory, typically at the + top level of your project. `scripts` and `bin` are good choices. + + The rest of these instructions assume you picked `scripts`, for ease of + exposition. Adjust accordingly. + +2. Pick a symbolic name for your project / product, e.g. that can (and will) be + used as a directory name. + + The rest of these instructions assume you named your project `my-project`. + Adjust accordingly. + +3. Copy the items from the `scripts` directory in _this_ project, that you are + interested in using, into `scripts` in your project. At a minimum, you need + to include the `lib/bashy-core` and `lib/_init.sh`. The files directly in + `scripts` (`_init.sh` and `ubik`) are needed if you want to expose library + scripts "publicly" in `scripts` (at least, in the standard way supported by + this project). + + **Note:** `lib/_init.sh` file will need to be adjusted if `scripts` is not + directly under your project's base directory. + +4. Make a directory for your own script sub-library, `scripts/lib/my-project`. + +5. Create a file called `scripts/lib/my-project/_init.sh`, to hook up + your project's script sub-library to `bashy-core`. The file should just + contain this: + + ```bash + . "${BASH_SOURCE[0]%/lib/*}/lib/_init.sh" || return "$?" + ``` + +6. Create one or more scripts in `scripts/lib/my-project`, or directly in + `scripts`. At the top of each script, include the following: + + ```bash + . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" + ``` + + **Note:** Scripts directly in `scripts` should generally not be called from + other scripts. See below about "exposing" a script in your unit for direct + "public" calling. + +7. Create one or more subcommand directories in `scripts/lib/my-project`. Add + an `_init.sh` file to it (same as in step 5), and one or more scripts or + subcommand directories (same as step 6 or this step). + +8. To expose a script in a unit for direct "public" usage, create a script with + the same name as the script in the top-level `scripts` directory, with the + following contents, which causes it to call through to the unit script: + + ```bash + #!/bin/bash + + . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" + lib "$(this-cmd-name)" "$@" + ``` + +**Note:** The files named with a `.sh` suffix are _not_ supposed to be marked +executable (`chmod +x ...`). These are _include_ files. + +## To add per-subproject subcommand-script + +Sometimes it makes sense to define subprojects as separate directories within +a project. In such cases, you may still want to have those subprojects' scripts +be available in the top level project for interactive or script-available use +(or both). Bashy-lib makes this arrangement possible via symlinking either per +se or via "reified link" files (a text file containing the path, which may be +preferable because of source control concerns). *Relative* reified links are +taken to use the main project base directory as the base directory of the link. + +For example, let's say you have a directory `my-subproject` under your main +project, which has its own `scripts` directory. Using symlinks per se: + +```bash +$ cd my-project/scripts/lib/my-project # Your project's main scripts. +$ ln -s ../../../my-subproject/scripts my-subproject +``` + +Or using a reified link file: + +```bash +$ cd my-project +$ echo 'my-subproject/scripts' > scripts/lib/my-subproject.link +``` + +With that, you can now say `ubik my-subproject ...` or (in scripts) `lib +my-subproject ...`. diff --git a/doc/user-guide.md b/doc/user-guide.md new file mode 100644 index 0000000..384e908 --- /dev/null +++ b/doc/user-guide.md @@ -0,0 +1,55 @@ +Bashy-Lib User Guide +==================== + +## To use included commands + +1. Copy the `scripts` directory from this project (or its contents) to a + directory that is part of your `$PATH`. You need to include the directory + `lib/bashy-core`, but the other directories under `lib` are all optional. + +2. To use a Bashy-lib command from the command line, call `ubik` and pass it + the (possibly-hierarchical) name of the command, e.g.: + + ``` + $ ubik timey secs now + 1685569338 + $ + ``` + + Just saying `ubik` will list all the available commands. Every command + responds to `--help` with a decently-detailed description. + +2. To call a Bashy-lib command from one of your own scripts, either: + + * Recommended: Include the top-level `_init.sh` from your script, and then + call `lib` with the (possibly-hierarchical) command, e.g.: + + ```bash + . "$(dirname "$(readlink -f "$0")")/_init.sh" || exit "$?" + + ... + nowSecs="$(lib timey secs now)" + ... + ``` + + * Not recommended: Just use `ubik` as above. (Not recommended because it is + less efficient, in that it does extra stuff to be a nice interactive + command.) + +## Setup for even better interactive use + +The top-level script named `ubik` (so named for historical reasons) is a +general dispatcher, which can be called like `ubik `, where +`` is any command defined in the `lib` next to the `ubik` script. + +You can of course set your `$PATH` to include the `scripts` directory in +question, however it's often useful to be able to switch from project to project +without having to reset the `$PATH`. To that end, you can include the contents +of the file `scripts/lib/bashy-core/ubik-interactive.sh` in your interactive +setup, e.g. by using `. .../ubik-interactive.sh` or by just pasting its +contents directly (it is self-contained). + +`ubik-interactive.sh` defines a shell function called `ubik`. This function +searches up the directory hierarchy from the CWD for an `ubik` script to run (in +a `bin` or `scripts` directory), and if it finds one it will run it with +whatever arguments you passed to the function. From 7a4fc3593a87456c8536b69515dfc2f66b364361 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 16:35:17 -0700 Subject: [PATCH 04/21] Add function `vals`. --- doc/arg-processor.md | 4 +- scripts/lib/bashy-core/misc.sh | 23 ++++++ tests/02-core/04-misc/01-vals/expect.md | 100 ++++++++++++++++++++++++ tests/02-core/04-misc/01-vals/info.md | 1 + tests/02-core/04-misc/01-vals/run | 48 ++++++++++++ 5 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 tests/02-core/04-misc/01-vals/expect.md create mode 100644 tests/02-core/04-misc/01-vals/info.md create mode 100755 tests/02-core/04-misc/01-vals/run diff --git a/doc/arg-processor.md b/doc/arg-processor.md index a143282..557b5d3 100644 --- a/doc/arg-processor.md +++ b/doc/arg-processor.md @@ -24,7 +24,7 @@ words if there are no special characters) and then a final `]`. This multi-value form will also work for options that don't allow values or allow only one (though there are probably few reasons to favor the form in those cases). -The helper function `values` is a convenient way to safely pass multiple values +The helper function `vals` is a convenient way to safely pass multiple values without having to worry about quoting hygiene. (That is, the helper handles it for you.) @@ -54,7 +54,7 @@ my-cmd -34 # One positional argument. my-cmd -- --foo # One positional argument, literally `--foo`. # Passing arbitrary strings safely to a multi-value option. -my-cmd --strings["$(values "${arrayOfStrings[@]}")"] +my-cmd --strings["$(vals "${arrayOfStrings[@]}")"] ``` ## Declaring options diff --git a/scripts/lib/bashy-core/misc.sh b/scripts/lib/bashy-core/misc.sh index b58203e..e219410 100644 --- a/scripts/lib/bashy-core/misc.sh +++ b/scripts/lib/bashy-core/misc.sh @@ -82,6 +82,29 @@ function sort-array { eval "${_bashy_arrayName}=(\"\${_bashy_arr[@]}\")" } +# Helper for passing multiple values to multi-value options (`--name[...]`), +# which formats its arguments so that the argument processor can recover the +# original multiple values. This works for any number of values including zero +# or one. Use it like `cmd --opt-name["$(values ...)"]`, or more specifically +# when you want to pass an array like `cmd --opt-name["$(values +# "${arrayName[@]}")"]`. +function vals { + case "$#" in + 0) + : # No need to emit anything. + ;; + 1) + printf '%q\n' "$1" + ;; + *) + printf '%q' "$1" + shift + printf ' %q' "$@" + printf '\n' + ;; + esac +} + # # Helper functions diff --git a/tests/02-core/04-misc/01-vals/expect.md b/tests/02-core/04-misc/01-vals/expect.md new file mode 100644 index 0000000..5734469 --- /dev/null +++ b/tests/02-core/04-misc/01-vals/expect.md @@ -0,0 +1,100 @@ +## no args + +### stdout +``` +Empty result. +Length: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## one arg + +### stdout +``` +Result: hello +Length: 1 + +0: hello +``` + +### exit: 0 + +- - - - - - - - - - + +## two args + +### stdout +``` +Result: how goes +Length: 2 + +0: how +1: goes +``` + +### exit: 0 + +- - - - - - - - - - + +## three args + +### stdout +``` +Result: Things are awesome. +Length: 3 + +0: Things +1: are +2: awesome. +``` + +### exit: 0 + +- - - - - - - - - - + +## space-type stuff in arguments + +### stdout +``` +Result: space \ more\ space\ $'yes\nno\n\n' $'\there\tthere' +Length: 4 + +0: space +1: more space +2: yes +no + + +3: here there +``` + +### exit: 0 + +- - - - - - - - - - + +## special characters in arguments + +### stdout +``` +Result: \& \| \( \) \< \> \? \! \* \[ \] \{ \} +Length: 13 + +0: & +1: | +2: ( +3: ) +4: < +5: > +6: ? +7: ! +8: * +9: [ +10: ] +11: { +12: } +``` + +### exit: 0 diff --git a/tests/02-core/04-misc/01-vals/info.md b/tests/02-core/04-misc/01-vals/info.md new file mode 100644 index 0000000..aacb5c4 --- /dev/null +++ b/tests/02-core/04-misc/01-vals/info.md @@ -0,0 +1 @@ +Test of the `vals` function. diff --git a/tests/02-core/04-misc/01-vals/run b/tests/02-core/04-misc/01-vals/run new file mode 100755 index 0000000..679d6e8 --- /dev/null +++ b/tests/02-core/04-misc/01-vals/run @@ -0,0 +1,48 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +function call-vals { + local result + + result="$(vals "$@")" || return "$?" + + if [[ ${result} == '' ]]; then + printf 'Empty result.\n' + else + printf 'Result: %s\n' "${result}" + fi + + local values + eval "values=(${result})" || return "$?" + + printf 'Length: %s\n' "${#values[@]}" + + if (( ${#values[@]} != 0 )); then + printf '\n' + local n + for n in "${!values[@]}"; do + printf '%s: %s\n' "${n}" "${values[n]}" + done + fi +} + +call-and-log-as-test 'no args' \ + call-vals + +call-and-log-as-test 'one arg' \ + call-vals hello + +call-and-log-as-test 'two args' \ + call-vals how goes + +call-and-log-as-test 'three args' \ + call-vals Things are awesome. + +call-and-log-as-test 'space-type stuff in arguments' \ + call-vals 'space' ' more space ' $'yes\nno\n\n' $'\there\tthere' + +call-and-log-as-test 'special characters in arguments' \ + call-vals '&' '|' '(' ')' '<' '>' '?' '!' '*' '[' ']' '{' '}' ';' From ffed9327c2f1ba82b9bf4a78037a5d7c4c579bc4 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 16:36:39 -0700 Subject: [PATCH 05/21] Changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3844d1c..d258d63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,13 @@ Changelog Notable changes: +* Cleaned up existing doc and added a handful more. * `bashy-core`: * `arg-processor`: * Tightened up error checking and reporting. * New recommended processing call `process-args "$@" || exit "$?"`, because of "magic" reduction noted below. + * Added multi-value option syntax `--opt-name[...]`. * `define-usage`: * New option `--with-help` to help reduce boilerplate. * Dropped "magical" `exit` behavior. From 593a9cc2d8a37c806dd9bbafed13326172d1454e Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Wed, 11 Oct 2023 16:41:39 -0700 Subject: [PATCH 06/21] Expand test. --- tests/02-core/04-misc/01-vals/expect.md | 63 +++++++++++++++++++++---- tests/02-core/04-misc/01-vals/run | 10 +++- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/tests/02-core/04-misc/01-vals/expect.md b/tests/02-core/04-misc/01-vals/expect.md index 5734469..579430c 100644 --- a/tests/02-core/04-misc/01-vals/expect.md +++ b/tests/02-core/04-misc/01-vals/expect.md @@ -75,19 +75,19 @@ no - - - - - - - - - - -## special characters in arguments +## special characters as arguments ### stdout ``` -Result: \& \| \( \) \< \> \? \! \* \[ \] \{ \} -Length: 13 - -0: & -1: | -2: ( -3: ) -4: < -5: > +Result: \" \' \; \& \| \$ \? \! \* \[ \] \{ \} \( \) \< \> +Length: 17 + +0: " +1: ' +2: ; +3: & +4: | +5: $ 6: ? 7: ! 8: * @@ -95,6 +95,49 @@ Length: 13 10: ] 11: { 12: } +13: ( +14: ) +15: < +16: > +``` + +### exit: 0 + +- - - - - - - - - - + +## special characters in the middle of arguments + +### stdout +``` +Result: a\"b a\'b a\;b a\&b a\|b a\$b a\?b a\!b a\*b +Length: 9 + +0: a"b +1: a'b +2: a;b +3: a&b +4: a|b +5: a$b +6: a?b +7: a!b +8: a*b +``` + +### exit: 0 + +- - - - - - - - - - + +## special characters surrounding arguments + +### stdout +``` +Result: \[ab\] \{ab\} \(ab\) \ +Length: 4 + +0: [ab] +1: {ab} +2: (ab) +3: ``` ### exit: 0 diff --git a/tests/02-core/04-misc/01-vals/run b/tests/02-core/04-misc/01-vals/run index 679d6e8..4db593b 100755 --- a/tests/02-core/04-misc/01-vals/run +++ b/tests/02-core/04-misc/01-vals/run @@ -44,5 +44,11 @@ call-and-log-as-test 'three args' \ call-and-log-as-test 'space-type stuff in arguments' \ call-vals 'space' ' more space ' $'yes\nno\n\n' $'\there\tthere' -call-and-log-as-test 'special characters in arguments' \ - call-vals '&' '|' '(' ')' '<' '>' '?' '!' '*' '[' ']' '{' '}' ';' +call-and-log-as-test 'special characters as arguments' \ + call-vals '"' "'" ';' '&' '|' '$' '?' '!' '*' '[' ']' '{' '}' '(' ')' '<' '>' + +call-and-log-as-test 'special characters in the middle of arguments' \ + call-vals 'a"b' "a'b" 'a;b' 'a&b' 'a|b' 'a$b' 'a?b' 'a!b' 'a*b' + +call-and-log-as-test 'special characters surrounding arguments' \ + call-vals '[ab]' '{ab}' '(ab)' '' From f7982d8e3e65d46c24a2aab31eb63096d8f76f15 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 08:36:23 -0700 Subject: [PATCH 07/21] Clarify. --- scripts/lib/bashy-core/arg-processor.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 5686478..0c26569 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -186,12 +186,12 @@ function opt-toggle { fi } -# Declares a "value" option, which requires a value when passed on a -# commandline. If a is passed in the spec, then the resulting option is -# value-optional, with the no-value form using the given . No is -# allowed in the argument spec. If left unspecified, the initial variable value -# for a value option is `''` (the empty string). This definer also accepts the -# `--required` option. +# Declares a "value" option, which allows passing an arbitrary value. If a +# is passed in the spec, then the resulting option is value-optional, +# with the no-value form using the given . No is allowed in the +# argument spec. If left unspecified, the default variable value for a value +# option is `''` (the empty string). This definer also accepts the `--required` +# option. function opt-value { local optCall='' local optFilter='' From 3ed48cda12b615271a0e990f71734309155ae464 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 08:40:17 -0700 Subject: [PATCH 08/21] Stub out `opt-multi`. --- scripts/lib/bashy-core/arg-processor.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 0c26569..7a30412 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -150,6 +150,17 @@ function opt-choice { fi } +# Declares a "multi-value" option, which allows passing zero or more values. No +# `` is allowed in the argument spec. These options are accepted via the +# syntax `--[]=` where is a space-separated list of +# literal values, with standard shell quoting and escaping allowed in order to +# pass special characters. +function opt-multi { + # TODO + error-msg 'TODO!' + return 1 +} + # Declares a "toggle" option, which allows setting of a value to `0` or `1`. No # `` is allowed in the argument spec. The main long form option name can # be used without a value to indicate "on" (`1`), or it can be used as From 63f801cb640a89f7a27f724fb43226dc00dbc353 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 08:53:18 -0700 Subject: [PATCH 09/21] Better choice of syntax. --- CHANGELOG.md | 2 +- doc/arg-processor.md | 35 +++++++++++++++---------- scripts/lib/bashy-core/arg-processor.sh | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d258d63..2381bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Notable changes: * Tightened up error checking and reporting. * New recommended processing call `process-args "$@" || exit "$?"`, because of "magic" reduction noted below. - * Added multi-value option syntax `--opt-name[...]`. + * Added multi-value option syntax `--opt-name[]=...`. * `define-usage`: * New option `--with-help` to help reduce boilerplate. * Dropped "magical" `exit` behavior. diff --git a/doc/arg-processor.md b/doc/arg-processor.md index 557b5d3..a86d5a8 100644 --- a/doc/arg-processor.md +++ b/doc/arg-processor.md @@ -15,18 +15,25 @@ arguments. Short options consist of a single dash (`-`) followed by a single letter, and in this system such options are not allowed to accept values. -Long options consist of two dashes (`--`) followed by a series of alphanumerics -and more dashes. For options that accept one value (or more), the option name -can be followed by an equal sign (`=`) and the option value. For options that -accept _more_ than one value, the option can be followed by an open square -bracket (`[`) and then a series of strings in shell syntax (including unquoted -words if there are no special characters) and then a final `]`. This multi-value -form will also work for options that don't allow values or allow only one -(though there are probably few reasons to favor the form in those cases). - -The helper function `vals` is a convenient way to safely pass multiple values -without having to worry about quoting hygiene. (That is, the helper handles it -for you.) +Long options all start with two dashes (`--`), followed by a series of +alphanumerics and more dashes, e.g. `--some-option`. In addition: + +* A single value can be passed to an option by following the option name + with an equal sign (`=`) and the arbitrary value, e.g. `--some-option='my + value'`. + +* Multiple values can be passed to an option by following the option name with + a pair of square brackets and an equal sign (`[]=`) and then a series of + space-separated words, with standard shell rules for quoting and escaping in + order to pass special characters, e.g. `--some-option[]='this "and that"'`. + + This multi-value form will also work for options that don't allow values or + allow only one value (though there are probably few reasons to favor this form + in those cases). + + The helper function `vals` is a convenient way to safely pass multiple values + without having to worry about quoting hygiene. (That is, the helper handles it + for you.) For example, `--some-option[]="$(vals "${myArray[@]}")"`. Special cases: * A single dash (`-`) is interpreted as a non-option argument. @@ -45,7 +52,7 @@ Examples: my-cmd -h # Short option. my-cmd --help # Long option, no value. my-cmd --size=27 # Long option, with value. -my-cmd --colors['red green blue'] # Long option, multi-value. +my-cmd --colors[]='red green blue' # Long option, multi-value. my-cmd --no-florp # Long option, turning off a toggle. my-cmd --florp=1 # Long option, setting a toggle explicitly. my-cmd some-argument # One positional argument. @@ -54,7 +61,7 @@ my-cmd -34 # One positional argument. my-cmd -- --foo # One positional argument, literally `--foo`. # Passing arbitrary strings safely to a multi-value option. -my-cmd --strings["$(vals "${arrayOfStrings[@]}")"] +my-cmd --strings[]="$(vals "${paths[@]}")" ``` ## Declaring options diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 7a30412..397f4aa 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -958,7 +958,7 @@ function _argproc_statements-from-args { # `:1` to drop the `=` from the start of `value`. _argproc_statements+=("${handler} $(_argproc_quote "${value:1}")") fi - elif [[ ${arg} =~ ^--([-a-zA-Z0-9]+)\[(.*)\]$ ]]; then + elif [[ ${arg} =~ ^--([-a-zA-Z0-9]+)'[]='(.*)$ ]]; then # Long-form multi-value option. name="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}" From 5521dac0fb175ae5deb9f81d0b77248b0ca6e9d8 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 09:29:25 -0700 Subject: [PATCH 10/21] Add rest argument tests. --- .../03-arg-processor/05-rest-arg/expect.md | 110 +++++++++++++++++ .../03-arg-processor/05-rest-arg/info.md | 1 + .../02-core/03-arg-processor/05-rest-arg/run | 34 ++++++ .../03-arg-processor/05-rest-arg/the-cmd | 28 +++++ .../03-arg-processor/06-reqpos-rest/expect.md | 114 ++++++++++++++++++ .../03-arg-processor/06-reqpos-rest/info.md | 1 + .../03-arg-processor/06-reqpos-rest/run | 34 ++++++ .../03-arg-processor/06-reqpos-rest/the-cmd | 30 +++++ 8 files changed, 352 insertions(+) create mode 100644 tests/02-core/03-arg-processor/05-rest-arg/expect.md create mode 100644 tests/02-core/03-arg-processor/05-rest-arg/info.md create mode 100755 tests/02-core/03-arg-processor/05-rest-arg/run create mode 100755 tests/02-core/03-arg-processor/05-rest-arg/the-cmd create mode 100644 tests/02-core/03-arg-processor/06-reqpos-rest/expect.md create mode 100644 tests/02-core/03-arg-processor/06-reqpos-rest/info.md create mode 100755 tests/02-core/03-arg-processor/06-reqpos-rest/run create mode 100755 tests/02-core/03-arg-processor/06-reqpos-rest/the-cmd diff --git a/tests/02-core/03-arg-processor/05-rest-arg/expect.md b/tests/02-core/03-arg-processor/05-rest-arg/expect.md new file mode 100644 index 0000000..06e1b62 --- /dev/null +++ b/tests/02-core/03-arg-processor/05-rest-arg/expect.md @@ -0,0 +1,110 @@ +## no args or options passed + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed one positional arg + +### stdout +``` +Count: 1 + 0: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed two positional args + +### stdout +``` +Count: 2 + 0: florp + 1: fleep +``` + +### exit: 0 + +- - - - - - - - - - + +## passed three positional args + +### stdout +``` +Count: 3 + 0: one + 1: two + 2: three +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then one positional arg + +### stdout +``` +Count: 1 + 0: ONE +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then two positional args + +### stdout +``` +Count: 2 + 0: YES + 1: MORE-YES +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then three positional args + +### stdout +``` +Count: 3 + 0: eep + 1: oop + 2: fleep +``` + +### exit: 0 + +- - - - - - - - - - + +## passed one option + +### stderr +``` +the-cmd: Unknown option: --not-valid + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/05-rest-arg/info.md b/tests/02-core/03-arg-processor/05-rest-arg/info.md new file mode 100644 index 0000000..9373ecd --- /dev/null +++ b/tests/02-core/03-arg-processor/05-rest-arg/info.md @@ -0,0 +1 @@ +Test of just taking a rest argument. diff --git a/tests/02-core/03-arg-processor/05-rest-arg/run b/tests/02-core/03-arg-processor/05-rest-arg/run new file mode 100755 index 0000000..1c38d3c --- /dev/null +++ b/tests/02-core/03-arg-processor/05-rest-arg/run @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed one positional arg' \ + "${cmd}" one + +call-and-log-as-test 'passed two positional args' \ + "${cmd}" florp fleep + +call-and-log-as-test 'passed three positional args' \ + "${cmd}" one two three + +call-and-log-as-test 'passed `--` then one positional arg' \ + "${cmd}" -- ONE + +call-and-log-as-test 'passed `--` then two positional args' \ + "${cmd}" -- YES MORE-YES + +call-and-log-as-test 'passed `--` then three positional args' \ + "${cmd}" -- eep oop fleep + +call-and-log-as-test 'passed one option' \ + "${cmd}" --not-valid diff --git a/tests/02-core/03-arg-processor/05-rest-arg/the-cmd b/tests/02-core/03-arg-processor/05-rest-arg/the-cmd new file mode 100755 index 0000000..8e87535 --- /dev/null +++ b/tests/02-core/03-arg-processor/05-rest-arg/the-cmd @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +rest-arg --var=stuff stuff + +process-args "$@" || exit "$?" + +echo "Count: ${#stuff[@]}" + +if (( ${#stuff[@]} != 0 )); then + for n in "${!stuff[@]}"; do + printf ' %s: %q\n' "${n}" "${stuff[n]}" + done +fi diff --git a/tests/02-core/03-arg-processor/06-reqpos-rest/expect.md b/tests/02-core/03-arg-processor/06-reqpos-rest/expect.md new file mode 100644 index 0000000..ac27f3b --- /dev/null +++ b/tests/02-core/03-arg-processor/06-reqpos-rest/expect.md @@ -0,0 +1,114 @@ +## no args or options passed + +### stderr +``` +the-cmd: Missing required argument . + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed `--` + +### stderr +``` +the-cmd: Missing required argument . + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed one positional arg + +### stdout +``` +First: one +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed two positional args + +### stdout +``` +First: florp +Count: 1 + 0: fleep +``` + +### exit: 0 + +- - - - - - - - - - + +## passed three positional args + +### stdout +``` +First: one +Count: 2 + 0: two + 1: three +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then one positional arg + +### stdout +``` +First: ONE +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then two positional args + +### stdout +``` +First: YES +Count: 1 + 0: MORE-YES +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` then three positional args + +### stdout +``` +First: eep +Count: 2 + 0: oop + 1: fleep +``` + +### exit: 0 + +- - - - - - - - - - + +## passed one option + +### stderr +``` +the-cmd: Unknown option: --not-valid + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/06-reqpos-rest/info.md b/tests/02-core/03-arg-processor/06-reqpos-rest/info.md new file mode 100644 index 0000000..3546958 --- /dev/null +++ b/tests/02-core/03-arg-processor/06-reqpos-rest/info.md @@ -0,0 +1 @@ +Test of taking a required positional argument followed by a rest argument. diff --git a/tests/02-core/03-arg-processor/06-reqpos-rest/run b/tests/02-core/03-arg-processor/06-reqpos-rest/run new file mode 100755 index 0000000..1c38d3c --- /dev/null +++ b/tests/02-core/03-arg-processor/06-reqpos-rest/run @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed one positional arg' \ + "${cmd}" one + +call-and-log-as-test 'passed two positional args' \ + "${cmd}" florp fleep + +call-and-log-as-test 'passed three positional args' \ + "${cmd}" one two three + +call-and-log-as-test 'passed `--` then one positional arg' \ + "${cmd}" -- ONE + +call-and-log-as-test 'passed `--` then two positional args' \ + "${cmd}" -- YES MORE-YES + +call-and-log-as-test 'passed `--` then three positional args' \ + "${cmd}" -- eep oop fleep + +call-and-log-as-test 'passed one option' \ + "${cmd}" --not-valid diff --git a/tests/02-core/03-arg-processor/06-reqpos-rest/the-cmd b/tests/02-core/03-arg-processor/06-reqpos-rest/the-cmd new file mode 100755 index 0000000..7f6b97f --- /dev/null +++ b/tests/02-core/03-arg-processor/06-reqpos-rest/the-cmd @@ -0,0 +1,30 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +positional-arg --required --var=first first +rest-arg --var=stuff stuff + +process-args "$@" || exit "$?" + +printf 'First: %q\n' "${first}" +echo "Count: ${#stuff[@]}" + +if (( ${#stuff[@]} != 0 )); then + for n in "${!stuff[@]}"; do + printf ' %s: %q\n' "${n}" "${stuff[n]}" + done +fi From 8835ff235cb1ba187510c517238c7b42162824f3 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 09:29:47 -0700 Subject: [PATCH 11/21] Add `opt-multi`. --- scripts/lib/bashy-core/arg-processor.sh | 91 ++++++++++++++----- .../94-duplicate-argument/expect.md | 12 +-- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 397f4aa..7e7d79c 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -154,11 +154,32 @@ function opt-choice { # `` is allowed in the argument spec. These options are accepted via the # syntax `--[]=` where is a space-separated list of # literal values, with standard shell quoting and escaping allowed in order to -# pass special characters. +# pass special characters. This definer also accepts the `--required` option. +# The initial variable value is `()` (the empty array). function opt-multi { - # TODO - error-msg 'TODO!' - return 1 + local optCall='' + local optFilter='' + local optVar='' + local args=("$@") + _argproc_janky-args call enum filter var \ + || return 1 + + local specName='' + _argproc_parse-spec "${args[0]}" \ + || return 1 + + if [[ ${optVar} != '' ]]; then + # Set up the variable initializer. + _argproc_initStatements+=("${optVar}=()") + fi + + _argproc_define-multi-value-arg --option \ + "${specName}" "${optFilter}" "${optCall}" "${optVar}" \ + || return "$?" + + if (( optRequired )); then + _argproc_add-required-arg-postcheck "${specName}" + fi } # Declares a "toggle" option, which allows setting of a value to `0` or `1`. No @@ -224,7 +245,8 @@ function opt-value { fi _argproc_define-value-taking-arg --option \ - "${specName}" "${specValue}" "${optFilter}" "${optCall}" "${optVar}" + "${specName}" "${specValue}" "${optFilter}" "${optCall}" "${optVar}" \ + || return "$?" if (( optRequired )); then _argproc_add-required-arg-postcheck "${specName}" @@ -355,11 +377,6 @@ function rest-arg { _argproc_janky-args call enum filter var \ || return 1 - if declare >/dev/null -F _argproc:rest; then - error-msg --file-line=1 'Duplicate definition of rest argument.' - return 1 - fi - local specName='' _argproc_parse-spec "${args[0]}" \ || return 1 @@ -369,7 +386,7 @@ function rest-arg { _argproc_initStatements+=("${optVar}=()") fi - _argproc_define-multi-value-arg \ + _argproc_define-multi-value-arg --rest \ "${specName}" "${optFilter}" "${optCall}" "${optVar}" \ || return "$?" } @@ -473,19 +490,41 @@ function _argproc_define-abbrev { # Defines an activation function for a multi-value argument. function _argproc_define-multi-value-arg { - local longName="$1" + local isOption=0 isRest=0 + if [[ $1 == '--option' ]]; then + isOption=1 + shift + elif [[ $1 == '--rest' ]]; then + isRest=1 + shift + if declare >/dev/null -F _argproc:rest; then + error-msg --file-line=2 'Duplicate definition of rest argument.' + return 1 + fi + fi + + local specName="$1" local filter="$2" local callFunc="$3" local varName="$4" - _argproc_set-arg-description "${specName}" rest-argument || return 1 + if (( isOption )); then + _argproc_set-arg-description "${specName}" multi-option || return 1 + handlerName="_argproc:long-${specName}" + elif (( isRest )); then + _argproc_set-arg-description "${specName}" rest-argument || return 1 + handlerName='_argproc:rest' + else + _argproc_set-arg-description "${specName}" multi-argument || return 1 + handlerName="_argproc:positional-${specName}" + fi - local desc="argument <${longName}>" + local desc="$(_argproc_arg-description "${specName}")" local handlerBody="$( - _argproc_handler-body "${longName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" )" - eval 'function _argproc:rest { + eval 'function '"${handlerName}"' { '"${handlerBody}"' }' } @@ -885,13 +924,13 @@ function _argproc_regex-filter-check { # Sets the description of the named argument based on its type. This function # will fail if an argument with the given name was already defined. function _argproc_set-arg-description { - local longName="$1" + local specName="$1" local typeName="$2" - local funcName="_argproc:arg-description-${longName}" + local funcName="_argproc:arg-description-${specName}" if declare -F "${funcName}" >/dev/null; then - error-msg --file-line=3 "Duplicate argument: ${longName}" + error-msg --file-line=3 "Duplicate argument declaration: ${specName}" _argproc_declarationError=1 return 1 fi @@ -899,16 +938,22 @@ function _argproc_set-arg-description { local desc case "${typeName}" in argument) - desc="argument <${longName}>" + desc="argument <${specName}>" + ;; + multi-argument) + desc="argument <${specName}[]>" + ;; + multi-option) + desc="option --${specName}[]" ;; option) - desc="option --${longName}" + desc="option --${specName}" ;; rest-argument) - desc="rest argument <${longName}...>" + desc="rest argument <${specName}...>" ;; *) - error-msg --file-line=1 "Unknown type: ${typeName}" + error-msg --file-line=1 "Unknown argument type: ${typeName}" _argproc_declarationError=1 return 1 ;; diff --git a/tests/02-core/03-arg-processor/94-duplicate-argument/expect.md b/tests/02-core/03-arg-processor/94-duplicate-argument/expect.md index a936fc2..6efdaae 100644 --- a/tests/02-core/03-arg-processor/94-duplicate-argument/expect.md +++ b/tests/02-core/03-arg-processor/94-duplicate-argument/expect.md @@ -3,12 +3,12 @@ ### stderr ``` the-cmd: -the-cmd:21: Duplicate argument: the-one -the-cmd:22: Duplicate argument: the-one -the-cmd:23: Duplicate argument: the-one -the-cmd:24: Duplicate argument: the-one -the-cmd:25: Duplicate argument: the-one -the-cmd:26: Duplicate argument: the-one +the-cmd:21: Duplicate argument declaration: the-one +the-cmd:22: Duplicate argument declaration: the-one +the-cmd:23: Duplicate argument declaration: the-one +the-cmd:24: Duplicate argument declaration: the-one +the-cmd:25: Duplicate argument declaration: the-one +the-cmd:26: Duplicate argument declaration: the-one the-cmd:31: Duplicate definition of rest argument. Cannot process arguments, due to declaration errors. From 1ea984ee5a9dc14a87d00cfff22f36fa24a1eb73 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 09:31:43 -0700 Subject: [PATCH 12/21] Rename for consistency. --- .../{02-one-required-positional => 02-reqpos}/expect.md | 0 .../{02-one-required-positional => 02-reqpos}/info.md | 0 .../{02-one-required-positional => 02-reqpos}/run | 0 .../{02-one-required-positional => 02-reqpos}/the-cmd | 0 .../{03-one-optional-positional => 03-optpos}/expect.md | 0 .../{03-one-optional-positional => 03-optpos}/info.md | 0 .../{03-one-optional-positional => 03-optpos}/run | 0 .../{03-one-optional-positional => 03-optpos}/the-cmd | 0 .../{04-positional-req-opt => 04-reqpos-optpos}/expect.md | 0 .../{04-positional-req-opt => 04-reqpos-optpos}/info.md | 0 .../{04-positional-req-opt => 04-reqpos-optpos}/run | 0 .../{04-positional-req-opt => 04-reqpos-optpos}/the-cmd | 0 tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/expect.md | 0 tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/info.md | 0 tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/run | 0 tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/the-cmd | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename tests/02-core/03-arg-processor/{02-one-required-positional => 02-reqpos}/expect.md (100%) rename tests/02-core/03-arg-processor/{02-one-required-positional => 02-reqpos}/info.md (100%) rename tests/02-core/03-arg-processor/{02-one-required-positional => 02-reqpos}/run (100%) rename tests/02-core/03-arg-processor/{02-one-required-positional => 02-reqpos}/the-cmd (100%) rename tests/02-core/03-arg-processor/{03-one-optional-positional => 03-optpos}/expect.md (100%) rename tests/02-core/03-arg-processor/{03-one-optional-positional => 03-optpos}/info.md (100%) rename tests/02-core/03-arg-processor/{03-one-optional-positional => 03-optpos}/run (100%) rename tests/02-core/03-arg-processor/{03-one-optional-positional => 03-optpos}/the-cmd (100%) rename tests/02-core/03-arg-processor/{04-positional-req-opt => 04-reqpos-optpos}/expect.md (100%) rename tests/02-core/03-arg-processor/{04-positional-req-opt => 04-reqpos-optpos}/info.md (100%) rename tests/02-core/03-arg-processor/{04-positional-req-opt => 04-reqpos-optpos}/run (100%) rename tests/02-core/03-arg-processor/{04-positional-req-opt => 04-reqpos-optpos}/the-cmd (100%) rename tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/expect.md (100%) rename tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/info.md (100%) rename tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/run (100%) rename tests/02-core/03-arg-processor/{05-rest-arg => 05-rest}/the-cmd (100%) diff --git a/tests/02-core/03-arg-processor/02-one-required-positional/expect.md b/tests/02-core/03-arg-processor/02-reqpos/expect.md similarity index 100% rename from tests/02-core/03-arg-processor/02-one-required-positional/expect.md rename to tests/02-core/03-arg-processor/02-reqpos/expect.md diff --git a/tests/02-core/03-arg-processor/02-one-required-positional/info.md b/tests/02-core/03-arg-processor/02-reqpos/info.md similarity index 100% rename from tests/02-core/03-arg-processor/02-one-required-positional/info.md rename to tests/02-core/03-arg-processor/02-reqpos/info.md diff --git a/tests/02-core/03-arg-processor/02-one-required-positional/run b/tests/02-core/03-arg-processor/02-reqpos/run similarity index 100% rename from tests/02-core/03-arg-processor/02-one-required-positional/run rename to tests/02-core/03-arg-processor/02-reqpos/run diff --git a/tests/02-core/03-arg-processor/02-one-required-positional/the-cmd b/tests/02-core/03-arg-processor/02-reqpos/the-cmd similarity index 100% rename from tests/02-core/03-arg-processor/02-one-required-positional/the-cmd rename to tests/02-core/03-arg-processor/02-reqpos/the-cmd diff --git a/tests/02-core/03-arg-processor/03-one-optional-positional/expect.md b/tests/02-core/03-arg-processor/03-optpos/expect.md similarity index 100% rename from tests/02-core/03-arg-processor/03-one-optional-positional/expect.md rename to tests/02-core/03-arg-processor/03-optpos/expect.md diff --git a/tests/02-core/03-arg-processor/03-one-optional-positional/info.md b/tests/02-core/03-arg-processor/03-optpos/info.md similarity index 100% rename from tests/02-core/03-arg-processor/03-one-optional-positional/info.md rename to tests/02-core/03-arg-processor/03-optpos/info.md diff --git a/tests/02-core/03-arg-processor/03-one-optional-positional/run b/tests/02-core/03-arg-processor/03-optpos/run similarity index 100% rename from tests/02-core/03-arg-processor/03-one-optional-positional/run rename to tests/02-core/03-arg-processor/03-optpos/run diff --git a/tests/02-core/03-arg-processor/03-one-optional-positional/the-cmd b/tests/02-core/03-arg-processor/03-optpos/the-cmd similarity index 100% rename from tests/02-core/03-arg-processor/03-one-optional-positional/the-cmd rename to tests/02-core/03-arg-processor/03-optpos/the-cmd diff --git a/tests/02-core/03-arg-processor/04-positional-req-opt/expect.md b/tests/02-core/03-arg-processor/04-reqpos-optpos/expect.md similarity index 100% rename from tests/02-core/03-arg-processor/04-positional-req-opt/expect.md rename to tests/02-core/03-arg-processor/04-reqpos-optpos/expect.md diff --git a/tests/02-core/03-arg-processor/04-positional-req-opt/info.md b/tests/02-core/03-arg-processor/04-reqpos-optpos/info.md similarity index 100% rename from tests/02-core/03-arg-processor/04-positional-req-opt/info.md rename to tests/02-core/03-arg-processor/04-reqpos-optpos/info.md diff --git a/tests/02-core/03-arg-processor/04-positional-req-opt/run b/tests/02-core/03-arg-processor/04-reqpos-optpos/run similarity index 100% rename from tests/02-core/03-arg-processor/04-positional-req-opt/run rename to tests/02-core/03-arg-processor/04-reqpos-optpos/run diff --git a/tests/02-core/03-arg-processor/04-positional-req-opt/the-cmd b/tests/02-core/03-arg-processor/04-reqpos-optpos/the-cmd similarity index 100% rename from tests/02-core/03-arg-processor/04-positional-req-opt/the-cmd rename to tests/02-core/03-arg-processor/04-reqpos-optpos/the-cmd diff --git a/tests/02-core/03-arg-processor/05-rest-arg/expect.md b/tests/02-core/03-arg-processor/05-rest/expect.md similarity index 100% rename from tests/02-core/03-arg-processor/05-rest-arg/expect.md rename to tests/02-core/03-arg-processor/05-rest/expect.md diff --git a/tests/02-core/03-arg-processor/05-rest-arg/info.md b/tests/02-core/03-arg-processor/05-rest/info.md similarity index 100% rename from tests/02-core/03-arg-processor/05-rest-arg/info.md rename to tests/02-core/03-arg-processor/05-rest/info.md diff --git a/tests/02-core/03-arg-processor/05-rest-arg/run b/tests/02-core/03-arg-processor/05-rest/run similarity index 100% rename from tests/02-core/03-arg-processor/05-rest-arg/run rename to tests/02-core/03-arg-processor/05-rest/run diff --git a/tests/02-core/03-arg-processor/05-rest-arg/the-cmd b/tests/02-core/03-arg-processor/05-rest/the-cmd similarity index 100% rename from tests/02-core/03-arg-processor/05-rest-arg/the-cmd rename to tests/02-core/03-arg-processor/05-rest/the-cmd From 1c007b37edcc037007f9aa83787348de79f5d505 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 09:42:30 -0700 Subject: [PATCH 13/21] Add test for action option. --- .../03-arg-processor/07-action/expect.md | 127 ++++++++++++++++++ .../03-arg-processor/07-action/info.md | 1 + tests/02-core/03-arg-processor/07-action/run | 37 +++++ .../03-arg-processor/07-action/the-cmd | 26 ++++ 4 files changed, 191 insertions(+) create mode 100644 tests/02-core/03-arg-processor/07-action/expect.md create mode 100644 tests/02-core/03-arg-processor/07-action/info.md create mode 100755 tests/02-core/03-arg-processor/07-action/run create mode 100755 tests/02-core/03-arg-processor/07-action/the-cmd diff --git a/tests/02-core/03-arg-processor/07-action/expect.md b/tests/02-core/03-arg-processor/07-action/expect.md new file mode 100644 index 0000000..ddd7c82 --- /dev/null +++ b/tests/02-core/03-arg-processor/07-action/expect.md @@ -0,0 +1,127 @@ +## no args or options passed + +### stdout +``` +run: 0 +perhaps: nopers +fly: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` + +### stdout +``` +run: 0 +perhaps: nopers +fly: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed positional arg + +### stderr +``` +the-cmd: Positional arguments are not allowed. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed long action with no default or defined value + +### stdout +``` +run: 1 +perhaps: nopers +fly: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long action with default and defined value + +### stdout +``` +run: 0 +perhaps: yeppers +fly: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long action with defined value and defined short form + +### stdout +``` +run: 0 +perhaps: nopers +fly: whee +``` + +### exit: 0 + +- - - - - - - - - - + +## passed short action with defined value + +### stdout +``` +run: 0 +perhaps: nopers +fly: whee +``` + +### exit: 0 + +- - - - - - - - - - + +## passed three long actions + +### stdout +``` +run: 1 +perhaps: yeppers +fly: whee +``` + +### exit: 0 + +- - - - - - - - - - + +## passed value to action + +### stderr +``` +the-cmd: Value not allowed for option --run. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed two values to action + +### stderr +``` +the-cmd: Value not allowed for option --run. + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/07-action/info.md b/tests/02-core/03-arg-processor/07-action/info.md new file mode 100644 index 0000000..a5e280f --- /dev/null +++ b/tests/02-core/03-arg-processor/07-action/info.md @@ -0,0 +1 @@ +Test of a action options. diff --git a/tests/02-core/03-arg-processor/07-action/run b/tests/02-core/03-arg-processor/07-action/run new file mode 100755 index 0000000..ec7d238 --- /dev/null +++ b/tests/02-core/03-arg-processor/07-action/run @@ -0,0 +1,37 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed positional arg' \ + "${cmd}" i-am-arguing + +call-and-log-as-test 'passed long action with no default or defined value' \ + "${cmd}" --run + +call-and-log-as-test 'passed long action with default and defined value' \ + "${cmd}" --perhaps + +call-and-log-as-test 'passed long action with defined value and defined short form' \ + "${cmd}" --fly + +call-and-log-as-test 'passed short action with defined value' \ + "${cmd}" -f + +call-and-log-as-test 'passed three long actions' \ + "${cmd}" --run --perhaps --fly + +call-and-log-as-test 'passed value to action' \ + "${cmd}" --run=florp + +call-and-log-as-test 'passed two values to action' \ + "${cmd}" --run[]='florp fleep' diff --git a/tests/02-core/03-arg-processor/07-action/the-cmd b/tests/02-core/03-arg-processor/07-action/the-cmd new file mode 100755 index 0000000..098e7c7 --- /dev/null +++ b/tests/02-core/03-arg-processor/07-action/the-cmd @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-action --var=runVar run +opt-action --var=perhapsVar --init=nopers perhaps=yeppers +opt-action --var=flyVar fly/f=whee + +process-args "$@" || exit "$?" + +printf 'run: %q\n' "${runVar}" +printf 'perhaps: %q\n' "${perhapsVar}" +printf 'fly: %q\n' "${flyVar}" From 9f5b3b441f8535ed521c851bc074b0e8d60ffac5 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 09:54:19 -0700 Subject: [PATCH 14/21] Add choice tests. --- .../03-arg-processor/07-action/info.md | 2 +- .../03-arg-processor/08-optchoice/expect.md | 146 +++++++++++++++++ .../03-arg-processor/08-optchoice/info.md | 1 + .../02-core/03-arg-processor/08-optchoice/run | 46 ++++++ .../03-arg-processor/08-optchoice/the-cmd | 22 +++ .../03-arg-processor/09-reqchoice/expect.md | 150 ++++++++++++++++++ .../03-arg-processor/09-reqchoice/info.md | 1 + .../02-core/03-arg-processor/09-reqchoice/run | 46 ++++++ .../03-arg-processor/09-reqchoice/the-cmd | 22 +++ 9 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 tests/02-core/03-arg-processor/08-optchoice/expect.md create mode 100644 tests/02-core/03-arg-processor/08-optchoice/info.md create mode 100755 tests/02-core/03-arg-processor/08-optchoice/run create mode 100755 tests/02-core/03-arg-processor/08-optchoice/the-cmd create mode 100644 tests/02-core/03-arg-processor/09-reqchoice/expect.md create mode 100644 tests/02-core/03-arg-processor/09-reqchoice/info.md create mode 100755 tests/02-core/03-arg-processor/09-reqchoice/run create mode 100755 tests/02-core/03-arg-processor/09-reqchoice/the-cmd diff --git a/tests/02-core/03-arg-processor/07-action/info.md b/tests/02-core/03-arg-processor/07-action/info.md index a5e280f..1d7b29f 100644 --- a/tests/02-core/03-arg-processor/07-action/info.md +++ b/tests/02-core/03-arg-processor/07-action/info.md @@ -1 +1 @@ -Test of a action options. +Test of action options. diff --git a/tests/02-core/03-arg-processor/08-optchoice/expect.md b/tests/02-core/03-arg-processor/08-optchoice/expect.md new file mode 100644 index 0000000..6465d8c --- /dev/null +++ b/tests/02-core/03-arg-processor/08-optchoice/expect.md @@ -0,0 +1,146 @@ +## no args or options passed + +### stdout +``` +what: '' +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` + +### stdout +``` +what: '' +``` + +### exit: 0 + +- - - - - - - - - - + +## passed positional arg + +### stderr +``` +the-cmd: Positional arguments are not allowed. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed long choice with no defined value or short form + +### stdout +``` +what: zero +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with short form but no defined value + +### stdout +``` +what: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed short choice with no defined value + +### stdout +``` +what: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with short form and defined value + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed short choice with defined value + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with defined value but no short form + +### stdout +``` +what: 3 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed two choices from the same set + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed three choices from the same set + +### stdout +``` +what: 3 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed value to choice + +### stderr +``` +the-cmd: Value not allowed for option --one. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed two values to choice + +### stderr +``` +the-cmd: Value not allowed for option --one. + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/08-optchoice/info.md b/tests/02-core/03-arg-processor/08-optchoice/info.md new file mode 100644 index 0000000..38d0dfc --- /dev/null +++ b/tests/02-core/03-arg-processor/08-optchoice/info.md @@ -0,0 +1 @@ +Test of an optional set of choice options. diff --git a/tests/02-core/03-arg-processor/08-optchoice/run b/tests/02-core/03-arg-processor/08-optchoice/run new file mode 100755 index 0000000..fda6afe --- /dev/null +++ b/tests/02-core/03-arg-processor/08-optchoice/run @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed positional arg' \ + "${cmd}" i-am-arguing + +call-and-log-as-test 'passed long choice with no defined value or short form' \ + "${cmd}" --zero + +call-and-log-as-test 'passed long choice with short form but no defined value' \ + "${cmd}" --one + +call-and-log-as-test 'passed short choice with no defined value' \ + "${cmd}" -o + +call-and-log-as-test 'passed long choice with short form and defined value' \ + "${cmd}" --two + +call-and-log-as-test 'passed short choice with defined value' \ + "${cmd}" -t + +call-and-log-as-test 'passed long choice with defined value but no short form' \ + "${cmd}" --three + +call-and-log-as-test 'passed two choices from the same set' \ + "${cmd}" --one --two + +call-and-log-as-test 'passed three choices from the same set' \ + "${cmd}" --one --two --three + +call-and-log-as-test 'passed value to choice' \ + "${cmd}" --one=florp + +call-and-log-as-test 'passed two values to choice' \ + "${cmd}" --one[]='florp fleep' diff --git a/tests/02-core/03-arg-processor/08-optchoice/the-cmd b/tests/02-core/03-arg-processor/08-optchoice/the-cmd new file mode 100755 index 0000000..e0efaf5 --- /dev/null +++ b/tests/02-core/03-arg-processor/08-optchoice/the-cmd @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-choice --var=what zero one/o two/t=2 three=3 + +process-args "$@" || exit "$?" + +printf 'what: %q\n' "${what}" diff --git a/tests/02-core/03-arg-processor/09-reqchoice/expect.md b/tests/02-core/03-arg-processor/09-reqchoice/expect.md new file mode 100644 index 0000000..e74bb7c --- /dev/null +++ b/tests/02-core/03-arg-processor/09-reqchoice/expect.md @@ -0,0 +1,150 @@ +## no args or options passed + +### stderr +``` +the-cmd: Missing required option from set: --zero --one --two --three + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed `--` + +### stderr +``` +the-cmd: Missing required option from set: --zero --one --two --three + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed positional arg + +### stderr +``` +the-cmd: Positional arguments are not allowed. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed long choice with no defined value or short form + +### stdout +``` +what: zero +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with short form but no defined value + +### stdout +``` +what: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed short choice with no defined value + +### stdout +``` +what: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with short form and defined value + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed short choice with defined value + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed long choice with defined value but no short form + +### stdout +``` +what: 3 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed two choices from the same set + +### stdout +``` +what: 2 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed three choices from the same set + +### stdout +``` +what: 3 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed value to choice + +### stderr +``` +the-cmd: Value not allowed for option --one. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed two values to choice + +### stderr +``` +the-cmd: Value not allowed for option --one. + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/09-reqchoice/info.md b/tests/02-core/03-arg-processor/09-reqchoice/info.md new file mode 100644 index 0000000..38d0dfc --- /dev/null +++ b/tests/02-core/03-arg-processor/09-reqchoice/info.md @@ -0,0 +1 @@ +Test of an optional set of choice options. diff --git a/tests/02-core/03-arg-processor/09-reqchoice/run b/tests/02-core/03-arg-processor/09-reqchoice/run new file mode 100755 index 0000000..fda6afe --- /dev/null +++ b/tests/02-core/03-arg-processor/09-reqchoice/run @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed positional arg' \ + "${cmd}" i-am-arguing + +call-and-log-as-test 'passed long choice with no defined value or short form' \ + "${cmd}" --zero + +call-and-log-as-test 'passed long choice with short form but no defined value' \ + "${cmd}" --one + +call-and-log-as-test 'passed short choice with no defined value' \ + "${cmd}" -o + +call-and-log-as-test 'passed long choice with short form and defined value' \ + "${cmd}" --two + +call-and-log-as-test 'passed short choice with defined value' \ + "${cmd}" -t + +call-and-log-as-test 'passed long choice with defined value but no short form' \ + "${cmd}" --three + +call-and-log-as-test 'passed two choices from the same set' \ + "${cmd}" --one --two + +call-and-log-as-test 'passed three choices from the same set' \ + "${cmd}" --one --two --three + +call-and-log-as-test 'passed value to choice' \ + "${cmd}" --one=florp + +call-and-log-as-test 'passed two values to choice' \ + "${cmd}" --one[]='florp fleep' diff --git a/tests/02-core/03-arg-processor/09-reqchoice/the-cmd b/tests/02-core/03-arg-processor/09-reqchoice/the-cmd new file mode 100755 index 0000000..1a15c0e --- /dev/null +++ b/tests/02-core/03-arg-processor/09-reqchoice/the-cmd @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-choice --required --var=what zero one/o two/t=2 three=3 + +process-args "$@" || exit "$?" + +printf 'what: %q\n' "${what}" From aa5feca8c4dec008617ea6d2bfe79e9c300a1fdb Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:13:12 -0700 Subject: [PATCH 15/21] Add multi-value option test. --- .../03-arg-processor/09-reqchoice/info.md | 2 +- .../03-arg-processor/10-optmulti/expect.md | 156 ++++++++++++++++++ .../03-arg-processor/10-optmulti/info.md | 1 + .../02-core/03-arg-processor/10-optmulti/run | 46 ++++++ .../03-arg-processor/10-optmulti/the-cmd | 28 ++++ 5 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 tests/02-core/03-arg-processor/10-optmulti/expect.md create mode 100644 tests/02-core/03-arg-processor/10-optmulti/info.md create mode 100755 tests/02-core/03-arg-processor/10-optmulti/run create mode 100755 tests/02-core/03-arg-processor/10-optmulti/the-cmd diff --git a/tests/02-core/03-arg-processor/09-reqchoice/info.md b/tests/02-core/03-arg-processor/09-reqchoice/info.md index 38d0dfc..d97043e 100644 --- a/tests/02-core/03-arg-processor/09-reqchoice/info.md +++ b/tests/02-core/03-arg-processor/09-reqchoice/info.md @@ -1 +1 @@ -Test of an optional set of choice options. +Test of a required set of choice options. diff --git a/tests/02-core/03-arg-processor/10-optmulti/expect.md b/tests/02-core/03-arg-processor/10-optmulti/expect.md new file mode 100644 index 0000000..2e1882d --- /dev/null +++ b/tests/02-core/03-arg-processor/10-optmulti/expect.md @@ -0,0 +1,156 @@ +## no args or options passed + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed `--` + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed positional arg + +### stderr +``` +the-cmd: Positional arguments are not allowed. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed multi-value option, no values + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value + +### stdout +``` +Count: 1 + 0: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value, using single-value syntax + +### stdout +``` +Count: 1 + 0: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one empty value, using single-value syntax + +### stdout +``` +Count: 1 + 0: '' +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value with escapes + +### stdout +``` +Count: 1 + 0: x\$y\&z +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one single-quoted value with a space + +### stdout +``` +Count: 1 + 0: yes\ yes +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one double-quoted value with a space + +### stdout +``` +Count: 1 + 0: two\ two +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one dollar-quoted value with a space + +### stdout +``` +Count: 1 + 0: yes\ sirree\! +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, two values + +### stdout +``` +Count: 2 + 0: one + 1: two +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, four values, omnibus + +### stdout +``` +Count: 5 + 0: one + 1: two + 2: three\ four + 3: five\ \&\ six + 4: \#7\(8\)9 +``` + +### exit: 0 diff --git a/tests/02-core/03-arg-processor/10-optmulti/info.md b/tests/02-core/03-arg-processor/10-optmulti/info.md new file mode 100644 index 0000000..af2121f --- /dev/null +++ b/tests/02-core/03-arg-processor/10-optmulti/info.md @@ -0,0 +1 @@ +Test of an optional multi-value option. diff --git a/tests/02-core/03-arg-processor/10-optmulti/run b/tests/02-core/03-arg-processor/10-optmulti/run new file mode 100755 index 0000000..2f952ce --- /dev/null +++ b/tests/02-core/03-arg-processor/10-optmulti/run @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed positional arg' \ + "${cmd}" i-am-arguing + +call-and-log-as-test 'passed multi-value option, no values' \ + "${cmd}" --items[]= + +call-and-log-as-test 'passed multi-value option, one value' \ + "${cmd}" --items[]='one' + +call-and-log-as-test 'passed multi-value option, one value, using single-value syntax' \ + "${cmd}" --items='one' + +call-and-log-as-test 'passed multi-value option, one empty value, using single-value syntax' \ + "${cmd}" --items='' + +call-and-log-as-test 'passed multi-value option, one value with escapes' \ + "${cmd}" --items[]='x\$y\&z' + +call-and-log-as-test 'passed multi-value option, one single-quoted value with a space' \ + "${cmd}" --items[]="'yes yes'" + +call-and-log-as-test 'passed multi-value option, one double-quoted value with a space' \ + "${cmd}" --items[]='"two two"' + +call-and-log-as-test 'passed multi-value option, one dollar-quoted value with a space' \ + "${cmd}" --items[]=$'$\'yes sirree!\'' + +call-and-log-as-test 'passed multi-value option, two values' \ + "${cmd}" --items[]='one two' + +call-and-log-as-test 'passed multi-value option, four values, omnibus' \ + "${cmd}" --items[]=$'one "two" \'three four\' $\'five & six\' \#7\(8\)9' diff --git a/tests/02-core/03-arg-processor/10-optmulti/the-cmd b/tests/02-core/03-arg-processor/10-optmulti/the-cmd new file mode 100755 index 0000000..c3815eb --- /dev/null +++ b/tests/02-core/03-arg-processor/10-optmulti/the-cmd @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-multi --var=items items + +process-args "$@" || exit "$?" + +echo "Count: ${#items[@]}" + +if (( ${#items[@]} != 0 )); then + for n in "${!items[@]}"; do + printf ' %s: %q\n' "${n}" "${items[n]}" + done +fi From 152418f7d60c0aef046d12597c88b4650b5bb039 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:15:59 -0700 Subject: [PATCH 16/21] Fix and add test for _required_ multi-value option. --- scripts/lib/bashy-core/arg-processor.sh | 3 +- .../03-arg-processor/11-reqmulti/expect.md | 160 ++++++++++++++++++ .../03-arg-processor/11-reqmulti/info.md | 1 + .../02-core/03-arg-processor/11-reqmulti/run | 46 +++++ .../03-arg-processor/11-reqmulti/the-cmd | 28 +++ 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 tests/02-core/03-arg-processor/11-reqmulti/expect.md create mode 100644 tests/02-core/03-arg-processor/11-reqmulti/info.md create mode 100755 tests/02-core/03-arg-processor/11-reqmulti/run create mode 100755 tests/02-core/03-arg-processor/11-reqmulti/the-cmd diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 7e7d79c..c2e6cc2 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -159,9 +159,10 @@ function opt-choice { function opt-multi { local optCall='' local optFilter='' + local optRequired=0 local optVar='' local args=("$@") - _argproc_janky-args call enum filter var \ + _argproc_janky-args call enum filter required var \ || return 1 local specName='' diff --git a/tests/02-core/03-arg-processor/11-reqmulti/expect.md b/tests/02-core/03-arg-processor/11-reqmulti/expect.md new file mode 100644 index 0000000..e6b367e --- /dev/null +++ b/tests/02-core/03-arg-processor/11-reqmulti/expect.md @@ -0,0 +1,160 @@ +## no args or options passed + +### stderr +``` +the-cmd: Missing required option --items[]. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed `--` + +### stderr +``` +the-cmd: Missing required option --items[]. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed positional arg + +### stderr +``` +the-cmd: Positional arguments are not allowed. + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed multi-value option, no values + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value + +### stdout +``` +Count: 1 + 0: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value, using single-value syntax + +### stdout +``` +Count: 1 + 0: one +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one empty value, using single-value syntax + +### stdout +``` +Count: 1 + 0: '' +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one value with escapes + +### stdout +``` +Count: 1 + 0: x\$y\&z +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one single-quoted value with a space + +### stdout +``` +Count: 1 + 0: yes\ yes +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one double-quoted value with a space + +### stdout +``` +Count: 1 + 0: two\ two +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one dollar-quoted value with a space + +### stdout +``` +Count: 1 + 0: yes\ sirree\! +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, two values + +### stdout +``` +Count: 2 + 0: one + 1: two +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, four values, omnibus + +### stdout +``` +Count: 5 + 0: one + 1: two + 2: three\ four + 3: five\ \&\ six + 4: \#7\(8\)9 +``` + +### exit: 0 diff --git a/tests/02-core/03-arg-processor/11-reqmulti/info.md b/tests/02-core/03-arg-processor/11-reqmulti/info.md new file mode 100644 index 0000000..39d5802 --- /dev/null +++ b/tests/02-core/03-arg-processor/11-reqmulti/info.md @@ -0,0 +1 @@ +Test of a required multi-value option. diff --git a/tests/02-core/03-arg-processor/11-reqmulti/run b/tests/02-core/03-arg-processor/11-reqmulti/run new file mode 100755 index 0000000..2f952ce --- /dev/null +++ b/tests/02-core/03-arg-processor/11-reqmulti/run @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'no args or options passed' \ + "${cmd}" + +call-and-log-as-test 'passed `--`' \ + "${cmd}" -- + +call-and-log-as-test 'passed positional arg' \ + "${cmd}" i-am-arguing + +call-and-log-as-test 'passed multi-value option, no values' \ + "${cmd}" --items[]= + +call-and-log-as-test 'passed multi-value option, one value' \ + "${cmd}" --items[]='one' + +call-and-log-as-test 'passed multi-value option, one value, using single-value syntax' \ + "${cmd}" --items='one' + +call-and-log-as-test 'passed multi-value option, one empty value, using single-value syntax' \ + "${cmd}" --items='' + +call-and-log-as-test 'passed multi-value option, one value with escapes' \ + "${cmd}" --items[]='x\$y\&z' + +call-and-log-as-test 'passed multi-value option, one single-quoted value with a space' \ + "${cmd}" --items[]="'yes yes'" + +call-and-log-as-test 'passed multi-value option, one double-quoted value with a space' \ + "${cmd}" --items[]='"two two"' + +call-and-log-as-test 'passed multi-value option, one dollar-quoted value with a space' \ + "${cmd}" --items[]=$'$\'yes sirree!\'' + +call-and-log-as-test 'passed multi-value option, two values' \ + "${cmd}" --items[]='one two' + +call-and-log-as-test 'passed multi-value option, four values, omnibus' \ + "${cmd}" --items[]=$'one "two" \'three four\' $\'five & six\' \#7\(8\)9' diff --git a/tests/02-core/03-arg-processor/11-reqmulti/the-cmd b/tests/02-core/03-arg-processor/11-reqmulti/the-cmd new file mode 100755 index 0000000..6de1e14 --- /dev/null +++ b/tests/02-core/03-arg-processor/11-reqmulti/the-cmd @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-multi --required --var=items items + +process-args "$@" || exit "$?" + +echo "Count: ${#items[@]}" + +if (( ${#items[@]} != 0 )); then + for n in "${!items[@]}"; do + printf ' %s: %q\n' "${n}" "${items[n]}" + done +fi From 75b26159205b23e44e0923869ecb3483a7876b6f Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:18:48 -0700 Subject: [PATCH 17/21] Rename `longName` -> `specName` for consistency. --- scripts/lib/bashy-core/arg-processor.sh | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index c2e6cc2..937a47a 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -412,14 +412,14 @@ function _argproc_add-required-arg-postcheck { local argNoun='option' local allNames='' - local longName desc - for longName in "$@"; do + local specName desc + for specName in "$@"; do _argproc_preReturnStatements+=("$( printf '[[ ${_argproc_receivedArgNames} =~ "<%s>" ]] && (( _argproc_count++ )) || true' \ - "${longName}" + "${specName}" )") - desc="$(_argproc_arg-description --short "${longName}")" || return 1 + desc="$(_argproc_arg-description --short "${specName}")" || return 1 if ! [[ ${desc} =~ ^- ]]; then # The description doesn't start with a dash (`-`), so it's an # argument, not an option. @@ -459,12 +459,12 @@ function _argproc_arg-description { shift fi - local longName="$1" - local funcName="_argproc:arg-description-${longName}" + local specName="$1" + local funcName="_argproc:arg-description-${specName}" local desc if ! declare -F "${funcName}" >/dev/null; then - error-msg --file-line=1 "No such argument: <${longName}>" + error-msg --file-line=1 "No such argument: <${specName}>" return 1 fi @@ -482,10 +482,10 @@ function _argproc_arg-description { # short-form option. function _argproc_define-abbrev { local abbrevChar="$1" - local longName="$2" + local specName="$2" eval 'function _argproc:abbrev-'"${abbrevChar}"' { - _argproc:long-'"${longName}"' "$@" + _argproc:long-'"${specName}"' "$@" }' } @@ -541,19 +541,19 @@ function _argproc_define-no-value-arg { return 1 fi - local longName="$1" + local specName="$1" local value="$2" local filter="$3" local callFunc="$4" local varName="$5" local abbrevChar="$6" - _argproc_set-arg-description "${longName}" option || return 1 + _argproc_set-arg-description "${specName}" option || return 1 - local desc="$(_argproc_arg-description "${longName}")" - local handlerName="_argproc:long-${longName}" + local desc="$(_argproc_arg-description "${specName}")" + local handlerName="_argproc:long-${specName}" local handlerBody="$( - _argproc_handler-body "${longName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" )" value="$(_argproc_quote "${value}")" @@ -568,7 +568,7 @@ function _argproc_define-no-value-arg { }' if [[ ${abbrevChar} != '' ]]; then - _argproc_define-abbrev "${abbrevChar}" "${longName}" + _argproc_define-abbrev "${abbrevChar}" "${specName}" fi } @@ -581,7 +581,7 @@ function _argproc_define-value-taking-arg { shift fi - local longName="$1" + local specName="$1" local eqDefault="$2" local filter="$3" local callFunc="$4" @@ -589,16 +589,16 @@ function _argproc_define-value-taking-arg { local handlerName if (( isOption )); then - _argproc_set-arg-description "${longName}" option || return 1 - handlerName="_argproc:long-${longName}" + _argproc_set-arg-description "${specName}" option || return 1 + handlerName="_argproc:long-${specName}" else - _argproc_set-arg-description "${longName}" argument || return 1 - handlerName="_argproc:positional-${longName}" + _argproc_set-arg-description "${specName}" argument || return 1 + handlerName="_argproc:positional-${specName}" fi - local desc="$(_argproc_arg-description "${longName}")" + local desc="$(_argproc_arg-description "${specName}")" local handlerBody="$( - _argproc_handler-body "${longName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" )" local ifNoValue='' @@ -623,7 +623,7 @@ function _argproc_define-value-taking-arg { }' if [[ ${abbrevChar} != '' ]]; then - _argproc_define-abbrev "${abbrevChar}" "${longName}" + _argproc_define-abbrev "${abbrevChar}" "${specName}" fi } @@ -644,7 +644,7 @@ function _argproc_error-coda { # Produces an argument handler body, from the given components. function _argproc_handler-body { - local longName="$1" + local specName="$1" local desc="$2" local filters="$3" local callFunc="$4" @@ -693,7 +693,7 @@ function _argproc_handler-body { fi result+=( - "$(printf '_argproc_receivedArgNames+="<%s>"' "${longName}")" + "$(printf '_argproc_receivedArgNames+="<%s>"' "${specName}")" ) printf '%s\n' "${result[@]}" From be8a29e73dc3ad7a3bfbfca3a108033a1ccdae6a Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:29:18 -0700 Subject: [PATCH 18/21] Add test for multi-value option with a filter. --- .../12-optmulti-filter/expect.md | 72 +++++++++++++++++++ .../12-optmulti-filter/info.md | 1 + .../03-arg-processor/12-optmulti-filter/run | 25 +++++++ .../12-optmulti-filter/the-cmd | 28 ++++++++ 4 files changed, 126 insertions(+) create mode 100644 tests/02-core/03-arg-processor/12-optmulti-filter/expect.md create mode 100644 tests/02-core/03-arg-processor/12-optmulti-filter/info.md create mode 100755 tests/02-core/03-arg-processor/12-optmulti-filter/run create mode 100755 tests/02-core/03-arg-processor/12-optmulti-filter/the-cmd diff --git a/tests/02-core/03-arg-processor/12-optmulti-filter/expect.md b/tests/02-core/03-arg-processor/12-optmulti-filter/expect.md new file mode 100644 index 0000000..0b0084c --- /dev/null +++ b/tests/02-core/03-arg-processor/12-optmulti-filter/expect.md @@ -0,0 +1,72 @@ +## passed multi-value option, no values + +### stdout +``` +Count: 0 +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one matching value + +### stdout +``` +Count: 1 + 0: 123x789z +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, one non-matching value + +### stderr +``` +the-cmd: Invalid value for option --items[]: 123789 + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed multi-value option, two matching values + +### stdout +``` +Count: 2 + 0: xz + 1: blorpx-z +``` + +### exit: 0 + +- - - - - - - - - - + +## passed multi-value option, first matching, second not + +### stderr +``` +the-cmd: Invalid value for option --items[]: blorp + +the-cmd -- test command +``` + +### exit: 1 + +- - - - - - - - - - + +## passed multi-value option, first non-matching, second matching + +### stderr +``` +the-cmd: Invalid value for option --items[]: beep + +the-cmd -- test command +``` + +### exit: 1 diff --git a/tests/02-core/03-arg-processor/12-optmulti-filter/info.md b/tests/02-core/03-arg-processor/12-optmulti-filter/info.md new file mode 100644 index 0000000..e1747d9 --- /dev/null +++ b/tests/02-core/03-arg-processor/12-optmulti-filter/info.md @@ -0,0 +1 @@ +Test of an optional multi-value option which uses a filter. diff --git a/tests/02-core/03-arg-processor/12-optmulti-filter/run b/tests/02-core/03-arg-processor/12-optmulti-filter/run new file mode 100755 index 0000000..2c16407 --- /dev/null +++ b/tests/02-core/03-arg-processor/12-optmulti-filter/run @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_test-init.sh" || exit 1 + +cmd="$(this-cmd-dir)/the-cmd" + +call-and-log-as-test 'passed multi-value option, no values' \ + "${cmd}" --items[]= + +call-and-log-as-test 'passed multi-value option, one matching value' \ + "${cmd}" --items[]='123x789z' + +call-and-log-as-test 'passed multi-value option, one non-matching value' \ + "${cmd}" --items[]='123789' + +call-and-log-as-test 'passed multi-value option, two matching values' \ + "${cmd}" --items[]='xz blorpx-z' + +call-and-log-as-test 'passed multi-value option, first matching, second not' \ + "${cmd}" --items[]='xz blorp' + +call-and-log-as-test 'passed multi-value option, first non-matching, second matching' \ + "${cmd}" --items[]='beep bxx-boopz' diff --git a/tests/02-core/03-arg-processor/12-optmulti-filter/the-cmd b/tests/02-core/03-arg-processor/12-optmulti-filter/the-cmd new file mode 100755 index 0000000..3798f69 --- /dev/null +++ b/tests/02-core/03-arg-processor/12-optmulti-filter/the-cmd @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2022-2023 the Bashy-lib Authors (Dan Bornstein et alia). +# SPDX-License-Identifier: Apache-2.0 + +[[ "$(readlink -f "$0")" =~ ^(.*/tests/) ]] && . "${BASH_REMATCH[1]}_init.sh" || exit 1 + + +# +# Argument parsing +# + +define-usage $' + ${name} -- test command + + This is a test command. +' + +opt-multi --var=items --filter='/x.*z$/' items + +process-args "$@" || exit "$?" + +echo "Count: ${#items[@]}" + +if (( ${#items[@]} != 0 )); then + for n in "${!items[@]}"; do + printf ' %s: %q\n' "${n}" "${items[n]}" + done +fi From 6929fdad0eb84f1075a64ee709bf5b4f4f723476 Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:29:33 -0700 Subject: [PATCH 19/21] Simplify. --- scripts/lib/bashy-core/arg-processor.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 937a47a..6bf8255 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -520,9 +520,8 @@ function _argproc_define-multi-value-arg { handlerName="_argproc:positional-${specName}" fi - local desc="$(_argproc_arg-description "${specName}")" local handlerBody="$( - _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${filter}" "${callFunc}" "${varName}" )" eval 'function '"${handlerName}"' { @@ -553,7 +552,7 @@ function _argproc_define-no-value-arg { local desc="$(_argproc_arg-description "${specName}")" local handlerName="_argproc:long-${specName}" local handlerBody="$( - _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${filter}" "${callFunc}" "${varName}" )" value="$(_argproc_quote "${value}")" @@ -598,7 +597,7 @@ function _argproc_define-value-taking-arg { local desc="$(_argproc_arg-description "${specName}")" local handlerBody="$( - _argproc_handler-body "${specName}" "${desc}" "${filter}" "${callFunc}" "${varName}" + _argproc_handler-body "${specName}" "${filter}" "${callFunc}" "${varName}" )" local ifNoValue='' @@ -645,10 +644,9 @@ function _argproc_error-coda { # Produces an argument handler body, from the given components. function _argproc_handler-body { local specName="$1" - local desc="$2" - local filters="$3" - local callFunc="$4" - local varName="$5" + local filters="$2" + local callFunc="$3" + local varName="$4" local result=() while [[ ${filters} =~ ^$'\n'*([^$'\n']+)(.*)$ ]]; do @@ -657,6 +655,7 @@ function _argproc_handler-body { if [[ ${f} =~ ^/(.*)/$ ]]; then # Add a call to perform the regex check on each argument. f="${BASH_REMATCH[1]}" + local desc="$(_argproc_arg-description "${specName}")" result+=("$(printf \ '_argproc_regex-filter-check %q %q "$@" || return "$?"\n' \ "${desc}" "${f}" From de7dde0549ec11cf36c5cd0c2bcac103eb038d1a Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:32:24 -0700 Subject: [PATCH 20/21] Add missing detail. --- scripts/lib/bashy-core/arg-processor.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/lib/bashy-core/arg-processor.sh b/scripts/lib/bashy-core/arg-processor.sh index 6bf8255..c572116 100644 --- a/scripts/lib/bashy-core/arg-processor.sh +++ b/scripts/lib/bashy-core/arg-processor.sh @@ -151,11 +151,12 @@ function opt-choice { } # Declares a "multi-value" option, which allows passing zero or more values. No -# `` is allowed in the argument spec. These options are accepted via the -# syntax `--[]=` where is a space-separated list of -# literal values, with standard shell quoting and escaping allowed in order to -# pass special characters. This definer also accepts the `--required` option. -# The initial variable value is `()` (the empty array). +# `` or `` is allowed in the argument spec. These options are +# accepted via the syntax `--[]=` where is a +# space-separated list of literal values, with standard shell quoting and +# escaping allowed in order to pass special characters. This definer also +# accepts the `--required` option. The initial variable value is `()` (the empty +# array). function opt-multi { local optCall='' local optFilter='' From 78c7d66af5085d73544b43214bef11c4cbd1303c Mon Sep 17 00:00:00 2001 From: Dan Bornstein Date: Thu, 12 Oct 2023 10:38:58 -0700 Subject: [PATCH 21/21] Changelog. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2381bb7..2fa0ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ Notable changes: * Tightened up error checking and reporting. * New recommended processing call `process-args "$@" || exit "$?"`, because of "magic" reduction noted below. - * Added multi-value option syntax `--opt-name[]=...`. + * Added multi-value option syntax `--opt-name[]=...`, along with helper + function `values` for use sites. + * Added a lot of tests. * `define-usage`: * New option `--with-help` to help reduce boilerplate. * Dropped "magical" `exit` behavior.