bempline/
lib.rs

1//! ### Syntax
2//! Variables are alphanumeric strings (underscores, too) surrounded by braces. Here's an `{example}`.
3//!
4//! You can prevent `{word}` from being seen as a variable by escaping the opening brace. Like `\{this}`.
5//!
6//! ## Example
7//! If you have this document in something like `template.bpl`
8//! ```text
9//! Dear {name},
10//!
11//! Some generic email text here!
12//!
13//! Sincerely,
14//! Some Company
15//! ```
16//!
17//! You can fill it out for the names `Ferris` and `Rusty` like so
18//! ```rust
19//! use bempline::{Document, Options};
20//!
21//! fn main() {
22//! 	let doc = Document::from_file("test/template.bpl", Options::default()).unwrap();
23//! 	let names = vec!["Ferris", "Rusty"];
24//!
25//! 	for name in names {
26//! 		let mut cloned = doc.clone();
27//! 		cloned.set("name", name);
28//!
29//! 		println!("{}", cloned.compile());
30//! 	}
31//! }
32//! ```
33
34mod document;
35pub mod options;
36
37pub use document::Document;
38pub use document::Token;
39pub use options::Options;
40
41#[macro_export]
42macro_rules! variables {
43	($template:expr, $variable:ident) => {
44		$template.set(stringify!($variable), $variable);
45	};
46
47	($template:expr, $variable:ident, $($variables:ident),+) => {
48		variables!($template, $variable);
49		variables!($template, $($variables),+)
50	}
51}
52
53#[macro_export]
54macro_rules! set {
55	($template:expr, $key:ident, $($arg:tt)*) => {
56		$template.set(stringify!($key), std::fmt::format(format_args!($($arg)*)));
57	};
58}
59
60#[cfg(test)]
61mod test {
62	use crate::options::IncludeMethod;
63
64	use super::*;
65	use std::path::PathBuf;
66
67	#[test]
68	fn compile_all_set() {
69		let mut doc = Document::from_str(
70			"One: {one} | Two: {two} | Three: {three}",
71			Options::default(),
72		)
73		.unwrap();
74		doc.set("one", "1");
75		doc.set("two", "2");
76		doc.set("three", "3");
77
78		assert_eq!(&doc.compile(), "One: 1 | Two: 2 | Three: 3");
79	}
80
81	#[test]
82	fn compile_some_set() {
83		let mut doc = Document::from_str(
84			"One: {one} | Two: {two} | Three: {three}",
85			Options::default(),
86		)
87		.unwrap();
88		doc.set("one", "1");
89		doc.set("three", "3");
90
91		assert_eq!(&doc.compile(), "One: 1 | Two: {two} | Three: 3");
92	}
93
94	// Parsing related tests
95
96	#[test]
97	fn no_text() {
98		let doc = Document::from_str("", Options::default()).unwrap();
99		assert_eq!(doc.tokens, vec![]);
100	}
101
102	#[test]
103	fn only_text() {
104		let doc = Document::from_str("Nothing but text", Options::default()).unwrap();
105		assert_eq!(
106			doc.tokens,
107			vec![Token::Text(String::from("Nothing but text"))]
108		);
109	}
110
111	#[test]
112	fn escaped_bracket() {
113		let doc =
114			Document::from_str("escape this: \\{, but not this \\n", Options::default()).unwrap();
115		assert_eq!(
116			doc.tokens,
117			vec![Token::Text(String::from(
118				"escape this: {, but not this \\n"
119			))]
120		);
121	}
122
123	#[test]
124	fn only_variable() {
125		let doc = Document::from_str("{variable}", Options::default()).unwrap();
126		assert_eq!(
127			doc.tokens,
128			vec![Token::Variable {
129				name: String::from("variable")
130			}]
131		);
132	}
133
134	#[test]
135	fn sandwhiched_variable() {
136		let doc = Document::from_str("Hello {name}, how are you?", Options::default()).unwrap();
137		assert_eq!(
138			doc.tokens,
139			vec![
140				Token::Text(String::from("Hello ")),
141				Token::Variable {
142					name: String::from("name")
143				},
144				Token::Text(String::from(", how are you?"))
145			]
146		);
147	}
148
149	#[test]
150	fn ends_variable() {
151		let doc = Document::from_str("Hello {name}", Options::default()).unwrap();
152		assert_eq!(
153			doc.tokens,
154			vec![
155				Token::Text(String::from("Hello ")),
156				Token::Variable {
157					name: String::from("name")
158				}
159			]
160		);
161	}
162
163	#[test]
164	fn starts_variable() {
165		let doc = Document::from_str("{name}, hello!", Options::default()).unwrap();
166		assert_eq!(
167			doc.tokens,
168			vec![
169				Token::Variable {
170					name: String::from("name")
171				},
172				Token::Text(String::from(", hello!"))
173			]
174		);
175	}
176
177	#[test]
178	fn multivariable() {
179		let doc = Document::from_str(
180			"The weather is {weather} in {location} today.",
181			Options::default(),
182		)
183		.unwrap();
184		assert_eq!(
185			doc.tokens,
186			vec![
187				Token::Text(String::from("The weather is ")),
188				Token::Variable {
189					name: String::from("weather")
190				},
191				Token::Text(String::from(" in ")),
192				Token::Variable {
193					name: String::from("location")
194				},
195				Token::Text(String::from(" today."))
196			]
197		);
198	}
199
200	#[test]
201	fn include_test() {
202		let doc = Document::from_file("test/include_test.bpl", Options::default()).unwrap();
203		assert_eq!(
204			doc.tokens,
205			vec![
206				Token::Text("Before the include!\n".into()),
207				Token::Text("The included file! With a ".into()),
208				Token::Variable {
209					name: "variable".into()
210				},
211				Token::Text("!".into()),
212				Token::Text("\naand after~".into())
213			]
214		)
215	}
216
217	#[test]
218	fn include_method_path_test() {
219		let doc = Document::from_file(
220			"test/include_some.bpl",
221			Options::default().include_path(IncludeMethod::Path(PathBuf::from("test/subdir"))),
222		)
223		.unwrap();
224		assert_eq!(
225			doc.tokens,
226			vec![
227				Token::Text("Testing IncludeMethod::Path here...\n".into()),
228				Token::Text("I'm in a subdir :D\n".into()),
229				Token::Variable {
230					name: "variable".into()
231				},
232				Token::Text("!".into())
233			]
234		)
235	}
236
237	#[test]
238	fn complex_include() {
239		let doc =
240			Document::from_file("test/pattern_include_ifset_base.bpl", Options::default()).unwrap();
241		assert_eq!(
242			doc.tokens,
243			vec![Token::Pattern {
244				pattern_name: String::from("name"),
245				tokens: vec![Token::IfSet {
246					variable_name: String::from("variable"),
247					tokens: vec![Token::Variable {
248						name: String::from("variable")
249					}],
250					else_tokens: None
251				}]
252			}]
253		)
254	}
255
256	#[test]
257	fn ifset_variable_set() {
258		let mut doc = Document::from_str("{%if-set foo}set!{%end}", Options::default()).unwrap();
259		doc.set("foo", "bar");
260
261		assert_eq!(doc.compile(), "set!")
262	}
263
264	#[test]
265	fn ifset_variable_set_empty_string() {
266		let mut doc = Document::from_str(
267			"{%if-set foo}set!{%end}{%if-set bar}barset!{%end}",
268			Options::default(),
269		)
270		.unwrap();
271		doc.set("foo", "");
272		doc.set("bar", "set!");
273
274		assert_eq!(doc.compile(), "barset!")
275	}
276
277	#[test]
278	fn iftest_else() {
279		let doc = Document::from_str(
280			"{%if-set donotset}wasset{%else}notset{%end}",
281			Options::default(),
282		)
283		.unwrap();
284
285		assert_eq!(doc.compile(), "notset");
286	}
287
288	#[test]
289	fn pattern_parse() {
290		let doc = Document::from_str("{%pattern name}blah{variable}lah{%end}", Options::default())
291			.unwrap();
292
293		assert_eq!(
294			doc.get_pattern("name").unwrap().tokens,
295			vec![
296				Token::Text(String::from("blah")),
297				Token::Variable {
298					name: String::from("variable")
299				},
300				Token::Text(String::from("lah"))
301			]
302		)
303	}
304
305	#[test]
306	fn pattern_fill() {
307		let mut doc =
308			Document::from_str("{%pattern name}-{variable}-{%end}", Options::default()).unwrap();
309
310		let mut pat = doc.get_pattern("name").unwrap();
311
312		let mut name = pat.clone();
313		name.set("variable", "one");
314		pat.set("variable", "two");
315
316		doc.set_pattern("name", name);
317		doc.set_pattern("name", pat);
318
319		assert_eq!(doc.compile(), String::from("-one--two-"))
320	}
321
322	#[test]
323	fn nested_scoped_commands() {
324		let doc = Document::from_str(
325			"{%pattern name}{%if-set var}{%end}{%end}",
326			Options::default(),
327		)
328		.unwrap();
329		assert_eq!(
330			doc.tokens,
331			vec![Token::Pattern {
332				pattern_name: String::from("name"),
333				tokens: vec![Token::IfSet {
334					variable_name: String::from("var"),
335					tokens: vec![],
336					else_tokens: None
337				}]
338			}]
339		)
340	}
341
342	#[test]
343	fn variables_macro() {
344		let mut doc = Document::from_str("{foo} and {bar}", Options::default()).unwrap();
345		let foo = "one";
346		let bar = "two";
347
348		variables!(doc, foo, bar);
349
350		assert_eq!(doc.compile(), String::from("one and two"))
351	}
352
353	#[test]
354	fn set_macro() {
355		let mut doc = Document::from_str("{foo}", Options::default()).unwrap();
356
357		set!(doc, foo, "{:X}", 17);
358
359		assert_eq!(doc.compile(), String::from("11"))
360	}
361
362	#[test]
363	fn wrapping_include() {
364		let expected = "<html><head>Foo<title>Test!</title></head></html>";
365		let mut doc = Document::from_file("test/wrapped_include.bpl", Options::default()).unwrap();
366		doc.set("var_in", "Foo");
367
368		assert_eq!(doc.compile(), expected)
369	}
370}