aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mmv.124
-rw-r--r--src/main.rs64
2 files changed, 73 insertions, 15 deletions
diff --git a/mmv.1 b/mmv.1
index 8f07e13..da2ec02 100644
--- a/mmv.1
+++ b/mmv.1
@@ -1,4 +1,4 @@
-.Dd $Mdocdate: September 18 2023 $
+.Dd $Mdocdate: September 24 2023 $
.Dt MMV 1
.Os
.Sh NAME
@@ -7,11 +7,11 @@
.Nd mapped file moves and -copies
.Sh SYNOPSIS
.Nm
-.Op Fl 0deinv
+.Op Fl 0bdeinv
.Ar command
.Op Ar argument ...
.Nm mcp
-.Op Fl 0deiv
+.Op Fl 0bdeiv
.Ar command
.Op Ar argument ...
.Sh DESCRIPTION
@@ -62,6 +62,14 @@ separated instead of newline
.Pq Sq \en
separated.
This is useful if input filenames might contain embedded newline characters.
+.It Fl b , Fl Fl basename
+Only apply the mapping command to the basenames of the given file paths.
+This stops you from accidentally mutating directory components, which is not
+typically a desired behavior.
+If no basename can be derived
+.Pq the root directory for example has no basename ,
+then a warning diagnostic will be printed to the standard error, and the path
+will remain unchanged.
.It Fl d , Fl Fl dry-run
Print the renamings that would take place with the given inputs and arguments to
the standard error without actually executing any moves.
@@ -192,6 +200,16 @@ knowledge of the input files position in the input list
.Pc :
.Pp
.Dl $ ls --zero | mmv -0i cmd
+.Pp
+Uppercase the files in the
+.Pa /foo/bar
+directory, while leaving the names of
+.Pa foo
+and
+.Pa bar
+unchanged:
+.Pp
+.Dl $ ls /foo/bar/* | mmv -b tr a-z A-Z
.Sh SEE ALSO
.Xr awk 1 ,
.Xr cp 1 ,
diff --git a/src/main.rs b/src/main.rs
index c1f1311..c3ac72c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
use std::{
cmp::Reverse,
- collections::{hash_map::DefaultHasher, HashSet},
+ collections::{hash_map::DefaultHasher, HashSet, VecDeque},
env,
ffi::OsString,
fs,
@@ -24,6 +24,7 @@ const MCP_DEFAULT_NAME: &str = "mcp";
struct Flags {
pub backup: bool,
+ pub basename: bool,
pub dryrun: bool,
pub encode: bool,
pub individual: bool,
@@ -36,6 +37,7 @@ impl Default for Flags {
fn default() -> Self {
Flags {
backup: true,
+ basename: false,
dryrun: false,
encode: false,
individual: false,
@@ -66,6 +68,7 @@ impl Flags {
while let Some(arg) = parser.next()? {
match arg {
Short('0') | Long("nul") => flags.nul = true,
+ Short('b') | Long("basename") => flags.basename = true,
Short('d') | Long("dry-run") => flags.dryrun = true,
Short('e') | Long("encode") => flags.encode = true,
Short('i') | Long("individual") => flags.individual = true,
@@ -92,12 +95,12 @@ fn usage(bad_flags: Option<lexopt::Error>) -> ! {
let mcp_name = option_env!("MCP_NAME").unwrap_or(MCP_DEFAULT_NAME);
if p == mcp_name {
eprintln!(
- "Usage: {} [-0deiv] command [argument ...]",
+ "Usage: {} [-0bdeiv] command [argument ...]",
p.to_str().unwrap()
);
} else {
eprintln!(
- "Usage: {} [-0deinv] command [argument ...]",
+ "Usage: {} [-0bdeinv] command [argument ...]",
p.to_str().unwrap()
);
}
@@ -303,17 +306,25 @@ fn run_indiv(
err!("Failed to spawn utility: “{}”: {e}", cmd.to_str().unwrap());
});
+ let mut components = vec![];
{
let mut ci = child.stdin.take().unwrap_or_else(|| {
err!("Could not open the child process’ stdin");
});
+ let s;
+ if flags.basename {
+ components = Path::new(src).components().collect_vec();
+ s = components.pop().unwrap().as_os_str().to_str().unwrap();
+ } else {
+ s = src;
+ }
write!(
ci,
"{}",
if flags.encode {
- encode_string(src)
+ encode_string(s)
} else {
- src.to_owned()
+ s.to_string()
}
)?;
}
@@ -323,11 +334,18 @@ fn run_indiv(
});
let mut s = String::with_capacity(src.len());
co.read_to_string(&mut s)?;
- dsts.push(if flags.encode {
+ let s = if flags.encode {
decode_string(s.as_str())
} else {
s
- });
+ };
+
+ if flags.basename {
+ let path = components.iter().collect::<PathBuf>().join(s);
+ dsts.push(path.to_str().unwrap().to_string());
+ } else {
+ dsts.push(s);
+ }
/* If the process failed, it is expected to print an error message; as such,
we exit directly. */
@@ -356,19 +374,36 @@ fn run_multi(
});
/* Pass the source files to the child process. */
+ let mut components_vec = VecDeque::with_capacity(srcs.len());
+
{
let ci = child.stdin.take().unwrap_or_else(|| {
err!("Could not open the child process’ stdin");
});
let mut ci = BufWriter::new(ci);
for src in srcs {
+ let s = if flags.basename {
+ let components = Path::new(src).components().collect_vec();
+ components_vec.push_back(components);
+ components_vec
+ .back_mut()
+ .unwrap()
+ .pop()
+ .unwrap()
+ .as_os_str()
+ .to_str()
+ .unwrap()
+ } else {
+ src
+ };
+
write!(
ci,
"{}",
if flags.encode {
- encode_string(src)
+ encode_string(s)
} else {
- src.to_owned()
+ s.to_owned()
}
)?;
ci.write_all(if flags.nul && !flags.encode {
@@ -395,11 +430,16 @@ fn run_multi(
})
.for_each(|x| {
let dst = require!(String::from_utf8(x.collect_vec()));
- dsts.push(if flags.encode {
- decode_string(&dst)
+
+ let s = if flags.basename {
+ let components = require!(components_vec.pop_front(), "WOW");
+ let path = components.iter().collect::<PathBuf>().join(dst);
+ path.to_str().unwrap().to_string()
} else {
dst
- });
+ };
+
+ dsts.push(if flags.encode { decode_string(&s) } else { s });
});
/* If the process failed, it is expected to print an error message; as such,