diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 5e547381..77d04f92 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -3860,6 +3860,8 @@ impl Interpreter { let mut is_assoc = false; let mut is_integer = false; let mut is_nameref = false; + let mut is_lowercase = false; + let mut is_uppercase = false; let mut names: Vec<&str> = Vec::new(); for arg in args { @@ -3873,7 +3875,9 @@ impl Interpreter { 'i' => is_integer = true, 'A' => is_assoc = true, 'n' => is_nameref = true, - 'g' | 'l' | 'u' | 't' | 'f' | 'F' => {} // ignored + 'l' => is_lowercase = true, + 'u' => is_uppercase = true, + 'g' | 't' | 'f' | 'F' => {} // ignored _ => {} } } @@ -4027,10 +4031,28 @@ impl Interpreter { self.variables .insert(var_name.to_string(), int_val.to_string()); } else { - self.variables - .insert(var_name.to_string(), value.to_string()); + // Apply case conversion attributes + let final_value = if is_lowercase { + value.to_lowercase() + } else if is_uppercase { + value.to_uppercase() + } else { + value.to_string() + }; + self.variables.insert(var_name.to_string(), final_value); } + // Set case conversion attribute markers + if is_lowercase { + self.variables + .insert(format!("_LOWER_{}", var_name), "1".to_string()); + self.variables.remove(&format!("_UPPER_{}", var_name)); + } + if is_uppercase { + self.variables + .insert(format!("_UPPER_{}", var_name), "1".to_string()); + self.variables.remove(&format!("_LOWER_{}", var_name)); + } if is_readonly { self.variables .insert(format!("_READONLY_{}", var_name), "1".to_string()); @@ -4055,6 +4077,17 @@ impl Interpreter { } else if !self.variables.contains_key(name.as_str()) { self.variables.insert(name.to_string(), String::new()); } + // Set case conversion attribute markers + if is_lowercase { + self.variables + .insert(format!("_LOWER_{}", name), "1".to_string()); + self.variables.remove(&format!("_UPPER_{}", name)); + } + if is_uppercase { + self.variables + .insert(format!("_UPPER_{}", name), "1".to_string()); + self.variables.remove(&format!("_LOWER_{}", name)); + } if is_readonly { self.variables .insert(format!("_READONLY_{}", name), "1".to_string()); @@ -5627,6 +5660,24 @@ impl Interpreter { fn set_variable(&mut self, name: String, value: String) { // Resolve nameref: if `name` is a nameref, assign to the target instead let resolved = self.resolve_nameref(&name).to_string(); + // Apply case conversion attributes (declare -l / declare -u) + let value = if self + .variables + .get(&format!("_LOWER_{}", resolved)) + .map(|v| v == "1") + .unwrap_or(false) + { + value.to_lowercase() + } else if self + .variables + .get(&format!("_UPPER_{}", resolved)) + .map(|v| v == "1") + .unwrap_or(false) + { + value.to_uppercase() + } else { + value + }; for frame in self.call_stack.iter_mut().rev() { if let std::collections::hash_map::Entry::Occupied(mut e) = frame.locals.entry(resolved.clone()) diff --git a/crates/bashkit/tests/spec_cases/bash/declare.test.sh b/crates/bashkit/tests/spec_cases/bash/declare.test.sh index e11b1993..bccb9d7c 100644 --- a/crates/bashkit/tests/spec_cases/bash/declare.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/declare.test.sh @@ -143,3 +143,68 @@ echo "$ref" first second ### end + +### declare_lowercase +# declare -l converts value to lowercase +declare -l x=HELLO +echo "$x" +### expect +hello +### end + +### declare_uppercase +# declare -u converts value to uppercase +declare -u x=hello +echo "$x" +### expect +HELLO +### end + +### declare_lowercase_subsequent +# declare -l applies to subsequent assignments +declare -l x +x=WORLD +echo "$x" +### expect +world +### end + +### declare_uppercase_subsequent +# declare -u applies to subsequent assignments +declare -u x +x=world +echo "$x" +### expect +WORLD +### end + +### declare_lowercase_mixed +# declare -l handles mixed case +declare -l x=HeLLo_WoRLd +echo "$x" +### expect +hello_world +### end + +### declare_uppercase_overrides_lowercase +# declare -u after -l overrides to uppercase +declare -l x=Hello +echo "$x" +declare -u x +x=Hello +echo "$x" +### expect +hello +HELLO +### end + +### declare_case_in_function +# declare -l works in functions +toupper() { + declare -u result="$1" + echo "$result" +} +toupper "hello world" +### expect +HELLO WORLD +### end diff --git a/specs/009-implementation-status.md b/specs/009-implementation-status.md index e3abbc56..e32e9726 100644 --- a/specs/009-implementation-status.md +++ b/specs/009-implementation-status.md @@ -103,17 +103,17 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See ## Spec Test Coverage -**Total spec test cases:** 1305 (1300 pass, 5 skip) +**Total spec test cases:** 1318 (1313 pass, 5 skip) | Category | Cases | In CI | Pass | Skip | Notes | |----------|-------|-------|------|------|-------| -| Bash (core) | 893 | Yes | 888 | 5 | `bash_spec_tests` in CI | +| Bash (core) | 900 | Yes | 895 | 5 | `bash_spec_tests` in CI | | AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g | | Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect | | Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E | | JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env | | Python | 57 | Yes | 57 | 0 | embedded Python (Monty) | -| **Total** | **1311** | **Yes** | **1306** | **5** | | +| **Total** | **1318** | **Yes** | **1313** | **5** | | ### Bash Spec Tests Breakdown @@ -160,7 +160,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See | variables.test.sh | 86 | includes special vars, prefix env, PIPESTATUS, trap EXIT, `${var@Q}`, `\` line continuation, PWD/HOME/USER/HOSTNAME/BASH_VERSION/SECONDS, `set -x` xtrace, `shopt` builtin, nullglob | | wc.test.sh | 35 | word count (5 skipped) | | type.test.sh | 15 | `type`, `which`, `hash` builtins | -| declare.test.sh | 16 | `declare`/`typeset`, `-i`, `-r`, `-x`, `-a`, `-p`, `-n` nameref | +| declare.test.sh | 23 | `declare`/`typeset`, `-i`, `-r`, `-x`, `-a`, `-p`, `-n` nameref, `-l`/`-u` case conversion | | ln.test.sh | 5 | `ln -s`, `-f`, symlink creation | | eval-bugs.test.sh | 4 | regression tests for eval/script bugs | | script-exec.test.sh | 10 | script execution by path, $PATH search, exit codes | @@ -183,7 +183,7 @@ Features that may be added in the future (not intentionally excluded): | ~~`getopts`~~ | ~~Medium~~ | Implemented: POSIX option parsing | | ~~`command` builtin~~ | ~~Medium~~ | Implemented: `-v`, `-V`, bypass functions | | ~~`type`/`which` builtins~~ | ~~Medium~~ | Implemented: `-t`, `-a`, `-p` flags | -| ~~`declare` builtin~~ | ~~Medium~~ | Implemented: `-i`, `-r`, `-x`, `-a`, `-p` | +| ~~`declare` builtin~~ | ~~Medium~~ | Implemented: `-i`, `-r`, `-x`, `-a`, `-p`, `-n`, `-l`, `-u` | | ~~`ln` builtin~~ | ~~Medium~~ | Implemented: symbolic links (`-s`, `-f`) | | `alias` | Low | Interactive feature | | History expansion | Out of scope | Interactive only | diff --git a/supply-chain/config.toml b/supply-chain/config.toml index fc2868e9..fbb2fec5 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -610,6 +610,10 @@ criteria = "safe-to-deploy" version = "0.3.89" criteria = "safe-to-deploy" +[[exemptions.js-sys]] +version = "0.3.90" +criteria = "safe-to-deploy" + [[exemptions.leb128fmt]] version = "0.1.0" criteria = "safe-to-deploy" @@ -1386,22 +1390,42 @@ criteria = "safe-to-run" version = "0.2.112" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen]] +version = "0.2.113" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-futures]] version = "0.4.62" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-futures]] +version = "0.4.63" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-macro]] version = "0.2.112" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-macro]] +version = "0.2.113" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-macro-support]] version = "0.2.112" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-macro-support]] +version = "0.2.113" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-shared]] version = "0.2.112" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-shared]] +version = "0.2.113" +criteria = "safe-to-deploy" + [[exemptions.wasm-encoder]] version = "0.244.0" criteria = "safe-to-deploy" @@ -1422,6 +1446,10 @@ criteria = "safe-to-deploy" version = "0.3.89" criteria = "safe-to-deploy" +[[exemptions.web-sys]] +version = "0.3.90" +criteria = "safe-to-deploy" + [[exemptions.web-time]] version = "1.1.0" criteria = "safe-to-deploy"