diff options
| author | Thomas Voss <mail@thomasvoss.com> | 2023-09-24 16:54:05 +0200 | 
|---|---|---|
| committer | Thomas Voss <mail@thomasvoss.com> | 2023-09-24 16:54:05 +0200 | 
| commit | bd5414ecb2cfa9c22ad18f925a53d76dc10cc413 (patch) | |
| tree | 67549d1e95527eb175abb36bb780cb42f274406b | |
| parent | 0e4574529670e5a3fa844b858a1331f12afd4eec (diff) | |
Add the ‘-b’ flag to only alter path basenamesv1.4.0
| -rw-r--r-- | mmv.1 | 24 | ||||
| -rw-r--r-- | src/main.rs | 64 | 
2 files changed, 73 insertions, 15 deletions
| @@ -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, |