Skip to content

Commit

Permalink
feat: add customizable terminators (#15)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
t3hmrman authored Oct 30, 2023
1 parent 8ad2b01 commit a5af173
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 5 deletions.
10 changes: 9 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -25,6 +25,14 @@ pub struct Cli {
help = "Delimiter for array values"
)]
pub delimiter: Option<Delimiter>,

#[clap(
global = true,
long,
value_name = "CR | LF | CRLF | Nul | String",
help = "String terminator for the output that is returned"
)]
pub terminator: Option<Terminator>,
}

#[derive(Args, Clone)]
Expand Down
15 changes: 11 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
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<dyn Error>> {
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);
}
}

Ok(())
}

/// Generate output string that will be printed to the console
pub fn output(cli: cli::Cli) -> Result<String, Box<dyn Error>> {
let entry_point = match cli.entry.clone() {
Some(p) => p,
Expand All @@ -48,8 +51,9 @@ pub fn output(cli: cli::Cli) -> Result<String, Box<dyn Error>> {

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)?
Expand Down Expand Up @@ -225,9 +229,12 @@ pub fn output(cli: cli::Cli) -> Result<String, Box<dyn Error>> {
.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<PathBuf> {
let manifest = dir.join("Cargo.toml");

Expand Down
73 changes: 73 additions & 0 deletions src/terminator.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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<dyn std::error::Error>> {
for (input, result) in [
("Cr", Terminator::Cr),
("CrLf", Terminator::CrLf),
("Lf", Terminator::Lf),
("Nul", Terminator::Nul),
] {
assert_eq!(input.parse::<Terminator>()?, result);
assert_eq!(input.to_lowercase().parse::<Terminator>()?, result);
assert_eq!(input.to_uppercase().parse::<Terminator>()?, result);
}
Ok(())
}
}
74 changes: 74 additions & 0 deletions tests/terminator.rs
Original file line number Diff line number Diff line change
@@ -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]));
}

0 comments on commit a5af173

Please sign in to comment.