Indented printing

This commit is contained in:
Tony Garnock-Jones 2021-08-02 12:53:17 +02:00
parent af1405e87a
commit 3176e5f8d0
2 changed files with 126 additions and 66 deletions

View File

@ -15,7 +15,12 @@ pub enum CommaStyle {
Terminating, Terminating,
} }
pub struct TextWriter<W: io::Write>(Suspendable<W>, CommaStyle); pub struct TextWriter<W: io::Write> {
w: Suspendable<W>,
pub comma_style: CommaStyle,
pub indentation: usize,
indent: String,
}
impl std::default::Default for CommaStyle { impl std::default::Default for CommaStyle {
fn default() -> Self { fn default() -> Self {
@ -25,15 +30,20 @@ impl std::default::Default for CommaStyle {
impl<W: io::Write> TextWriter<W> { impl<W: io::Write> TextWriter<W> {
pub fn new(w: W) -> Self { pub fn new(w: W) -> Self {
TextWriter(Suspendable::new(w), CommaStyle::default()) TextWriter {
w: Suspendable::new(w),
comma_style: CommaStyle::default(),
indentation: 0,
indent: "\n".to_owned(),
}
} }
pub fn suspend(&mut self) -> Self { pub fn suspend(&mut self) -> Self {
TextWriter(self.0.suspend(), self.1) TextWriter { w: self.w.suspend(), indent: self.indent.clone(), .. *self }
} }
pub fn resume(&mut self, other: Self) { pub fn resume(&mut self, other: Self) {
self.0.resume(other.0) self.w.resume(other.w)
} }
pub fn write_stringlike_char_fallback<F>( pub fn write_stringlike_char_fallback<F>(
@ -44,68 +54,109 @@ impl<W: io::Write> TextWriter<W> {
F: FnOnce(&mut W, char) -> io::Result<()> F: FnOnce(&mut W, char) -> io::Result<()>
{ {
match c { match c {
'\\' => write!(self.0, "\\\\"), '\\' => write!(self.w, "\\\\"),
'\x08' => write!(self.0, "\\b"), '\x08' => write!(self.w, "\\b"),
'\x0c' => write!(self.0, "\\f"), '\x0c' => write!(self.w, "\\f"),
'\x0a' => write!(self.0, "\\n"), '\x0a' => write!(self.w, "\\n"),
'\x0d' => write!(self.0, "\\r"), '\x0d' => write!(self.w, "\\r"),
'\x09' => write!(self.0, "\\t"), '\x09' => write!(self.w, "\\t"),
_ => f(&mut self.0, c), _ => f(&mut self.w, c),
} }
} }
pub fn write_stringlike_char(&mut self, c: char) -> io::Result<()> { pub fn write_stringlike_char(&mut self, c: char) -> io::Result<()> {
self.write_stringlike_char_fallback(c, |w, c| write!(w, "{}", c)) 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, " ")
}
}
} }
impl<W: io::Write> CompoundWriter for TextWriter<W> { impl<W: io::Write> CompoundWriter for TextWriter<W> {
fn boundary(&mut self, b: &B::Type) -> io::Result<()> { fn boundary(&mut self, b: &B::Type) -> io::Result<()> {
match (b.closing.as_ref(), b.opening.as_ref()) { match (b.closing.as_ref(), b.opening.as_ref()) {
(None, Some(B::Item::Annotation)) => (None, Some(B::Item::RecordLabel)) |
write!(self.0, "@"), (Some(B::Item::RecordLabel), None) |
(Some(_), Some(B::Item::Annotation)) => (Some(B::Item::RecordField), None) =>
write!(self.0, " @"), return Ok(()),
(Some(B::Item::Annotation), Some(B::Item::AnnotatedValue)) => (_, Some(B::Item::RecordField)) =>
write!(self.0, " "), return write!(self.w, " "),
(Some(B::Item::DictionaryKey), Some(B::Item::DictionaryValue)) => (Some(B::Item::DictionaryKey), Some(B::Item::DictionaryValue)) => {
write!(self.0, ": "), return write!(self.w, ": ")
}
(Some(B::Item::RecordLabel), Some(B::Item::RecordField)) | (None, Some(B::Item::Annotation)) => {
(Some(B::Item::RecordField), Some(B::Item::RecordField)) => return write!(self.w, "@")
write!(self.0, " "), }
(Some(_), Some(B::Item::Annotation)) => {
return write!(self.w, " @")
}
(Some(B::Item::Annotation), Some(B::Item::AnnotatedValue)) => {
return write!(self.w, " ")
}
(Some(B::Item::AnnotatedValue), None) =>
return Ok(()),
(Some(B::Item::DictionaryValue), Some(B::Item::DictionaryKey)) | _ => (),
(Some(B::Item::SequenceValue), Some(B::Item::SequenceValue)) |
(Some(B::Item::SetValue), Some(B::Item::SetValue)) =>
match self.1 {
CommaStyle::Separating | CommaStyle::Terminating =>
write!(self.0, ", "),
CommaStyle::None =>
write!(self.0, " "),
},
(Some(B::Item::DictionaryValue), None) |
(Some(B::Item::SequenceValue), None) |
(Some(B::Item::SetValue), None) =>
match self.1 {
CommaStyle::Terminating =>
write!(self.0, ","),
CommaStyle::Separating | CommaStyle::None =>
Ok(()),
},
_ =>
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(())
} }
} }
macro_rules! simple_writer_method { macro_rules! simple_writer_method {
($n:ident, $argty:ty) => ($n:ident, $argty:ty) =>
(fn $n (&mut self, v: $argty) -> io::Result<()> { (fn $n (&mut self, v: $argty) -> io::Result<()> {
write!(self.0, "{}", v) write!(self.w, "{}", v)
}); });
} }
@ -127,16 +178,16 @@ impl<W: io::Write> Writer for TextWriter<W> {
} }
fn write_bool(&mut self, v: bool) -> io::Result<()> { fn write_bool(&mut self, v: bool) -> io::Result<()> {
write!(self.0, "{}", if v { "#t" } else { "#f" }) write!(self.w, "{}", if v { "#t" } else { "#f" })
} }
fn write_f32(&mut self, v: f32) -> io::Result<()> { fn write_f32(&mut self, v: f32) -> io::Result<()> {
dtoa::write(&mut *self.0, v)?; dtoa::write(&mut *self.w, v)?;
write!(self.0, "f") write!(self.w, "f")
} }
fn write_f64(&mut self, v: f64) -> io::Result<()> { fn write_f64(&mut self, v: f64) -> io::Result<()> {
dtoa::write(&mut *self.0, v)?; dtoa::write(&mut *self.w, v)?;
Ok(()) Ok(())
} }
@ -153,79 +204,79 @@ impl<W: io::Write> Writer for TextWriter<W> {
simple_writer_method!(write_int, &BigInt); simple_writer_method!(write_int, &BigInt);
fn write_string(&mut self, v: &str) -> io::Result<()> { fn write_string(&mut self, v: &str) -> io::Result<()> {
write!(self.0, "\"")?; write!(self.w, "\"")?;
for c in v.chars() { for c in v.chars() {
match c { match c {
'"' => write!(self.0, "\\\"")?, '"' => write!(self.w, "\\\"")?,
_ => self.write_stringlike_char(c)?, _ => self.write_stringlike_char(c)?,
} }
} }
write!(self.0, "\"") write!(self.w, "\"")
} }
fn write_bytes(&mut self, v: &[u8]) -> io::Result<()> { fn write_bytes(&mut self, v: &[u8]) -> io::Result<()> {
write!(self.0, "#[{}]", base64::encode_config(v, base64::STANDARD_NO_PAD)) write!(self.w, "#[{}]", base64::encode_config(v, base64::STANDARD_NO_PAD))
} }
fn write_symbol(&mut self, v: &str) -> io::Result<()> { fn write_symbol(&mut self, v: &str) -> io::Result<()> {
// FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic. // FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic.
let re = regex::Regex::new("^[a-zA-Z~!$%^&*?_=+/.][-a-zA-Z~!$%^&*?_=+/.0-9]*$").unwrap(); let re = regex::Regex::new("^[a-zA-Z~!$%^&*?_=+/.][-a-zA-Z~!$%^&*?_=+/.0-9]*$").unwrap();
if re.is_match(v) { if re.is_match(v) {
write!(self.0, "{}", v) write!(self.w, "{}", v)
} else { } else {
write!(self.0, "|")?; write!(self.w, "|")?;
for c in v.chars() { for c in v.chars() {
match c { match c {
'|' => write!(self.0, "\\|")?, '|' => write!(self.w, "\\|")?,
_ => self.write_stringlike_char(c)?, _ => self.write_stringlike_char(c)?,
} }
} }
write!(self.0, "|") write!(self.w, "|")
} }
} }
fn start_record(&mut self, _field_count: Option<usize>) -> io::Result<Self::RecWriter> { fn start_record(&mut self, _field_count: Option<usize>) -> io::Result<Self::RecWriter> {
write!(self.0, "<")?; write!(self.w, "<")?;
Ok(self.suspend()) Ok(self.suspend())
} }
fn end_record(&mut self, rec: Self::RecWriter) -> io::Result<()> { fn end_record(&mut self, rec: Self::RecWriter) -> io::Result<()> {
self.resume(rec); self.resume(rec);
write!(self.0, ">") write!(self.w, ">")
} }
fn start_sequence(&mut self, _item_count: Option<usize>) -> io::Result<Self::SeqWriter> { fn start_sequence(&mut self, _item_count: Option<usize>) -> io::Result<Self::SeqWriter> {
write!(self.0, "[")?; write!(self.w, "[")?;
Ok(self.suspend()) Ok(self.suspend())
} }
fn end_sequence(&mut self, seq: Self::SeqWriter) -> io::Result<()> { fn end_sequence(&mut self, seq: Self::SeqWriter) -> io::Result<()> {
self.resume(seq); self.resume(seq);
write!(self.0, "]") write!(self.w, "]")
} }
fn start_set(&mut self, _item_count: Option<usize>) -> io::Result<Self::SetWriter> { fn start_set(&mut self, _item_count: Option<usize>) -> io::Result<Self::SetWriter> {
write!(self.0, "#{{")?; write!(self.w, "#{{")?;
Ok(self.suspend()) Ok(self.suspend())
} }
fn end_set(&mut self, set: Self::SetWriter) -> io::Result<()> { fn end_set(&mut self, set: Self::SetWriter) -> io::Result<()> {
self.resume(set); self.resume(set);
write!(self.0, "}}") write!(self.w, "}}")
} }
fn start_dictionary(&mut self, _entry_count: Option<usize>) -> io::Result<Self::DictWriter> { fn start_dictionary(&mut self, _entry_count: Option<usize>) -> io::Result<Self::DictWriter> {
write!(self.0, "{{")?; write!(self.w, "{{")?;
Ok(self.suspend()) Ok(self.suspend())
} }
fn end_dictionary(&mut self, dict: Self::DictWriter) -> io::Result<()> { fn end_dictionary(&mut self, dict: Self::DictWriter) -> io::Result<()> {
self.resume(dict); self.resume(dict);
write!(self.0, "}}") write!(self.w, "}}")
} }
fn start_embedded(&mut self) -> io::Result<Self::EmbeddedWriter> { fn start_embedded(&mut self) -> io::Result<Self::EmbeddedWriter> {
write!(self.0, "#!")?; write!(self.w, "#!")?;
Ok(self.suspend()) Ok(self.suspend())
} }

View File

@ -67,7 +67,16 @@ fn decode_all(bytes: &'_ [u8]) -> io::Result<Vec<IOValue>> {
let s = String::from_utf8(bs).unwrap(); let s = String::from_utf8(bs).unwrap();
preserves::value::text::annotated_iovalue_from_str(&s)? preserves::value::text::annotated_iovalue_from_str(&s)?
}; };
let roundtripped_indented = {
let mut bs = Vec::new();
let mut w = preserves::value::TextWriter::new(&mut bs);
w.indentation = 4;
preserves::ser::to_writer(&mut w, &from_text)?;
let s = String::from_utf8(bs).unwrap();
preserves::value::text::annotated_iovalue_from_str(&s)?
};
assert_eq!(from_text, roundtripped); assert_eq!(from_text, roundtripped);
assert_eq!(from_text, roundtripped_indented);
Ok(()) Ok(())
} }