diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/error.rs | 29 | ||||
-rw-r--r-- | src/flags.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 78 |
6 files changed, 100 insertions, 32 deletions
@@ -1 +1,2 @@ /target +USAGE @@ -30,6 +30,12 @@ dependencies = [ ] [[package]] +name = "getopt" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1289e6cb1ae7c28a9b9776bd599f8afb31c20965ff257a8ea20eaa4da5e3c7" + +[[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -57,6 +63,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" name = "mmv" version = "1.0.0" dependencies = [ + "getopt", "itertools", "tempfile", ] @@ -4,5 +4,6 @@ version = "1.0.0" edition = "2021" [dependencies] +getopt = "1.1.3" itertools = "0.10.5" tempfile = "3.3.0" diff --git a/src/error.rs b/src/error.rs index f303780..7df065d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,15 +1,18 @@ use std::{ env, fmt::{self, Display}, - io + io, + string }; pub enum Error { + BadArgs, BadLengths, - DuplicateElems(Vec<String>), + DupInputElems(Vec<String>), + DupOutputElems(Vec<String>), IOError(io::Error), - NoArgs, - NoEditor, + Nop, + UTF8Error(string::FromUtf8Error), SpawnFailed(String, io::Error), } @@ -17,13 +20,17 @@ impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let p = env::args().next().unwrap(); match self { + Self::BadArgs => writeln!(f, "Usage: {p} [-0ei] [--] utility [argument ...]"), Self::BadLengths => writeln!(f, "{p}: Files have been added or removed during editing"), - Self::DuplicateElems(ds) => ds.iter().try_for_each( - |d| writeln!(f, "{p}: Multiple files named \"{}\" specified", d) + Self::DupInputElems(ds) => ds.iter().try_for_each( + |d| writeln!(f, "{p}: Multiple input files named \"{}\" specified", d) + ), + Self::DupOutputElems(ds) => ds.iter().try_for_each( + |d| writeln!(f, "{p}: Multiple output files named \"{}\" specified", d) ), Self::IOError(e) => writeln!(f, "{p}: {e}"), - Self::NoArgs => writeln!(f, "Usage: {p} file ..."), - Self::NoEditor => writeln!(f, "{p}: \"EDITOR\" environment variable is not set"), + Self::Nop => Ok(()), + Self::UTF8Error(e) => writeln!(f, "{p}: {e}"), Self::SpawnFailed(ed, e) => writeln!(f, "{p}: Failed to spawn editor \"{ed}\": {e}") } } @@ -34,3 +41,9 @@ impl From<io::Error> for Error { Self::IOError(e) } } + +impl From<string::FromUtf8Error> for Error { + fn from(e: string::FromUtf8Error) -> Self { + Self::UTF8Error(e) + } +} diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000..504f0f9 --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,16 @@ +#[derive(Debug)] +pub struct Flags { + pub encode: bool, + pub individual: bool, + pub nul: bool +} + +impl Default for Flags { + fn default() -> Flags { + Flags { + encode: false, + individual: false, + nul: false + } + } +} diff --git a/src/main.rs b/src/main.rs index 71052a2..3272041 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod encoded_string; mod error; +mod flags; mod main_result; use std::{ @@ -7,9 +8,9 @@ use std::{ env, fs, hash::Hash, - io::{self, BufRead, BufReader, BufWriter, Write, Seek, SeekFrom}, + io::{self, BufRead, BufReader, Write}, path::Path, - process::Command + process::{Command, Stdio} }; use { @@ -19,6 +20,8 @@ use { }; use { + flags::Flags, + getopt::{Opt, Parser}, itertools::Itertools, tempfile::{Builder, NamedTempFile, TempDir} }; @@ -28,44 +31,71 @@ fn main() -> MainResult { } fn work() -> Result<(), Error> { - let old_files = env::args().skip(1).collect::<Vec<String>>(); - if old_files.is_empty() { - return Err(Error::NoArgs); + let mut argv = env::args().collect::<Vec<String>>(); + let mut flags = Flags { ..Default::default() }; + let mut opts = Parser::new(&argv, ":0a"); + + loop { + match opts.next().transpose() { + Ok(v) => match v { + None => break, + Some(opt) => match opt { + Opt('0', None) => flags.nul = true, + Opt('e', None) => flags.encode = true, + Opt('i', None) => flags.individual = true, + _ => { return Err(Error::BadArgs); } + } + }, + Err(_) => { return Err(Error::BadArgs); } + } + } + + let rest = argv.split_off(opts.index()); + let cmd = rest.get(0).ok_or(Error::BadArgs)?; + let args = &rest[1..]; + + let old_files: Vec<_> = io::stdin() + .lines() + .collect::<Result<_, _>>() + .unwrap(); + if old_files.iter().any(|s| s.is_empty()) { + return Err(Error::BadArgs); } let dups = duplicate_elements(old_files.clone()); if !dups.is_empty() { - return Err(Error::DuplicateElems(dups)); + return Err(Error::DupInputElems(dups)); } - let mut tmpfile = NamedTempFile::new()?; - let mut writer = BufWriter::new(&tmpfile); - - old_files.iter().try_for_each(|f| encode_to_file(&mut writer, f))?; - writer.flush()?; - drop(writer); + let mut child = Command::new(cmd) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_err(|e| Error::SpawnFailed(cmd.to_owned(), e))?; - let editor = env::var("EDITOR") - .ok() - .filter(|e| !e.is_empty()) - .ok_or(Error::NoEditor)?; + { + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + old_files.iter().try_for_each(|f| writeln!(stdin, "{f}"))?; + } - Command::new(&editor) - .arg(tmpfile.path().as_os_str()) - .spawn() - .map_err(|err| Error::SpawnFailed(editor, err))? - .wait()?; + let output = child.wait_with_output()?; + if !output.status.success() { + return Err(Error::Nop); + } - tmpfile.seek(SeekFrom::Start(0))?; + let new_files = String::from_utf8(output.stdout)? + .lines() + .map(|s| s.to_string()) + .collect::<Vec<String>>(); - let new_files = decode_from_file(&tmpfile)?; if old_files.len() != new_files.len() { return Err(Error::BadLengths); } let dups = duplicate_elements(new_files.iter().cloned().collect::<Vec<_>>()); if !dups.is_empty() { - return Err(Error::DuplicateElems(dups)); + return Err(Error::DupOutputElems(dups)); } let tmpdir = Builder::new().prefix("mmv").tempdir()?; |