diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -15,6 +15,7 @@ use format_bytes::{write_bytes, DisplayBytes}; use std::env; use std::path::{Path, PathBuf}; +use std::str; use crate::errors::{HgResultExt, IoResultExt}; @@ -61,6 +62,32 @@ } } +pub fn parse_byte_size(value: &[u8]) -> Option { + let value = str::from_utf8(value).ok()?.to_ascii_lowercase(); + const UNITS: &[(&str, u64)] = &[ + ("g", 1 << 30), + ("gb", 1 << 30), + ("m", 1 << 20), + ("mb", 1 << 20), + ("k", 1 << 10), + ("kb", 1 << 10), + ("b", 1 << 0), // Needs to be last + ]; + for &(unit, multiplier) in UNITS { + // TODO: use `value.strip_suffix(unit)` when we require Rust 1.45+ + if value.ends_with(unit) { + let value_before_unit = &value[..value.len() - unit.len()]; + let float: f64 = value_before_unit.trim().parse().ok()?; + if float >= 0.0 { + return Some((float * multiplier as f64).round() as u64); + } else { + return None; + } + } + } + value.parse().ok() +} + impl Config { /// Load system and user configuration from various files. /// @@ -231,16 +258,14 @@ Ok(repo_config) } - /// Returns an `Err` if the first value found is not a valid boolean. - /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if - /// found, or `None`. - pub fn get_option( - &self, + fn get_parse<'config, T: 'config>( + &'config self, section: &[u8], item: &[u8], - ) -> Result, ConfigParseError> { + parse: impl Fn(&'config [u8]) -> Option, + ) -> Result, ConfigParseError> { match self.get_inner(§ion, &item) { - Some((layer, v)) => match parse_bool(&v.bytes) { + Some((layer, v)) => match parse(&v.bytes) { Some(b) => Ok(Some(b)), None => Err(ConfigParseError { origin: layer.origin.to_owned(), @@ -252,6 +277,50 @@ } } + /// Returns an `Err` if the first value found is not a valid UTF-8 string. + /// Otherwise, returns an `Ok(value)` if found, or `None`. + pub fn get_str( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigParseError> { + self.get_parse(section, item, |value| str::from_utf8(value).ok()) + } + + /// Returns an `Err` if the first value found is not a valid unsigned + /// integer. Otherwise, returns an `Ok(value)` if found, or `None`. + pub fn get_u32( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigParseError> { + self.get_parse(section, item, |value| { + str::from_utf8(value).ok()?.parse().ok() + }) + } + + /// Returns an `Err` if the first value found is not a valid file size + /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`. + /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`. + pub fn get_byte_size( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigParseError> { + self.get_parse(section, item, parse_byte_size) + } + + /// Returns an `Err` if the first value found is not a valid boolean. + /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if + /// found, or `None`. + pub fn get_option( + &self, + section: &[u8], + item: &[u8], + ) -> Result, ConfigParseError> { + self.get_parse(section, item, parse_bool) + } + /// Returns the corresponding boolean in the config. Returns `Ok(false)` /// if the value is not found, an `Err` if it's not a valid boolean. pub fn get_bool( @@ -317,7 +386,8 @@ let base_config_path = tmpdir_path.join("base.rc"); let mut config_file = File::create(&base_config_path).unwrap(); let data = - b"[section]\nitem=value0\n%include included.rc\nitem=value2"; + b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\ + [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub"; config_file.write_all(data).unwrap(); let sources = vec![ConfigSource::AbsPath(base_config_path)]; @@ -339,5 +409,13 @@ config.get_all(b"section", b"item"), [b"value2", b"value1", b"value0"] ); + + assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4)); + assert_eq!( + config.get_byte_size(b"section2", b"size").unwrap(), + Some(1024 + 512) + ); + assert!(config.get_u32(b"section2", b"not-count").is_err()); + assert!(config.get_byte_size(b"section2", b"not-size").is_err()); } }