handlebars/
support.rs
1pub mod str {
2 use std::io::{Result, Write};
3
4 use crate::Output;
5
6 #[derive(Debug)]
7 pub struct StringWriter {
8 buf: Vec<u8>,
9 }
10
11 impl Default for StringWriter {
12 fn default() -> Self {
13 Self::new()
14 }
15 }
16
17 impl StringWriter {
18 pub fn new() -> StringWriter {
19 StringWriter {
20 buf: Vec::with_capacity(8 * 1024),
21 }
22 }
23
24 pub fn into_string(self) -> String {
25 String::from_utf8(self.buf).unwrap_or_default()
26 }
27 }
28
29 impl Write for StringWriter {
30 fn write(&mut self, buf: &[u8]) -> Result<usize> {
31 self.buf.extend_from_slice(buf);
32 Ok(buf.len())
33 }
34
35 fn flush(&mut self) -> Result<()> {
36 Ok(())
37 }
38 }
39
40 pub fn escape_html(s: &str) -> String {
42 let mut output = String::new();
43 for c in s.chars() {
44 match c {
45 '<' => output.push_str("<"),
46 '>' => output.push_str(">"),
47 '"' => output.push_str("""),
48 '&' => output.push_str("&"),
49 '\'' => output.push_str("'"),
50 '`' => output.push_str("`"),
51 '=' => output.push_str("="),
52 _ => output.push(c),
53 }
54 }
55 output
56 }
57
58 pub fn with_indent(s: &str, indent: &str) -> String {
60 let mut output = String::new();
61
62 let mut it = s.chars().peekable();
63 while let Some(c) = it.next() {
64 output.push(c);
65 if c == '\n' && it.peek().is_some() {
68 output.push_str(indent);
69 }
70 }
71
72 output
73 }
74
75 pub fn write_indented(s: &str, indent: &str, w: &mut dyn Output) -> std::io::Result<()> {
77 let mut i = 0;
78 let len = s.len();
79 loop {
80 let Some(next_newline) = s[i..].find('\n') else {
81 w.write(&s[i..])?;
82 return Ok(());
83 };
84 w.write(&s[i..i + next_newline + 1])?;
85 i += next_newline + 1;
86 if i == len {
87 return Ok(());
88 }
89 w.write(indent)?;
90 }
91 }
92
93 #[inline]
94 pub(crate) fn whitespace_matcher(c: char) -> bool {
95 c == ' ' || c == '\t'
96 }
97
98 #[inline]
99 pub(crate) fn newline_matcher(c: char) -> bool {
100 c == '\n' || c == '\r'
101 }
102
103 #[inline]
104 pub(crate) fn strip_first_newline(s: &str) -> &str {
105 if let Some(s) = s.strip_prefix("\r\n") {
106 s
107 } else if let Some(s) = s.strip_prefix('\n') {
108 s
109 } else {
110 s
111 }
112 }
113
114 pub(crate) fn find_trailing_whitespace_chars(s: &str) -> Option<&str> {
115 let trimmed = s.trim_end_matches(whitespace_matcher);
116 if trimmed.len() == s.len() {
117 None
118 } else {
119 Some(&s[trimmed.len()..])
120 }
121 }
122
123 pub(crate) fn ends_with_empty_line(text: &str) -> bool {
124 let s = text.trim_end_matches(whitespace_matcher);
125 s.ends_with(newline_matcher) || s.is_empty()
127 }
128
129 pub(crate) fn starts_with_empty_line(text: &str) -> bool {
130 text.trim_start_matches(whitespace_matcher)
131 .starts_with(newline_matcher)
132 }
133
134 #[cfg(test)]
135 mod test {
136 use crate::support::str::StringWriter;
137 use std::io::Write;
138
139 #[test]
140 fn test_string_writer() {
141 let mut sw = StringWriter::new();
142
143 let _ = sw.write("hello".to_owned().into_bytes().as_ref());
144 let _ = sw.write("world".to_owned().into_bytes().as_ref());
145
146 let s = sw.into_string();
147 assert_eq!(s, "helloworld".to_string());
148 }
149 }
150}