From bd7ec94ad864da5c0c76ca8812cc42be8eaf978f Mon Sep 17 00:00:00 2001 From: Apache Date: Tue, 15 Oct 2024 21:38:19 -0500 Subject: [PATCH] add echo --- Cargo.toml | 16 ++++++++++++++ src/echo.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/echo.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/echo.rs create mode 100644 tests/echo.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..00aafbe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "boreutils" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "echo" +path = "src/echo.rs" + +[dependencies] +aho-corasick = "1.1.3" +clap = { version = "4.5.20", features = ["derive"] } + +[dev-dependencies] +assert_cmd = "2.0.16" +predicates = "3.1.2" \ No newline at end of file diff --git a/src/echo.rs b/src/echo.rs new file mode 100644 index 0000000..fa37604 --- /dev/null +++ b/src/echo.rs @@ -0,0 +1,59 @@ +use std::io::Write; + +use clap::Parser; +use aho_corasick::AhoCorasick; + +/// Utility command to output information +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Removes the \n from the end of the output string + #[arg(short, long)] + noendl: bool, + + /// Parse escapes in the given string + #[arg(short, long)] + escape: bool, + + /// String to be output + #[arg()] + data: Option, +} + +fn main() { + let args = Args::parse(); + + let mut data = match args.data { + Some(s) => s, + None => String::new() + }; + + let mut noendl = args.noendl; + + let patterns = &["\\\\", "\\a", "\\b", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v"]; + let replacements = &["\\", "\x07", "\x08", "\x1B", "\x0C", "\n", "\r", "\t", "\x0B"]; + + if args.escape { + let ac = AhoCorasick::new(patterns).unwrap(); + data = ac.replace_all(data.as_str(), replacements); + let idx = data.find("\\c"); + if let Some(n) = idx { + data = (&data[0..n]).to_string(); + noendl = true; + } + } + + if !noendl { + data.push('\n'); + } + + let mut out = std::io::stdout(); + + match out.write_all(data.as_bytes()) { + Err(e) => { + eprintln!("Failed to write to stdout: {e}"); + std::process::exit(1); + }, + Ok(()) => () + }; +} \ No newline at end of file diff --git a/tests/echo.rs b/tests/echo.rs new file mode 100644 index 0000000..c1f4ed6 --- /dev/null +++ b/tests/echo.rs @@ -0,0 +1,53 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; +use std::process::Command; + +#[test] +fn literal_output() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("echo")?; + + cmd.arg("abc\\ndef"); + cmd.assert() + .success() + .stdout(predicate::str::contains("abc\\ndef\n")); + + Ok(()) +} + +#[test] +fn newline_escape() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("echo")?; + + cmd.arg("-e").arg("abc\\ndef"); + cmd.assert() + .success() + .stdout(predicate::str::contains("abc\ndef")); + + Ok(()) +} + +#[test] +fn cut_output_escape() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("echo")?; + + cmd.arg("-e").arg("abc\\cdef"); + cmd.assert() + .success() + .stdout(predicate::str::contains("abc")) + .stdout(predicate::str::contains("def\n").not()); + + Ok(()) +} + +#[test] +fn omit_endline() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("echo")?; + + cmd.arg("-n").arg("abcdef"); + cmd.assert() + .success() + .stdout(predicate::str::contains("abcdef")) + .stdout(predicate::str::contains("\n").not()); + + Ok(()) +} \ No newline at end of file