use std::fmt;
use std::io::{self, Write};
use std::thread;
const TAB: &str = " ";
#[derive(Debug)]
pub struct Context<W: Write> {
writer: W,
indent_level: usize,
pending_line_indents: usize,
}
impl<W: Write> Context<W> {
#[inline]
pub fn new(writer: W) -> Self {
Context {
writer: writer,
indent_level: 0,
pending_line_indents: 0,
}
}
}
impl<W: Write> Drop for Context<W> {
fn drop(&mut self) {
if thread::panicking() {
return;
}
if self.indent_level > 0 {
panic!(format!("Dropping a Context with non-zero indent level (got {})",
self.indent_level));
}
}
}
impl<W: Write> Context<W> {
pub fn begin(&mut self, head: &str) -> io::Result<&mut Self> {
writeln!(self.writer, "{}", head)?;
self.indent();
Ok(self)
}
pub fn dedent(&mut self) -> &mut Self {
if self.indent_level == 0 {
panic!("Context::dedent() with no indentation");
}
self.indent_level -= 1;
if self.pending_line_indents > 0 {
self.pending_line_indents = self.indent_level;
}
self
}
pub fn end(&mut self, footer: &str) -> io::Result<&mut Self> {
self.dedent();
writeln!(self.writer, "{}", footer)?;
Ok(self)
}
pub fn indent(&mut self) -> &mut Self {
self.indent_level += 1;
self.pending_line_indents += 1;
self
}
}
impl<W: Write> Context<W> {
pub fn emit<L: AsRef<str>>(&mut self, line: L) -> io::Result<&mut Self> {
let mut line = line.as_ref();
if line.ends_with("\n") && !line.ends_with("\n\n") {
line = line.trim_right_matches("\n");
}
if line.contains('\n') {
panic!(format!("Context::emit() got a string with newline: {:?}", line));
}
while self.pending_line_indents > 0 {
write!(self.writer, "{}", TAB)?;
self.pending_line_indents -= 1;
}
writeln!(self.writer, "{}\n", line)?;
self.pending_line_indents = self.indent_level;
Ok(self)
}
pub fn emit_fmt(&mut self, args: fmt::Arguments) -> io::Result<&mut Self> {
self.emit(format!("{}", args))
}
}
macro_rules! emit {
($ctx:expr, $fmt:expr, $($args:tt)*) => (
$ctx.emit_fmt(format_args!($fmt, $($args)*))
);
}