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