blob: 79e3be86494b5d562292a4ca4a2f139ec45ba408 [file] [log] [blame]
//! Internal only utilities
use std::io::{Error, ErrorKind, Write};
use std::str;
use unicode_width::UnicodeWidthStr;
use super::format::Alignment;
#[cfg(any(not(windows), not(feature="win_crlf")))]
pub static NEWLINE: &'static [u8] = b"\n";
#[cfg(all(windows, feature="win_crlf"))]
pub static NEWLINE: &'static [u8] = b"\r\n";
/// Internal utility for writing data into a string
pub struct StringWriter {
string: String,
}
impl StringWriter {
/// Create a new `StringWriter`
pub fn new() -> StringWriter {
StringWriter { string: String::new() }
}
/// Return a reference to the internally written `String`
pub fn as_string(&self) -> &str {
&self.string
}
}
impl Write for StringWriter {
fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
let string = match str::from_utf8(data) {
Ok(s) => s,
Err(e) => {
return Err(Error::new(ErrorKind::Other,
format!("Cannot decode utf8 string : {}", e)))
}
};
self.string.push_str(string);
Ok(data.len())
}
fn flush(&mut self) -> Result<(), Error> {
// Nothing to do here
Ok(())
}
}
/// Align/fill a string and print it to `out`
/// If `skip_right_fill` is set to `true`, then no space will be added after the string
/// to complete alignment
pub fn print_align<T: Write + ?Sized>(out: &mut T,
align: Alignment,
text: &str,
fill: char,
size: usize,
skip_right_fill: bool)
-> Result<(), Error> {
let text_len = display_width(text);
let mut nfill = if text_len < size { size - text_len } else { 0 };
let n = match align {
Alignment::LEFT => 0,
Alignment::RIGHT => nfill,
Alignment::CENTER => nfill / 2,
};
if n > 0 {
out.write_all(&vec![fill as u8; n])?;
nfill -= n;
}
out.write_all(text.as_bytes())?;
if nfill > 0 && !skip_right_fill {
out.write_all(&vec![fill as u8; nfill])?;
}
Ok(())
}
/// Return the display width of a unicode string.
/// This functions takes ANSI-escaped color codes into account.
pub fn display_width(text: &str) -> usize {
let width = UnicodeWidthStr::width(text);
let mut state = 0;
let mut hidden = 0;
for c in text.chars() {
state = match (state, c) {
(0, '\u{1b}') => 1,
(1, '[') => 2,
(1, _) => 0,
(2, 'm') => 3,
_ => state,
};
// We don't count escape characters as hidden as
// UnicodeWidthStr::width already considers them.
if state > 1 {
hidden += 1;
}
if state == 3 {
state = 0;
}
}
width - hidden
}
#[cfg(test)]
mod tests {
use super::*;
use format::Alignment;
use std::io::Write;
#[test]
fn string_writer() {
let mut out = StringWriter::new();
out.write("foo".as_bytes()).unwrap();
out.write(" ".as_bytes()).unwrap();
out.write("".as_bytes()).unwrap();
out.write("bar".as_bytes()).unwrap();
assert_eq!(out.as_string(), "foo bar");
}
#[test]
fn fill_align() {
let mut out = StringWriter::new();
print_align(&mut out, Alignment::RIGHT, "foo", '*', 10, false).unwrap();
assert_eq!(out.as_string(), "*******foo");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::LEFT, "foo", '*', 10, false).unwrap();
assert_eq!(out.as_string(), "foo*******");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::CENTER, "foo", '*', 10, false).unwrap();
assert_eq!(out.as_string(), "***foo****");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::CENTER, "foo", '*', 1, false).unwrap();
assert_eq!(out.as_string(), "foo");
}
#[test]
fn skip_right_fill() {
let mut out = StringWriter::new();
print_align(&mut out, Alignment::RIGHT, "foo", '*', 10, true).unwrap();
assert_eq!(out.as_string(), "*******foo");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::LEFT, "foo", '*', 10, true).unwrap();
assert_eq!(out.as_string(), "foo");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::CENTER, "foo", '*', 10, true).unwrap();
assert_eq!(out.as_string(), "***foo");
let mut out = StringWriter::new();
print_align(&mut out, Alignment::CENTER, "foo", '*', 1, false).unwrap();
assert_eq!(out.as_string(), "foo");
}
#[test]
fn utf8_error() {
let mut out = StringWriter::new();
let res = out.write_all(&vec![0, 255]);
assert!(res.is_err());
}
}