use crate::DomainEncode; use crate::ValueImpl; use crate::Writer; use crate::hex::HexFormatter; use lazy_static::lazy_static; use num_bigint::BigInt; use std::io; use super::super::boundary as B; #[derive(Clone, Copy, Debug)] pub enum CommaStyle { None, Separating, Terminating, } pub struct TextWriter { w: W, pub comma_style: CommaStyle, pub indentation: usize, pub escape_spaces: bool, indent: String, } impl std::default::Default for CommaStyle { fn default() -> Self { CommaStyle::Separating } } impl TextWriter<&mut Vec> { pub fn fmt_value<'de, V: ValueImpl<'de>, Enc: DomainEncode<'de, V::Embedded>>( f: &mut std::fmt::Formatter<'_>, enc: &mut Enc, v: &V, ) -> io::Result<()> { let mut buf: Vec = Vec::new(); let mut w = TextWriter::new(&mut buf); if f.alternate() { w.indentation = 4 } v.write(&mut w, enc)?; f.write_str(std::str::from_utf8(&buf).expect("valid UTF-8 from TextWriter")).map_err( |_| io::Error::new(io::ErrorKind::Other, "could not append to Formatter")) } pub fn encode<'de, V: ValueImpl<'de>, Enc: DomainEncode<'de, V::Embedded>>( enc: &mut Enc, v: &V, ) -> io::Result { let mut buf: Vec = Vec::new(); v.write(&mut TextWriter::new(&mut buf), enc)?; Ok(String::from_utf8(buf).expect("valid UTF-8 from TextWriter")) } } impl TextWriter { pub fn new(w: W) -> Self { TextWriter { w, comma_style: CommaStyle::default(), indentation: 0, escape_spaces: false, indent: "\n".to_owned(), } } pub fn set_comma_style(mut self, v: CommaStyle) -> Self { self.comma_style = v; self } pub fn set_escape_spaces(mut self, v: bool) -> Self { self.escape_spaces = v; self } pub fn write_stringlike_char_fallback( &mut self, c: char, f: F, ) -> io::Result<()> where F: FnOnce(&mut W, char) -> io::Result<()> { match c { '\\' => write!(self.w, "\\\\"), '\x08' => write!(self.w, "\\b"), '\x0c' => write!(self.w, "\\f"), '\x0a' => write!(self.w, "\\n"), '\x0d' => write!(self.w, "\\r"), '\x09' => write!(self.w, "\\t"), _ => f(&mut self.w, c), } } pub fn write_stringlike_char(&mut self, c: char) -> io::Result<()> { self.write_stringlike_char_fallback(c, |w, c| write!(w, "{}", c)) } pub fn add_indent(&mut self) { for _ in 0 .. self.indentation { self.indent.push(' ') } } pub fn del_indent(&mut self) { if self.indentation > 0 { self.indent.truncate(self.indent.len() - self.indentation) } } pub fn indent(&mut self) -> io::Result<()> { if self.indentation > 0 { write!(self.w, "{}", &self.indent) } else { Ok(()) } } pub fn indent_sp(&mut self) -> io::Result<()> { if self.indentation > 0 { write!(self.w, "{}", &self.indent) } else { write!(self.w, " ") } } pub fn borrow_write(&mut self) -> &mut W { &mut self.w } } impl Writer for TextWriter { #[inline] fn boundary(&mut self, b: &B::Type) -> io::Result<()> { match (b.closing.as_ref(), b.opening.as_ref()) { (None, Some(B::Item::RecordLabel)) | (Some(B::Item::RecordLabel), None) | (Some(B::Item::RecordField), None) => return Ok(()), (_, Some(B::Item::RecordField)) => return write!(self.w, " "), (Some(B::Item::DictionaryKey), Some(B::Item::DictionaryValue)) => { return write!(self.w, ": ") } (None, Some(B::Item::Annotation)) => { return write!(self.w, "@") } (Some(_), Some(B::Item::Annotation)) => { return write!(self.w, " @") } (Some(B::Item::Annotation), Some(B::Item::AnnotatedValue)) => { return write!(self.w, " ") } (None, Some(B::Item::AnnotatedValue)) | // ^ strictly speaking, this combination is not permitted; a ValueImpl that yields // a zero-length vector of annotations instead of `None` is in error. (Some(B::Item::AnnotatedValue), None) => return Ok(()), _ => (), } match (b.closing.as_ref(), b.opening.as_ref()) { (None, None) => (), (None, Some(_)) => { self.add_indent(); self.indent()? }, (Some(_), Some(_)) => { match self.comma_style { CommaStyle::Separating | CommaStyle::Terminating => write!(self.w, ",")?, CommaStyle::None => (), } self.indent_sp()? } (Some(_), None) => { match self.comma_style { CommaStyle::Terminating => write!(self.w, ",")?, CommaStyle::Separating | CommaStyle::None => (), } self.del_indent(); self.indent()? } } Ok(()) } fn start_annotations(&mut self) -> io::Result<()> { Ok(()) } fn end_annotations(&mut self) -> io::Result<()> { Ok(()) } fn write_bool(&mut self, v: bool) -> io::Result<()> { write!(self.w, "{}", if v { "#t" } else { "#f" }) } fn write_f32(&mut self, v: f32) -> io::Result<()> { if v.is_nan() || v.is_infinite() { write!(self.w, "#xf\"{}\"", HexFormatter::Packed.encode(&u32::to_be_bytes(f32::to_bits(v)))) } else { dtoa::write(&mut self.w, v)?; write!(self.w, "f") } } fn write_f64(&mut self, v: f64) -> io::Result<()> { if v.is_nan() || v.is_infinite() { write!(self.w, "#xd\"{}\"", HexFormatter::Packed.encode(&u64::to_be_bytes(f64::to_bits(v)))) } else { dtoa::write(&mut self.w, v)?; Ok(()) } } fn write_i128(&mut self, v: i128) -> io::Result<()> { write!(self.w, "{}", v) } fn write_u128(&mut self, v: u128) -> io::Result<()> { write!(self.w, "{}", v) } fn write_int(&mut self, v: &BigInt) -> io::Result<()> { write!(self.w, "{}", v) } fn write_string(&mut self, v: &str) -> io::Result<()> { write!(self.w, "\"")?; for c in v.chars() { match c { '"' => write!(self.w, "\\\"")?, ' ' if self.escape_spaces => write!(self.w, "\\u0020")?, _ => self.write_stringlike_char(c)?, } } write!(self.w, "\"") } fn write_bytes(&mut self, v: &[u8]) -> io::Result<()> { write!(self.w, "#[{}]", base64::encode_config(v, base64::STANDARD_NO_PAD)) } fn write_symbol(&mut self, v: &str) -> io::Result<()> { lazy_static! { // FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic. static ref RE: regex::Regex = regex::Regex::new("^[-a-zA-Z0-9~!$%^&*?_=+/.]+$").unwrap(); } if RE.is_match(v) { write!(self.w, "{}", v) } else { write!(self.w, "|")?; for c in v.chars() { match c { '|' => write!(self.w, "\\|")?, ' ' if self.escape_spaces => write!(self.w, "\\u0020")?, _ => self.write_stringlike_char(c)?, } } write!(self.w, "|") } } fn start_record(&mut self) -> io::Result<()> { write!(self.w, "<") } fn end_record(&mut self) -> io::Result<()> { write!(self.w, ">") } fn start_sequence(&mut self) -> io::Result<()> { write!(self.w, "[") } fn end_sequence(&mut self) -> io::Result<()> { write!(self.w, "]") } fn start_set(&mut self) -> io::Result<()> { write!(self.w, "#{{") } fn end_set(&mut self) -> io::Result<()> { write!(self.w, "}}") } fn start_dictionary(&mut self) -> io::Result<()> { write!(self.w, "{{") } fn end_dictionary(&mut self) -> io::Result<()> { write!(self.w, "}}") } fn start_embedded(&mut self) -> io::Result<()> { write!(self.w, "#!") } fn end_embedded(&mut self) -> io::Result<()> { Ok(()) } fn flush(&mut self) -> io::Result<()> { self.w.flush() } }