Skip to content

Commit a17d25a

Browse files
chaliyclaude
andauthored
feat: associative arrays, chown/kill builtins, array slicing tests (#236)
## Summary - Implement associative arrays (`declare -A`): key-value access, iteration with `${!m[@]}`, `${#m[@]}`, unset element, variable keys - Add `chown` builtin (no-op in VFS, validates file existence, `-R` flag) - Add `kill` builtin (no-op in VFS, `-l` lists signals) - Handle `unset` for array elements at interpreter level (both indexed and assoc) - Fix `expand_word_to_fields` to produce separate fields for assoc array `@`/`*` and `ArrayIndices` - Add 27 new tests: 12 assoc-arrays, 8 array-slicing, 7 chown-kill - Update specs: 95 builtins, 711 bash tests (705 pass, 6 skip) ## Test plan - [x] All 27 new tests pass - [x] `cargo test --all-features` passes (all 14 test suites) - [x] `cargo clippy --all-targets --all-features -- -D warnings` clean - [x] `cargo fmt --check` clean - [x] Bash comparison tests pass (no mismatches with real bash) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a2717bd commit a17d25a

File tree

10 files changed

+594
-41
lines changed

10 files changed

+594
-41
lines changed

crates/bashkit/src/builtins/fileops.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,92 @@ impl Builtin for Ln {
556556
}
557557
}
558558

559+
/// The chown builtin - change file ownership (no-op in VFS).
560+
///
561+
/// Usage: chown [-R] OWNER[:GROUP] FILE...
562+
///
563+
/// In the virtual filesystem there are no real UIDs/GIDs, so chown is a no-op
564+
/// that simply validates arguments and succeeds silently.
565+
pub struct Chown;
566+
567+
#[async_trait]
568+
impl Builtin for Chown {
569+
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
570+
let mut recursive = false;
571+
let mut positional: Vec<&str> = Vec::new();
572+
573+
for arg in ctx.args {
574+
match arg.as_str() {
575+
"-R" | "--recursive" => recursive = true,
576+
_ if arg.starts_with('-') => {} // ignore other flags
577+
_ => positional.push(arg),
578+
}
579+
}
580+
let _ = recursive; // accepted but irrelevant in VFS
581+
582+
if positional.len() < 2 {
583+
return Ok(ExecResult::err("chown: missing operand\n".to_string(), 1));
584+
}
585+
586+
// Validate that target files exist
587+
let _owner = positional[0]; // accepted but not applied
588+
for file in &positional[1..] {
589+
let path = resolve_path(ctx.cwd, file);
590+
if !ctx.fs.exists(&path).await.unwrap_or(false) {
591+
return Ok(ExecResult::err(
592+
format!(
593+
"chown: cannot access '{}': No such file or directory\n",
594+
file
595+
),
596+
1,
597+
));
598+
}
599+
}
600+
601+
Ok(ExecResult::ok(String::new()))
602+
}
603+
}
604+
605+
/// The kill builtin - send signal to process (no-op in VFS).
606+
///
607+
/// Usage: kill [-s SIGNAL] [-SIGNAL] PID...
608+
///
609+
/// Since there are no real processes in the virtual environment, kill is a no-op
610+
/// that accepts the command syntax for compatibility.
611+
pub struct Kill;
612+
613+
#[async_trait]
614+
impl Builtin for Kill {
615+
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
616+
let mut pids: Vec<&str> = Vec::new();
617+
618+
for arg in ctx.args {
619+
if arg == "-l" || arg == "-L" {
620+
// List signal names
621+
return Ok(ExecResult::ok(
622+
"HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM\n"
623+
.to_string(),
624+
));
625+
}
626+
if arg.starts_with('-') {
627+
continue; // skip signal spec
628+
}
629+
pids.push(arg);
630+
}
631+
632+
if pids.is_empty() {
633+
return Ok(ExecResult::err(
634+
"kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ...\n"
635+
.to_string(),
636+
2,
637+
));
638+
}
639+
640+
// In VFS, no real processes exist — just succeed silently
641+
Ok(ExecResult::ok(String::new()))
642+
}
643+
}
644+
559645
#[cfg(test)]
560646
#[allow(clippy::unwrap_used)]
561647
mod tests {

crates/bashkit/src/builtins/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub use disk::{Df, Du};
8181
pub use echo::Echo;
8282
pub use environ::{Env, History, Printenv};
8383
pub use export::Export;
84-
pub use fileops::{Chmod, Cp, Ln, Mkdir, Mv, Rm, Touch};
84+
pub use fileops::{Chmod, Chown, Cp, Kill, Ln, Mkdir, Mv, Rm, Touch};
8585
pub use flow::{Break, Colon, Continue, Exit, False, Return, True};
8686
pub use grep::Grep;
8787
pub use headtail::{Head, Tail};

0 commit comments

Comments
 (0)