1mod builtins;
178pub mod context;
179pub mod renderer;
180pub mod template;
181
182pub use crate::renderer::{Renderer, RenderValue, Attribute, Attributes, RenderError};
183pub use crate::template::{Template, TemplateExprNode};
184pub use crate::context::{RenderContext, ContextValue};
185
186
187#[cfg(test)]
188mod tests {
189 use crate::context::{RenderContext, ContextValue};
190 use crate::renderer::{Renderer, RenderValue};
191 use crate::template::{Template, TemplateExprNode};
192
193 #[test]
194 fn test_no_builtins() {
195 let renderer = Renderer::builder()
196 .build();
197 let expr = r#"(head (title "test title"))"#;
198 let template = Template::from_str(expr).unwrap();
199 let html = renderer.render(&template, &RenderContext::default()).unwrap();
200
201 assert_eq!(html, "<head><title>test title</title></head>")
202 }
203
204 #[test]
205 fn test_basic_render() {
206 let renderer = Renderer::builder()
207 .build();
208 let expr = r#"(html (head (title "test title")))"#;
209 let template = Template::from_str(expr).unwrap();
210 let html = renderer.render(&template, &RenderContext::default()).unwrap();
211
212 assert_eq!(html, "<!doctype html5><html><head><title>test title</title></head></html>")
213 }
214
215 #[test]
216 fn test_attributes() {
217 let renderer = Renderer::builder()
218 .build();
219 let expr = r#"(html (head (@ (asdf qwer) (zxc asd))(title "test title")))"#;
220 let template = Template::from_str(expr).unwrap();
221 let html = renderer.render(&template, &RenderContext::default()).unwrap();
222 assert_eq!(html, r#"<!doctype html5><html><head asdf="qwer" zxc="asd"><title>test title</title></head></html>"#)
223 }
224
225 #[test]
226 fn test_attributes_on_empty_tag() {
227 let renderer = Renderer::builder()
228 .build();
229 let expr = r#"(html (body (@ (asdf qwer))))"#;
230 let template = Template::from_str(expr).unwrap();
231 let html = renderer.render(&template, &RenderContext::default()).unwrap();
232 assert_eq!(html, r#"<!doctype html5><html><body asdf="qwer" /></html>"#)
233 }
234
235 #[test]
236 fn test_basic_substitution() {
237 let renderer = Renderer::builder()
238 .build();
239 let expr = r#"(html (head (title $title)))"#;
240 let template = Template::from_str(expr).unwrap();
241 let context = RenderContext::builder()
242 .insert("title", "some sort of title")
243 .build();
244 let html = renderer.render(&template, &context).unwrap();
245 assert_eq!(html, r#"<!doctype html5><html><head><title>some sort of title</title></head></html>"#)
246 }
247
248 #[test]
249 fn test_vec_substitution() {
250 let renderer = Renderer::builder()
251 .build();
252 let expr = r#"(html (div $vec))"#;
253 let template = Template::from_str(expr).unwrap();
254 let context = RenderContext::builder()
255 .insert("asdf", "qwer")
256 .insert("vec", vec!["this", "that", "$asdf"])
257 .build();
258 let html = renderer.render(&template, &context).unwrap();
259 assert_eq!(html, r#"<!doctype html5><html><div>thisthatqwer</div></html>"#)
260 }
261
262 #[test]
263 fn test_if_is_set() {
264 let renderer = Renderer::builder()
265 .build();
266 let expr = r#"(html (head (if (is-set $title) (title $title) (title "not set"))))"#;
267 let template = Template::from_str(expr).unwrap();
268 let context = RenderContext::builder()
269 .insert("title", "some sort of title")
270 .build();
271 let html = renderer.render(&template, &context).unwrap();
272 assert_eq!(html, r#"<!doctype html5><html><head><title>some sort of title</title></head></html>"#)
273 }
274
275 #[test]
276 fn test_if_not_is_set() {
277 let renderer = Renderer::builder()
278 .build();
279 let expr = r#"(html (head (if (is-set $title) (title $title) (title "not set"))))"#;
280 let template = Template::from_str(expr).unwrap();
281 let context = RenderContext::builder()
282 .insert("title2", "some sort of title")
283 .build();
284 let html = renderer.render(&template, &context).unwrap();
285 assert_eq!(html, r#"<!doctype html5><html><head><title>not set</title></head></html>"#)
286 }
287
288 #[test]
289 fn test_array_iteration() {
290 let renderer = Renderer::builder()
291 .build();
292 let expr = r#"(html (body (for i in $asdf (div "iter " $i))))"#;
293 let template = Template::from_str(expr).unwrap();
294 let context = RenderContext::builder()
295 .insert("asdf", vec!["qaz", "wsx", "edc"])
296 .build();
297 let html = renderer.render(&template, &context).unwrap();
298 assert_eq!(html, r#"<!doctype html5><html><body><div>iter qaz</div><div>iter wsx</div><div>iter edc</div></body></html>"#)
299 }
300
301 #[test]
302 fn test_range_iteration() {
303 let renderer = Renderer::builder()
304 .build();
305 let expr = r#"(html (body (for i in (range 0 3) (div "iter " $i))))"#;
306 let template = Template::from_str(expr).unwrap();
307 let html = renderer.render(&template, &RenderContext::default()).unwrap();
308 assert_eq!(html, r#"<!doctype html5><html><body><div>iter 0</div><div>iter 1</div><div>iter 2</div></body></html>"#)
309 }
310
311 #[test]
312 fn test_array_index_iteration() {
313 let renderer = Renderer::builder()
314 .build();
315 let expr = r#"(html (body (for (enumerate k i) in $asdf (div $k ": iter " $i))))"#;
316 let template = Template::from_str(expr).unwrap();
317 let context = RenderContext::builder()
318 .insert("asdf", vec!["qaz", "wsx", "edc"])
319 .build();
320 let html = renderer.render(&template, &context).unwrap();
321 assert_eq!(html, r#"<!doctype html5><html><body><div>0: iter qaz</div><div>1: iter wsx</div><div>2: iter edc</div></body></html>"#)
322 }
323
324 #[test]
325 fn test_object_iteration() {
326 let renderer = Renderer::builder()
327 .build();
328 let expr = r#"(html (body (for k v in $asdf (div "key " $k ", value " $v))))"#;
329 let template = Template::from_str(expr).unwrap();
330 let internal_obj = RenderContext::builder()
331 .insert("as", "df")
332 .insert("qw", "er")
333 .build();
334 let context = RenderContext::builder()
335 .insert("asdf", internal_obj)
336 .build();
337 let html = renderer.render(&template, &context).unwrap();
338 assert_eq!(html, r#"<!doctype html5><html><body><div>key as, value df</div><div>key qw, value er</div></body></html>"#)
339 }
340
341 #[test]
342 fn test_object_access() {
343 let renderer = Renderer::builder()
344 .build();
345 let expr = r#"(html (body $asdf.as))"#;
346 let template = Template::from_str(expr).unwrap();
347
348 let internal_obj = RenderContext::builder()
349 .insert("as", "df")
350 .build();
351 let context = RenderContext::builder()
352 .insert("asdf", internal_obj)
353 .build();
354 let html = renderer.render(&template, &context).unwrap();
355 assert_eq!(html, r#"<!doctype html5><html><body>df</body></html>"#)
356 }
357
358 #[test]
359 fn test_deep_object_access() {
360 let renderer = Renderer::builder()
361 .build();
362 let expr = r#"(html (body $nested_object.as.df.qw.er))"#;
363 let template = Template::from_str(expr).unwrap();
364
365 let nested3_obj = RenderContext::builder()
366 .insert("er", "look at this nested thing")
367 .build();
368 let nested2_obj = RenderContext::builder()
369 .insert("qw", nested3_obj)
370 .build();
371 let nested1_obj = RenderContext::builder()
372 .insert("df", nested2_obj)
373 .build();
374 let nested0_obj = RenderContext::builder()
375 .insert("as", nested1_obj)
376 .build();
377
378 let context = RenderContext::builder()
379 .insert("nested_object", nested0_obj)
380 .build();
381 let html = renderer.render(&template, &context).unwrap();
382 assert_eq!(html, r#"<!doctype html5><html><body>look at this nested thing</body></html>"#)
383 }
384
385 #[test]
386 fn test_variable_in_attributes() {
387 let renderer = Renderer::builder()
388 .build();
389 let expr = r#"(html (head (@ (asdf $qwer) (zxc asd))(title "test title")))"#;
390 let template = Template::from_str(expr).unwrap();
391 let context = RenderContext::builder()
392 .insert("qwer", "zxcv")
393 .build();
394 let html = renderer.render(&template, &context).unwrap();
395 assert_eq!(html, r#"<!doctype html5><html><head asdf="zxcv" zxc="asd"><title>test title</title></head></html>"#)
396 }
397
398 #[test]
399 fn test_object_variable_in_attributes() {
400 let renderer = Renderer::builder()
401 .build();
402 let expr = r#"(html (for qw in $as (div (@ (class $qw.er)) $qw.zx)))"#;
403 let template = Template::from_str(expr).unwrap();
404
405 let obj1 = RenderContext::builder()
406 .insert("er", "cv")
407 .insert("zx", "hj")
408 .build();
409 let obj2 = RenderContext::builder()
410 .insert("er", "df")
411 .insert("zx", "nm")
412 .build();
413
414 let context = RenderContext::builder()
415 .insert("as", vec![obj1, obj2])
416 .build();
417 let html = renderer.render(&template, &context).unwrap();
418 assert_eq!(html, r#"<!doctype html5><html><div class="cv">hj</div><div class="df">nm</div></html>"#)
419 }
420
421 #[test]
422 fn test_case() {
423 let renderer = Renderer::builder()
424 .build();
425 let expr = r#"(html (div (switch $blah (case asdf qwer) (case zxcv (div what else)) (case hjkl nm))))"#;
426 let template = Template::from_str(expr).unwrap();
427 let context = RenderContext::builder()
428 .insert("blah", "zxcv")
429 .build();
430 let html = renderer.render(&template, &context).unwrap();
431 assert_eq!(html, r#"<!doctype html5><html><div><div>whatelse</div></div></html>"#)
432 }
433
434 #[test]
435 fn test_custom_closure() {
436 let renderer = Renderer::builder()
437 .function("blah", Box::new(|_, _, _, _| {
438 Ok("hello there".into())
439 }))
440 .build();
441 let expr = r#"(html (div (blah something or other)))"#;
442 let template = Template::from_str(expr).unwrap();
443 let html = renderer.render(&template, &RenderContext::default()).unwrap();
444 assert_eq!(html, r#"<!doctype html5><html><div>hello there</div></html>"#)
445 }
446
447 #[test]
448 fn test_custom_function() {
449 fn blah(_: crate::renderer::Attributes, _: &[TemplateExprNode], _: &Renderer, _: &RenderContext) -> Result<RenderValue, crate::renderer::RenderError> {
450 Ok("hello there".into())
451 }
452
453 let renderer = Renderer::builder()
454 .function("blarg", Box::new(blah))
455 .build();
456 let expr = r#"(html (div (blarg something or other)))"#;
457 let template = Template::from_str(expr).unwrap();
458 let html = renderer.render(&template, &RenderContext::default()).unwrap();
459 assert_eq!(html, r#"<!doctype html5><html><div>hello there</div></html>"#)
460 }
461
462 #[test]
463 fn test_using_attrs_in_closure() {
464 let renderer = Renderer::builder()
465 .function("blah", Box::new(|attrs, _, _, _| {
466 let mut output: Vec<String> = Vec::new();
467
468 for attr in &attrs {
469 output.push("[".into());
470 output.push(attr.0.clone());
471 output.push(" = ".into());
472 output.push(attr.1.clone());
473 output.push("] ".into());
474 }
475
476 Ok(output.into())
477 }))
478 .build();
479 let expr = r#"(html (div (blah (@ (this is) (the attr)) something or other)))"#;
480 let template = Template::from_str(expr).unwrap();
481 let html = renderer.render(&template, &RenderContext::default()).unwrap();
482 assert_eq!(html, r#"<!doctype html5><html><div>[this = is] [the = attr] </div></html>"#)
483 }
484
485 #[test]
486 fn test_more_html_in_closure() {
487 let renderer = Renderer::builder()
488 .function("blah", Box::new(|_, expr, renderer, context| {
489 let mut output: Vec<RenderValue> = Vec::new();
490 output.push("<blah>".into());
491 output.push(renderer.evaluate_multiple(expr, context)?.into());
492 output.push("</blah>".into());
493 Ok(output.into())
494 }))
495 .build();
496 let expr = r#"(html (div (blah (span hello))))"#;
497 let template = Template::from_str(expr).unwrap();
498 let html = renderer.render(&template, &RenderContext::default()).unwrap();
499 assert_eq!(html, r#"<!doctype html5><html><div><blah><span>hello</span></blah></div></html>"#)
500 }
501
502 #[test]
503 fn test_renderer_in_closure() {
504 let subexpr = r#"(sub str)"#;
505 let subtemplate = Template::from_str(subexpr).unwrap();
506
507 let renderer = Renderer::builder()
508 .function("blah", Box::new(move |_, _, renderer, _| {
509 let mut output: Vec<String> = Vec::new();
510 output.push("<blah>".into());
511 let suboutput = renderer.render(&subtemplate, &RenderContext::default())?;
512 output.push(suboutput);
513 output.push("</blah>".into());
514 Ok(output.into())
515 }))
516 .build();
517 let expr = r#"(html (div (blah (span hello))))"#;
518 let template = Template::from_str(expr).unwrap();
519 let html = renderer.render(&template, &RenderContext::default()).unwrap();
520 assert_eq!(html, r#"<!doctype html5><html><div><blah><sub>str</sub></blah></div></html>"#)
521 }
522
523 #[test]
524 fn test_context_in_closure() {
525 let renderer = Renderer::builder()
526 .function("blah", Box::new(move |_, _, _, context| {
527 let s = match context.get("blah").unwrap() {
528 ContextValue::String(s) => s,
529 _ => panic!("not a str")
530 }.clone();
531
532 let mut output: Vec<String> = Vec::new();
533 output.push("<blah>".into());
534 output.push(s);
535 output.push("</blah>".into());
536 Ok(output.into())
537 }))
538 .build();
539 let expr = r#"(html (div (blah (span hello))))"#;
540 let template = Template::from_str(expr).unwrap();
541 let context = RenderContext::builder()
542 .insert("blah", "zxcv")
543 .build();
544 let html = renderer.render(&template, &context).unwrap();
545 assert_eq!(html, r#"<!doctype html5><html><div><blah>zxcv</blah></div></html>"#)
546 }
547
548 #[test]
549 fn test_closure_with_everything() {
550 let subexpr = r#"(sub $content)"#;
551 let subtemplate = Template::from_str(subexpr).unwrap();
552
553 let renderer = Renderer::builder()
554 .function("blah", Box::new(move |attr, expr, renderer, context| {
555 let mut output: Vec<RenderValue> = Vec::new();
556 output.push("<blah>".into());
557
558 let mut subcontext = RenderContext::default();
559 subcontext.insert("content", attr.get("something").unwrap().clone());
560 let suboutput = renderer.render(&subtemplate, &subcontext)?;
561 output.push(suboutput.into());
562 output.push(renderer.evaluate_multiple(expr, context)?.into());
563 output.push(match context.get("blah").unwrap() {
564 ContextValue::String(s) => s,
565 _ => panic!("not a str")
566 }.clone().into());
567
568 output.push("</blah>".into());
569 Ok(output.into())
570 }))
571 .build();
572 let expr = r#"(html (div (blah (@ (something extra)) (span hello))))"#;
573 let template = Template::from_str(expr).unwrap();
574 let context = RenderContext::builder()
575 .insert("blah", "zxcv")
576 .build();
577 let html = renderer.render(&template, &context).unwrap();
578 assert_eq!(html, r#"<!doctype html5><html><div><blah><sub>extra</sub><span>hello</span>zxcv</blah></div></html>"#)
579 }
580
581 #[test]
582 fn test_math_op_mod() {
583 let renderer = Renderer::builder()
584 .build();
585 let expr = r#"(html (body (for i in (range 0 5) (if (eq (% $i 2) 0) (div $i)))))"#;
586 let template = Template::from_str(expr).unwrap();
587 let context = RenderContext::builder()
588 .build();
589 let html = renderer.render(&template, &context).unwrap();
590 assert_eq!(html, r#"<!doctype html5><html><body><div>0</div><div>2</div><div>4</div></body></html>"#)
591 }
592
593 #[test]
594 fn test_variable_range_iteration() {
595 let renderer = Renderer::builder()
596 .build();
597 let expr = r#"(html (body (for i in (range $a $b) (div "iter " $i))))"#;
598 let template = Template::from_str(expr).unwrap();
599 let context = RenderContext::builder()
600 .insert("a", 4)
601 .insert("b", 7)
602 .build();
603 let html = renderer.render(&template, &context).unwrap();
604 assert_eq!(html, r#"<!doctype html5><html><body><div>iter 4</div><div>iter 5</div><div>iter 6</div></body></html>"#)
605 }
606
607 #[test]
608 fn test_math_ops() {
609 let renderer = Renderer::builder()
610 .build();
611 let expr = r#"(html (body (+ (/ (* $b (- $c (+ 2 3))) $c) (+ $b $c))))"#;
612 let template = Template::from_str(expr).unwrap();
613 let context = RenderContext::builder()
614 .insert("a", 4)
615 .insert("b", 7)
616 .insert("c", 12)
617 .build();
618 let html = renderer.render(&template, &context).unwrap();
619 assert_eq!(html, r#"<!doctype html5><html><body>23</body></html>"#)
620 }
621
622 #[test]
623 fn test_math_ops_in_attributes() {
624 let renderer = Renderer::builder()
625 .build();
626 let expr = r#"(html (body (@ (blah (+ 2 3)) (asdf (* $a $b)))))"#;
627 let template = Template::from_str(expr).unwrap();
628 let context = RenderContext::builder()
629 .insert("a", 4)
630 .insert("b", 7)
631 .build();
632 let html = renderer.render(&template, &context).unwrap();
633 assert_eq!(html, r#"<!doctype html5><html><body blah="5" asdf="28" /></html>"#)
634 }
635
636 #[test]
637 fn test_math_ops_in_for_range() {
638 let renderer = Renderer::builder()
639 .build();
640 let expr = r#"(html (body (for i in (range (- $b $a) (+ $b 1)) (div "iter " $i))))"#;
641 let template = Template::from_str(expr).unwrap();
642 let context = RenderContext::builder()
643 .insert("a", 4)
644 .insert("b", 7)
645 .build();
646 let html = renderer.render(&template, &context).unwrap();
647 assert_eq!(html, r#"<!doctype html5><html><body><div>iter 3</div><div>iter 4</div><div>iter 5</div><div>iter 6</div><div>iter 7</div></body></html>"#)
648 }
649
650 #[test]
651 fn test_get_from_array() {
652 let renderer = Renderer::builder()
653 .build();
654 let expr = r#"(html (body (get $a 0) (get $a 2)))"#;
655 let template = Template::from_str(expr).unwrap();
656 let context = RenderContext::builder()
657 .insert("a", vec!["asd", "qwe", "zxc"])
658 .build();
659 let html = renderer.render(&template, &context).unwrap();
660 assert_eq!(html, r#"<!doctype html5><html><body>asdzxc</body></html>"#)
661 }
662
663 #[test]
664 fn test_get_from_object() {
665 let renderer = Renderer::builder()
666 .build();
667 let expr = r#"(html (body (get $asdf as) (get $asdf zx)))"#;
668 let template = Template::from_str(expr).unwrap();
669 let internal_obj = RenderContext::builder()
670 .insert("as", "df")
671 .insert("qw", "er")
672 .insert("zx", "cv")
673 .build();
674 let context = RenderContext::builder()
675 .insert("asdf", internal_obj)
676 .build();
677 let html = renderer.render(&template, &context).unwrap();
678 assert_eq!(html, r#"<!doctype html5><html><body>dfcv</body></html>"#)
679 }
680
681 #[test]
682 fn test_nested_gets_from_array() {
683 let renderer = Renderer::builder()
684 .build();
685 let expr = r#"(html (body (get (get $a 2) 0)))"#;
686 let template = Template::from_str(expr).unwrap();
687
688 let subvec: ContextValue = vec![ContextValue::from("blah"), "123".into(), "this".into()].into();
689 let v: ContextValue = vec!["asd".into(), "qwe".into(), subvec, "zxc".into()].into();
690 let context = RenderContext::builder()
691 .insert("a", v)
692 .build();
693 let html = renderer.render(&template, &context).unwrap();
694 assert_eq!(html, r#"<!doctype html5><html><body>blah</body></html>"#)
695 }
696
697 #[test]
698 fn test_multi_type_vec() {
699 let renderer = Renderer::builder()
700 .build();
701 let expr = r#"(html (body $a))"#;
702 let template = Template::from_str(expr).unwrap();
703
704 let v: ContextValue = vec!["blah".into(), ContextValue::from(123), "this".into(), ContextValue::from(true)].into();
705 let context = RenderContext::builder()
706 .insert("a", v)
707 .build();
708 let html = renderer.render(&template, &context).unwrap();
709 assert_eq!(html, r#"<!doctype html5><html><body>blah123thistrue</body></html>"#)
710 }
711
712 #[test]
713 fn test_multielement_attributes() {
714 let renderer = Renderer::builder()
715 .build();
716 let expr = r#"(html (body (@ (asdf this that) (some thing $a))))"#;
717 let template = Template::from_str(expr).unwrap();
718 let context = RenderContext::builder()
719 .insert("a", "blah")
720 .build();
721 let html = renderer.render(&template, &context).unwrap();
722 assert_eq!(html, r#"<!doctype html5><html><body asdf="thisthat" some="thingblah" /></html>"#)
723 }
724
725 #[test]
726 fn test_var_in_both_attribute_positions() {
727 let renderer = Renderer::builder()
728 .build();
729 let expr = r#"(html (body (@ ($b asdf $a))))"#;
730 let template = Template::from_str(expr).unwrap();
731 let context = RenderContext::builder()
732 .insert("a", "blah")
733 .insert("b", "this")
734 .build();
735 let html = renderer.render(&template, &context).unwrap();
736 assert_eq!(html, r#"<!doctype html5><html><body this="asdfblah" /></html>"#)
737 }
738
739 #[test]
740 fn test_template_as_context_variable() {
741 let renderer = Renderer::builder()
742 .build();
743 let main_expr = r#"(html (body $body))"#;
744 let main_template = Template::from_str(main_expr).unwrap();
745 let sub_expr = r#"(span hello)"#;
746 let sub_template = Template::from_str(sub_expr).unwrap();
747 let context = RenderContext::builder()
748 .insert("body", sub_template)
749 .build();
750 let html = renderer.render(&main_template, &context).unwrap();
751 assert_eq!(html, r#"<!doctype html5><html><body><span>hello</span></body></html>"#)
752 }
753
754 #[test]
755 fn test_nested_for() {
756 let renderer = Renderer::builder()
757 .build();
758 let main_expr = r#"(html
759 (for item in $items
760 (div $item.item
761 [(for id in $item.sub
762 $id)])))"#;
763
764 let items = [1, 2]
765 .into_iter()
766 .map(|i| {
767 RenderContext::builder()
768 .insert("item", i)
769 .insert("sub", vec![i+1, i+2])
770 .build()
771 })
772 .collect::<Vec<_>>();
773
774 let context = RenderContext::builder()
775 .insert("items", items)
776 .build();
777
778 let main_template = Template::from_str(main_expr).unwrap();
779 let html = renderer.render(&main_template, &context).unwrap();
780
781 assert_eq!(html, r#"<!doctype html5><html><div>1[23]</div><div>2[34]</div></html>"#)
782 }
783
784 #[test]
785 fn test_if_unset_var_defaults_to_false() {
786 let renderer = Renderer::builder()
787 .build();
788 let expr = r#"(html (if $as.df 1 2))"#;
789 let template = Template::from_str(expr).unwrap();
790
791 let subcontext = RenderContext::builder()
792 .build();
793
794 let context = RenderContext::builder()
795 .insert("as", subcontext)
796 .build();
797 let html = renderer.render(&template, &context).unwrap();
798 assert_eq!(html, r#"<!doctype html5><html>2</html>"#)
799 }
800}