1mod error;
34pub use crate::error::{Error, Result};
35
36#[cfg(feature = "markdown")]
37mod md;
38
39#[cfg(feature = "date")]
40mod date;
41
42pub mod filters {
43 use super::error::Result;
47 use regex as re;
48 use std::fmt;
49
50 #[cfg(feature = "markdown")]
51 pub use crate::md::*;
52
53 #[cfg(feature = "date")]
54 pub use crate::date::*;
55
56 pub fn smarty(s: &dyn fmt::Display) -> Result<String> {
60 Ok(s.to_string()
61 .split_whitespace()
63 .map(|w| {
64 let mut w = String::from(w);
65 if w.starts_with('\'') {
66 w = w.replacen('\'', "‘", 1);
67 } else if w.starts_with('"') {
68 w = w.replacen('"', "“", 1);
69 }
70 if w.ends_with('\'') {
71 w.pop();
72 w.push_str("’");
73 } else if w.ends_with('"') {
74 w.pop();
75 w.push_str("”");
76 }
77 w
78 })
79 .collect::<Vec<String>>()
80 .join(" ")
81 .replace("---", "—")
83 .replace("--", "–")
85 .replace("...", "…"))
87 }
88
89 pub fn title(s: &dyn fmt::Display) -> Result<String> {
91 Ok(s.to_string()
93 .split_whitespace()
94 .map(|w| w.chars())
95 .map(|mut c| {
96 c.next()
97 .into_iter()
98 .flat_map(|c| c.to_uppercase())
99 .chain(c.flat_map(|c| c.to_lowercase()))
100 })
101 .map(|c| c.collect::<String>())
102 .collect::<Vec<String>>()
103 .join(" "))
104 }
105
106 pub fn link(s: &dyn fmt::Display) -> Result<String> {
109 let r = re::Regex::new(
111 r"[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)",
112 )?;
113 Ok(s.to_string()
114 .split_whitespace()
115 .map(|w| {
116 if r.is_match(w) {
117 format!("<a href=\"{}\">{}</a>", w, w)
118 } else {
119 String::from(w)
120 }
121 })
122 .collect::<Vec<String>>()
123 .join(" "))
124 }
125
126 pub fn regex<S, R>(s: S, regex: R, replace: R) -> Result<String>
131 where
132 S: fmt::Display,
133 R: AsRef<str>,
134 {
135 let r = re::Regex::new(regex.as_ref())?;
136 Ok(String::from(
137 r.replace_all(&s.to_string(), replace.as_ref()),
138 ))
139 }
140
141 pub fn prefix<S, E>(s: S, prefix: E, expand_to: E) -> Result<String>
145 where
146 S: fmt::Display,
147 E: AsRef<str>,
148 {
149 Ok(s.to_string()
150 .split_whitespace()
151 .map(|w| {
152 if w.starts_with(prefix.as_ref()) {
153 w.replace(prefix.as_ref(), expand_to.as_ref())
154 } else {
155 String::from(w)
156 }
157 })
158 .collect())
159 }
160
161 pub fn postfix<S, E>(s: S, postfix: E, expand_to: E) -> Result<String>
164 where
165 S: fmt::Display,
166 E: AsRef<str>,
167 {
168 Ok(s.to_string()
169 .split_whitespace()
170 .map(|w| {
171 if w.ends_with(postfix.as_ref()) {
172 w.replace(postfix.as_ref(), expand_to.as_ref())
173 } else {
174 String::from(w)
175 }
176 })
177 .collect())
178 }
179
180 pub fn tag<S, E>(s: S, prefix: E, expand_to: E) -> Result<String>
185 where
186 S: fmt::Display,
187 E: AsRef<str>,
188 {
189 Ok(s.to_string()
190 .split_whitespace()
191 .map(|w| {
192 if w.starts_with(prefix.as_ref()) {
193 format!(
194 "<a href=\"{}\">{}</a>",
195 w.replace(prefix.as_ref(), expand_to.as_ref()),
196 w
197 )
198 } else {
199 String::from(w)
200 }
201 })
202 .collect())
203 }
204
205 pub fn list<T, L>(list: L, ordered: bool) -> Result<String>
208 where
209 T: fmt::Display,
210 L: AsRef<[T]>,
211 {
212 let ltag = if ordered { "ol" } else { "ul" };
213 Ok(format!(
214 "<{}>{}</{}>",
215 ltag,
216 list.as_ref()
217 .iter()
218 .map(|e| { format!("<li>{}</li>", e) })
219 .collect::<String>(),
220 ltag
221 ))
222 }
223
224}
225
226#[cfg(test)]
227mod tests {
228 use super::filters;
229
230 #[test]
231 fn smarty() {
232 assert_eq!(
233 filters::smarty(&"This is a \"smart\" 'string' don't... -- --- ").unwrap(),
234 "This is a “smart” ‘string’ don't… – —"
235 );
236 }
237
238 #[test]
239 fn title() {
240 assert_eq!(
241 filters::title(&"title CASED sTrInG").unwrap(),
242 "Title Cased String"
243 );
244 }
245
246 #[test]
247 fn link() {
248 assert_eq!(
249 filters::link(&"https://rust-lang.org").unwrap(),
250 "<a href=\"https://rust-lang.org\">https://rust-lang.org</a>"
251 );
252 }
253
254 #[test]
255 fn regex() {
256 assert_eq!(
257 filters::regex(&"Test 12345 String", r"\d+", "numbers").unwrap(),
258 "Test numbers String"
259 );
260 }
261
262 #[test]
263 fn prefix() {
264 assert_eq!(
265 filters::prefix("#world", "#", "hello ").unwrap(),
266 "hello world"
267 );
268 }
269
270 #[test]
271 fn postfix() {
272 assert_eq!(
273 filters::postfix("hello#", "#", " world").unwrap(),
274 "hello world"
275 );
276 }
277
278 #[test]
279 fn tag() {
280 assert_eq!(
281 filters::tag("#rust", "#", "https://rust-lang.org/").unwrap(),
282 "<a href=\"https://rust-lang.org/rust\">#rust</a>"
283 );
284 }
285
286 #[test]
287 fn list() {
288 assert_eq!(
289 filters::list(vec![1, 2, 3], false).unwrap(),
290 "<ul><li>1</li><li>2</li><li>3</li></ul>"
291 );
292 }
293}