use ::clap::builder::BoolishValueParser;
use ::clap::builder::TypedValueParser;
use ::clap::Parser;
use ::std::str::FromStr;
use ::clap::ArgAction;
#[derive(Parser, Debug)]
#[command(
name = "namesafe",
about = "Convert each line to a string that is safe for names (no whitespace or special characters, not too long)."
)]
pub struct NamesafeArgs {
#[arg(action = ArgAction::SetTrue, value_parser = BoolishValueParser::new().map(Charset::from_allow), short = 'u', long = "allow-unicode")]
pub charset: Charset,
#[arg(
short = 'x',
long = "hash",
default_value = "changed", //TODO @mverleg: not sure why Default impl doesn't work
)]
pub hash_policy: HashPolicy,
#[arg(short = 'l', long = "max-length", default_value = "32")]
pub max_length: u32,
#[arg(short = 'e', long = "extension")]
pub keep_extension: bool,
#[arg(short = 'E', long = "keep-tail")]
pub keep_tail: bool,
#[arg(short = '0', long = "allow-empty", conflicts_with = "input")]
pub allow_empty: bool,
#[arg(short = 'c', long = "lowercase")]
pub lowercase: bool,
#[arg(short = 'C', long)]
pub allow_outer_connector: bool,
#[arg(short = 'i', long)]
pub input: Option<String>,
#[arg(short = '1', long = "single", conflicts_with = "input")]
pub single_line: bool,
#[arg(short = 'S', long)]
pub separator: Option<char>,
}
#[test]
fn test_cli_args() {
NamesafeArgs::try_parse_from(&["cmd", "-l", "16", "-x", "a", "--keep-tail"]).unwrap();
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum HashPolicy {
Always,
#[default]
Changed,
TooLong,
Never,
}
impl HashPolicy {
pub fn should_hash(&self, was_changed: bool, was_too_long: bool) -> bool {
match self {
HashPolicy::Always => true,
HashPolicy::Changed => was_changed || was_too_long,
HashPolicy::TooLong => was_too_long,
HashPolicy::Never => false,
}
}
}
impl FromStr for HashPolicy {
type Err = String;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Ok(match text.to_lowercase().as_str() {
"always" | "a" => HashPolicy::Always,
"changed" | "c" => HashPolicy::Changed,
"too-long" | "long" | "l" => HashPolicy::TooLong,
"never" | "n" => HashPolicy::Never,
other => return Err(format!("unknown hash policy: {}", other)),
})
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Charset {
AllowUnicode,
#[default]
AsciiOnly,
}
impl Charset {
fn from_allow(allow_unicode: bool) -> Self {
if allow_unicode {
Charset::AllowUnicode
} else {
Charset::AsciiOnly
}
}
pub fn is_allowed(&self, symbol: char, separator: Option<char>) -> bool {
let is_sep = match separator {
None => symbol == '-' || symbol == '_',
Some(sep) => symbol == sep,
};
if is_sep {
return true;
}
match self {
Charset::AllowUnicode => symbol.is_alphanumeric(),
Charset::AsciiOnly => {
('a'..='z').contains(&symbol)
|| ('A'..='Z').contains(&symbol)
|| ('0'..='9').contains(&symbol)
}
}
}
}
impl Default for NamesafeArgs {
fn default() -> Self {
NamesafeArgs {
charset: Default::default(),
hash_policy: Default::default(),
max_length: 32,
keep_extension: false,
keep_tail: false,
allow_empty: false,
lowercase: false,
allow_outer_connector: false,
input: None,
single_line: false,
separator: None,
}
}
}