dd-lib 0.1.0

library functions for a clone of the unix coreutil dd
Documentation
#[cfg(test)]
mod tests;
/// SI unit constants: K, M, G, KB...
pub mod units;
type Result<T> = ::std::result::Result<T, ::std::num::ParseIntError>;

/// Parse a numeric string with an optional one-or-two character multiplicative
/// suffix (K, KB, M, MB, ...)
/// into an integer. Numeric strings may be decimal, octal, or hexadecimal.
/// Multiple numeric strings may be multipled together by separating them with
/// the 'X' character. See `number/units` for a list of the SI suffixes.
///
/// ```
/// #use dd::opts::number::parse;
///    vec![
///        ("base case", "2", Some(2)),
///        ("two ok", "0x18X21", Some(0x18 * 21)),
///        ("mixed types", "0x11X0o22X2", Some(0x11 * 0o22 * 2)),
///        ("multiple suffixes", "0x13MX9K", Some(0x13 * M * 9 * K)),
///        ("bad string", "asjopdajsdomasd", None),
///    ]
///    .into_iter()
///    .for_each(|(name, s, want)| {
///        let got = parse(s).ok();
///        assert_eq!(
///            want, got,
///            r#"{}: want number("{}") = {:?}, but got {:?}"#,
///            name, s, want, got
///        );
///    })
/// ```
pub fn parse(s: &str) -> Result<usize> {
    let res: Result<Vec<usize>> = s.split('X').map(|s| scaled_part(s.trim())).collect();
    Ok(res?.into_iter().product())
}

/// Wrapper around [`parse`] that wraps the usize in an [Option]
pub fn parse_opt(s: &str) -> Result<Option<usize>> { Ok(Some(parse(s)?)) }

/// parse a number in the form [NUM][SCALE] to a usize,
/// where NUM is a hexidecimal, octal, or decimal number,
/// and scale is a one-or two byte suffix. see the documentation of parse for
/// more.
fn scaled_part(s: &str) -> Result<usize> {
    let (scale_unit, offset) = scaling_factor(s);
    let end = s.len() - offset;
    Ok(scale_unit * numeric_part(&s[..end])?)
}

/// parse a string to a number, using the starting two characters to determine
/// whether it is hexidecimal, octal, or decimal
fn numeric_part(s: &str) -> Result<usize> {
    let mut bytes = s.bytes();
    match (bytes.next(), bytes.next()) {
        // hexadecimal
        (Some(b'0'), Some(b'x')) | (Some(b'0'), Some(b'X')) => usize::from_str_radix(&s[2..], 16),
        // octal
        (Some(b'0'), Some(b'o')) => usize::from_str_radix(&s[2..], 8),
        // assume deciaml
        _ => usize::from_str_radix(s, 10),
    }
}
/// parse the suffix of the string to find a scaling factor, if any
/// and return the length of the suffix
fn scaling_factor(s: &str) -> (usize, usize) {
    let (scaling, offset) = if let Some(x) = two_byte_scaling_factor(s) {
        (x, 2)
    } else if let Some(x) = one_byte_scaling_factor(s) {
        (x, 1)
    } else {
        (1, 0)
    };
    (scaling, offset)
}

fn two_byte_scaling_factor(s: &str) -> Option<usize> {
    use self::units::*;
    let mut bytes = s.bytes();
    if Some(b'B') != bytes.next_back() {
        return None;
    }
    match bytes.next_back()? {
        b'K' => Some(KB),
        b'M' => Some(MB),
        b'G' => Some(GB),
        b'T' => Some(TB),
        b'P' => Some(PB),
        b'Z' => Some(ZB),
        _ => None,
    }
}
fn one_byte_scaling_factor(s: &str) -> Option<usize> {
    use self::units::*;
    Some(match s.bytes().next_back()? {
        b'C' => 1,
        b'W' => BITSIZE,
        b'B' => B,
        b'K' => K,
        b'M' => M,
        b'G' => G,
        b'T' => T,
        b'P' => P,
        b'Z' => Z,
        //  (_, b'y') => Y, // would have to use u128
        _ => return None,
    })
}