Skip to content

Commit

Permalink
[GH-15] Fixed double hyphen handling (#28)
Browse files Browse the repository at this point in the history
- Fixed double hyphen handling in short options
- Added more tests to cover #15 
- Updated documentation
- Test suit improvements
  • Loading branch information
UrsaDK authored Dec 14, 2024
1 parent f266f21 commit 55a6959
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 204 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,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 @@ -177,7 +188,11 @@ 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: IMPORTANT: There’s only one exception to the rule that says getopts_long behaves like getopts: when handling `-` within short options!
>
> To support long options, getopts_long provides its own implementation for `-` option, which means the user can no longer define `-` as part of the short option OPTSPEC.
### Internal variables

Expand Down
7 changes: 6 additions & 1 deletion lib/getopts_long.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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}"

Expand All @@ -17,6 +17,11 @@ getopts_long() {
set -- "${args[@]}"
fi

# Sanitize and normalize short optspec
optspec_short="${optspec_short//-:}"
optspec_short="${optspec_short//-}"
[[ "${!OPTIND:0:2}" == "--" ]] && optspec_short+='-:'

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

Expand Down
50 changes: 25 additions & 25 deletions test/bats/github_13.bats
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bats

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

@test "${FEATURE}: long option, silent" {
compare '-o user_val' \
Expand All @@ -16,77 +16,77 @@ export GETOPTS_LONG_TEST_BIN='getopts_long-github_13'
compare '-v user_val' \
'--variable=user_val' \
'/^OPTIND: /d'
expect "${bash_getopts_lines[4]}" == 'OPTIND: 3'
expect "${getopts_long_lines[4]}" == 'OPTIND: 2'
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_lines[4]}" == 'OPTIND: 3'
expect "${getopts_long_lines[4]}" == 'OPTIND: 2'
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_lines[5]}" == 'OPTIND: 4'
expect "${getopts_long_lines[5]}" == 'OPTIND: 3'
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_lines[5]}" == 'OPTIND: 4'
expect "${getopts_long_lines[5]}" == 'OPTIND: 3'
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_lines[5]}" == 'OPTIND: 4'
expect "${getopts_long_lines[5]}" == 'OPTIND: 3'
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_lines[5]}" == 'OPTIND: 4'
expect "${getopts_long_lines[5]}" == 'OPTIND: 3'
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_lines[5]}" == '$@: -v user_val'
expect "${getopts_long_lines[5]}" == '$@: --variable=user_val'
expect "${bash_getopts[6]}" == '$@: -v user_val'
expect "${getopts_long[6]}" == '$@: --variable=user_val'
}
@test "${FEATURE}: terminator followed by long variable, verbose" {
compare '-t -- -v user_val' \
'--toggle -- --variable=user_val' \
'/^\$@: /d'
expect "${bash_getopts_lines[5]}" == '$@: -v user_val'
expect "${getopts_long_lines[5]}" == '$@: --variable=user_val'
expect "${bash_getopts[6]}" == '$@: -v user_val'
expect "${getopts_long[6]}" == '$@: --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_lines[4]}" == 'OPTIND: 4'
expect "${getopts_long_lines[4]}" == 'OPTIND: 3'
expect "${bash_getopts_lines[5]}" == '$@: -t'
expect "${getopts_long_lines[5]}" == '$@: --toggle'
expect "${bash_getopts[5]}" == 'OPTIND: 4'
expect "${getopts_long[5]}" == 'OPTIND: 3'
expect "${bash_getopts[6]}" == '$@: -t'
expect "${getopts_long[6]}" == '$@: --toggle'
}
@test "${FEATURE}: long variable followed by terminator, verbose" {
compare '-v user_val -- -t' \
'--variable=user_val -- --toggle' \
'/^(OPTIND|\$@): /d'
expect "${bash_getopts_lines[4]}" == 'OPTIND: 4'
expect "${getopts_long_lines[4]}" == 'OPTIND: 3'
expect "${bash_getopts_lines[5]}" == '$@: -t'
expect "${getopts_long_lines[5]}" == '$@: --toggle'
expect "${bash_getopts[5]}" == 'OPTIND: 4'
expect "${getopts_long[5]}" == 'OPTIND: 3'
expect "${bash_getopts[6]}" == '$@: -t'
expect "${getopts_long[6]}" == '$@: --toggle'
}
63 changes: 30 additions & 33 deletions test/bats/github_15a.bats
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,25 @@

load ../test_helper

# Neither bash getopts nor getopts_long OPTSPEC includes [-], but
# getopts_long always appends [-:] to the end of the short OPTSPEC.
# 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/'
}

# Standard getopts should see:
# -t - a toggle
# -- - an invalid option
# -- - an invalid option
# -t - a toggle
# Getopts_long should see:
# -t - a toggle
# --- - an invalid option
# -t - a toggle
@test "${FEATURE}: short toggle, silent" {
compare '-t-- -t user_arg' \
'-t-- -t user_arg' \
'3{/^INVALID OPTION/d}'
'-t-- -t user_arg'
}
@test "${FEATURE}: short toggle, verbose" {
compare '-t-- -t user_arg' \
'-t-- -t user_arg' \
'4{/getopts-verbose: illegal option -- -$/d}' \
'5{/^INVALID OPTION or MISSING ARGUMENT -- OPTARG is unset$/d}' \
's/getopts[[:alpha:]_-]*/GETOPTS-NORMALISED/'
}

Expand All @@ -40,12 +37,12 @@ load ../test_helper
'--toggle-- --toggle user_arg' \
'1{/^toggle triggered/d}' \
'/^INVALID OPTION/d'
expect "${bash_getopts_lines[0]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts_lines[1]}" == 'INVALID OPTION -- OPTARG=-'
expect "${bash_getopts_lines[2]}" == 'INVALID OPTION -- OPTARG=-'
expect "${bash_getopts_lines[3]}" == 'toggle triggered -- OPTARG is unset'
expect "${getopts_long_lines[0]}" == 'INVALID OPTION -- OPTARG=toggle--'
expect "${getopts_long_lines[1]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts[1]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts[2]}" == 'INVALID OPTION -- OPTARG=-'
expect "${bash_getopts[3]}" == 'INVALID OPTION -- OPTARG=-'
expect "${bash_getopts[4]}" == 'toggle triggered -- OPTARG is unset'
expect "${getopts_long[1]}" == 'INVALID OPTION -- OPTARG=toggle--'
expect "${getopts_long[2]}" == 'toggle triggered -- OPTARG is unset'
}
@test "${FEATURE}: long toggle, verbose" {
compare '-t-- -t user_arg' \
Expand All @@ -55,16 +52,16 @@ load ../test_helper
'5{/^INVALID OPTION or MISSING ARGUMENT/d}' \
's/getopts[[:alpha:]_-]*/GETOPTS-NORMALISED/' \
's/(illegal option --) (-|toggle--)/\1 TOGGLE-NORMALISED/'
expect "${bash_getopts_lines[0]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts_lines[1]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts_lines[3]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts_lines[5]}" == 'toggle triggered -- OPTARG is unset'
expect "${getopts_long_lines[0]}" =~ 'getopts_long-verbose: illegal option -- toggle--$'
expect "${getopts_long_lines[2]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts[1]}" == 'toggle triggered -- OPTARG is unset'
expect "${bash_getopts[2]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts[4]}" =~ 'getopts-verbose: illegal option -- -$'
expect "${bash_getopts[6]}" == 'toggle triggered -- OPTARG is unset'
expect "${getopts_long[1]}" =~ 'getopts_long-verbose: illegal option -- toggle--$'
expect "${getopts_long[3]}" == 'toggle triggered -- OPTARG is unset'
}

# Both implementations should see:
# -o -- - an option (-o) with a value (--)
# -o -- - an option with a value
# -t - a toggle
@test "${FEATURE}: short option, silent" {
compare '-o-- -t user_arg' \
Expand All @@ -76,7 +73,7 @@ load ../test_helper
}

# Standard getopts should see:
# -o -- - an option with a value (--)
# -o -- - an option with a value
# -t - a toggle
# Getopts_long should see:
# --option-- - an invalid option
Expand All @@ -85,14 +82,14 @@ load ../test_helper
compare '-o-- -t user_arg' \
'--option-- --toggle user_arg' \
'1{/(option supplied|INVALID OPTION)/d}'
expect "${bash_getopts_lines[0]}" == 'option supplied -- OPTARG=--'
expect "${getopts_long_lines[0]}" == 'INVALID OPTION -- OPTARG=option--'
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_lines[0]}" == 'option supplied -- OPTARG=--'
expect "${getopts_long_lines[0]}" =~ "getopts_long-verbose: illegal option -- option--$"
expect "${bash_getopts[1]}" == 'option supplied -- OPTARG=--'
expect "${getopts_long[1]}" =~ "getopts_long-verbose: illegal option -- option--$"
}
Loading

0 comments on commit 55a6959

Please sign in to comment.