diff --git a/.#Cargo.toml b/.#Cargo.toml new file mode 120000 index 0000000..eda3399 --- /dev/null +++ b/.#Cargo.toml @@ -0,0 +1 @@ +avril@flan-laptop.299665:1596751578 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 216e15a..515adf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ progress = ["termprogress"] threads = ["tokio/rt-threaded"] splash = [] colour = ["recolored"] +checked_pass = [] [dependencies] lazy_static = "1.4" diff --git a/README.md b/README.md index 7075482..27565b4 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,40 @@ You can set the number of children with `--max-children `, and/or you ca You can specify the max recursion depth with `--recursive `, or set it to unlimited with `-r`. +### Passing arguments to the children + +You can also pass arguments through to the children with `--passthrough` or `-p`, and also with the ones aliased here. + +The full list of arguments can be found here <[https://github.com/JayXon/Leanify#usage]>. + +| Option | Description | +|------------------------------|----------------------------------------------| +| `-i`, `--iteration ` | Number of iterations | +| `-d`, `--max_depth ` | Max depth of leanify's archive compressesion | +| `-f`, `--fastmode` | Fast mode, no recompression | +| `-q`, `--quiet` | No output to stdout | +| `-v`, `--verbose` | Verbose output | +| `--keep-exif` | Do not remove exif | + +The following are the same: + +``` shell +$ leanify-many --passthrough '--max_depth 10 -f -v' -m files/ +$ leanify-many --max_depth 10 -f -v -m files/ +``` + +But, the following is not: + +``` shell +$ leanify-many --passthrough '--max_depth 0 -f -v' -m files/ +$ leanify-many --max_depth 0 -f -v -m files/ +``` + +Without the `checked_pass` feature enabled, the top command will cause all subprocesses to silently error, since `0` is not a valid argument for `--max_depth`. The same is true of any invalid arguments passed to `--passthrough`. +The other aliases, however, make these checks for validity before starting the subprocesses. + +If you want to pass something 'as is' to the subprocesses, do not enable the `checked_pass` feature flag when building, and pass the args with `--passthrough` instead of using the aliases. Alternatively, if the `checked_pass` feature was enabled when the binary was compiled (check the output of `--help`, and see below for features legend), pass `-P` instead of `-p` or `--passthough`. + ### Misc. | Option | Description | Notes | @@ -49,12 +83,13 @@ You can specify the max recursion depth with `--recursive `, or set it t ## Optional features There are a few compile-time features that can be enabled/disabled for additional functionality. -| Name | Description | Default | -|------------|----------------------------------------------------|---------| -| `splash` | Show program information when printing help | On | -| `colour` | Enable colouring of certain outputs, like warnings | On | -| `progress` | Enable progress bar | On | -| `threads` | Enable threaded scheduler | Off | +| Name | Description | Default | +|----------------|--------------------------------------------------------|---------| +| `splash` | Show program information when printing help | On | +| `colour` | Enable colouring of certain outputs, like warnings | On | +| `progress` | Enable progress bar | On | +| `threads` | Enable threaded scheduler | Off | +| `checked_pass` | Check the arguments sent to leanify with `passthrough` | Off | When building with Rust nightly, some other optimisations and features will be present. diff --git a/TODO b/TODO new file mode 100644 index 0000000..4c33494 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +Add `collect_stderr` feature, to not immediately print stderr, but collect it like stdout. + +Add `--install` mode with feature flag to enable using it diff --git a/src/arg.rs b/src/arg.rs index e16bfd3..ad3aa92 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -8,7 +8,7 @@ use std::{ PathBuf, Path, }, - marker::PhantomData, + borrow::Cow, }; use lazy_static::lazy_static; @@ -162,7 +162,8 @@ fn comp_flags() check!(on "splash", "Show splash-screen"); check!(on "colour", "Enable coloured output"); check!(on "progress", "Enable progress bar"); - check!(off "threads", "Enable threaded scheduler (usually not needed)."); + 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() -> ! @@ -179,6 +180,15 @@ OPTIONS: -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. @@ -196,7 +206,8 @@ pub enum Error { NoExist(PathBuf), Walking(dir::Error), - Other(&'static str), + Other(Cow<'static, str>), + UnknownArg(String), NoFiles, } impl std::error::Error for Error{} @@ -223,6 +234,7 @@ impl std::fmt::Display for Error 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), } } @@ -237,7 +249,6 @@ impl From for Error } } - /// Any extra options #[derive(Debug, Clone, PartialEq, Eq)] pub struct Flags @@ -248,9 +259,8 @@ pub struct Flags #[cfg(feature="colour")] pub coloured: Option, /// Limit max children to this number pub hard_limit: Option, - - /// To shut the linter up when we have no elements here - _data: PhantomData<()>, + /// Other flags to pass to the `leanify` subprocesses + pub leanify_flags: flags::LeanifyFlags, } impl Default for Flags @@ -262,8 +272,7 @@ impl Default for Flags #[cfg(feature="progress")] progress: true, #[cfg(feature="colour")] coloured: None, hard_limit: None, - - _data: PhantomData, + leanify_flags: Default::default(), } } } @@ -334,7 +343,7 @@ where I: IntoIterator, "-m" => { cfg.flags.hard_limit = NonZeroUsize::new(num_cpus::get()); if cfg.flags.hard_limit.is_none() { - return Err(Error::Other("-m: Could not determine number of CPUs, try setting max children manually with `--max-children`")); + return Err(Error::Other(Cow::Borrowed("-m: Could not determine number of CPUs, try setting max children manually with `--max-children`"))); } continue; }, @@ -366,7 +375,53 @@ where I: IntoIterator, 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; diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000..faf260a --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,163 @@ +use super::*; +use std::{ + collections::HashSet, + num::NonZeroUsize, + hash::{Hash, Hasher}, + borrow::Cow, +}; + +/// Flags for `leanify` +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub enum LeanifyFlag +{ + /// Corresponds to `-i` + Iteration(NonZeroUsize), + /// Corresponds to `-d` + Depth(NonZeroUsize), + /// Corresponds to `-f` + Fastmode, + /// Corresponds to `-q` + Quiet, + /// Corresponds to `-v` + Verbose, + /// Corresponds to `--keep-exif` + KeepExif, + /// Any other the user specified + Custom(Vec), +} + +impl Hash for LeanifyFlag { + fn hash(&self, state: &mut H) { + // Don't let us pass multiple of the same discriminant when their inner values are different + if let Self::Custom(custom) = self { + // Except custom + custom.hash(state) + } + std::mem::discriminant(self).hash(state); + } +} + +impl LeanifyFlag +{ + pub fn try_parse,T: Iterator + ?Sized, S: AsRef>(path: P, iter: &mut T) -> Result, arg::Error> + { + Ok(match path.as_ref() { + "-i" | "--iteration" => { + if let Some(iter) = iter.next() { + Some(Self::Iteration(iter.as_ref().parse()?)) + } else { + None + } + }, + "-d" | "--max_depth" => { + if let Some(iter) = iter.next() { + Some(Self::Depth(iter.as_ref().parse()?)) + } else { + None + } + }, + "-f" | "--fastmode" => { + Some(Self::Fastmode) + }, + "-q" | "--quiet" => { + Some(Self::Quiet) + }, + "-v" | "--verbose" => { + Some(Self::Verbose) + }, + "--keep-exif" => { + Some(Self::KeepExif) + }, + _ => None, + }) + } + fn try_into_string(self) -> Option + { + Some(match self { + Self::Iteration(iter) => format!("--iteration {}", iter), + Self::Depth(depth) => format!("--max_depth {}", depth), + Self::Fastmode => format!("-f"), + Self::Quiet => format!("-q"), + Self::Verbose => format!("-v"), + Self::KeepExif => format!("--keep-exif"), + Self::Custom(string) => string.into_iter().join(" "), + }) + } +} + +impl IntoIterator for LeanifyFlag +{ + type Item= Cow<'static, str>; + type IntoIter = maybe_single::IntoIter>; + + fn into_iter(self) -> Self::IntoIter + { + use maybe_single::MaybeSingle; + match self { + Self::Iteration(iter) => MaybeSingle::from(vec![Cow::Borrowed("--iteration"), Cow::Owned(iter.to_string())]), + Self::Depth(depth) => MaybeSingle::from(vec![Cow::Borrowed("--max_depth"), Cow::Owned(depth.to_string())]), + Self::Fastmode => MaybeSingle::single(Cow::Borrowed("-f")), + Self::Quiet => MaybeSingle::single(Cow::Borrowed("-q")), + Self::Verbose => MaybeSingle::single(Cow::Borrowed("-v")), + Self::KeepExif => MaybeSingle::single(Cow::Borrowed("--keep-exif")), + Self::Custom(string) => string.into_iter().map(|x| Cow::Owned(x)).collect(), + }.into_iter() + } +} + + +/// Extra flags to pass to `leanify` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LeanifyFlags +{ + flags: HashSet, +} + +impl Default for LeanifyFlags +{ + #[inline] + fn default() -> Self + { + Self{flags:HashSet::new()} + } +} + +impl LeanifyFlags +{ + /// Create a new empty flag list + #[inline] pub fn new() -> Self + { + Self::default() + } + + /// Add one + pub fn add(&mut self, flag: LeanifyFlag) + { + self.flags.insert(flag); + } + + /// Create the `leanify` arg string + pub fn into_arg_string(self) -> String + { + self.flags.into_iter().filter_map(|x| x.try_into_string()).join(" ") + } + + pub fn iter_cloned<'a>(&'a self) -> impl Iterator> + 'a + { + self.flags.iter().map(|x| x.clone().into_iter()).flatten() + } +} + +pub type IntoIter = std::iter::FilterMap, fn (LeanifyFlag) -> Option>; //fuck this is annoying + +impl IntoIterator for LeanifyFlags +{ + type Item= String; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter + { + self.flags.into_iter().filter_map(|x| x.try_into_string()) + } +} + diff --git a/src/main.rs b/src/main.rs index 6da2225..593bb3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ pub use ext::JoinStrsExt as _; #[cfg(feature="splash")] mod splash; +mod flags; mod arg; mod error; pub use error::{ErrorExt as _, ResultExt as _}; diff --git a/src/process.rs b/src/process.rs index d5fc059..fbb55fe 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,4 +1,5 @@ //! Handles spawning the process +use super::*; use tokio::{ process::{ Command, @@ -15,19 +16,52 @@ use tokio::{ }; use std::{ io, - path::Path, + path::{ + Path, + PathBuf, + }, }; -use cfg_if::cfg_if; + +/// Process information +pub struct Process +{ + name: PathBuf, + args: flags::LeanifyFlags, +} + +impl Process +{ + #[inline] pub fn new(name: impl Into, args: flags::LeanifyFlags) -> Self + { + Self { + name: name.into(), + args: args + } + } +} + + +impl AsRef for Process +{ + fn as_ref(&self) -> &Path + { + Path::new(&self.name) + } +} /// Spawn the process, and contain its standard output. /// /// # Notes /// Standard error is printed immediately instead. pub async fn contained_spawn(process: T, args: U, mut output_to: mpsc::Sender<(bool, String)>) -> Result<(), Error> -where T: AsRef, - U: IntoIterator, - V: AsRef +where U: IntoIterator, + V: AsRef, + T: AsRef { + let Process{ + name: process, + args: process_args + } = process.as_ref(); cfg_if!{ if #[cfg(feature="progress")] { let stderr = std::process::Stdio::piped(); @@ -35,7 +69,9 @@ where T: AsRef, let stderr = std::process::Stdio::inherit(); } }; - let mut child = match Command::new(process.as_ref()) + + let mut child = match Command::new(process) + .args(process_args.iter_cloned().map(|x| x.into_owned())) //Do we need `into_owned()` here? .args(args) .stdout(std::process::Stdio::piped()) .stderr(stderr) @@ -46,6 +82,7 @@ where T: AsRef, return Err(Error::Spawning(sp)); } }; + let stdout = child.stdout.take().unwrap(); #[cfg(feature="progress")] let stderr_sender = { let stderr = child.stderr.take().unwrap(); diff --git a/src/work.rs b/src/work.rs index 8d8f867..b918c2f 100644 --- a/src/work.rs +++ b/src/work.rs @@ -6,7 +6,7 @@ use std::{ Send, Sync, }, - path::{Path,PathBuf,}, + path::PathBuf, ffi::OsStr, }; #[allow(unused_imports)] @@ -24,6 +24,7 @@ use futures::future::{ Future, OptionFuture, }; +use process::Process; async fn maybe_await(from: Option) -> Option<::Output> where T: Future @@ -42,9 +43,8 @@ type ProgressSender = (); #[allow(unused_mut)] #[allow(unused_variables)] -async fn do_work(process: impl AsRef, file: impl AsRef, mut prog: ProgressSender) -> Result, process::Error> +async fn do_work(process: impl AsRef, file: impl AsRef, mut prog: ProgressSender) -> Result, process::Error> { - let process = process.as_ref(); let file = file.as_ref(); let (tx, mut rx) = mpsc::channel::<(bool, String)>(16); @@ -82,7 +82,7 @@ async fn do_work(process: impl AsRef, file: impl AsRef, mut prog: P } } -pub async fn work(#[allow(unused_variables)] flags: &arg::Flags, process: U, files: I, children: Option) -> Result<(), Box> +pub async fn work(flags: &arg::Flags, process: U, files: I, children: Option) -> Result<(), Box> where I: IntoIterator, ::IntoIter: ExactSizeIterator, T: AsRef + Send + Sync + 'static + Clone, @@ -90,8 +90,7 @@ where I: IntoIterator, { let (tx,mut rx) = mpsc::channel::<(T, fixed_stack::IntoIter, usize)>(children.as_ref().map(|&x| usize::from(x)).unwrap_or(16)); let semaphore = children.map(|children| Arc::new(Semaphore::new(children.into()))); - let process = Arc::new(process.into()); - + let process = Arc::new(Process::new(process, flags.leanify_flags.clone())); let files = files.into_iter(); #[cfg(feature="progress")] let (mut progress, prog_handle) = { @@ -147,7 +146,7 @@ where I: IntoIterator, let worker = { cfg_if!{ if #[cfg(feature="progress")] { - let worker = do_work(process.as_ref(), &filename, progress.clone()); + let worker = do_work(&process, &filename, progress.clone()); let task = progress.add_task(format!("{:?}", filename.as_ref())); future::join(task, worker).await } else {