Skip to content

Commit e3d814c

Browse files
chaliyclaude
andauthored
feat(builtins): implement set -o / set +o option display (#257)
## Summary - Implement `set -o` human-readable option display (name + on/off) - Implement `set +o` re-executable option display (`set -/+o name`) - Format matches real bash exactly (15-char padded name, tab separator) ## Changes - `crates/bashkit/src/builtins/vars.rs`: Add `SET_O_OPTIONS` list, `format_set_dash_o()`, `format_set_plus_o()`, handle bare `-o`/`+o` in Set execute - `crates/bashkit/tests/spec_cases/bash/variables.test.sh`: 6 new spec tests - `specs/009-implementation-status.md`: Update test counts (variables 86→92, Bash 900→906, Total 1318→1324) ## Test plan - [x] `cargo test --all-features` passes (including bash comparison tests) - [x] `cargo fmt --check` clean - [x] `cargo clippy --all-targets --all-features -- -D warnings` clean - [x] Output format verified against real bash byte-for-byte Co-authored-by: Claude <noreply@anthropic.com>
1 parent 966071f commit e3d814c

File tree

3 files changed

+97
-7
lines changed

3 files changed

+97
-7
lines changed

crates/bashkit/src/builtins/vars.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,40 @@ fn option_name_to_var(name: &str) -> Option<&'static str> {
4848
}
4949
}
5050

51+
/// All known `set -o` options with their variable names, in display order.
52+
const SET_O_OPTIONS: &[(&str, &str)] = &[
53+
("errexit", "SHOPT_e"),
54+
("noglob", "SHOPT_f"),
55+
("noclobber", "SHOPT_C"),
56+
("noexec", "SHOPT_n"),
57+
("nounset", "SHOPT_u"),
58+
("pipefail", "SHOPT_pipefail"),
59+
("verbose", "SHOPT_v"),
60+
("xtrace", "SHOPT_x"),
61+
];
62+
63+
/// Format option display for `set -o` (human-readable).
64+
fn format_set_dash_o(variables: &std::collections::HashMap<String, String>) -> String {
65+
let mut output = String::new();
66+
for (name, var) in SET_O_OPTIONS {
67+
let enabled = variables.get(*var).map(|v| v == "1").unwrap_or(false);
68+
let state = if enabled { "on" } else { "off" };
69+
output.push_str(&format!("{:<15}\t{}\n", name, state));
70+
}
71+
output
72+
}
73+
74+
/// Format option display for `set +o` (re-executable).
75+
fn format_set_plus_o(variables: &std::collections::HashMap<String, String>) -> String {
76+
let mut output = String::new();
77+
for (name, var) in SET_O_OPTIONS {
78+
let enabled = variables.get(*var).map(|v| v == "1").unwrap_or(false);
79+
let flag = if enabled { "-o" } else { "+o" };
80+
output.push_str(&format!("set {} {}\n", flag, name));
81+
}
82+
output
83+
}
84+
5185
#[async_trait]
5286
impl Builtin for Set {
5387
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
@@ -69,14 +103,23 @@ impl Builtin for Set {
69103
&& arg.len() > 1
70104
&& (arg.as_bytes()[1] == b'o' && arg.len() == 2)
71105
{
72-
// -o option_name / +o option_name
106+
// -o / +o: either display options or set/unset a named option
73107
let enable = arg.starts_with('-');
74-
i += 1;
75-
if i < ctx.args.len() {
108+
if i + 1 < ctx.args.len() {
109+
// -o option_name / +o option_name
110+
i += 1;
76111
if let Some(var) = option_name_to_var(&ctx.args[i]) {
77112
ctx.variables
78113
.insert(var.to_string(), if enable { "1" } else { "0" }.to_string());
79114
}
115+
} else {
116+
// Bare -o or +o: display options
117+
let output = if enable {
118+
format_set_dash_o(ctx.variables)
119+
} else {
120+
format_set_plus_o(ctx.variables)
121+
};
122+
return Ok(ExecResult::ok(output));
80123
}
81124
} else if arg.starts_with('-') || arg.starts_with('+') {
82125
let enable = arg.starts_with('-');

crates/bashkit/tests/spec_cases/bash/variables.test.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,3 +672,50 @@ shopt -q extglob && echo "ext:on" || echo "ext:off"
672672
null:on
673673
ext:on
674674
### end
675+
676+
### set_dash_o_display
677+
# set -o shows options in human-readable format with grep
678+
set -o | grep "^errexit"
679+
### expect
680+
errexit off
681+
### end
682+
683+
### set_dash_o_shows_enabled
684+
# set -o reflects enabled options
685+
set -e
686+
set -o | grep "^errexit"
687+
### expect
688+
errexit on
689+
### end
690+
691+
### set_plus_o_display
692+
# set +o shows options in re-executable format
693+
set +o | grep "errexit"
694+
### expect
695+
set +o errexit
696+
### end
697+
698+
### set_plus_o_shows_enabled
699+
# set +o reflects enabled options
700+
set -o pipefail
701+
set +o | grep "pipefail"
702+
### expect
703+
set -o pipefail
704+
### end
705+
706+
### set_dash_o_noclobber
707+
# set -o noclobber and display
708+
set -o noclobber
709+
set -o | grep "^noclobber"
710+
### expect
711+
noclobber on
712+
### end
713+
714+
### set_plus_o_restore
715+
# set +o output can be used to restore state
716+
set -o xtrace
717+
state=$(set +o 2>/dev/null | grep xtrace)
718+
echo "$state"
719+
### expect
720+
set -o xtrace
721+
### end

specs/009-implementation-status.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,17 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
103103

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 1318 (1313 pass, 5 skip)
106+
**Total spec test cases:** 1324 (1319 pass, 5 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 900 | Yes | 895 | 5 | `bash_spec_tests` in CI |
110+
| Bash (core) | 906 | Yes | 901 | 5 | `bash_spec_tests` in CI |
111111
| AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
112112
| Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
113113
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
114114
| JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115115
| Python | 57 | Yes | 57 | 0 | embedded Python (Monty) |
116-
| **Total** | **1318** | **Yes** | **1313** | **5** | |
116+
| **Total** | **1324** | **Yes** | **1319** | **5** | |
117117

118118
### Bash Spec Tests Breakdown
119119

@@ -157,7 +157,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
157157
| test-operators.test.sh | 17 | file/string tests |
158158
| time.test.sh | 11 | Wall-clock only (user/sys always 0) |
159159
| timeout.test.sh | 17 | |
160-
| variables.test.sh | 86 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\<newline>` line continuation, PWD/HOME/USER/HOSTNAME/BASH_VERSION/SECONDS, `set -x` xtrace, `shopt` builtin, nullglob |
160+
| variables.test.sh | 92 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\<newline>` line continuation, PWD/HOME/USER/HOSTNAME/BASH_VERSION/SECONDS, `set -x` xtrace, `shopt` builtin, nullglob, `set -o`/`set +o` display |
161161
| wc.test.sh | 35 | word count (5 skipped) |
162162
| type.test.sh | 15 | `type`, `which`, `hash` builtins |
163163
| declare.test.sh | 23 | `declare`/`typeset`, `-i`, `-r`, `-x`, `-a`, `-p`, `-n` nameref, `-l`/`-u` case conversion |

0 commit comments

Comments
 (0)