use std::cell::Cell;
use std::fmt;
use std::rc::Rc;
pub struct CodeWriter<W> {
writer: W,
indent_level: Rc<Cell<usize>>,
indent_string: String,
at_line_start: Cell<bool>,
}
impl<W: fmt::Write> CodeWriter<W> {
pub fn new(writer: W, indent_string: String) -> Self {
Self {
writer,
indent_level: Rc::new(Cell::new(0)),
indent_string,
at_line_start: Cell::new(true),
}
}
pub fn with_indent_spaces(writer: W, spaces: usize) -> Self {
Self::new(writer, " ".repeat(spaces))
}
pub fn write(&mut self, text: &str) -> fmt::Result {
if text.is_empty() {
return Ok(());
}
if self.at_line_start.get() && !text.trim().is_empty() {
for _ in 0..self.indent_level.get() {
self.writer.write_str(&self.indent_string)?;
}
self.at_line_start.set(false);
}
self.writer.write_str(text)
}
pub fn writeln(&mut self, text: &str) -> fmt::Result {
self.write(text)?;
self.writer.write_char('\n')?;
self.at_line_start.set(true);
Ok(())
}
pub fn blank_line(&mut self) -> fmt::Result {
self.writer.write_char('\n')?;
self.at_line_start.set(true);
Ok(())
}
pub fn indent(&mut self) -> IndentGuard {
self.indent_level.set(self.indent_level.get() + 1);
IndentGuard {
indent_level: Rc::clone(&self.indent_level),
}
}
pub fn comment(&mut self, comment_prefix: &str, text: &str) -> fmt::Result {
self.writeln(&format!("{} {}", comment_prefix, text))
}
pub fn doc_comment(&mut self, comment_prefix: &str, text: &str) -> fmt::Result {
for line in text.lines() {
self.writeln(&format!("{} {}", comment_prefix, line))?;
}
Ok(())
}
pub fn begin_block(&mut self, header: &str) -> Result<IndentGuard, fmt::Error> {
self.writeln(&format!("{} {{", header))?;
Ok(self.indent())
}
pub fn end_block(&mut self) -> fmt::Result {
self.writeln("}")
}
pub fn block<F>(&mut self, header: &str, body: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
self.writeln(&format!("{} {{", header))?;
{
let _indent = self.indent();
body(self)?;
}
self.writeln("}")
}
pub fn indent_level(&self) -> usize {
self.indent_level.get()
}
pub fn into_inner(self) -> W {
self.writer
}
pub fn inner(&self) -> &W {
&self.writer
}
pub fn inner_mut(&mut self) -> &mut W {
&mut self.writer
}
#[doc(hidden)]
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
let formatted = format!("{}", args);
self.write(&formatted)
}
#[doc(hidden)]
pub fn writeln_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
let formatted = format!("{}", args);
self.writeln(&formatted)
}
pub fn write_separated<I, F>(
&mut self,
items: I,
separator: &str,
mut write_item: F,
) -> fmt::Result
where
I: IntoIterator,
F: FnMut(&mut Self, I::Item) -> fmt::Result,
{
let mut first = true;
for item in items {
if !first {
self.write(separator)?;
}
write_item(self, item)?;
first = false;
}
Ok(())
}
pub fn write_separated_lines<I, F>(
&mut self,
items: I,
separator: &str,
mut write_item: F,
) -> fmt::Result
where
I: IntoIterator,
F: FnMut(&mut Self, I::Item) -> fmt::Result,
{
let mut first = true;
for item in items {
if !first {
self.writeln(separator)?;
}
write_item(self, item)?;
first = false;
}
Ok(())
}
pub fn write_if<F>(&mut self, condition: bool, f: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
if condition { f(self) } else { Ok(()) }
}
pub fn write_parens<F>(&mut self, f: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
self.write("(")?;
f(self)?;
self.write(")")
}
pub fn write_brackets<F>(&mut self, f: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
self.write("[")?;
f(self)?;
self.write("]")
}
pub fn write_angles<F>(&mut self, f: F) -> fmt::Result
where
F: FnOnce(&mut Self) -> fmt::Result,
{
self.write("<")?;
f(self)?;
self.write(">")
}
}
pub struct IndentGuard {
indent_level: Rc<Cell<usize>>,
}
impl Drop for IndentGuard {
fn drop(&mut self) {
let current = self.indent_level.get();
self.indent_level.set(current.saturating_sub(1));
}
}
#[macro_export]
macro_rules! cw_write {
($writer:expr, $($arg:tt)*) => {
$writer.write_fmt(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! cw_writeln {
($writer:expr, $($arg:tt)*) => {
$writer.writeln_fmt(format_args!($($arg)*))
};
}