From a5af17360a8729af4eea4216900e0b4a66d5fcdf Mon Sep 17 00:00:00 2001 From: "Victor Adossi (\"vados\")" Date: Mon, 30 Oct 2023 19:11:40 +0900 Subject: [PATCH] feat: add customizable terminators (#15) While using `cargo-get` in scripts, it is often useful to be able to change the ending character of the output -- ensuring that a newline does *not* get printed, adding a NUL terminator, etc. This commit adds an option `--terminator` which changes the terminator that is printed after the requested output from `cargo-get`. Signed-off-by: vados --- src/cli.rs | 10 +++++- src/main.rs | 15 ++++++--- src/terminator.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++ tests/terminator.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/terminator.rs create mode 100644 tests/terminator.rs diff --git a/src/cli.rs b/src/cli.rs index ca74123..c7fa088 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use std::{error::Error, path::PathBuf}; use clap::{Args, Parser, Subcommand}; -use crate::delimiter::Delimiter; +use crate::{delimiter::Delimiter, terminator::Terminator}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -25,6 +25,14 @@ pub struct Cli { help = "Delimiter for array values" )] pub delimiter: Option, + + #[clap( + global = true, + long, + value_name = "CR | LF | CRLF | Nul | String", + help = "String terminator for the output that is returned" + )] + pub terminator: Option, } #[derive(Args, Clone)] diff --git a/src/main.rs b/src/main.rs index 5ceb26e..66d36f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,28 @@ mod cli; mod delimiter; mod error; +mod terminator; use cargo_toml::Manifest; use clap::Parser; use delimiter::Delimiter; use error::NotSpecified; use std::{error::Error, path::PathBuf}; +use terminator::Terminator; fn main() -> Result<(), Box> { let mut args: Vec<_> = std::env::args().collect(); - if args.get(1) == Some(&"get".to_owned()) { + if let Some("get") = args.get(1).map(String::as_ref) { args.remove(1); } let cli = cli::Cli::parse_from(args); match output(cli) { - Ok(out) => println!("{}", out), + Ok(out) => print!("{out}"), Err(err) => { - eprintln!("Error: {}", err); + eprintln!("Error: {err}"); std::process::exit(1); } } @@ -28,6 +30,7 @@ fn main() -> Result<(), Box> { Ok(()) } +/// Generate output string that will be printed to the console pub fn output(cli: cli::Cli) -> Result> { let entry_point = match cli.entry.clone() { Some(p) => p, @@ -48,8 +51,9 @@ pub fn output(cli: cli::Cli) -> Result> { let delimiter: Delimiter = cli.delimiter.unwrap_or_default(); let delim_string = delimiter.to_string(); + let terminator: Terminator = cli.terminator.unwrap_or_default(); - let output = match cli.command { + let mut output = match cli.command { cli::Command::PackageVersion { inner } => { let v: semver::Version = package()?.version().parse()?; inner.match_version(v, &delimiter)? @@ -225,9 +229,12 @@ pub fn output(cli: cli::Cli) -> Result> { .to_string(), }; + output.push_str(terminator.to_string().as_ref()); + Ok(output) } +/// Search the given directory for Cargo.toml, recursively searching upwards fn search_manifest_path(dir: &std::path::Path) -> Option { let manifest = dir.join("Cargo.toml"); diff --git a/src/terminator.rs b/src/terminator.rs new file mode 100644 index 0000000..e914902 --- /dev/null +++ b/src/terminator.rs @@ -0,0 +1,73 @@ +use std::fmt; + +#[derive(Debug, PartialEq, Clone, Default)] +pub enum Terminator { + Cr, + CrLf, + #[default] + Lf, + Nul, + String(String), +} + +impl fmt::Display for Terminator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + Self::Cr => "\r", + Self::CrLf => "\r\n", + Self::Lf => "\n", + Self::Nul => "\0", + Self::String(s) => s, + }) + } +} + +impl std::str::FromStr for Terminator { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> std::result::Result { + match s.to_lowercase().as_ref() { + "cr" => Ok(Self::Cr), + "crlf" => Ok(Self::CrLf), + "lf" => Ok(Self::Lf), + "nul" => Ok(Self::Nul), + _ => Ok(Self::String(s.to_owned())), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn display_ok() { + let res = format!( + "{} {} {} {} {}", + Terminator::Cr, + Terminator::CrLf, + Terminator::Lf, + Terminator::Nul, + Terminator::String("abc!@#$%^&*()".to_owned()) + ); + + let expected = "\r \r\n \n \0 abc!@#$%^&*()".to_owned(); + + assert_eq!(res, expected); + } + + #[test] + fn parse_ok() -> Result<(), Box> { + for (input, result) in [ + ("Cr", Terminator::Cr), + ("CrLf", Terminator::CrLf), + ("Lf", Terminator::Lf), + ("Nul", Terminator::Nul), + ] { + assert_eq!(input.parse::()?, result); + assert_eq!(input.to_lowercase().parse::()?, result); + assert_eq!(input.to_uppercase().parse::()?, result); + } + Ok(()) + } +} diff --git a/tests/terminator.rs b/tests/terminator.rs new file mode 100644 index 0000000..072d2f5 --- /dev/null +++ b/tests/terminator.rs @@ -0,0 +1,74 @@ +use assert_cmd::Command; +use predicates::prelude::*; + +#[test] +fn pkg_name_with_custom_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=.exe").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name.exe" as &[u8])); +} + +#[test] +fn pkg_name_with_cr_as_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=cr").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name\r" as &[u8])); +} + +#[test] +fn pkg_name_with_lf_as_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=lf").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name\n" as &[u8])); +} + +#[test] +fn pkg_name_with_crlf_as_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=crlf").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name\r\n" as &[u8])); +} + +#[test] +fn pkg_name_with_semicolon_as_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=;").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name;" as &[u8])); +} + +#[test] +fn pkg_name_with_nul_as_terminator() { + let mut cmd = Command::cargo_bin("cargo-get").unwrap(); + let p = std::fs::canonicalize("tests/data/toml_02").unwrap(); + cmd.current_dir(p); + + let assert = cmd.arg("package.name").arg("--terminator=nul").assert(); + assert + .success() + .stdout(predicate::eq(b"test-name\0" as &[u8])); +}