add echo
This commit is contained in:
parent
568e9f0644
commit
bd7ec94ad8
3 changed files with 128 additions and 0 deletions
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
59
src/echo.rs
Normal 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
53
tests/echo.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in a new issue