You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
11 KiB

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<W: Write + ?Sized>(#[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<W: Write + ?Sized>(#[allow(unused_variables)] output: &mut W) -> fmt::Result
{
//TODO: For extra modes `--install`
Ok(())
}
struct ExtraArgs
{
args: &'static str,
modes: &'static str,
}
fn work<F>(from: F) -> &'static str
where F: FnOnce() -> Result<String, fmt::Error>
{
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<str>, default: bool, desc: impl AsRef<str>)
{
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<str>, default: bool, desc: impl AsRef<str>)
{
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] [-] <files...>
Usage: {prog} --help
{modes}
OPTIONS:
- Stop parsing args here.
--max-children <number> Max subprocesses allowed to live at once. Infinite by default.
-m Limit subprocesses to number of CPU cores.
--recursive <number> Recurse up to `<number>` times. Must be at least `1`. Default is off.
-r Resurse infinitely.
-p, --passthrough <args> Pass these args to the leanify subprocesses. See <https://github.com/JayXon/Leanify#usage> for details.
-P <args> 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> Number of iterations
-d, --max_depth <number> 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=<process name> 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<ParseIntError> 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<bool>,
/// Limit max children to this number
pub hard_limit: Option<NonZeroUsize>,
/// 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<NonZeroUsize>,
pub files: Vec<PathBuf>,
pub recursive: Option<NonZeroUsize>,
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<Config, Error>
{
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<I,T>(args: I) -> Result<Config, Error>
where I: IntoIterator<Item=T>,
T: Into<String>
{
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<dir::Error> for Error
{
fn from(from: dir::Error) -> Self
{
Self::Walking(from)
}
}