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
43 changes: 25 additions & 18 deletions crates/bashkit/src/builtins/cuttr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ impl Builtin for Cut {
let mut mode = CutMode::Fields;
let mut complement = false;
let mut only_delimited = false;
let mut zero_terminated = false;
let mut output_delimiter: Option<String> = None;
let mut files = Vec::new();

Expand Down Expand Up @@ -68,6 +69,8 @@ impl Builtin for Cut {
mode = CutMode::Chars;
} else if arg == "-s" {
only_delimited = true;
} else if arg == "-z" {
zero_terminated = true;
} else if arg == "--complement" {
complement = true;
} else if let Some(od) = arg.strip_prefix("--output-delimiter=") {
Expand Down Expand Up @@ -142,26 +145,30 @@ impl Builtin for Cut {
};

let mut output = String::new();
let line_sep = if zero_terminated { '\0' } else { '\n' };
let out_sep = if zero_terminated { "\0" } else { "\n" };

let process_input = |text: &str, output: &mut String| {
for line in text.split(line_sep) {
if line.is_empty() {
continue;
}
if let Some(result) = process_line(line) {
output.push_str(&result);
output.push_str(out_sep);
}
}
};

if files.is_empty() || files.iter().all(|f| f.as_str() == "-") {
if let Some(stdin) = ctx.stdin {
for line in stdin.lines() {
if let Some(result) = process_line(line) {
output.push_str(&result);
output.push('\n');
}
}
process_input(stdin, &mut output);
}
} else {
for file in &files {
if file.as_str() == "-" {
if let Some(stdin) = ctx.stdin {
for line in stdin.lines() {
if let Some(result) = process_line(line) {
output.push_str(&result);
output.push('\n');
}
}
process_input(stdin, &mut output);
}
continue;
}
Expand All @@ -175,12 +182,7 @@ impl Builtin for Cut {
match ctx.fs.read_file(&path).await {
Ok(content) => {
let text = String::from_utf8_lossy(&content);
for line in text.lines() {
if let Some(result) = process_line(line) {
output.push_str(&result);
output.push('\n');
}
}
process_input(&text, &mut output);
}
Err(e) => {
return Ok(ExecResult::err(format!("cut: {}: {}\n", file, e), 1));
Expand Down Expand Up @@ -464,6 +466,11 @@ fn expand_char_set(spec: &str) -> Vec<char> {
i += 2;
continue;
}
b'0' => {
chars.push('\0');
i += 2;
continue;
}
b'\\' => {
chars.push('\\');
i += 2;
Expand Down
77 changes: 77 additions & 0 deletions crates/bashkit/src/builtins/fileops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,83 @@ impl Builtin for Chmod {
}
}

/// The ln builtin - create links.
///
/// Usage: ln [-s] [-f] TARGET LINK_NAME
/// ln [-s] [-f] TARGET... DIRECTORY
///
/// Options:
/// -s Create symbolic link (default in Bashkit; hard links not supported in VFS)
/// -f Force: remove existing destination files
///
/// Note: In Bashkit's virtual filesystem, all links are symbolic.
/// Hard links are not supported; `-s` is implied.
pub struct Ln;

#[async_trait]
impl Builtin for Ln {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
let mut force = false;
let mut files: Vec<&str> = Vec::new();

for arg in ctx.args.iter() {
if arg.starts_with('-') && arg.len() > 1 {
for c in arg[1..].chars() {
match c {
's' => {} // symbolic — always symbolic in VFS
'f' => force = true,
_ => {
return Ok(ExecResult::err(
format!("ln: invalid option -- '{}'\n", c),
1,
));
}
}
}
} else {
files.push(arg);
}
}

if files.len() < 2 {
return Ok(ExecResult::err("ln: missing file operand\n".to_string(), 1));
}

let target = files[0];
let link_name = files[1];
let link_path = resolve_path(ctx.cwd, link_name);

// If link already exists
if ctx.fs.exists(&link_path).await.unwrap_or(false) {
if force {
// Remove existing
let _ = ctx.fs.remove(&link_path, false).await;
} else {
return Ok(ExecResult::err(
format!(
"ln: failed to create symbolic link '{}': File exists\n",
link_name
),
1,
));
}
}

let target_path = Path::new(target);
if let Err(e) = ctx.fs.symlink(target_path, &link_path).await {
return Ok(ExecResult::err(
format!(
"ln: failed to create symbolic link '{}': {}\n",
link_name, e
),
1,
));
}

Ok(ExecResult::ok(String::new()))
}
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub use disk::{Df, Du};
pub use echo::Echo;
pub use environ::{Env, History, Printenv};
pub use export::Export;
pub use fileops::{Chmod, Cp, Mkdir, Mv, Rm, Touch};
pub use fileops::{Chmod, Cp, Ln, Mkdir, Mv, Rm, Touch};
pub use flow::{Break, Colon, Continue, Exit, False, Return, True};
pub use grep::Grep;
pub use headtail::{Head, Tail};
Expand Down
21 changes: 15 additions & 6 deletions crates/bashkit/src/builtins/sortuniq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Builtin for Sort {
let mut delimiter: Option<char> = None;
let mut key_field: Option<usize> = None;
let mut output_file: Option<String> = None;
let mut zero_terminated = false;
let mut files = Vec::new();

let mut i = 0;
Expand Down Expand Up @@ -147,6 +148,7 @@ impl Builtin for Sort {
'c' | 'C' => check_sorted = true,
'h' => human_numeric = true,
'M' => month_sort = true,
'z' => zero_terminated = true,
_ => {}
}
}
Expand All @@ -159,10 +161,14 @@ impl Builtin for Sort {
// Collect all input
let mut all_lines = Vec::new();

let line_sep = if zero_terminated { '\0' } else { '\n' };

if files.is_empty() {
if let Some(stdin) = ctx.stdin {
for line in stdin.lines() {
all_lines.push(line.to_string());
for line in stdin.split(line_sep) {
if !line.is_empty() {
all_lines.push(line.to_string());
}
}
}
} else {
Expand All @@ -176,8 +182,10 @@ impl Builtin for Sort {
match ctx.fs.read_file(&path).await {
Ok(content) => {
let text = String::from_utf8_lossy(&content);
for line in text.lines() {
all_lines.push(line.to_string());
for line in text.split(line_sep) {
if !line.is_empty() {
all_lines.push(line.to_string());
}
}
}
Err(e) => {
Expand Down Expand Up @@ -266,9 +274,10 @@ impl Builtin for Sort {
all_lines.dedup();
}

let mut output = all_lines.join("\n");
let sep = if zero_terminated { "\0" } else { "\n" };
let mut output = all_lines.join(sep);
if !output.is_empty() {
output.push('\n');
output.push_str(sep);
}

// Write to output file if -o specified
Expand Down
Loading
Loading