diff --git a/README.md b/README.md index b83528d..97d415c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This began as, and continues to be, a learning exercise to better understand the | b2sum | :white_check_mark: | | base32 | :white_check_mark: | | base64 | :white_check_mark: | -| basename | :white_large_square: | +| basename | :white_check_mark: | | cat | :white_large_square: | | chcon | :white_large_square: | | chgrp | :white_large_square: | diff --git a/basename/Cargo.toml b/basename/Cargo.toml index 43f28bc..4c134ef 100644 --- a/basename/Cargo.toml +++ b/basename/Cargo.toml @@ -1,10 +1,13 @@ [package] name = "basename" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] clap = { workspace = true } -shellexpand = "2.1.2" +coreutils = { workspace = true } +tabled = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } diff --git a/basename/src/main.rs b/basename/src/main.rs index 3468ca4..07461df 100644 --- a/basename/src/main.rs +++ b/basename/src/main.rs @@ -1,50 +1,91 @@ use std::path::MAIN_SEPARATOR; -use clap::Parser; - -/// A rust implementation of basename -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - /// Treat every argument as a string - #[arg(short)] - a: bool, - - /// The suffix to strip from the basename - #[arg(short)] - suffix: Option, - - /// The path(s) to return the directory of - args: Vec, -} +use clap::{Arg, ArgAction}; +use tabled::{builder::Builder, settings::Style}; + +use coreutils::{clap_args, clap_base_command}; + +clap_args!(Args { + flag multiple: bool, + maybe suffix: Option, + multi args: Vec, +}); + +/// usage: basename string [suffix] +/// basename [-a] [-s suffix] string [...] fn main() { - // usage: basename string [suffix] - // basename [-a] [-s suffix] string [...] - let args = Args::parse(); - let basenames = run(args); - for basename in &basenames { - println!("{}", basename); + let matches = clap_base_command() + .arg( + Arg::new("multiple") + .short('a') + .long("multiple") + .action(ArgAction::SetTrue) + .help("treat every argument as a string"), + ) + .arg( + Arg::new("suffix") + .short('s') + .help("the suffix to strip from the basename"), + ) + .arg( + Arg::new("args") + .action(ArgAction::Append) + .help("the path(s) to return the directory of"), + ) + .get_matches(); + + let args = Args::from_matches(&matches); + + let basenames = run(&args); + + if let Some(output) = &args.output { + match output.as_str() { + "table" => { + let mut builder = Builder::new(); + builder.push_column(["Basename"]); + + for basename in basenames { + builder.push_record([basename]); + } + let mut table = builder.build(); + println!("{}", table.with(Style::rounded())); + } + "json" => { + println!("{}", serde_json::to_string(&basenames).unwrap()); + } + "yaml" => { + println!("basenames:"); + for basename in basenames { + println!(" - basename: \"{}\"", basename); + } + } + _ => { + for basename in &basenames { + println!("{}", basename); + } + } + } } } -fn run(args: Args) -> Vec { +fn run(args: &Args) -> Vec { let mut basenames: Vec = Vec::new(); - if !args.a && args.suffix.is_none() && args.args.len() == 2 { + if !args.multiple && args.suffix.is_none() && args.args.len() == 2 { // Got a string and suffix - let path = shellexpand::tilde(&args.args[0]); + let path = &args.args[0]; let suffix = &args.args[1]; // Get the basename minus the suffix - let bms = path.strip_suffix(suffix).unwrap_or(&path); + let basename = get_basename(path); + let bms = basename.strip_suffix(suffix).unwrap_or(&basename); basenames.push(bms.to_string()); - } else if (args.a || args.args.len() == 1) || args.suffix.is_some() { + } else if (args.multiple || args.args.len() == 1) || args.suffix.is_some() { // treat all args as strings for arg in &args.args { - let path = shellexpand::tilde(&arg); - let basename = get_basename(&path); + let basename = get_basename(arg); if let Some(suffix) = &args.suffix { let bms = basename.strip_suffix(suffix).unwrap_or(&basename); @@ -71,7 +112,7 @@ fn get_basename(path: &str) -> String { basename.to_string() } } - None => '.'.to_string(), + None => path.to_string(), } } @@ -94,12 +135,13 @@ mod tests { paths.push(String::from("/etc/motd")); let args = Args { - a: false, + multiple: false, args: paths, + output: Some(String::from("plain")), suffix: None, }; - let basenames = run(args); + let basenames = run(&args); assert_eq!(basenames.len(), 1); assert_eq!(basenames[0], "motd"); } @@ -112,12 +154,13 @@ mod tests { paths.push(String::from("/etc/issue")); let args = Args { - a: true, + multiple: true, args: paths, + output: Some(String::from("plain")), suffix: None, }; - let basenames = run(args); + let basenames = run(&args); assert_eq!(basenames.len(), 2); assert_eq!(basenames[0], "motd"); assert_eq!(basenames[1], "issue"); @@ -130,12 +173,13 @@ mod tests { paths.push(String::from("/etc/init.d")); let args = Args { - a: false, + multiple: false, args: paths, + output: Some(String::from("plain")), suffix: Some(String::from(".d")), }; - let basenames = run(args); + let basenames = run(&args); assert_eq!(basenames.len(), 1); assert_eq!(basenames[0], "init"); } @@ -148,12 +192,13 @@ mod tests { paths.push(String::from("/etc/locate.rc")); let args = Args { - a: false, + multiple: false, args: paths, + output: Some(String::from("plain")), suffix: Some(String::from(".rc")), }; - let basenames = run(args); + let basenames = run(&args); assert_eq!(basenames.len(), 2); assert_eq!(basenames[0], "mail"); assert_eq!(basenames[1], "locate"); @@ -167,12 +212,13 @@ mod tests { paths.push(String::from("/etc/locate.rc")); let args = Args { - a: true, + multiple: true, args: paths, + output: Some(String::from("plain")), suffix: Some(String::from(".rc")), }; - let basenames = run(args); + let basenames = run(&args); assert_eq!(basenames.len(), 2); assert_eq!(basenames[0], "mail"); assert_eq!(basenames[1], "locate");