Skip to content

Commit

Permalink
Merge branch 'master' into billyzkid-patch-3
Browse files Browse the repository at this point in the history
  • Loading branch information
UrsaDK authored Dec 17, 2024
2 parents 4c5e2d1 + 737a918 commit ea81a25
Show file tree
Hide file tree
Showing 43 changed files with 1,578 additions and 153 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
DOCKER_IMAGE: ci/${{ github.job }}
BUILD_CACHE: /home/runner/.docker/buildkit
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: actions/cache@v1
- uses: actions/cache@v4
with:
path: ${{ env.BUILD_CACHE }}
key: ${{ hashFiles('Dockerfile') }}
Expand Down Expand Up @@ -52,4 +52,4 @@ jobs:
- name: Prepare coverage report
run: ./bin/docker run ${DOCKER_IMAGE} cp -a /home/coverage /mnt/coverage

- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v5
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
FROM debian:stable-slim AS base
ARG BUILD_SHA
ARG BUILD_DATE
LABEL org.opencontainers.image.vendor="Dmytro Konstantinov" \
org.opencontainers.image.source="https://github.com/UrsaDK/getopts_long" \
org.opencontainers.image.revision="${BUILD_SHA}" \
Expand Down Expand Up @@ -103,6 +105,7 @@ RUN TZ=UTC git show --pretty="%H%+ad" | head -2 > ./VERSION \
&& rm -Rf \
./.git \
./dockerfs \
&& ./bin/bats \
&& ./bin/kcov
WORKDIR /mnt
VOLUME ["/mnt"]
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
[![stable branch](https://img.shields.io/badge/dynamic/json.svg?color=lightgrey&label=stable&query=%24.default_branch&url=https%3A%2F%2Fapi.github.com%2Frepos%2FUrsaDK%2Fgetopts_long&logo=github)](https://github.com/UrsaDK/getopts_long/tree/master)
[![latest release](https://img.shields.io/badge/dynamic/json.svg?color=blue&label=release&query=%24.name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FUrsaDK%2Fgetopts_long%2Freleases%2Flatest&logo=docker)](https://hub.docker.com/r/ursadk/getopts_long)
[![test coverage](https://codecov.io/gh/UrsaDK/getopts_long/graph/badge.svg)](https://codecov.io/gh/UrsaDK/getopts_long)
[![donate link](https://img.shields.io/badge/donate-coinbase-gold.svg?colorB=ff8e00&logo=bitcoin)](https://commerce.coinbase.com/checkout/17ae30c2-9c3f-45fb-a911-36d01a3c81b6)

</div>

Expand All @@ -19,6 +18,17 @@ This is a pure BASH implementation of `getopts_long` function, which "upgrades"

This function is 100% compatible with the built-in `getopts`. It is implemented with no external dependencies, and relies solely on BASH built-in tools to provide all of its functionality.

The implementation supports the following option syntax:

- Short options are compatible with bash’s built-in getopts:
- `-o`
- `-o value`
- `-ovalue`
- Long options support GNU-like syntax:
- `--option`
- `--option value`
- `--option=value`

Table of Content
----------------

Expand Down Expand Up @@ -157,7 +167,6 @@ while getopts_long ':af: all file:' OPTKEY; do
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

...
```
Expand All @@ -179,7 +188,10 @@ while getopts ...; do
done
```

Identical to `getopts`, `getopts_long` will parse options and their possible arguments. It will stop parsing on the first non-option argument (a string that doesn't begin with a hyphen (`-`) that isn't an argument for any option in front of it). It will also stop parsing when it sees the `--` (double-hyphen), which means end of options.
Identical to `getopts`, `getopts_long` will parse options and their possible arguments. It will stop parsing on the first non-option argument (a string that doesn't begin with a hyphen (`-`) that isn't an argument for any option in front of it). It will also stop parsing when it sees the `--` (double-hyphen) as a stand-alone argument.

> [!IMPORTANT]
> To support long options and enforce identical behaviour between getopts and getopts_long when handling hyphens, getopts_long provides its own implementation for `-` option. This means that the user can not use hyphen (`-`) within their short option OPTSPEC, as this would override getopts_long implementation.
### Internal variables

Expand Down
3 changes: 1 addition & 2 deletions bin/bats
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,4 @@ shellcheck --format="tty" --shell="bash" --check-sourced \

# Run bats without generating code coverage
echo "Running tests: ${*:-"${TEST_DIR}"}"
/usr/local/bin/bats ${*:+'--tap'} --recursive "${@:-"${TEST_DIR}"}"
[[ -z "${*}" ]] && "$(dirname "${0}")/kcov"
/usr/local/bin/bats --recursive "${*:-"${TEST_DIR}"}"
2 changes: 2 additions & 0 deletions bin/docker
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ case "${COMMAND}" in
;;
'r'|'run')
[[ -t 1 ]] && IT='-it' # Check if output is attached to a TTY
# shellcheck disable=SC2086
docker container run ${GITHUB_ACTIONS:+-u root} \
--rm --init ${IT} -v "${PWD}:/mnt" "${IMAGE}" "${@}"
;;
Expand All @@ -72,6 +73,7 @@ case "${COMMAND}" in
'e'|'exec')
[[ -t 1 ]] && IT='-it' # Check if output is attached to a TTY
CONTAINER_ID="$(docker container ls -qlf "ancestor=${IMAGE}")"
#shellcheck disable=SC2086
docker container exec ${IT} "${CONTAINER_ID}" "${@}"
;;
's'|'stop')
Expand Down
16 changes: 10 additions & 6 deletions bin/kcov
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ path_exists "${TEST_DIR}"
TEMP_DIR="$(mktemp -d -t kcov.XXXXXXXXX)"
trap 'rm -Rf ${TEMP_DIR}' EXIT

# Run kcov
echo "Generating coverage report: ${COVERAGE_DIR}/index.html"
KCOV_CMD="/usr/local/bin/kcov --clean --include-path=${LIB_DIR} ${@} \
# Run kcov + bats
echo "Generating coverage report: ${TEMP_DIR}/bats/index.html"
echo -n "Running the whole tests suite: "
KCOV_CMD="/usr/local/bin/kcov --clean --include-path=${LIB_DIR} ${*} \
${TEMP_DIR} /usr/local/bin/bats --recursive ${TEST_DIR}"
if [[ ${EUID} -eq 0 ]]; then
KCOV_USER="$(ls -l "${0}" | awk '{print $3}')"
echo
KCOV_USER="$(find "${0}" -maxdepth 0 -printf '%u')"
[[ "${KCOV_USER}" == "root" ]] && die_with_kcov_user_error

chown -R "${KCOV_USER}" "${TEMP_DIR}"
Expand All @@ -83,8 +85,10 @@ sed -i -E 's#/(home|mnt)/##g' \
"${TEMP_DIR}"/bats/*.{json,xml} "${TEMP_DIR}"/bats/metadata/*

# Present kcov report
rm -Rf "${COVERAGE_DIR}"
cp -LR "${TEMP_DIR}/bats" "${COVERAGE_DIR}"
if rm -Rf "${COVERAGE_DIR}" 2>/dev/null; then
echo "Publishing coverage report: ${COVERAGE_DIR}/index.html";
cp -LR "${TEMP_DIR}/bats" "${COVERAGE_DIR}"
fi

# Total code coverage
COVERAGE="$(jq -r .percent_covered "${COVERAGE_DIR}/coverage.json")"
Expand Down
39 changes: 31 additions & 8 deletions lib/getopts_long.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,50 @@ getopts_long() {
: "${1:?Missing required parameter -- long optspec}"
: "${2:?Missing required parameter -- variable name}"

local optspec_short="${1%% *}-:"
local optspec_short="${1%% *}"
local optspec_long="${1#* }"
local optvar="${2}"

shift 2

if [[ "${#}" == 0 ]]; then
local args=()
while [[ ${#BASH_ARGV[@]} -gt ${#args[@]} ]]; do
local index=$(( ${#BASH_ARGV[@]} - ${#args[@]} - 1 ))
args[${#args[@]}]="${BASH_ARGV[${index}]}"
local -i start_index=0
local -i end_index=$(( ${#BASH_ARGV[@]} - 1 ))

# Minimise the number of times `declare -f` is executed
if [[ -n "${FUNCNAME[1]}" ]]; then
if [[ "${FUNCNAME[1]}" == "( anon )" ]] \
|| declare -f "${FUNCNAME[1]}" > /dev/null 2>&1; then
if ! shopt -q extdebug; then
echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}:" \
"${FUNCNAME[0]} failed to detect supplied arguments" \
"-- enable extdebug or pass arguments explicitly" >&2
return 2
fi
start_index=${BASH_ARGC[0]}
end_index=$(( start_index + BASH_ARGC[1] - 1 ))
fi
fi

for (( i = end_index; i >= start_index; i-- )); do
args+=("${BASH_ARGV[i]}")
done
set -- "${args[@]}"
fi

builtin getopts "${optspec_short}" "${optvar}" "${@}" || return 1
# Sanitize and normalize short optspec
optspec_short="${optspec_short//-:}"
optspec_short="${optspec_short//-}"
[[ "${!OPTIND:0:2}" == "--" ]] && optspec_short+='-:'

builtin getopts -- "${optspec_short}" "${optvar}" "${@}" || return ${?}
[[ "${!optvar}" == '-' ]] || return 0

printf -v "${optvar}" "%s" "${OPTARG%%=*}"

if [[ "${optspec_long}" =~ (^|[[:space:]])${!optvar}:([[:space:]]|$) ]]; then
OPTARG="${OPTARG#${!optvar}}"
if [[ " ${optspec_long} " == *" ${!optvar}: "* ]]; then
OPTARG="${OPTARG#"${!optvar}"}"
OPTARG="${OPTARG#=}"

# Missing argument
Expand All @@ -38,8 +60,9 @@ getopts_long() {
unset OPTARG && printf -v "${optvar}" '?'
fi
fi
elif [[ "${optspec_long}" =~ (^|[[:space:]])${!optvar}([[:space:]]|$) ]]; then
elif [[ " ${optspec_long} " == *" ${!optvar} "* ]]; then
unset OPTARG
declare -g OPTARG
else
# Invalid option
if [[ "${optspec_short:0:1}" == ':' ]]; then
Expand Down
92 changes: 92 additions & 0 deletions test/bats/github_13.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bats

load ../test_helper
export GETOPTS_LONG_TEST_BIN='getopts_long-no_shortspec'

@test "${FEATURE}: long option, silent" {
compare '-o user_val' \
'--option user_val'
}
@test "${FEATURE}: long option, verbose" {
compare '-o user_val' \
'--option user_val'
}

@test "${FEATURE}: long variable, silent" {
compare '-v user_val' \
'--variable=user_val' \
'/^OPTIND: /d'
expect "${bash_getopts[5]}" == 'OPTIND: 3'
expect "${getopts_long[5]}" == 'OPTIND: 2'
}
@test "${FEATURE}: long variable, verbose" {
compare '-v user_val' \
'--variable=user_val' \
'/^OPTIND: /d'
expect "${bash_getopts[5]}" == 'OPTIND: 3'
expect "${getopts_long[5]}" == 'OPTIND: 2'
}

@test "${FEATURE}: toggle followed by long variable, silent" {
compare '-t -v user_val' \
'--toggle --variable=user_val' \
'/^OPTIND: /d'
expect "${bash_getopts[6]}" == 'OPTIND: 4'
expect "${getopts_long[6]}" == 'OPTIND: 3'
}
@test "${FEATURE}: toggle followed by long variable, verbose" {
compare '-t -v user_val' \
'--toggle --variable=user_val' \
'/^OPTIND: /d'
expect "${bash_getopts[6]}" == 'OPTIND: 4'
expect "${getopts_long[6]}" == 'OPTIND: 3'
}

@test "${FEATURE}: long variable followed by toggle, silent" {
compare '-v user_val -t' \
'--variable=user_val --toggle' \
'/^OPTIND: /d'
expect "${bash_getopts[6]}" == 'OPTIND: 4'
expect "${getopts_long[6]}" == 'OPTIND: 3'
}
@test "${FEATURE}: long variable followed by toggle, verbose" {
compare '-v user_val -t' \
'--variable=user_val --toggle' \
'/^OPTIND: /d'
expect "${bash_getopts[6]}" == 'OPTIND: 4'
expect "${getopts_long[6]}" == 'OPTIND: 3'
}

@test "${FEATURE}: terminator followed by long variable, silent" {
compare '-t -- -v user_val' \
'--toggle -- --variable=user_val' \
'/^\$@: /d'
expect "${bash_getopts[6]}" == '$@: ([0]="-v" [1]="user_val")'
expect "${getopts_long[6]}" == '$@: ([0]="--variable=user_val")'
}
@test "${FEATURE}: terminator followed by long variable, verbose" {
compare '-t -- -v user_val' \
'--toggle -- --variable=user_val' \
'/^\$@: /d'
expect "${bash_getopts[6]}" == '$@: ([0]="-v" [1]="user_val")'
expect "${getopts_long[6]}" == '$@: ([0]="--variable=user_val")'
}

@test "${FEATURE}: long variable followed by terminator, silent" {
compare '-v user_val -- -t' \
'--variable=user_val -- --toggle' \
'/^(OPTIND|\$@): /d'
expect "${bash_getopts[5]}" == 'OPTIND: 4'
expect "${getopts_long[5]}" == 'OPTIND: 3'
expect "${bash_getopts[6]}" == '$@: ([0]="-t")'
expect "${getopts_long[6]}" == '$@: ([0]="--toggle")'
}
@test "${FEATURE}: long variable followed by terminator, verbose" {
compare '-v user_val -- -t' \
'--variable=user_val -- --toggle' \
'/^(OPTIND|\$@): /d'
expect "${bash_getopts[5]}" == 'OPTIND: 4'
expect "${getopts_long[5]}" == 'OPTIND: 3'
expect "${bash_getopts[6]}" == '$@: ([0]="-t")'
expect "${getopts_long[6]}" == '$@: ([0]="--toggle")'
}
95 changes: 95 additions & 0 deletions test/bats/github_15a.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env bats

load ../test_helper

# Neither bash getopts nor getopts_long OPTSPEC includes [-]

@test "${FEATURE}: short toggle, single, silent" {
compare '-t- -t user_arg' \
'-t- -t user_arg'
}
@test "${FEATURE}: short toggle, single, verbose" {
compare '-t- -t user_arg' \
'-t- -t user_arg' \
's/getopts[[:alpha:]_-]*/GETOPTS-NORMALISED/'
}

@test "${FEATURE}: short toggle, silent" {
compare '-t-- -t user_arg' \
'-t-- -t user_arg'
}
@test "${FEATURE}: short toggle, verbose" {
compare '-t-- -t user_arg' \
'-t-- -t user_arg' \
's/getopts[[:alpha:]_-]*/GETOPTS-NORMALISED/'
}

# Standard getopts should see:
# -t - a toggle
# -- - an invalid option
# -- - an invalid option
# -t - a toggle
# Getopts_long should see:
# --toggle-- - an invalid option
# --toggle - a toggle
@test "${FEATURE}: long toggle, silent" {
compare '-t-- -t user_arg' \
'--toggle-- --toggle user_arg' \
'1{/^toggle triggered/d}' \
'/^INVALID OPTION/d'
expect "${bash_getopts[2]}" == 'INVALID OPTION -- OPTARG="-"'
expect "${bash_getopts[3]}" == 'INVALID OPTION -- OPTARG="-"'
expect "${getopts_long[1]}" == 'INVALID OPTION -- OPTARG="toggle--"'
expect "${bash_getopts[1]}" == 'toggle triggered -- OPTARG'
expect "${bash_getopts[4]}" == 'toggle triggered -- OPTARG'
expect "${getopts_long[2]}" == 'toggle triggered -- OPTARG'
}
@test "${FEATURE}: long toggle, verbose" {
compare '-t-- -t user_arg' \
'--toggle-- --toggle user_arg' \
'1{/^toggle triggered/d}' \
'4{/getopts-verbose: illegal option -- -$/d}' \
'5{/^INVALID OPTION or MISSING ARGUMENT/d}' \
's/getopts[[:alpha:]_-]*/GETOPTS-NORMALISED/' \
's/(illegal option --) (-|toggle--)/\1 TOGGLE-NORMALISED/'
expect "${bash_getopts[1]}" == 'toggle triggered -- OPTARG'
expect "${bash_getopts[2]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts[4]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts[6]}" == 'toggle triggered -- OPTARG'
expect "${getopts_long[1]}" =~ 'getopts_long-verbose: illegal option -- toggle--$'
expect "${getopts_long[3]}" == 'toggle triggered -- OPTARG'
}

# Both implementations should see:
# -o -- - an option with a value
# -t - a toggle
@test "${FEATURE}: short option, silent" {
compare '-o-- -t user_arg' \
'-o-- -t user_arg'
}
@test "${FEATURE}: short option, verbose" {
compare '-o-- -t user_arg' \
'-o-- -t user_arg'
}

# Standard getopts should see:
# -o -- - an option with a value
# -t - a toggle
# Getopts_long should see:
# --option-- - an invalid option
# --toggle - a toggle
@test "${FEATURE}: long option, silent" {
compare '-o-- -t user_arg' \
'--option-- --toggle user_arg' \
'1{/(option supplied|INVALID OPTION)/d}'
expect "${bash_getopts[1]}" == 'option supplied -- OPTARG="--"'
expect "${getopts_long[1]}" == 'INVALID OPTION -- OPTARG="option--"'
}
@test "${FEATURE}: long option, verbose" {
compare '-o-- -t user_arg' \
'--option-- --toggle user_arg' \
'1{/(option supplied|illegal option)/d}' \
'2{/^INVALID OPTION or MISSING ARGUMENT/d}'
expect "${bash_getopts[1]}" == 'option supplied -- OPTARG="--"'
expect "${getopts_long[1]}" =~ "getopts_long-verbose: illegal option -- option--$"
}
Loading

0 comments on commit ea81a25

Please sign in to comment.