strfmt 0.2.5

strfmt: rust library for formatting dynamic strings
Documentation
use 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<I>(fmt: &mut Formatter, f: I, n: usize) -> usize
where
    I: Iterator<Item = char>,
{
    // 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<F>(fmtstr: &str, f: F) -> Result<String>
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)
}