use super::*; use std::{ num::{ NonZeroUsize, ParseIntError, }, path::{ PathBuf, Path, }, borrow::Cow, }; use lazy_static::lazy_static; #[inline] pub fn program_name() -> &'static str { lazy_static!{ static ref PROG: &'static str = Box::leak(std::env::args().next().unwrap().into_boxed_str()); } &PROG[..] } mod extra { use lazy_static::lazy_static; use std::fmt::{self, Write}; #[inline] fn extra_args(#[allow(unused_variables)] output: &mut W) -> fmt::Result { #[cfg(feature="progress")] writeln!(output, " --no-progress Do not display progress bar")?; #[cfg(feature="colour")] writeln!(output, " --no-colour Do not display terminal colours")?; #[cfg(feature="colour")] writeln!(output, " --colour Always display terminal colour, even if env flags tell us not to")?; Ok(()) } #[inline] fn extra_mode(#[allow(unused_variables)] output: &mut W) -> fmt::Result { //TODO: For extra modes `--install` Ok(()) } struct ExtraArgs { args: &'static str, modes: &'static str, } fn work(from: F) -> &'static str where F: FnOnce() -> Result { match from() { Ok(output) => Box::leak(output.into_boxed_str()), Err(err) => { eprintln!("Failed to write usage message: {}", err); "" }, } } lazy_static! { static ref EXTRA: ExtraArgs = { ExtraArgs{ args: { work(|| { let mut output = String::new(); extra_args(&mut output)?; Ok(output) }) }, modes: { work(|| { let mut output = String::new(); extra_mode(&mut output)?; Ok(output) }) }, } }; } pub fn args() -> &'static str { &EXTRA.args[..] } pub fn mode() -> &'static str { &EXTRA.modes[..] } } #[allow(unused_variables)] mod feature { use cfg_if::cfg_if; #[macro_export] macro_rules! check { (on $name:literal, $desc:expr) => { cfg_if!{ if #[cfg(feature = $name)] { feature::on($name, true, $desc); } else { feature::off($name, false, $desc); } } }; (off $name:literal, $desc:expr) => { cfg_if!{ if #[cfg(feature = $name)] { feature::on($name, false, $desc); } else { feature::off($name, true, $desc); } } }; (feature $name:meta, $desc:expr) => { cfg_if! { if #[cfg($name)] { feature::on(stringify!($name), false, $desc); } else {} } } } pub fn on(name: impl AsRef, default: bool, desc: impl AsRef) { cfg_if! { if #[cfg(feature="colour")] { use recolored::Colorize; if default { println!(" {} {}", format!("+{}", name.as_ref()).red(), desc.as_ref().bright_black()); } else { println!(" {} {}", format!("+{}", name.as_ref()).bright_red(), desc.as_ref()); } } else { println!(" +{} {}", name.as_ref(), desc.as_ref()) } } } pub fn off(name: impl AsRef, default: bool, desc: impl AsRef) { cfg_if! { if #[cfg(feature="colour")] { use recolored::Colorize; if default { println!(" {} {}", format!("-{}", name.as_ref()).blue(), desc.as_ref().bright_black()); } else { println!(" {} {}", format!("-{}", name.as_ref()).bright_blue(), desc.as_ref()); } } else { println!(" -{} {}", name.as_ref(), desc.as_ref()) } } } } fn comp_flags() { check!(feature nightly, "Compiled with Rust nightly optimisations and functionality"); check!(on "splash", "Show splash-screen"); check!(on "colour", "Enable coloured output"); check!(on "progress", "Enable progress bar"); check!(on "collect_err", "Collect the output of children's stderr instead of printing immediately"); check!(off "threads", "Enable threaded scheduler (usually not needed)"); check!(off "checked_pass", "Check the arguments passed with `--passthrough` to leanify. By default they are passed as is"); } pub fn usage() -> ! { #[cfg(feature="splash")] splash::print(); println!(r"Usage: {prog} [OPTIONS] [-] Usage: {prog} --help {modes} OPTIONS: - Stop parsing args here. --max-children Max subprocesses allowed to live at once. Infinite by default. -m Limit subprocesses to number of CPU cores. --recursive Recurse up to `` times. Must be at least `1`. Default is off. -r Resurse infinitely. -p, --passthrough Pass these args to the leanify subprocesses. See for details. -P Same as `--passthrough`, except do not check the passed args (does nothing without the `checked_pass` feature enabled. Additionally, these leanify flags can be passed directly: -i, --iteration Number of iterations -d, --max_depth Max depth of leanify's archive compression -f, --fastmode Fast mode, no recompression -q, --quiet No output to stdout -v, --verbose Verbose output --keep-exif Do not remove Exif {extra} ENVIRONMENT VARS: LEANIFY= Path to leanify executable, defaults to looking in `PATH' if set. ", prog = program_name(), extra = extra::args(), modes= extra::mode()); println!("Compiled with:"); comp_flags(); std::process::exit(1) } #[derive(Debug)] pub enum Error { #[cfg(nightly)] BadNumber(std::num::IntErrorKind), #[cfg(not(nightly))] BadNumber(()), NoExist(PathBuf), Walking(dir::Error), Other(Cow<'static, str>), UnknownArg(String), NoFiles, } impl std::error::Error for Error{} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { //write!(f, "arg parsing failed: ")?; match self { Self::BadNumber(_v) => { write!(f, "expected non-zero integer")?; #[cfg(nightly)] use std::num::IntErrorKind; #[cfg(nightly)] return match _v { IntErrorKind::Empty => write!(f, ": argument empty"), IntErrorKind::InvalidDigit => write!(f, ": invalid digit"), IntErrorKind::Overflow => write!(f, ": conversion would result in overflow"), IntErrorKind::Underflow => write!(f, ": conversion would result in underflow"), IntErrorKind::Zero => write!(f, ": found zero"), _=> Ok(()), }; #[cfg(not(nightly))] Ok(()) }, Self::NoExist(path) => write!(f, "path {:?} does not exist", path), Self::NoFiles => write!(f, "need at least one argument"), Self::Walking(dir) => write!(f, "error walking directory structure(s): {}", dir), Self::UnknownArg(arg) => write!(f, "unknown argument `{}'", arg), Self::Other(msg) => write!(f, "{}", msg), } } } impl From for Error { fn from(_er: ParseIntError) -> Self { #[cfg(nightly)] return Self::BadNumber(_er.kind().clone()); #[cfg(not(nightly))] Self::BadNumber(()) } } /// Any extra options #[derive(Debug, Clone, PartialEq, Eq)] pub struct Flags { /// Display the progress bar #[cfg(feature="progress")] pub progress: bool, /// Force use of colour #[cfg(feature="colour")] pub coloured: Option, /// Limit max children to this number pub hard_limit: Option, /// Other flags to pass to the `leanify` subprocesses pub leanify_flags: flags::LeanifyFlags, } impl Default for Flags { #[inline] fn default() -> Self { Self { #[cfg(feature="progress")] progress: true, #[cfg(feature="colour")] coloured: None, hard_limit: None, leanify_flags: Default::default(), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { pub max_children: Option, pub files: Vec, pub recursive: Option, pub flags: Flags } impl Default for Config { #[inline] fn default() -> Self { Self { max_children: None, files: Vec::new(), recursive: Some(unsafe{NonZeroUsize::new_unchecked(1)}), flags: Flags::default(), } } } /// Parse the `env::args()` #[inline] pub async fn parse_args() -> Result { let args = std::env::args(); if args.len() <= 1 { println!("Warning: No arguments specified, try passing `--help`."); } parse(args.skip(1)).await } async fn parse(args: I) -> Result where I: IntoIterator, T: Into { let mut args = args.into_iter().map(|x| x.into()); let mut cfg = Config::default(); let mut reading=true; let mut first=true; while let Some(arg) = args.next() { if reading { let lw_arg = arg.trim().to_lowercase(); if first { match lw_arg.as_str() { "--help" => { usage() }, _ => (), } } first=false; match lw_arg.as_str() { "--max-children" => { if let Some(nzi) = args.next() { cfg.max_children = Some(nzi.parse()?); continue; } }, "-m" => { cfg.flags.hard_limit = NonZeroUsize::new(num_cpus::get()); if cfg.flags.hard_limit.is_none() { return Err(Error::Other(Cow::Borrowed("-m: Could not determine number of CPUs, try setting max children manually with `--max-children`"))); } continue; }, "--recursive" => { if let Some(nzi) = args.next() { cfg.recursive = Some(nzi.parse()?); continue; } }, #[cfg(feature="progress")] "--no-progress" => { cfg.flags.progress = false; continue; }, #[cfg(feature="colour")] "--no-colour" => { cfg.flags.coloured = Some(false); continue; }, #[cfg(feature="colour")] "--colour" => { cfg.flags.coloured = Some(true); continue; }, "-r" => { cfg.recursive = None; continue; }, "-" => { reading= false; continue; }, "-p" | "--passthrough" => { if let Some(pass) = args.next() { cfg_if! { if #[cfg(feature="checked_pass")] { if arg == "-P" { cfg.flags.leanify_flags.add(flags::LeanifyFlag::Custom(pass.split(" ").filter_map(|x| { match x.trim() { y if y.len()< 1 => None, x => Some(x.to_owned()) } }).collect())); } else { let mut args = pass.split(" ").map(|x| x.to_owned()); while let Some((args, orig)) = args.next().map(|x| (flags::LeanifyFlag::try_parse(&x, &mut args), x)) { if let Some(args) = args? { cfg.flags.leanify_flags.add(args); } else { return Err(Error::UnknownArg(orig)); } } } } else { cfg.flags.leanify_flags.add(flags::LeanifyFlag::Custom(pass.split(" ").filter_map(|x| { match x.trim() { y if y.len()< 1 => None, x => Some(x.to_owned()) } }).collect())); } } continue; } }, // Passthrough passthrough => { match flags::LeanifyFlag::try_parse(passthrough, &mut args) { Ok(Some(flag)) => { cfg.flags.leanify_flags.add(flag); continue; }, Err(err) => { return Err(err); }, _ => () } }, } } reading = false; let path = Path::new(&arg); if path.is_dir() { cfg.files.extend(dir::walk(path, cfg.recursive, dir::recommended_max_walkers()).await?); } else if path.is_file() { cfg.files.push(path.to_owned()); } else { return Err(Error::NoExist(path.to_owned())); } } if cfg.files.len() == 0 { return Err(Error::NoFiles); } Ok(cfg) } impl From for Error { fn from(from: dir::Error) -> Self { Self::Walking(from) } }