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
57 changes: 54 additions & 3 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
_ => {}
}
}
Expand Down Expand Up @@ -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());
Expand All @@ -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());
Expand Down Expand Up @@ -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())
Expand Down
65 changes: 65 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/declare.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 5 additions & 5 deletions specs/009-implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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}`, `\<newline>` 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 |
Expand All @@ -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 |
Expand Down
28 changes: 28 additions & 0 deletions supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
Loading