codebiber 0.1.0

Library for mixing handwritten and autogenerated code.
Documentation
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Indentation(pub usize);

impl Indentation
{
  pub fn indent_str(self, text: &str) -> String
  {
    indent_lines(text, self.0)
  }

  pub fn unindent_str(self, text: &str) -> Result<String, Unindent_Error>
  {
    unindent_lines(text, self.0)
  }

  pub fn parse(code: &mut &str) -> Self
  {
    let index = code.find(|x| x!=' ').unwrap_or(code.len());
    *code = &(*code)[index..];
    return Self(index);
  }
}

fn indent_lines(input: &str, indentation: usize) -> String
{
  let mut output = String::with_capacity((input.len()+1)*(indentation+1));

  let indentation = std::iter::repeat(' ').take(indentation);
  for line in input.lines()
  {
    if !line.is_empty()
    {
      output.extend(indentation.clone());
      output.push_str(line);
    }
    output.push('\n');
  }

  output
}

fn unindent_lines(input: &str, indentation: usize) -> Result<String, Unindent_Error>
{
  let mut output = String::with_capacity(input.len());

  for line in input.lines()
  {
    let keep_after = line.len().min(indentation);
    if !line.as_bytes()[..keep_after].iter().all(|&x| x==b' ')
    { return Err(Unindent_Error::NON_WS_IN_INDENTATION) }
    output.push_str(&line[keep_after..]);
    output.push('\n');
  }

  Ok(output)
}

impl fmt::Display for Indentation
{
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
  {
    for _ in 0..self.0
    {
      f.write_char(' ')?;
    }
    Ok(())
  }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Unindent_Error
{
  NON_WS_IN_INDENTATION,
}

impl fmt::Display for Unindent_Error
{
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
  {
    use Unindent_Error::*;
    match self
    {
    NON_WS_IN_INDENTATION => write!(f, "Non whitespace character in indentation"),
    }
  }
}

pub fn ensure_tailing_linebreak(mut xs: String) -> String
{
  if !xs.is_empty() && !xs.ends_with('\n')
  {
    xs.push('\n');
  }

  xs
}

#[cfg(test)]
mod test
{
  macro_rules! indent {
    ($i:expr, $str:expr) => {
      crate::indentation::Indentation($i).indent_str($str).as_str()
    };
  }

  macro_rules! unindent {
    ($i:expr, $str:expr) => {
      crate::indentation::Indentation($i).unindent_str($str).unwrap().as_str()
    };
  }

  macro_rules! assert_indent {
    ($i:expr, $unindented:expr, $indented:expr) => {
      assert_eq!(indent!($i, $unindented), $indented);
      assert_eq!(unindent!($i, $indented), crate::indentation::ensure_tailing_linebreak($unindented.to_owned()));
    };
  }

  #[test]
  fn test_trivial()
  {
    assert_indent!(0, "", "");
    assert_indent!(0, "x", "x\n");
    assert_indent!(0, "x\ny", "x\ny\n");
  }

  #[test]
  fn test_simpl()
  {
    assert_indent!(2, "", "");
    assert_indent!(2, "x", "  x\n");
    assert_indent!(2, "x\ny", "  x\n  y\n");
    assert_indent!(4, "Hello, World!", "    Hello, World!\n");
  }

  #[test]
  fn test_with_lienbreak()
  {
    assert_indent!(2, "x\ny\nz", "  x\n  y\n  z\n");
  }

  #[test]
  fn test_dont_add_trailing_whitespace()
  {
    assert_indent!(2, "x\n\n\ny", "  x\n\n\n  y\n");
    assert_indent!(2, "x\n\ny\n\n\nz", "  x\n\n  y\n\n\n  z\n");
    assert_indent!(2, "x\n", "  x\n");
  }

  #[test]
  fn test_difficult_cases()
  {
    assert_indent!(2, "\nx", "\n  x\n");
  }

  #[test]
  fn test_unindent_invalid_indentation()
  {
    assert_eq!(crate::indentation::Indentation(2).unindent_str("xyz"), Err(crate::indentation::Unindent_Error::NON_WS_IN_INDENTATION));
    assert_eq!(crate::indentation::Indentation(2).unindent_str(" \n").unwrap(), "\n");
  }

  #[test]
  fn test_parse_indent()
  {
    fn parse(mut code: &str) -> (usize, &str)
    {
      use crate::indentation::Indentation;
      let Indentation(i) = Indentation::parse(&mut code);
      return (i, code);
    }

    assert_eq!(parse(""), (0, ""));
    assert_eq!(parse(" "), (1, ""));
    assert_eq!(parse("   x"), (3, "x"));
  }
}

use std::fmt::{self, Write};