1pub mod str {
2 use std::io::{Result, Write};
3
4 #[derive(Debug)]
5 pub struct StringWriter {
6 buf: Vec<u8>,
7 }
8
9 impl Default for StringWriter {
10 fn default() -> Self {
11 Self::new()
12 }
13 }
14
15 impl StringWriter {
16 pub fn new() -> StringWriter {
17 StringWriter {
18 buf: Vec::with_capacity(8 * 1024),
19 }
20 }
21
22 pub fn into_string(self) -> String {
23 if let Ok(s) = String::from_utf8(self.buf) {
24 s
25 } else {
26 String::new()
27 }
28 }
29 }
30
31 impl Write for StringWriter {
32 fn write(&mut self, buf: &[u8]) -> Result<usize> {
33 self.buf.extend_from_slice(buf);
34 Ok(buf.len())
35 }
36
37 fn flush(&mut self) -> Result<()> {
38 Ok(())
39 }
40 }
41
42 /// See https://github.com/handlebars-lang/handlebars.js/blob/37411901da42200ced8e1a7fc2f67bf83526b497/lib/handlebars/utils.js#L1
43 pub fn escape_html(s: &str) -> String {
44 let mut output = String::new();
45 for c in s.chars() {
46 match c {
47 '<' => output.push_str("&lt;"),
48 '>' => output.push_str("&gt;"),
49 '"' => output.push_str("&quot;"),
50 '&' => output.push_str("&amp;"),
51 '\'' => output.push_str("&#x27;"),
52 '`' => output.push_str("&#x60;"),
53 '=' => output.push_str("&#x3D;"),
54 _ => output.push(c),
55 }
56 }
57 output
58 }
59
60 /// add indent for lines but last
61 pub fn with_indent(s: &str, indent: &str) -> String {
62 let mut output = String::new();
63
64 let mut it = s.chars().peekable();
65 while let Some(c) = it.next() {
66 output.push(c);
67 // check if c is not the last character, we don't append
68 // indent for last line break
69 if c == '\n' && it.peek().is_some() {
70 output.push_str(indent);
71 }
72 }
73
74 output
75 }
76
77 #[inline]
78 pub(crate) fn whitespace_matcher(c: char) -> bool {
79 c == ' ' || c == '\t'
80 }
81
82 #[inline]
83 pub(crate) fn newline_matcher(c: char) -> bool {
84 c == '\n' || c == '\r'
85 }
86
87 #[inline]
88 pub(crate) fn strip_first_newline(s: &str) -> &str {
89 if let Some(s) = s.strip_prefix("\r\n") {
90 s
91 } else if let Some(s) = s.strip_prefix('\n') {
92 s
93 } else {
94 s
95 }
96 }
97
98 pub(crate) fn find_trailing_whitespace_chars(s: &str) -> Option<&str> {
99 let trimmed = s.trim_end_matches(whitespace_matcher);
100 if trimmed.len() == s.len() {
101 None
102 } else {
103 Some(&s[trimmed.len()..])
104 }
105 }
106
107 pub(crate) fn ends_with_empty_line(text: &str) -> bool {
108 let s = text.trim_end_matches(whitespace_matcher);
109 // also matches when text is just whitespaces
110 s.ends_with(newline_matcher) || s.is_empty()
111 }
112
113 pub(crate) fn starts_with_empty_line(text: &str) -> bool {
114 text.trim_start_matches(whitespace_matcher)
115 .starts_with(newline_matcher)
116 }
117
118 #[cfg(test)]
119 mod test {
120 use crate::support::str::StringWriter;
121 use std::io::Write;
122
123 #[test]
124 fn test_string_writer() {
125 let mut sw = StringWriter::new();
126
127 let _ = sw.write("hello".to_owned().into_bytes().as_ref());
128 let _ = sw.write("world".to_owned().into_bytes().as_ref());
129
130 let s = sw.into_string();
131 assert_eq!(s, "helloworld".to_string());
132 }
133 }
134}
135