use crate::Lang;
use std::fmt;
use std::io;
use std::iter;
#[derive(Clone)]
pub struct FormatterConfig {
indentation: usize,
newline: &'static str,
}
impl FormatterConfig {
pub fn from_lang<L>() -> FormatterConfig
where
L: Lang,
{
Self {
indentation: L::default_indentation(),
newline: "\n",
}
}
pub fn with_indentation(self, indentation: usize) -> Self {
Self {
indentation,
..self
}
}
pub fn with_newline(self, newline: &'static str) -> Self {
Self { newline, ..self }
}
#[inline(always)]
pub fn newline(&self) -> &str {
self.newline
}
}
pub trait Write: fmt::Write {
fn write_line(&mut self, config: &FormatterConfig) -> fmt::Result;
}
pub struct FmtWriter<W>
where
W: fmt::Write,
{
writer: W,
}
impl<W> FmtWriter<W>
where
W: fmt::Write,
{
pub fn new(writer: W) -> Self {
Self { writer }
}
pub fn into_writer(self) -> W {
self.writer
}
}
impl<W> fmt::Write for FmtWriter<W>
where
W: fmt::Write,
{
#[inline(always)]
fn write_char(&mut self, c: char) -> fmt::Result {
self.writer.write_char(c)
}
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.writer.write_str(s)
}
}
impl<W> Write for FmtWriter<W>
where
W: fmt::Write,
{
#[inline(always)]
fn write_line(&mut self, config: &FormatterConfig) -> fmt::Result {
self.writer.write_str(config.newline())
}
}
pub struct IoWriter<W>
where
W: io::Write,
{
writer: W,
}
impl<W> IoWriter<W>
where
W: io::Write,
{
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl<W> fmt::Write for IoWriter<W>
where
W: io::Write,
{
#[inline(always)]
fn write_char(&mut self, c: char) -> fmt::Result {
self.writer
.write_all(c.encode_utf8(&mut [0; 4]).as_bytes())
.map_err(|_| fmt::Error)
}
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.writer.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
}
impl<W> Write for IoWriter<W>
where
W: io::Write,
{
#[inline(always)]
fn write_line(&mut self, config: &FormatterConfig) -> fmt::Result {
self.writer
.write_all(config.newline().as_bytes())
.map_err(|_| fmt::Error)
}
}
#[derive(Default)]
pub struct VecWriter {
line_buffer: String,
target: Vec<String>,
}
impl VecWriter {
pub fn new() -> Self {
Self::default()
}
pub fn into_vec(mut self) -> Vec<String> {
self.target.push(self.line_buffer);
self.target
}
}
impl fmt::Write for VecWriter {
#[inline(always)]
fn write_char(&mut self, c: char) -> fmt::Result {
self.line_buffer.write_char(c)
}
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.line_buffer.write_str(s)
}
}
impl Write for VecWriter {
#[inline(always)]
fn write_line(&mut self, _: &FormatterConfig) -> fmt::Result {
self.target.push(self.line_buffer.clone());
self.line_buffer.clear();
Ok(())
}
}
pub struct Formatter<'write> {
write: &'write mut dyn Write,
current_line_empty: bool,
indent: usize,
pub(crate) config: FormatterConfig,
buffer: String,
}
impl<'write> Formatter<'write> {
pub fn new(write: &mut dyn Write, config: FormatterConfig) -> Formatter {
Formatter {
write: write,
current_line_empty: true,
indent: 0usize,
config,
buffer: String::from(" "),
}
}
fn check_indent(&mut self) -> fmt::Result {
if self.current_line_empty && self.indent > 0 {
self.write
.write_str(&self.buffer[0..(self.indent * self.config.indentation)])?;
self.current_line_empty = false;
}
Ok(())
}
pub fn write_str(&mut self, s: &str) -> fmt::Result {
if s.len() > 0 {
self.check_indent()?;
self.write.write_str(s)?;
self.current_line_empty = false;
}
Ok(())
}
pub fn new_line(&mut self) -> fmt::Result {
self.write.write_line(&self.config)?;
self.current_line_empty = true;
Ok(())
}
pub fn new_line_unless_empty(&mut self) -> fmt::Result {
if !self.current_line_empty {
self.new_line()?;
}
Ok(())
}
pub fn indent(&mut self) {
self.indent += 1;
let extra = (self.indent * self.config.indentation).saturating_sub(self.buffer.len());
for c in iter::repeat(' ').take(extra) {
self.buffer.push(c);
}
}
pub fn unindent(&mut self) {
self.indent = self.indent.saturating_sub(1);
}
}
impl<'write> fmt::Write for Formatter<'write> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s.len() > 0 {
self.check_indent()?;
self.write.write_str(s)?;
self.current_line_empty = false;
}
Ok(())
}
}