You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
3.0KB

  1. use std::collections::HashMap;
  2. use regex::Regex;
  3. use tera::{Tera, Context};
  4. use errors::{Result, ResultExt};
  5. lazy_static!{
  6. pub static ref SHORTCODE_RE: Regex = Regex::new(r#"\{(?:%|\{)\s+([[:alnum:]]+?)\(([[:alnum:]]+?="?.+?"?)\)\s+(?:%|\})\}"#).unwrap();
  7. }
  8. /// A shortcode that has a body
  9. /// Called by having some content like {% ... %} body {% end %}
  10. /// We need the struct to hold the data while we're processing the markdown
  11. #[derive(Debug)]
  12. pub struct ShortCode {
  13. name: String,
  14. args: HashMap<String, String>,
  15. body: String,
  16. }
  17. impl ShortCode {
  18. pub fn new(name: &str, args: HashMap<String, String>) -> ShortCode {
  19. ShortCode {
  20. name: name.to_string(),
  21. args: args,
  22. body: String::new(),
  23. }
  24. }
  25. pub fn append(&mut self, text: &str) {
  26. self.body.push_str(text)
  27. }
  28. pub fn render(&self, tera: &Tera) -> Result<String> {
  29. let mut context = Context::new();
  30. for (key, value) in &self.args {
  31. context.add(key, value);
  32. }
  33. context.add("body", &self.body);
  34. let tpl_name = format!("shortcodes/{}.html", self.name);
  35. tera.render(&tpl_name, &context)
  36. .chain_err(|| format!("Failed to render {} shortcode", self.name))
  37. }
  38. }
  39. /// Parse a shortcode without a body
  40. pub fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) {
  41. let mut args = HashMap::new();
  42. let caps = SHORTCODE_RE.captures(input).unwrap();
  43. // caps[0] is the full match
  44. let name = &caps[1];
  45. let arg_list = &caps[2];
  46. for arg in arg_list.split(',') {
  47. let bits = arg.split('=').collect::<Vec<_>>();
  48. args.insert(bits[0].trim().to_string(), bits[1].replace("\"", ""));
  49. }
  50. (name.to_string(), args)
  51. }
  52. /// Renders a shortcode or return an error
  53. pub fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, String>) -> Result<String> {
  54. let mut context = Context::new();
  55. for (key, value) in args.iter() {
  56. context.add(key, value);
  57. }
  58. let tpl_name = format!("shortcodes/{}.html", name);
  59. tera.render(&tpl_name, &context).chain_err(|| format!("Failed to render {} shortcode", name))
  60. }
  61. #[cfg(test)]
  62. mod tests {
  63. use super::parse_shortcode;
  64. #[test]
  65. fn can_parse_simple_shortcode_one_arg() {
  66. let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc") }}"#);
  67. assert_eq!(name, "youtube");
  68. assert_eq!(args["id"], "w7Ft2ymGmfc");
  69. }
  70. #[test]
  71. fn can_parse_simple_shortcode_several_arg() {
  72. let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#);
  73. assert_eq!(name, "youtube");
  74. assert_eq!(args["id"], "w7Ft2ymGmfc");
  75. assert_eq!(args["autoplay"], "true");
  76. }
  77. #[test]
  78. fn can_parse_block_shortcode_several_arg() {
  79. let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#);
  80. assert_eq!(name, "youtube");
  81. assert_eq!(args["id"], "w7Ft2ymGmfc");
  82. assert_eq!(args["autoplay"], "true");
  83. }
  84. }