use crate::{bounds::UserBoundsList, options::Trim};
use anyhow::Result;
use std::{ffi::OsString, path::PathBuf};
#[derive(Debug)]
pub struct Args {
pub cut_by_fields: Option<UserBoundsList>,
pub cut_by_characters: Option<UserBoundsList>,
pub cut_by_bytes: Option<UserBoundsList>,
pub cut_by_lines: Option<UserBoundsList>,
pub delimiter: Option<Vec<u8>>,
pub replace_delimiter: Option<Vec<u8>>,
pub trim: Option<Trim>,
pub fixed_memory_kb: Option<usize>,
pub fallback_oob: Option<Vec<u8>>,
pub path: Option<PathBuf>,
pub regex: Option<String>,
pub complement: bool,
pub compress_delimiter: bool,
pub greedy_delimiter: bool,
pub join_yes: bool,
pub join_no: bool,
pub json: bool,
pub mmap_no: bool,
pub only_delimited: bool,
pub zero_terminated: bool,
}
pub fn parse_args(args: Vec<OsString>) -> Result<Args, ArgsParseError> {
let mut pargs = pico_args::Arguments::from_vec(args);
if pargs.contains(["-h", "--help"]) {
return Err(ArgsParseError::HelpRequested);
}
if pargs.contains(["-V", "--version"]) {
return Err(ArgsParseError::VersionRequested);
}
let cut_by_fields: Option<UserBoundsList> = pargs.opt_value_from_str(["-f", "--fields"])?;
let cut_by_characters: Option<UserBoundsList> =
pargs.opt_value_from_str(["-c", "--characters"])?;
let cut_by_bytes: Option<UserBoundsList> = pargs.opt_value_from_str(["-b", "--bytes"])?;
let cut_by_lines: Option<UserBoundsList> = pargs.opt_value_from_str(["-l", "--lines"])?;
let delimiter = pargs
.opt_value_from_str(["-d", "--delimiter"])?
.map(|x: String| x.into());
let greedy_delimiter = pargs.contains(["-g", "--greedy-delimiter"]);
let tmp_replace_delimiter: Option<String> =
pargs.opt_value_from_str(["-r", "--replace-delimiter"])?;
let replace_delimiter: Option<Vec<u8>> = tmp_replace_delimiter.map(|x| x.into());
let fixed_memory_kb: Option<usize> = pargs.opt_value_from_str(["-M", "--fixed-memory"])?;
let has_json = pargs.contains("--json");
let join_yes = pargs.contains(["-j", "--join"]);
let join_no = pargs.contains("--no-join");
#[cfg(not(feature = "regex"))]
let regex = None;
#[cfg(feature = "regex")]
let regex = pargs.opt_value_from_str::<_, String>(["-e", "--regex"])?;
let complement = pargs.contains(["-m", "--complement"]);
let only_delimited = pargs.contains(["-s", "--only-delimited"]);
let compress_delimiter = pargs.contains(["-p", "--compress-delimiter"]);
let trim: Option<Trim> = pargs.opt_value_from_str(["-t", "--trim"])?;
let zero_terminated = pargs.contains(["-z", "--zero-terminated"]);
let fallback_oob: Option<Vec<u8>> = pargs
.opt_value_from_str("--fallback-oob")
.or_else(|e| match e {
pico_args::Error::OptionWithoutAValue(_) => {
pargs.contains("--fallback-oob=");
Ok(Some("".into()))
}
_ => Err(e),
})?
.map(|x: String| x.into());
let mmap_no = pargs.contains("--no-mmap");
let remaining = pargs.finish();
if remaining.len() > 1 {
eprintln!("tuc: unexpected arguments: {remaining:?}");
eprintln!("Try 'tuc --help' for more information.");
std::process::exit(1);
}
let path = remaining
.first()
.and_then(|x| x.to_str())
.map(PathBuf::from);
if let Some(some_path) = path.as_ref() {
if !some_path.exists() {
if some_path.as_path().to_string_lossy().starts_with("-") {
eprintln!("tuc: unexpected arguments: {remaining:?}");
eprintln!("Try 'tuc --help' for more information.");
std::process::exit(1);
}
eprintln!("tuc: runtime error. The file {some_path:?} does not exist");
std::process::exit(1);
}
if !some_path.is_file() {
eprintln!("tuc: runtime error. The path {some_path:?} is not a file");
std::process::exit(1);
}
}
let args = Args {
cut_by_fields,
cut_by_characters,
cut_by_bytes,
cut_by_lines,
complement,
only_delimited,
greedy_delimiter,
compress_delimiter,
zero_terminated,
join_yes,
join_no,
json: has_json,
fixed_memory_kb,
delimiter,
replace_delimiter,
trim,
fallback_oob,
regex,
path,
mmap_no,
};
Ok(args)
}
#[derive(Debug)]
pub enum ArgsParseError {
PicoArgs(pico_args::Error),
HelpRequested,
VersionRequested,
}
impl std::fmt::Display for ArgsParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ArgsParseError::PicoArgs(e) => write!(f, "Argument parsing error: {}", e),
ArgsParseError::HelpRequested => write!(f, "Help requested"),
ArgsParseError::VersionRequested => write!(f, "Version requested"),
}
}
}
impl std::error::Error for ArgsParseError {}
impl PartialEq for ArgsParseError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ArgsParseError::HelpRequested, ArgsParseError::HelpRequested) => true,
(ArgsParseError::VersionRequested, ArgsParseError::VersionRequested) => true,
(ArgsParseError::PicoArgs(_), ArgsParseError::PicoArgs(_)) => {
self.to_string() == other.to_string()
}
_ => false,
}
}
}
impl From<pico_args::Error> for ArgsParseError {
fn from(error: pico_args::Error) -> Self {
ArgsParseError::PicoArgs(error)
}
}
#[cfg(test)]
impl std::str::FromStr for Args {
type Err = ArgsParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let args_os = shlex::split(s)
.unwrap_or_else(|| panic!("Malformed input: {}", s))
.into_iter()
.map(std::ffi::OsString::from)
.collect();
parse_args(args_os)
}
}