Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/bash/arg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ Spec entries use `name|kind|token[|token...]`:
- each `token` is an exact option token, such as `--verbose` or `-v`.

The parser supports `--option value`, `--option=value`, repeated options where
the last value wins, and `--` to stop option parsing. A value option followed by
another registered option token is treated as missing its value; use
the last value wins, and `--` to stop option parsing. When a value option is
waiting for a value, a standalone `--` is treated as that value; use another
`--` if you also need to stop option parsing after it. A value option followed
by another registered option token is treated as missing its value; use
`--option=value` when a value is intentionally option-like. Unknown options,
malformed specs, and missing values return status `2`.

Expand Down
2 changes: 1 addition & 1 deletion lib/bash/arg/lib_arg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ arg_parse() {
continue
fi

if (($# == 0)) || [[ "${1-}" == "--" ]]; then
if (($# == 0)); then
log_error "arg_parse: option '$__arg_option_token' requires a value."
return 2
fi
Expand Down
12 changes: 12 additions & 0 deletions lib/bash/arg/tests/lib_arg.bats
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ create_script() {
[ "${positionals[0]}" = "item" ]
}

@test "arg_parse accepts -- as a value option value" {
local -a specs=("output|value|--output|-o")
local -A options=()
local -a positionals=()

arg_parse options positionals specs -- --output -- item

[ "${options[output]}" = "--" ]
[ "${#positionals[@]}" -eq 1 ]
[ "${positionals[0]}" = "item" ]
}

@test "arg_parse returns usage status for unknown options" {
local -a specs=("verbose|flag|--verbose|-v")
local -A options=()
Expand Down
8 changes: 8 additions & 0 deletions lib/bash/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ Use `std_run` for commands plus arguments. Keep shell features such as
pipelines, redirection, process substitution, and complex conditionals explicit
in the calling script so the code remains clear.

Unknown `std_run` options beginning with `--` are rejected before command
execution. If the command itself begins with `--`, terminate `std_run` options
first:

```bash
std_run -- --command-name arg
```

`run` remains available as a compatibility wrapper for existing callers, but new
code should use `std_run` to avoid collisions with test frameworks and other
Bash libraries that define their own `run` helper.
Expand Down
4 changes: 4 additions & 0 deletions lib/bash/std/lib_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ __std_run_impl__() {
break
;;
*)
if [[ "${1-}" == --* ]]; then
log_error "$helper_name: unknown option '$1'. Use -- before commands that begin with --."
return 1
fi
break
;;
esac
Expand Down
30 changes: 30 additions & 0 deletions lib/bash/std/tests/lib_std.bats
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,36 @@ EOF
[[ "$(cat "$stderr_file")" == *"std_run: No command provided."* ]]
}

@test "std_run rejects unknown long options before command execution" {
local stderr_file="$TEST_TMPDIR/run-unknown-option.err"
local rc

if std_run --typo echo "should not run" 2>"$stderr_file"; then
rc=0
else
rc=$?
fi

[ "$rc" -eq 1 ]
[[ "$(cat "$stderr_file")" == *"std_run: unknown option '--typo'."* ]]
[[ "$(cat "$stderr_file")" == *"Use -- before commands that begin with --."* ]]
}

@test "std_run allows command names beginning with -- after option terminator" {
local fake_bin="$TEST_TMPDIR/bin"
local output_file="$TEST_TMPDIR/option-like-command.out"

mkdir -p "$fake_bin"
create_script "$fake_bin/--record-command" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$1" > "$2"
EOF

PATH="$fake_bin:$PATH" std_run -- --record-command "ran" "$output_file"

[ "$(cat "$output_file")" = "ran" ]
}

@test "std_run honors dry-run mode without executing the command" {
local target="$TEST_TMPDIR/dry-run.txt"
DRY_RUN=true
Expand Down
Loading