indented 0.1.0

Format data with indentation
Documentation
// Copyright 2018 Peter James Delevoryas
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//    http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt;

pub fn indented<D: fmt::Display>(data: D) -> Indented<D, Space4> {
    Indented { data, indent: Space4 }
}

pub fn indented_with<D, I>(data: D, indent: I) -> Indented<D, I>
where
    D: fmt::Display,
    I: Indent,
{
    Indented { data, indent }
}

pub struct Indented<D, I>
where
    D: fmt::Display,
    I: Indent,
{
    data: D,
    indent: I,
}

impl<D, I> fmt::Display for Indented<D, I>
where
    D: fmt::Display,
    I: Indent,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut f = IndentedFormatter::new(f, &self.indent);
        fmt::write(&mut f, format_args!("{}", self.data))?;
        Ok(())
    }
}

/// Write an indent.
pub trait Indent {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result;
}

impl Indent for str {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_str(self)
    }
}

impl<'a, T: Indent + ?Sized> Indent for &'a T {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        (*self).indent(output)
    }
}

impl Indent for char {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_char(*self)
    }
}

/// 2 spaces
pub struct Space2;

impl Indent for Space2 {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_str("  ")
    }
}

/// 4 spaces
pub struct Space4;

impl Indent for Space4 {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_str("    ")
    }
}

/// 8 spaces
pub struct Space8;

impl Indent for Space8 {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_str("        ")
    }
}

/// Tab character
pub struct Tab;

impl Indent for Tab {
    fn indent<W: fmt::Write>(&self, output: &mut W) -> fmt::Result {
        output.write_char('\t')
    }
}

struct IndentedFormatter<'a, W, I>
where
    W: 'a + fmt::Write + ?Sized,
    I: 'a + Indent + ?Sized,
{
    /// Currently at the start of a newline
    newline: bool,
    output: &'a mut W,
    indent: &'a I,
}

impl<'a, W, I> IndentedFormatter<'a, W, I>
where
    W: 'a + fmt::Write + ?Sized,
    I: 'a + Indent + ?Sized,
{
    fn new(output: &'a mut W, indent: &'a I) -> Self {
        IndentedFormatter {
            // assume newline at start
            newline: true,
            output,
            indent,
        }
    }
}

impl<'a, W, I> fmt::Write for IndentedFormatter<'a, W, I>
where
    W: 'a + fmt::Write,
    I: 'a + Indent + ?Sized,
{
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            if c == '\n' {
                self.output.write_char(c)?;
                self.newline = true;
                continue
            }
            if self.newline {
                self.indent.indent(self.output)?;
            }
            self.output.write_char(c)?;
            self.newline = false;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::indented;

    #[test]
    fn single_line() {
        assert_eq!(format!("{}", indented("hello")), "    hello");
    }
}