pax_global_header00006660000000000000000000000064143731623030014514gustar00rootroot0000000000000052 comment=948c21461d0a1575ce7845f77a1f662c1bcccf29 strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/000077500000000000000000000000001437316230300177635ustar00rootroot00000000000000strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/.gitignore000066400000000000000000000000221437316230300217450ustar00rootroot00000000000000target Cargo.lock strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/.travis.yml000066400000000000000000000001431437316230300220720ustar00rootroot00000000000000language: rust rust: - stable - beta - nightly matrix: allow_failures: - rust: nightly strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/Cargo.toml000066400000000000000000000005011437316230300217070ustar00rootroot00000000000000[package] name = "strfmt" version = "0.2.4" description = "strfmt: rust library for formatting dynamic strings" repository = "https://github.com/vitiral/strfmt" readme = "README.md" keywords = ["format", "string", "str", "dynamic", "static"] authors = ["Garrett Berg "] license = "MIT" [dependencies] strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/LICENSE.txt000066400000000000000000000021241437316230300216050ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Garrett Berg, Garrett@CloudformDesign.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/README.md000066400000000000000000000102441437316230300212430ustar00rootroot00000000000000# strfmt: rust library for formatting dynamic strings This library is for rust developers who want to bring rust-like formatting to non-static strings. ## Basic use of formatting Display types ``` rust extern crate strfmt; use strfmt::strfmt; use std::collections::HashMap; #[test] fn it_works() { let mut vars = HashMap::new(); vars.insert("name".to_string(), "bob"); vars.insert("job".to_string(), "python developer"); let fmt = "hi, my name is {name} and I am a {job}!".to_string(); assert_eq!( strfmt(&fmt, &vars).unwrap(), "hi, my name is bob and I am a python developer!") } ``` In addition to the `strfmt` function, this library has the `Format` trait which adds the `format` method to `str` and `String` types. ``` rust assert_eq!("hi, my name is {name}".format(&vars), "hi, my name is bob") ``` You can use this library any time you have dynamic strings you want to format, such as if you are providing your users a ui or are reading configuration files. strfmt does not support empty identifiers (i.e. `{}` or `{:<10}`. Integer identifiers will be read as str keys to the hashmap (i.e. `{1:<10}` will have key == "1") ## Legacy In the 0.2.0 update, the signature of `strfmt` and `Format::format` changed to fix a bug with numeric formatting. For easy migration the `strfmt_display` and `Format::format_display` function provide the old behaviour. ## **BETA**: Formatting numeric types > This feature is in Beta and may change. I expect it to be fairly stable > at this point but would appreciate feedback on development. > > In addition, "signed 0 padding" (i.e. +000042) is not yet supported > for numeric types Using `strfmt_map` it is also possible to format integers and floats: ``` rust let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), 42.4242); vars.insert("y".to_string(), -100.11111); vars.insert("z".to_string(), 0.); let f = |mut fmt: Formatter| { fmt.f64(*vars.get(fmt.key).unwrap()) }; assert_eq!(strfmt_map("{x:<7.2}", f).unwrap(), "42.42 "); assert_eq!(strfmt_map("{y:+.2E}", f).unwrap(), "-1.00E2"); assert_eq!(strfmt_map("{z:+.2E}", f).unwrap(), "+0.00E0"); ``` # Status and Goals **strfmt** aims to support all of the formatting options defined in [`std::fmt`](https://doc.rust-lang.org/std/fmt/). Currently it officially only supports the format options for strings (beta support for i64 and f64) See the [syntax](https://doc.rust-lang.org/std/fmt/#syntax) for how to create a formatted string ### Current Status (in order of priority) - [ ] get strfmt_map out of Beta and create Format.format_map method - [ ] handle sign aware zero padding for numeric types - [x] format any Display type - [x] stabilize `strfmt_map` and add `format_map` to the `Format` trait. - [x] add `f64` method to `Formatter` allowing those using `strfmt_map` to format f64s according to the spec - [x] add `i64` method to `Formatter` allowing those using `strfmt_map` to format i64s according to the spec - [x] use DisplayStr trait for formatting, permitting proper formatting of integer types. - [ ] look for a rust library has "unbounded float" (like python) and add that to the formatter - [ ] look for a rust library has "unbounded integer" (like python) and add that to the formatter - [ ] Implement `vec` method to `Formatter` allowing those usin `strfmt_map` to format types of `Vec` in a way that uses precision and width (precision will limit the number of elements displayed, width the width of each element) - [ ] special suppport to format HashMap for improved speed - [ ] special suppport to format HashMap for improved speed - [ ] special suppport to format HashMap<&str, &str> for improved speed ### HELP I (@vitiral) am no longer an active maintainer of this library or any rust code, but I accept pull requests that fix bugs or implement the above features. All pull requests must be tested appropriately. Adding functionality should be fairly easy, the main piece of work is checking and handling the flags correctly and creating comprehensive tests. Hopefully I will be creating the `f64` method soon to show how it can be done, but I could really use all the help I can get on making this libray complete. strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/design.rsk000066400000000000000000000025221437316230300217560ustar00rootroot00000000000000 [REQ-purpose] refs = ["{repo}/README.md"] [REQ-syntax] text = ''' The format string will follow python's exactly. This is to get away from the `#` and extra `0` formats that are there for almost no reason. ''' [REQ-syntax-ineractions] text = ''' - alignment is ignored without a width - precision is ignored if a width is given - precsion only pads if align == Left ''' [REQ-crate] text = ''' This library **shall** be accessible on crates.io, rust's library repository ''' [REQ-performance] text = ''' This library **shall** run as fast as possible and with as little memory as possible ''' [REQ-trait] text = ''' This library **shall** export `Format` traits so that all supported types can use the `format` method. ''' [SPC-strfmt] partof = "REQ-purpose" text = ''' there **shall** be a public function that accepts a string to format and a `HashMap` to use as values. It will format the values according to the standard in rust's std::fmt module. ''' [SPC-fmt] loc = "LOC-fmt" text = ''' The `Fmt` struct **shall** contain information of how to format a string and have a method that accepts the map to format The `Fmt` struct **will** be the primary workhorse of actually formatting the string ``` [SPC-errors] loc = "LOC-error: {lib}" text = ''' any errors on formatting a string **shall** result in a returned error object ''' strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/example/000077500000000000000000000000001437316230300214165ustar00rootroot00000000000000strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/example/Cargo.toml000066400000000000000000000001741437316230300233500ustar00rootroot00000000000000[package] name = "example" version = "0.1.0" authors = ["Garrett Berg "] [dependencies] strfmt = "0.1" strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/example/src/000077500000000000000000000000001437316230300222055ustar00rootroot00000000000000strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/example/src/lib.rs000066400000000000000000000016201437316230300233200ustar00rootroot00000000000000extern crate strfmt; use strfmt::{strfmt, strfmt_map, Formatter}; use std::collections::HashMap; #[test] fn it_works() { let mut vars = HashMap::new(); vars.insert("name".to_string(), "bob".to_string()); vars.insert("job".to_string(), "python developer".to_string()); let fmt = "hi, my name is {name} and I am a {job}!".to_string(); assert!(strfmt(&fmt, &vars).unwrap() == "hi, my name is bob and I am a python developer!"); let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), 42.4242); vars.insert("y".to_string(), -100.11111); vars.insert("z".to_string(), 0.); let f = |mut fmt: Formatter| { fmt.f64(*vars.get(fmt.key).unwrap()) }; assert_eq!(strfmt_map("{x:<7.2}", f).unwrap(), "42.42 "); assert_eq!(strfmt_map("{y:+.2E}", f).unwrap(), "-1.00E2"); assert_eq!(strfmt_map("{z:+.2E}", f).unwrap(), "+0.00E0"); } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/000077500000000000000000000000001437316230300205525ustar00rootroot00000000000000strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/fmtnum.rs000066400000000000000000000110741437316230300224310ustar00rootroot00000000000000macro_rules! fmtint { ($($t:ident)*) => ($( #[allow(unused_comparisons)] impl<'a, 'b> Formatter<'a, 'b> { pub fn $t(&mut self, x: $t) -> Result<()> { self.set_default_align(Alignment::Right); let ty = match self.ty() { None => ' ', Some(c) => c, }; if !self.is_int_type() { let mut msg = String::new(); write!(msg, "Unknown format code {:?} for type", ty).unwrap(); return Err(FmtError::TypeError(msg)); } if self.precision() != None { return Err(FmtError::TypeError("precision not allowed for integers".to_string())); } if self.thousands() { return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); } if self.fill() == '0' && self.align() == Alignment::Right { return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); } let mut s = String::new(); if x >= 0 && self.sign_plus() { self.write_str("+").unwrap(); } if self.alternate() { match ty { 'b' => self.write_str("0b").unwrap(), 'o' => self.write_str("0o").unwrap(), 'x' | 'X' => self.write_str("0x").unwrap(), _ => { let mut msg = String::new(); write!(msg, "alternate ('#') cannot be used with type {:?}", ty).unwrap(); return Err(FmtError::Invalid(msg)); } } } match ty { ' ' => write!(s, "{}", x).unwrap(), 'b' => write!(s, "{:b}", x).unwrap(), 'o' => write!(s, "{:o}", x).unwrap(), 'x' => write!(s, "{:x}", x).unwrap(), 'X' => write!(s, "{:X}", x).unwrap(), _ => unreachable!(), } self.str_unchecked(s.as_str()) } })*) } macro_rules! fmtfloat { ($($t:ident)*) => ($( impl<'a, 'b> Formatter<'a, 'b> { pub fn $t(&mut self, x: $t) -> Result<()> { self.set_default_align(Alignment::Right); let ty = match self.ty() { None => 'f', Some(c) => c, }; if !self.is_float_type() { let mut msg = String::new(); write!(msg, "Unknown format code {:?} for type", ty).unwrap(); return Err(FmtError::TypeError(msg)); } if self.alternate() { return Err(FmtError::TypeError("Alternate form (#) not allowed for floats".to_string())); } if self.thousands() { return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); } if self.fill() == '0' && self.align() == Alignment::Right { return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); } let mut s = String::new(); if x >= (0 as $t) && self.sign_plus() { self.write_str("+").unwrap(); } match self.precision() { None => { match ty { 'f' => write!(s, "{}", x).unwrap(), 'e' => write!(s, "{:e}", x).unwrap(), 'E' => write!(s, "{:E}", x).unwrap(), _ => unreachable!(), } } Some(p) => { match ty { 'f' => write!(s, "{:.*}", p, x).unwrap(), 'e' => write!(s, "{:.*e}", p, x).unwrap(), 'E' => write!(s, "{:.*E}", p, x).unwrap(), _ => unreachable!(), } } } let prev_prec = self.precision(); self.set_precision(None); let out = self.str_unchecked(s.as_str()); self.set_precision(prev_prec); out } })*) } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/fmtstr.rs000066400000000000000000000172471437316230300224520ustar00rootroot00000000000000use std::fmt::Write; use std::string::String; use formatter::Formatter; use types::*; fn write_char(f: &mut Formatter, c: char, n: usize) { for _ in 0..n { f.write_char(c).unwrap(); } } #[test] fn test_write_char() { let mut s = String::new(); s.write_str("h ").unwrap(); { let mut f = Formatter::from_str("{}", &mut s).unwrap(); write_char(&mut f, 'f', 3); } assert!(s == "h fff"); } fn write_from(fmt: &mut Formatter, f: I, n: usize) -> usize where I: Iterator, { // eexaust f or run out of n, return chars written if n == 0 { return 0; } let mut n_written: usize = 0; for c in f { fmt.write_char(c).unwrap(); n_written += 1; if n_written == n { return n_written; } } n_written } #[test] fn test_write_from() { let mut s = String::new(); s.write_str("h ").unwrap(); { let mut f = Formatter::from_str("{}", &mut s).unwrap(); write_from(&mut f, "fff".chars(), 5); } assert!(s == "h fff"); { let mut f = Formatter::from_str("{}", &mut s).unwrap(); write_from(&mut f, "xxxx".chars(), 2); } assert!(s == "h fffxx"); { let mut f = Formatter::from_str("{}", &mut s).unwrap(); write_from(&mut f, "333".chars(), 3); } assert!(s == "h fffxx333"); s.clear(); { let mut f = Formatter::from_str("{}", &mut s).unwrap(); write!(f, "hello").unwrap(); } assert!(s == "hello"); } /// implement formatting of strings impl<'a, 'b> Formatter<'a, 'b> { /// format the given string onto the buffer pub fn str(&mut self, s: &str) -> Result<()> { self.set_default_align(Alignment::Left); if !(self.ty() == None || self.ty() == Some('s')) { let mut msg = String::new(); write!( msg, "Unknown format code {:?} for object of type 'str'", self.ty() ) .unwrap(); return Err(FmtError::TypeError(msg)); } else if self.alternate() { return Err(FmtError::TypeError( "Alternate form (#) not allowed in string \ format specifier" .to_string(), )); } else if self.thousands() { return Err(FmtError::TypeError( "Cannot specify ',' with 's'".to_string(), )); } else if self.sign().is_unspecified() { return Err(FmtError::TypeError( "Sign not allowed in string format specifier".to_string(), )); } self.str_unchecked(s) } /// UNSTABLE+UNTESTED: do not use in your own code (yet) /// Do the same as `str` but do not check the format string for errors. /// This gives a moderate performance boost. /// This isn't exactly unsafe, it just ends up ignoring extranious format /// specifiers /// For example, {x:<-#10} should technically be formatting an int, but ignoring the /// integer specific formatting is probably not the end of the world /// This can also be used by the `u64` etc methods to finish their formatting while /// still using the str formatter for width and alignment pub fn str_unchecked(&mut self, s: &str) -> Result<()> { let fill = self.fill(); let width = self.width(); let precision = self.precision(); let chars_count = s.chars().count(); // precision will limit length let len = match precision { Some(p) => { if p < chars_count { p } else { chars_count } } None => chars_count, }; let mut chars = s.chars(); let mut pad: usize = 0; if let Some(mut width) = width { if width > len { let align = self.align(); match align { Alignment::Left => pad = width - len, Alignment::Center => { width -= len; pad = width / 2; write_char(self, fill, pad); pad += width % 2; } Alignment::Right => { write_char(self, fill, width - len); } Alignment::Equal => { return Err(FmtError::Invalid( "sign aware zero padding and Align '=' not yet supported".to_string(), )) } Alignment::Unspecified => unreachable!(), } } } write_from(self, &mut chars, len); write_char(self, fill, pad); Ok(()) } } /// UNSTABLE: the Formatter object is still considered unstable /// Do not use this function if you aren't willing to have changes /// forced on you! /// /// format a string given the string and a closure that uses /// a Formatter pub fn strfmt_map(fmtstr: &str, f: F) -> Result where F: FnMut(Formatter) -> Result<()>, { let mut f = f; let mut out = String::with_capacity(fmtstr.len() * 2); let mut bytes_read: usize = 0; let mut opening_brace: usize = 0; let mut closing_brace: bool = false; let mut reading_fmt = false; let mut remaining = fmtstr; for c in fmtstr.chars() { bytes_read += c.len_utf8(); if c == '{' { if reading_fmt && opening_brace == bytes_read - 2 { // found {{ out.push(c); reading_fmt = false; } else if !reading_fmt { // found a first { reading_fmt = true; opening_brace = bytes_read - 1; } else { // found a { after finding an opening brace, error! out.clear(); out.write_str("extra { found").unwrap(); return Err(FmtError::Invalid(out)); } } else if c == '}' { if !reading_fmt && !closing_brace { // found a '}' that isn't after a '{' closing_brace = true; } else if closing_brace { // found "}}" out.push(c); closing_brace = false; } else { // found a format string // discard before opening brace let (_, r) = remaining.split_at(opening_brace); // get the fmt pattern and remaining let (fmt_pattern, r) = r.split_at(bytes_read - opening_brace); remaining = r; // discard the braces let (_, fmt_pattern) = fmt_pattern.split_at(1); let (fmt_pattern, _) = fmt_pattern.split_at(fmt_pattern.len() - 1); // use the closure to write the formatted string let fmt = Formatter::from_str(fmt_pattern, &mut out)?; f(fmt)?; reading_fmt = false; bytes_read = 0; } } else if closing_brace { return Err(FmtError::Invalid( "Single '}' encountered in format string".to_string(), )); } else if !reading_fmt { out.push(c) } // else we are currently reading a format string, so don't push } if closing_brace { return Err(FmtError::Invalid( "Single '}' encountered in format string".to_string(), )); } else if reading_fmt { return Err(FmtError::Invalid( "Expected '}' before end of string".to_string(), )); } out.shrink_to_fit(); Ok(out) } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/formatter.rs000066400000000000000000000261621437316230300231320ustar00rootroot00000000000000use std::fmt; use std::fmt::Write; use std::iter::Iterator; use std::str; use std::string::String; use types::*; #[derive(Debug, PartialEq)] pub struct Formatter<'a, 'b> { pub key: &'a str, fill: char, align: Alignment, // default Right for numbers, Left for strings sign: Sign, alternate: bool, width: Option, thousands: bool, precision: Option, ty: Option, buff: &'b mut String, pattern: &'a str, } fn is_alignment_token(c: char) -> bool { match c { '=' | '<' | '^' | '>' => true, _ => false, } } fn is_sign_element(c: char) -> bool { match c { ' ' | '-' | '+' => true, _ => false, } } fn is_type_element(c: char) -> bool { match c { 'b' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | 'F' | '%' | 's' | '?' => true, _ => false, } } // get an integer from pos, returning the number of bytes // consumed and the integer fn get_integer(s: &[u8], pos: usize) -> (usize, Option) { let (_, rest) = s.split_at(pos); let mut consumed: usize = 0; for b in rest { match *b as char { '0'..='9' => {} _ => break, }; consumed += 1; } if consumed == 0 { (0, None) } else { let (intstr, _) = rest.split_at(consumed); let val = unsafe { // I think I can be reasonably sure that 0-9 chars are utf8 :) match str::from_utf8_unchecked(intstr).parse::() { Ok(v) => Some(v), Err(_) => None, } }; (consumed, val) } } #[derive(Debug)] /// The format struct as it is defined in the python source struct FmtPy { pub fill: char, pub align: char, pub alternate: bool, pub sign: char, pub width: i64, pub thousands: bool, pub precision: i64, pub ty: char, } fn parse_like_python(rest: &str) -> Result { // The rest of this was pretty much strait up copied from python's format parser // All credit goes to python source file: formatter_unicode.c // let mut format = FmtPy { fill: ' ', align: '\0', alternate: false, sign: '\0', width: -1, thousands: false, precision: -1, ty: '\0', }; let mut chars = rest.chars(); let fake_fill = match chars.next() { Some(c) => c, None => return Ok(format), }; // from now on all format characters MUST be valid // ASCII characters (fill and identifier were the // only ones that weren't. // Therefore we can use bytes for the rest let rest = rest.as_bytes(); let mut align_specified = false; let mut fill_specified = false; let end: usize = rest.len(); let mut pos: usize = 0; // If the second char is an alignment token, // then fake_fill as fill if end - pos >= 1 + fake_fill.len_utf8() && is_alignment_token(rest[pos + fake_fill.len_utf8()] as char) { format.align = rest[pos + fake_fill.len_utf8()] as char; format.fill = fake_fill; fill_specified = true; align_specified = true; pos += 1 + fake_fill.len_utf8(); } else if end - pos >= 1 && is_alignment_token(fake_fill) { format.align = fake_fill; pos += fake_fill.len_utf8(); } // Parse the various sign options if end - pos >= 1 && is_sign_element(rest[pos] as char) { format.sign = rest[pos] as char; pos += 1; } // If the next character is #, we're in alternate mode. This only // applies to integers. if end - pos >= 1 && rest[pos] as char == '#' { format.alternate = true; pos += 1; } // The special case for 0-padding (backwards compat) if !fill_specified && end - pos >= 1 && rest[pos] == '0' as u8 { format.fill = '0'; if !align_specified { format.align = '='; } pos += 1; } // check to make sure that val is good let (consumed, val) = get_integer(rest, pos); pos += consumed; if consumed != 0 { match val { None => { return Err(FmtError::Invalid( "overflow error when parsing width".to_string(), )) } Some(v) => { format.width = v; } } } // Comma signifies add thousands separators if end - pos > 0 && rest[pos] as char == ',' { format.thousands = true; pos += 1; } // Parse field precision if end - pos > 0 && rest[pos] as char == '.' { pos += 1; let (consumed, val) = get_integer(rest, pos); if consumed != 0 { match val { None => { return Err(FmtError::Invalid( "overflow error when parsing precision".to_string(), )) } Some(v) => { format.precision = v; } } } else { // Not having a precision after a dot is an error. if consumed == 0 { return Err(FmtError::Invalid( "Format specifier missing precision".to_string(), )); } } pos += consumed; } // Finally, parse the type field. if end - pos > 1 { // More than one char remain, invalid format specifier. return Err(FmtError::Invalid("Invalid format specifier".to_string())); } if end - pos == 1 { format.ty = rest[pos] as char; if !is_type_element(format.ty) { let mut msg = String::new(); write!(msg, "Invalid type specifier: {:?}", format.ty).unwrap(); return Err(FmtError::TypeError(msg)); } // pos+=1; } // Do as much validating as we can, just by looking at the format // specifier. Do not take into account what type of formatting // we're doing (int, float, string). if format.thousands { match format.ty { 'd' | 'e' | 'f' | 'g' | 'E' | 'G' | '%' | 'F' | '\0' => {} /* These are allowed. See PEP 378.*/ _ => { let mut msg = String::new(); write!(msg, "Invalid comma type: {}", format.ty).unwrap(); return Err(FmtError::Invalid(msg)); } } } Ok(format) } impl<'a, 'b> Formatter<'a, 'b> { /// create Formatter from format string pub fn from_str(s: &'a str, buff: &'b mut String) -> Result> { let mut found_colon = false; let mut chars = s.chars(); let mut c = match chars.next() { Some(':') | None => { return Err(FmtError::Invalid("must specify identifier".to_string())) } Some(c) => c, }; let mut consumed = 0; // find the identifier loop { consumed += c.len_utf8(); if c == ':' { found_colon = true; break; } c = match chars.next() { Some(c) => c, None => { break; } }; } let (identifier, rest) = s.split_at(consumed); let identifier = if found_colon { let (i, _) = identifier.split_at(identifier.len() - 1); // get rid of ':' i } else { identifier }; let format = parse_like_python(rest)?; Ok(Formatter { key: identifier, fill: format.fill, align: match format.align { '\0' => Alignment::Unspecified, '<' => Alignment::Left, '^' => Alignment::Center, '>' => Alignment::Right, '=' => Alignment::Equal, _ => unreachable!(), }, sign: match format.sign { '\0' => Sign::Unspecified, '+' => Sign::Plus, '-' => Sign::Minus, ' ' => Sign::Space, _ => unreachable!(), }, alternate: format.alternate, width: match format.width { -1 => None, _ => Some(format.width as usize), }, thousands: format.thousands, precision: match format.precision { -1 => None, _ => Some(format.precision as usize), }, ty: match format.ty { '\0' => None, _ => Some(format.ty), }, buff: buff, pattern: s, }) } /// call this to re-write the original format string verbatum /// back to the output pub fn skip(mut self) -> Result<()> { self.buff.push('{'); self.write_str(self.pattern).unwrap(); self.buff.push('}'); Ok(()) } /// fill getter pub fn fill(&self) -> char { self.fill } /// align getter pub fn align(&self) -> Alignment { self.align.clone() } // provide default for unspecified alignment pub fn set_default_align(&mut self, align: Alignment) { if self.align == Alignment::Unspecified { self.align = align } } /// width getter pub fn width(&self) -> Option { self.width } /// thousands getter pub fn thousands(&self) -> bool { self.thousands } /// precision getter pub fn precision(&self) -> Option { self.precision } /// set precision to None, used for formatting int, float, etc pub fn set_precision(&mut self, precision: Option) { self.precision = precision; } /// sign getter pub fn sign(&self) -> Sign { self.sign.clone() } /// sign plus getter /// here because it is in fmt::Formatter pub fn sign_plus(&self) -> bool { self.sign == Sign::Plus } /// sign minus getter /// here because it is in fmt::Formatter pub fn sign_minus(&self) -> bool { self.sign == Sign::Minus } /// alternate getter pub fn alternate(&self) -> bool { self.alternate } // sign_aware_zero_pad // Not supported /// type getter pub fn ty(&self) -> Option { self.ty } /// UNSTABLE: in the future, this may return true if all validty /// checks for a float return true /// return true if ty is valid for formatting integers pub fn is_int_type(&self) -> bool { match self.ty { None => true, Some(c) => match c { 'b' | 'o' | 'x' | 'X' => true, _ => false, }, } } /// UNSTABLE: in the future, this may return true if all validty /// checks for a float return true /// return true if ty is valid for formatting floats pub fn is_float_type(&self) -> bool { match self.ty { None => true, Some(c) => match c { 'f' | 'e' | 'E' => true, _ => false, }, } } } impl<'a, 'b> fmt::Write for Formatter<'a, 'b> { fn write_str(&mut self, s: &str) -> fmt::Result { self.buff.write_str(s) } } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/lib.rs000066400000000000000000000135241437316230300216730ustar00rootroot00000000000000//! strfmt crate use std::collections::HashMap; use std::fmt; use std::fmt::Write; use std::hash::Hash; use std::str::FromStr; use std::string::String; mod fmtstr; mod formatter; #[cfg(test)] mod tests; mod types; #[macro_use] mod fmtnum; mod macros; pub use fmtstr::strfmt_map; pub use formatter::Formatter; pub use types::{Alignment, FmtError, Result, Sign}; // u128 & i128 unstable (see https://github.com/rust-lang/rust/issues/35118) fmtint!(u8 i8 u16 i16 u32 i32 u64 i64 usize isize); fmtfloat!(f32 f64); /// Rust-style format a string given a `HashMap` of the variables. /// # Arguments /// /// * `fmtstr` - A string defining the format /// * `vars` - A `HashMap` holding the variables to use /// /// # Exceptions /// /// * [FmtError::Invalid] - The format string is structured incorrectly /// * [FmtError::KeyError] - `vars` contains an invalid key /// * [FmtError::TypeError] - the given format code for a section contains an unexpected option /// /// # Examples /// /// ``` /// use std::collections::HashMap; /// use std::f64::consts::PI; /// use strfmt::strfmt; /// /// let mut my_vars: HashMap = HashMap::new(); /// my_vars.insert("Alpha".to_string(),42.0); /// my_vars.insert("Beta".to_string(),PI); /// /// println!("{}", strfmt("{Alpha} {Beta:<5.2}",&my_vars).unwrap()); /// ``` pub fn strfmt<'a, K, T: DisplayStr>(fmtstr: &str, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { let formatter = |mut fmt: Formatter| { let k: K = match fmt.key.parse() { Ok(k) => k, Err(_) => { return Err(new_key_error(fmt.key)); } }; let v = match vars.get(&k) { Some(v) => v, None => { return Err(new_key_error(fmt.key)); } }; v.display_str(&mut fmt) }; strfmt_map(fmtstr, &formatter) } /// Rust-style format a string given a `HashMap` of the variables. /// see [strfmt] for details #[deprecated( since = "0.2.0", note = "This function contains a bug when formatting numbers. Use strfmt instead" )] pub fn strfmt_display<'a, K, T: fmt::Display>(fmtstr: &str, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { let formatter = |mut fmt: Formatter| { let k: K = match fmt.key.parse() { Ok(k) => k, Err(_) => { return Err(new_key_error(fmt.key)); } }; let v = match vars.get(&k) { Some(v) => v, None => { return Err(new_key_error(fmt.key)); } }; fmt.str(v.to_string().as_str()) }; strfmt_map(fmtstr, &formatter) } macro_rules! display_str_impl { ($($t:ident)*) => ($( impl DisplayStr for $t { fn display_str(&self,f:&mut Formatter) -> Result<()> { f.$t(*self) } } )*) } display_str_impl!(u8 i8 u16 i16 u32 i32 u64 i64 usize isize); display_str_impl!(f32 f64); impl DisplayStr for String { fn display_str(&self, f: &mut Formatter) -> Result<()> { f.str(self.as_str()) } } impl DisplayStr for &str { fn display_str(&self, f: &mut Formatter) -> Result<()> { f.str(self) } } impl DisplayStr for &dyn DisplayStr { fn display_str(&self, f: &mut Formatter) -> Result<()> { (*self).display_str(f) } } impl DisplayStr for Box { fn display_str(&self, f: &mut Formatter) -> Result<()> { self.as_ref().display_str(f) } } /// This trait is effectively an re-implementation for [std::fmt::Display] /// It is used to disguise between the value types that should be formatted pub trait DisplayStr { fn display_str(&self, f: &mut Formatter) -> Result<()>; } /// This trait is a shortcut for [strfmt] /// for an example see [Format::format] pub trait Format { /// format a string using strfmt /// # Arguments /// /// * `vars` - A `HashMap` holding the variables to use /// /// # Errors /// Errors are passed directly from strfmt, for details see [strfmt] /// /// # Examples /// /// ``` /// use std::collections::HashMap; /// use std::f64::consts::PI; /// use strfmt::Format; /// /// let mut my_vars: HashMap = HashMap::new(); /// my_vars.insert("Alpha".to_string(),42.0); /// my_vars.insert("Beta".to_string(),PI); /// /// println!("{}", "|{Alpha}|{Beta:<5.2}|".format(&my_vars).unwrap()); /// ``` fn format(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr; /// format a string using strfmt_display /// see [Format::format] for usage #[deprecated( since = "0.2.0", note = "This function contains a bug when formatting numbers. Use format instead" )] fn format_display(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr; } impl Format for String { fn format<'a, K, D: DisplayStr>(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { strfmt(self.as_str(), vars) } fn format_display<'a, K, D: fmt::Display>(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { #[allow(deprecated)] strfmt_display(self.as_str(), vars) } } impl Format for str { fn format(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { strfmt(self, vars) } fn format_display<'a, K, D: fmt::Display>(&self, vars: &HashMap) -> Result where K: Hash + Eq + FromStr, { #[allow(deprecated)] strfmt_display(self, vars) } } fn new_key_error(key: &str) -> FmtError { let mut msg = String::new(); write!(msg, "Invalid key: {}", key).unwrap(); FmtError::KeyError(msg) } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/macros.rs000066400000000000000000000034251437316230300224100ustar00rootroot00000000000000/// Format a given string with the passed variables. /// This macro is creating an single used Hashmap, for performance optimizations it might be /// more efficient to reuse an existing one. /// /// # Arguments /// * `inst` - A string with an Rust-style format instructions /// * `values` - A list of values to use for formatting /// /// # Errors /// see [strfmt] /// /// # Example /// ``` /// use strfmt::FmtError; /// use strfmt::{strfmt,strfmt_builder}; /// /// let fmt = "{first}{second:7.2}"; /// // ... do stuff and adjust fmt as you need /// let first = "test"; /// //test 77.65 /// println!("{}",strfmt!(fmt, first,second => 77.6543210).unwrap()); /// ``` #[macro_export] macro_rules! strfmt { ($inst:expr, $($key:ident => $value:tt),*,) => { strfmt!($inst, $($key => $value)*) }; ($inst:expr, $($values:tt),*,) => { strfmt!($inst, $($values)*) }; ($inst:expr,$($values:tt)*) =>({ use std::collections::HashMap; use $crate::{DisplayStr,strfmt_builder}; let mut vars: HashMap> = HashMap::new(); strfmt_builder!(vars,$($values)*); strfmt($inst,&vars) }); } #[macro_export] macro_rules! strfmt_builder { ($vars:expr,$value:expr) => ( $vars.insert(stringify!($value).to_string(),Box::new($value)); ); ($vars:expr,$name:ident => $value:expr) => { $vars.insert(stringify!($name).to_string(),Box::new($value)); }; ($vars:expr,$value:expr,$($values:tt)*) => { $vars.insert(stringify!($value).to_string(),Box::new($value)); strfmt_builder!($vars,$($values)*) }; ($vars:expr,$name:ident => $value:expr,$($values:tt)*) => { $vars.insert(stringify!($name).to_string(),Box::new($value)); strfmt_builder!($vars,$($values)*) }; } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/000077500000000000000000000000001437316230300217145ustar00rootroot00000000000000strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/float.rs000066400000000000000000000022671437316230300233760ustar00rootroot00000000000000use std::collections::HashMap; use {FmtError, Format}; #[test] fn test_fmt_float64() -> Result<(), FmtError> { let mut vars: HashMap = HashMap::new(); vars.insert("Zero".to_string(), 0.0); vars.insert("Three".to_string(), 10.0 / 3.0); vars.insert("Two".to_string(), 2.0); assert_eq!("0".to_string(), "{Zero}".format(&vars)?); assert_eq!("0.00".to_string(), "{Zero:.2}".format(&vars)?); assert_eq!("3.33333".to_string(), "{Three:.5}".format(&vars)?); assert_eq!("2".to_string(), "{Two}".format(&vars)?); assert_eq!("0.00 ".to_string(), "{Zero:<5.2}".format(&vars)?); Ok(()) } #[test] fn test_fmt_float32() -> Result<(), FmtError> { let mut vars: HashMap = HashMap::new(); vars.insert("Zero".to_string(), 0.0); vars.insert("Three".to_string(), 10.0 / 3.0); vars.insert("Two".to_string(), 2.0); assert_eq!("0".to_string(), "{Zero}".format(&vars)?); assert_eq!("0.00".to_string(), "{Zero:.2}".format(&vars)?); assert_eq!("3.33333".to_string(), "{Three:.5}".format(&vars)?); assert_eq!("2".to_string(), "{Two}".format(&vars)?); assert_eq!("0.00 ".to_string(), "{Zero:<5.2}".format(&vars)?); Ok(()) } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/fmt.rs000066400000000000000000000016401437316230300230510ustar00rootroot00000000000000use super::super::formatter::Formatter; use super::super::types::*; #[test] fn test_fmt_from_str() { let s = String::new(); { let mut s = s.clone(); let f = Formatter::from_str("x:<.3", &mut s).unwrap(); // defaults assert_eq!(f.fill(), ' '); assert_eq!(f.sign(), Sign::Unspecified); assert_eq!(f.alternate(), false); assert_eq!(f.width(), None); assert_eq!(f.thousands(), false); assert_eq!(f.ty(), None); // specified assert_eq!(f.key, "x"); assert_eq!(f.precision().unwrap(), 3); assert_eq!(f.align(), Alignment::Left); } assert!(Formatter::from_str("x:^.3", &mut s.clone()).is_ok()); assert!(Formatter::from_str("xxx: <88.3", &mut s.clone()).is_ok()); assert!(Formatter::from_str("xxx: <88.3", &mut s.clone()).is_err()); assert!(Formatter::from_str("xxx:a34", &mut s.clone()).is_err()); } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/key.rs000066400000000000000000000036211437316230300230540ustar00rootroot00000000000000//! Test keys other than String. use super::super::*; use std::collections::HashMap; use std::str::FromStr; #[test] fn test_key_u32() { let mut vars: HashMap = HashMap::new(); vars.insert(0, "X".to_string()); assert_eq!("hi {0}".format(&vars).unwrap(), "hi X"); assert_eq!("hi {0}".to_string().format(&vars).unwrap(), "hi X"); assert_eq!( "hi {1}".format(&vars), Err(FmtError::KeyError("Invalid key: 1".into())) ); assert_eq!( "hi {you}".format(&vars), Err(FmtError::KeyError("Invalid key: you".into())) ); } #[test] fn test_key_i32() { let mut vars: HashMap = HashMap::new(); vars.insert(-1, "X".to_string()); assert_eq!("hi {-1}".format(&vars).unwrap(), "hi X"); assert_eq!("hi {-1}".to_string().format(&vars).unwrap(), "hi X"); assert_eq!( "hi {1}".format(&vars), Err(FmtError::KeyError("Invalid key: 1".into())) ); assert_eq!( "hi {you}".format(&vars), Err(FmtError::KeyError("Invalid key: you".into())) ); } #[derive(PartialEq, Eq, Hash)] enum Key { Zero, One, Two, } impl FromStr for Key { type Err = FmtError; fn from_str(s: &str) -> Result { match s { "Zero" => Ok(Key::Zero), "One" => Ok(Key::One), "Two" => Ok(Key::Two), _ => Err(FmtError::KeyError(s.to_string())), } } } #[test] fn test_key_enum() { let mut vars: HashMap = HashMap::new(); vars.insert(Key::Zero, "X".to_string()); assert_eq!("hi {Zero}".format(&vars).unwrap(), "hi X"); assert_eq!("hi {Zero}".to_string().format(&vars).unwrap(), "hi X"); assert_eq!( "hi {One}".format(&vars), Err(FmtError::KeyError("Invalid key: One".into())) ); assert_eq!( "hi {you}".format(&vars), Err(FmtError::KeyError("Invalid key: you".into())) ); } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/legacy.rs000066400000000000000000000014501437316230300235260ustar00rootroot00000000000000use std::collections::HashMap; use {FmtError, Format}; /// cautione here, this test just checks if the old float formatting behaviour is still present, /// as this was changed in 0.2.0 #[test] #[allow(deprecated)] fn test_legacy() -> Result<(), FmtError> { let mut vars: HashMap = HashMap::new(); vars.insert("Zero".to_string(), 0.0); vars.insert("One".to_string(), 10.0 / 3.0); vars.insert("Two".to_string(), 2.0); assert_eq!("0".to_string(), "{Zero}".format_display(&vars)?); assert_eq!("0".to_string(), "{Zero:.2}".format_display(&vars)?); assert_eq!("3.333".to_string(), "{One:.5}".format_display(&vars)?); assert_eq!("2".to_string(), "{Two}".format_display(&vars)?); assert_eq!("0 ".to_string(), "{Zero:<5.2}".format_display(&vars)?); Ok(()) } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/macros.rs000066400000000000000000000012441437316230300235470ustar00rootroot00000000000000///wrap to simulate external use without uses of mod.rs mod macro_test { use crate::FmtError; use crate::{strfmt, strfmt_builder}; #[test] fn test_macros() -> Result<(), FmtError> { let first = "test"; let second = 2; assert_eq!("test", strfmt!("{first}", first)?); assert_eq!("test2", strfmt!("{first}{second}", first, second)?); assert_eq!( "test77.65 ", strfmt!("{first}{third:<7.2}", first,second, third => 77.6543210)? ); assert_eq!( "test 77.65", strfmt!("{first}{third:7.2}", first,second, third => 77.6543210)? ); Ok(()) } } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/mod.rs000066400000000000000000000004351437316230300230430ustar00rootroot00000000000000mod float; mod fmt; mod key; mod legacy; mod macros; mod strfmt; mod test_trait; use super::FmtError; #[test] fn test_error() { // just make sure this compiles mostly let err = FmtError::Invalid("fmt error".to_string()); let v = err.to_string(); println!("{}", v); } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/strfmt.rs000066400000000000000000000264551437316230300236150ustar00rootroot00000000000000#![allow(unused_variables)] use super::super::*; use std::collections::HashMap; use std::fmt; macro_rules! matches { ($e:expr, $p:pat) => { match $e { $p => true, _ => false, } }; } fn run_tests) -> Result>( values: &Vec<(&str, &str, u8)>, vars: &HashMap, call: F, ) { let mut call = call; for &(fmtstr, expected, expect_err) in values.iter() { let result = call(fmtstr, vars); let mut failure = match expect_err { 0 => result.is_err(), 1 => !matches!(result, Err(FmtError::Invalid(_))), 2 => !matches!(result, Err(FmtError::KeyError(_))), 3 => !matches!(result, Err(FmtError::TypeError(_))), c @ _ => panic!("error code {} DNE", c), }; let result = match result { Err(e) => e.to_string(), Ok(s) => s, }; if !failure && expect_err == 0 { failure = !(expected == result); } if failure { println!("FAIL:"); println!(" input: {:?}", fmtstr); println!(" output: {:?}", result); if expect_err != 0 { let expected = match expect_err { 1 => "FmtError::Invalid", 2 => "FmtError::KeyError", 3 => "FmtError::TypeError", _ => unreachable!(), }; println!(" expected: {}", expected) } else { println!(" expected: {:?}", expected); } assert!(false); } } } #[test] fn test_values() { let mut vars: HashMap = HashMap::new(); let too_long = "toooloooong".to_string(); let rust_unicode = format!(">{:^7}<", "ಠ_ಠ"); vars.insert("x".to_string(), "X".to_string()); vars.insert("long".to_string(), too_long.clone()); // len=10 vars.insert("hi".to_string(), "hi".to_string()); vars.insert("unicode".to_string(), "ಠ_ಠ".to_string()); // format, expected, error // error codes: 0 == no error, 1 == Invalid, 2 == KeyError let values: Vec<(&str, &str, u8)> = vec![ // simple positioning ("{x}", "X", 0), ("{x:}", "X", 0), ("{x:3}", "X ", 0), ("{x:>3}", " X", 0), ("{x:<3}", "X ", 0), ("{x:^3}", " X ", 0), ("{x:^4}", " X ", 0), // extra text (" {x}yz", " Xyz", 0), (" hi {x:^4}-you rock", " hi X -you rock", 0), // unicode (">{unicode:^7}<", "> ಠ_ಠ <", 0), (">{unicode:^7}<", &rust_unicode, 0), ("{unicode:<5}", "ಠ_ಠ ", 0), ("{unicode:>5}", " ಠ_ಠ", 0), // fill confusion ("{x:10}", "X ", 0), ("{x:>10}", " X", 0), ("{x:0<5}", "X0000", 0), ("{x:0>5}", "0000X", 0), ("{long:.3}", "too", 0), ("{long:5.3}", "too ", 0), ("{long:>5.3}", " too", 0), ("{long:5.7}", "toooloo", 0), ("{long:<5.7}", "toooloo", 0), ("{long:>5.7}", "toooloo", 0), ("{long:^5.7}", "toooloo", 0), ("{long:<}", &too_long, 0), ("{long:<<}", &too_long, 0), ("{long:<<5}", &too_long, 0), // valid types ("{x:<4s}", "X ", 0), // escape ("{{}}", "{}", 0), ("{{long}}", "{long}", 0), ("{{{x}}}", "{X}", 0), // fun ("{x:<>}", "X", 0), ("{x:<>3}", "< = HashMap::new(); vars.insert("x".to_string(), 6); vars.insert("long".to_string(), 100000); // len=10 vars.insert("hi".to_string(), 42); // format, expected, error // error codes: 0 == no error, 1 == Invalid, 2 == KeyError let values: Vec<(&str, &str, u8)> = vec![ // simple positioning ("{x}", "6", 0), ("{long}", "100000", 0), ( " the answer is {hi}, haven't you read anything?", " the answer is 42, haven't you read anything?", 0, ), ]; run_tests(&values, &vars, &strfmt); } #[test] fn test_ignore_missing() { let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), "X".to_string()); let values: Vec<(&str, &str, u8)> = vec![ // simple positioning ("{y}", "{y}", 0), ("{y} {x}", "{y} X", 0), ( "{x} {longish:<32.3} {x} is nice", "X {longish:<32.3} X is nice", 0, ), ]; let f = |mut fmt: Formatter| match vars.get(fmt.key) { Some(v) => fmt.str(v), None => fmt.skip(), }; let strfmt_ignore = |fmtstr: &str, vars: &HashMap| -> Result { strfmt_map(fmtstr, &f) }; run_tests(&values, &vars, &strfmt_ignore); } #[test] fn test_mut_closure() { let mut key_list = vec![]; let f = |fmt: Formatter| { match fmt.key.parse::() { Ok(key) => { key_list.push(key); }, Err(_) => { return Err(FmtError::KeyError(format!("Invalid key: {}", fmt.key))); } }; fmt.skip() }; let _ = strfmt_map("{one} two {three:4.4}", f).unwrap(); assert_eq!(key_list, vec!["one", "three"]); } #[test] fn test_trailing_comma() { assert!(strfmt!("{foo}", foo => "bar", ) == Ok("bar".into())); let foo = "bar"; assert!(strfmt!("{foo}", foo,) == Ok("bar".into())); } macro_rules! test_float { ($($name:ident $t:ident),*) => ($( #[test] fn $name() { let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), 42.4242); vars.insert("y".to_string(), -100.11111); vars.insert("z".to_string(), 0.); let values: Vec<(&str, &str, u8)> = vec![ // simple valid ("{x}", "42.4242", 0), ("{x:.2}", "42.42", 0), ("{x:<7.2}", "42.42 ", 0), ("{x:.2e}", "4.24e1", 0), ("{x:.2E}", "4.24E1", 0), ("{x:+}", "+42.4242", 0), ("{y:.2E}", "-1.00E2", 0), ("{y:+.2E}", "-1.00E2", 0), ("{z:+.2E}", "+0.00E0", 0), // invalid ("{x:s}", "", 3), ("{x:#}", "", 3), // TODO ("{x:+010.2}", "+0042.4242", 1), ]; let f = |mut fmt: Formatter| { match vars.get(fmt.key) { Some(v) => fmt.$t(*v), None => panic!(), } }; let strfmt_float = |fmtstr: &str, vars: &HashMap| -> Result { strfmt_map(fmtstr, &f) }; run_tests(&values, &vars, &strfmt_float); } )*) } test_float!(test_f32 f32, test_f64 f64); macro_rules! test_uint { ($($name:ident $t:ident),*) => ($( #[test] fn $name() { let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), 42); vars.insert("y".to_string(), 0); let values: Vec<(&str, &str, u8)> = vec![ ("{x}", "42", 0), ("{x:<7}", "42 ", 0), ("{x:>7}", " 42", 0), ("{x:^7}", " 42 ", 0), ("{x:x}", "2a", 0), ("{x:X}", "2A", 0), ("{x:+x}", "+2a", 0), ("{x:#x}", "0x2a", 0), ("{x:#X}", "0x2A", 0), ("{x:b}", "101010", 0), ("{x:#b}", "0b101010", 0), ("{x:o}", "52", 0), ("{x:#o}", "0o52", 0), ("{x:+}", "+42", 0), ("{y:-}", "0", 0), ("{y:+}", "+0", 0), // invalid ("{x:.2}", "", 3), ("{x:s}", "", 3), // TODO ("{x:+010}", "+000000042", 1), ]; let f = |mut fmt: Formatter| { match vars.get(fmt.key) { Some(v) => fmt.$t(*v), None => panic!(), } }; let strfmt_int = |fmtstr: &str, vars: &HashMap| -> Result { strfmt_map(fmtstr, &f) }; run_tests(&values, &vars, &strfmt_int); } )*) } macro_rules! test_int { ($($name:ident $t:ident),*) => ($( #[test] fn $name() { let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), 42); vars.insert("y".to_string(), -100); vars.insert("z".to_string(), 0); let values: Vec<(&str, &str, u8)> = vec![ // simple valid ("{x}", "42", 0), ("{x:<7}", "42 ", 0), ("{x:X}", "2A", 0), ("{x:#x}", "0x2a", 0), ("{x:#X}", "0x2A", 0), ("{x:b}", "101010", 0), ("{x:#b}", "0b101010", 0), ("{x:o}", "52", 0), ("{x:#o}", "0o52", 0), ("{x:+}", "+42", 0), ("{y}", "-100", 0), ("{y:+}", "-100", 0), ("{z}", "0", 0), ("{z:-}", "0", 0), ("{z:+}", "+0", 0), // invalid ("{x:.2}", "", 3), ("{x:s}", "", 3), // TODO ("{x:+010}", "+000000042", 1), ]; let f = |mut fmt: Formatter| { match vars.get(fmt.key) { Some(v) => fmt.$t(*v), None => panic!(), } }; let strfmt_uint = |fmtstr: &str, vars: &HashMap| -> Result { strfmt_map(fmtstr, &f) }; run_tests(&values, &vars, &strfmt_uint); } )*) } test_uint!(test_u8 u8, test_u16 u16, test_u32 u32, test_u64 u64, test_usize usize); test_int!(test_i8 i8, test_i16 i16, test_i32 i32, test_i64 i64, test_isize isize); // #[bench] // fn bench_strfmt(b: &mut Bencher) { // let mut vars: HashMap = HashMap::new(); // let too_long = "toooloooong".to_string(); // vars.insert("x".to_string(), "X".to_string()); // let fmtstr = "short: {x:*^10.3} long: {long:%<14.9}"; // b.iter(|| strfmt(fmtstr, &vars)); // } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/tests/test_trait.rs000066400000000000000000000012151437316230300244430ustar00rootroot00000000000000use super::super::*; use std::collections::HashMap; #[test] fn test_trait() { let mut vars: HashMap = HashMap::new(); vars.insert("x".to_string(), "X".to_string()); assert_eq!("hi {x}".format(&vars).unwrap(), "hi X"); assert_eq!("hi {x}".to_string().format(&vars).unwrap(), "hi X"); } #[test] fn test_heterogenous_values() { let mut data: HashMap = HashMap::new(); data.insert("body".to_string(), &"someString"); data.insert("some_number".to_string(), &5.0); let output = strfmt("{body} = {some_number:2.4}", &data).unwrap(); assert_eq!(output, "someString = 5.0000"); } strfmt-948c21461d0a1575ce7845f77a1f662c1bcccf29/src/types.rs000066400000000000000000000035071437316230300222710ustar00rootroot00000000000000use std::error; use std::fmt; use std::result; use std::string::String; #[derive(Debug, Clone, PartialEq)] pub enum Alignment { Unspecified, // default Left for strings, Right for numbers Left, Center, Right, Equal, } #[derive(Debug, Clone, PartialEq)] pub enum Sign { Unspecified, Plus, Minus, Space, } impl Sign { pub fn is_unspecified(&self) -> bool { match *self { Sign::Unspecified => false, _ => true, } } } pub type Result = result::Result; /// LOC-error #[derive(Debug, PartialEq)] pub enum FmtError { Invalid(String), // format string is structued incorrectly KeyError(String), // key error in formatting string TypeError(String), // invalid type used } impl fmt::Display for FmtError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { FmtError::Invalid(ref s) => write!(f, "Invalid({})", s), FmtError::KeyError(ref s) => write!(f, "KeyError({})", s), FmtError::TypeError(ref s) => write!(f, "TypeError({})", s), } } } impl error::Error for FmtError { fn description(&self) -> &str { match *self { FmtError::Invalid(_) => "invalid format string", FmtError::KeyError(_) => "invalid key", FmtError::TypeError(_) => "error during type resolution", } } fn cause(&self) -> Option<&dyn error::Error> { None } } // enum Type { // // integer types // Bin, // Char, // Decimal, // Octal, // Hex, // HexUpper, // // both // Number, // // Floating point types // Exponent, // ExponentUpper, // Fixed, // General, // GeneralUppercase, // Percengage, // // other types // None, // String, // Debug, // }