use std::fmt;
use std::io::{Error, ErrorKind, Write};
use std::str;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use super::format::Alignment;
#[cfg(any(not(windows), not(feature = "win_crlf")))]
pub static NEWLINE: &[u8] = b"\n";
#[cfg(all(windows, feature = "win_crlf"))]
pub static NEWLINE: &[u8] = b"\r\n";
pub struct StringWriter {
string: String,
}
impl StringWriter {
pub fn new() -> StringWriter {
StringWriter {
string: String::new(),
}
}
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> {
Ok(())
}
}
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(())
}
pub fn display_width(text: &str) -> usize {
#[derive(PartialEq, Eq, Clone, Copy)]
enum State {
Normal,
EscapeChar,
OpenBracket,
AfterEscape,
}
let width = UnicodeWidthStr::width(text);
let mut state = State::Normal;
let mut hidden = 0;
for c in text.chars() {
state = match (state, c) {
(State::Normal, '\u{1b}') => State::EscapeChar,
(State::EscapeChar, '[') => State::OpenBracket,
(State::EscapeChar, _) => State::Normal,
(State::OpenBracket, 'm') => State::AfterEscape,
_ => state,
};
if matches!(state, State::OpenBracket | State::AfterEscape) {
if UnicodeWidthChar::width(c).unwrap_or(0) > 0 {
hidden += 1;
}
}
if state == State::AfterEscape {
state = State::Normal;
}
}
assert!(
width >= hidden,
"internal error: width {} less than hidden {} on string {:?}",
width,
hidden,
text
);
width - hidden
}
pub struct HtmlEscape<'a>(pub &'a str);
impl<'a> fmt::Display for HtmlEscape<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let HtmlEscape(s) = *self;
let pile_o_bits = s;
let mut last = 0;
for (i, ch) in s.bytes().enumerate() {
match ch as char {
'<' | '>' | '&' | '\'' | '"' => {
fmt.write_str(&pile_o_bits[last..i])?;
let s = match ch as char {
'>' => ">",
'<' => "<",
'&' => "&",
'\'' => "'",
'"' => """,
_ => unreachable!(),
};
fmt.write_str(s)?;
last = i + 1;
}
_ => {}
}
}
if last < s.len() {
fmt.write_str(&pile_o_bits[last..])?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::Alignment;
use std::io::Write;
#[test]
fn string_writer() {
let mut out = StringWriter::new();
out.write_all(b"foo").unwrap();
out.write_all(b" ").unwrap();
out.write_all(b"").unwrap();
out.write_all(b"bar").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(&[0, 255]);
assert!(res.is_err());
}
}