codebiber/
indentation.rs

1#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2pub struct Indentation(pub usize);
3
4impl Indentation
5{
6  pub fn indent_str(self, text: &str) -> String
7  {
8    indent_lines(text, self.0)
9  }
10
11  pub fn unindent_str(self, text: &str) -> Result<String, Unindent_Error>
12  {
13    unindent_lines(text, self.0)
14  }
15
16  pub fn parse(code: &mut &str) -> Self
17  {
18    let index = code.find(|x| x!=' ').unwrap_or(code.len());
19    *code = &(*code)[index..];
20    return Self(index);
21  }
22}
23
24fn indent_lines(input: &str, indentation: usize) -> String
25{
26  let mut output = String::with_capacity((input.len()+1)*(indentation+1));
27
28  let indentation = std::iter::repeat(' ').take(indentation);
29  for line in input.lines()
30  {
31    if !line.is_empty()
32    {
33      output.extend(indentation.clone());
34      output.push_str(line);
35    }
36    output.push('\n');
37  }
38
39  output
40}
41
42fn unindent_lines(input: &str, indentation: usize) -> Result<String, Unindent_Error>
43{
44  let mut output = String::with_capacity(input.len());
45
46  for line in input.lines()
47  {
48    let keep_after = line.len().min(indentation);
49    if !line.as_bytes()[..keep_after].iter().all(|&x| x==b' ')
50    { return Err(Unindent_Error::NON_WS_IN_INDENTATION) }
51    output.push_str(&line[keep_after..]);
52    output.push('\n');
53  }
54
55  Ok(output)
56}
57
58impl fmt::Display for Indentation
59{
60  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
61  {
62    for _ in 0..self.0
63    {
64      f.write_char(' ')?;
65    }
66    Ok(())
67  }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum Unindent_Error
72{
73  NON_WS_IN_INDENTATION,
74}
75
76impl fmt::Display for Unindent_Error
77{
78  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
79  {
80    use Unindent_Error::*;
81    match self
82    {
83    NON_WS_IN_INDENTATION => write!(f, "Non whitespace character in indentation"),
84    }
85  }
86}
87
88pub fn ensure_tailing_linebreak(mut xs: String) -> String
89{
90  if !xs.is_empty() && !xs.ends_with('\n')
91  {
92    xs.push('\n');
93  }
94
95  xs
96}
97
98#[cfg(test)]
99mod test
100{
101  macro_rules! indent {
102    ($i:expr, $str:expr) => {
103      crate::indentation::Indentation($i).indent_str($str).as_str()
104    };
105  }
106
107  macro_rules! unindent {
108    ($i:expr, $str:expr) => {
109      crate::indentation::Indentation($i).unindent_str($str).unwrap().as_str()
110    };
111  }
112
113  macro_rules! assert_indent {
114    ($i:expr, $unindented:expr, $indented:expr) => {
115      assert_eq!(indent!($i, $unindented), $indented);
116      assert_eq!(unindent!($i, $indented), crate::indentation::ensure_tailing_linebreak($unindented.to_owned()));
117    };
118  }
119
120  #[test]
121  fn test_trivial()
122  {
123    assert_indent!(0, "", "");
124    assert_indent!(0, "x", "x\n");
125    assert_indent!(0, "x\ny", "x\ny\n");
126  }
127
128  #[test]
129  fn test_simpl()
130  {
131    assert_indent!(2, "", "");
132    assert_indent!(2, "x", "  x\n");
133    assert_indent!(2, "x\ny", "  x\n  y\n");
134    assert_indent!(4, "Hello, World!", "    Hello, World!\n");
135  }
136
137  #[test]
138  fn test_with_lienbreak()
139  {
140    assert_indent!(2, "x\ny\nz", "  x\n  y\n  z\n");
141  }
142
143  #[test]
144  fn test_dont_add_trailing_whitespace()
145  {
146    assert_indent!(2, "x\n\n\ny", "  x\n\n\n  y\n");
147    assert_indent!(2, "x\n\ny\n\n\nz", "  x\n\n  y\n\n\n  z\n");
148    assert_indent!(2, "x\n", "  x\n");
149  }
150
151  #[test]
152  fn test_difficult_cases()
153  {
154    assert_indent!(2, "\nx", "\n  x\n");
155  }
156
157  #[test]
158  fn test_unindent_invalid_indentation()
159  {
160    assert_eq!(crate::indentation::Indentation(2).unindent_str("xyz"), Err(crate::indentation::Unindent_Error::NON_WS_IN_INDENTATION));
161    assert_eq!(crate::indentation::Indentation(2).unindent_str(" \n").unwrap(), "\n");
162  }
163
164  #[test]
165  fn test_parse_indent()
166  {
167    fn parse(mut code: &str) -> (usize, &str)
168    {
169      use crate::indentation::Indentation;
170      let Indentation(i) = Indentation::parse(&mut code);
171      return (i, code);
172    }
173
174    assert_eq!(parse(""), (0, ""));
175    assert_eq!(parse(" "), (1, ""));
176    assert_eq!(parse("   x"), (3, "x"));
177  }
178}
179
180use std::fmt::{self, Write};