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.
463 lines
12 KiB
463 lines
12 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")?;
|
|
#[cfg(feature="shutdown")] writeln!(output, " -n, --no-cancel Do not capture `SIGINT`s for graceful shutdown.")?;
|
|
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");
|
|
check!(on "shutdown", "Enables the trapping of `SIGINT` for graceful shutdowns, allowing already running children to complete before exiting.");
|
|
}
|
|
|
|
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::PosOverflow => write!(f, ": conversion would result in overflow"),
|
|
IntErrorKind::NegOverflow => 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,
|
|
/// Enable graceful-shutdown on `SIGINT`.
|
|
#[cfg(feature="shutdown")] pub graceful_shutdown: bool,
|
|
}
|
|
|
|
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(),
|
|
#[cfg(feature="shutdown")] graceful_shutdown: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[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() {
|
|
#[cfg(feature="shutdown")]
|
|
"--no-cancel" | "-n" => {
|
|
cfg.flags.graceful_shutdown = false;
|
|
continue;
|
|
},
|
|
"--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)
|
|
}
|
|
}
|