This commit is contained in:
Apache 2024-10-15 21:38:19 -05:00
parent 568e9f0644
commit bd7ec94ad8
Signed by: apache
GPG key ID: 78BA80EB40E43123
3 changed files with 128 additions and 0 deletions

16
Cargo.toml Normal file
View file

@ -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"

59
src/echo.rs Normal file
View file

@ -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<String>,
}
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(()) => ()
};
}

53
tests/echo.rs Normal file
View file

@ -0,0 +1,53 @@
use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::process::Command;
#[test]
fn literal_output() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}