1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 12:37:49 +00:00

truncate: add parse_mode_and_size() helper func

Add a helper function to contain the code for parsing the size and the
modifier symbol, if any. This commit also changes the `TruncateMode`
enum so that the parameter for each "mode" is stored along with the
enumeration value. This is because the parameter has a different meaning
in each mode.
This commit is contained in:
Jeffrey Finkelstein 2021-05-21 21:06:45 -04:00
parent 5eb2a5c3e1
commit 544ae87575

View file

@ -15,16 +15,16 @@ use std::fs::{metadata, OpenOptions};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::Path; use std::path::Path;
#[derive(Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
enum TruncateMode { enum TruncateMode {
Absolute, Reference(u64),
Reference, Absolute(u64),
Extend, Extend(u64),
Reduce, Reduce(u64),
AtMost, AtMost(u64),
AtLeast, AtLeast(u64),
RoundDown, RoundDown(u64),
RoundUp, RoundUp(u64),
} }
static ABOUT: &str = "Shrink or extend the size of each file to the specified size."; static ABOUT: &str = "Shrink or extend the size of each file to the specified size.";
@ -133,46 +133,21 @@ fn truncate(
size: Option<String>, size: Option<String>,
filenames: Vec<String>, filenames: Vec<String>,
) { ) {
let (modsize, mode) = match size { let mode = match size {
Some(size_string) => { Some(size_string) => match parse_mode_and_size(&size_string) {
// Trim any whitespace. Ok(m) => m,
let size_string = size_string.trim(); Err(_) => crash!(1, "Invalid number: {}", size_string),
},
// Get the modifier character from the size string, if any. For None => TruncateMode::Reference(0),
// example, if the argument is "+123", then the modifier is '+'.
let c = size_string.chars().next().unwrap();
let mode = match c {
'+' => TruncateMode::Extend,
'-' => TruncateMode::Reduce,
'<' => TruncateMode::AtMost,
'>' => TruncateMode::AtLeast,
'/' => TruncateMode::RoundDown,
'%' => TruncateMode::RoundUp,
_ => TruncateMode::Absolute, /* assume that the size is just a number */
};
// If there was a modifier character, strip it.
let size_string = match mode {
TruncateMode::Absolute => size_string,
_ => &size_string[1..],
};
let num_bytes = match parse_size(size_string) {
Ok(b) => b,
Err(_) => crash!(1, "Invalid number: {}", size_string),
};
(num_bytes, mode)
}
None => (0, TruncateMode::Reference),
}; };
let refsize = match reference { let refsize = match reference {
Some(ref rfilename) => { Some(ref rfilename) => {
match mode { match mode {
// Only Some modes work with a reference // Only Some modes work with a reference
TruncateMode::Reference => (), //No --size was given TruncateMode::Reference(_) => (), //No --size was given
TruncateMode::Extend => (), TruncateMode::Extend(_) => (),
TruncateMode::Reduce => (), TruncateMode::Reduce(_) => (),
_ => crash!(1, "you must specify a relative --size with --reference"), _ => crash!(1, "you must specify a relative --size with --reference"),
}; };
match metadata(rfilename) { match metadata(rfilename) {
@ -202,14 +177,14 @@ fn truncate(
}, },
}; };
let tsize: u64 = match mode { let tsize: u64 = match mode {
TruncateMode::Absolute => modsize, TruncateMode::Absolute(modsize) => modsize,
TruncateMode::Reference => fsize, TruncateMode::Reference(_) => fsize,
TruncateMode::Extend => fsize + modsize, TruncateMode::Extend(modsize) => fsize + modsize,
TruncateMode::Reduce => fsize - modsize, TruncateMode::Reduce(modsize) => fsize - modsize,
TruncateMode::AtMost => fsize.min(modsize), TruncateMode::AtMost(modsize) => fsize.min(modsize),
TruncateMode::AtLeast => fsize.max(modsize), TruncateMode::AtLeast(modsize) => fsize.max(modsize),
TruncateMode::RoundDown => fsize - fsize % modsize, TruncateMode::RoundDown(modsize) => fsize - fsize % modsize,
TruncateMode::RoundUp => fsize + fsize % modsize, TruncateMode::RoundUp(modsize) => fsize + fsize % modsize,
}; };
match file.set_len(tsize) { match file.set_len(tsize) {
Ok(_) => {} Ok(_) => {}
@ -221,6 +196,52 @@ fn truncate(
} }
} }
/// Decide whether a character is one of the size modifiers, like '+' or '<'.
fn is_modifier(c: char) -> bool {
c == '+' || c == '-' || c == '<' || c == '>' || c == '/' || c == '%'
}
/// Parse a size string with optional modifier symbol as its first character.
///
/// A size string is as described in [`parse_size`]. The first character
/// of `size_string` might be a modifier symbol, like `'+'` or
/// `'<'`. The first element of the pair returned by this function
/// indicates which modifier symbol was present, or
/// [`TruncateMode::Absolute`] if none.
///
/// # Panics
///
/// If `size_string` is empty, or if no number could be parsed from the
/// given string (for example, if the string were `"abc"`).
///
/// # Examples
///
/// ```rust,ignore
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
/// ```
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> {
// Trim any whitespace.
let size_string = size_string.trim();
// Get the modifier character from the size string, if any. For
// example, if the argument is "+123", then the modifier is '+'.
let c = size_string.chars().next().unwrap();
let size_string = if is_modifier(c) {
&size_string[1..]
} else {
size_string
};
parse_size(size_string).map(match c {
'+' => TruncateMode::Extend,
'-' => TruncateMode::Reduce,
'<' => TruncateMode::AtMost,
'>' => TruncateMode::AtLeast,
'/' => TruncateMode::RoundDown,
'%' => TruncateMode::RoundUp,
_ => TruncateMode::Absolute,
})
}
/// Parse a size string into a number of bytes. /// Parse a size string into a number of bytes.
/// ///
/// A size string comprises an integer and an optional unit. The unit /// A size string comprises an integer and an optional unit. The unit
@ -280,7 +301,9 @@ fn parse_size(size: &str) -> Result<u64, ()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::parse_mode_and_size;
use crate::parse_size; use crate::parse_size;
use crate::TruncateMode;
#[test] #[test]
fn test_parse_size_zero() { fn test_parse_size_zero() {
@ -306,4 +329,15 @@ mod tests {
assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024); assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024);
assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000); assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000);
} }
#[test]
fn test_parse_mode_and_size() {
assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));
assert_eq!(parse_mode_and_size("+10"), Ok(TruncateMode::Extend(10)));
assert_eq!(parse_mode_and_size("-10"), Ok(TruncateMode::Reduce(10)));
assert_eq!(parse_mode_and_size("<10"), Ok(TruncateMode::AtMost(10)));
assert_eq!(parse_mode_and_size(">10"), Ok(TruncateMode::AtLeast(10)));
assert_eq!(parse_mode_and_size("/10"), Ok(TruncateMode::RoundDown(10)));
assert_eq!(parse_mode_and_size("%10"), Ok(TruncateMode::RoundUp(10)));
}
} }