Properly parse shortcode arg value types

This commit is contained in:
Vincent Prouillet 2017-10-26 17:03:26 +02:00
parent 1d8df5774f
commit bfdfe3bba3
3 changed files with 60 additions and 9 deletions

View file

@ -9,6 +9,7 @@
- Fix permalink generation for index page - Fix permalink generation for index page
- Add Nim syntax highlighting - Add Nim syntax highlighting
- Allow static folder to be missing - Allow static folder to be missing
- Fix shortcodes args being only passed as strings
## 0.2.1 (2017-10-17) ## 0.2.1 (2017-10-17)

View file

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use regex::Regex; use regex::Regex;
use tera::{Tera, Context}; use tera::{Tera, Context, Value, to_value};
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
@ -15,15 +15,15 @@ lazy_static!{
#[derive(Debug)] #[derive(Debug)]
pub struct ShortCode { pub struct ShortCode {
name: String, name: String,
args: HashMap<String, String>, args: HashMap<String, Value>,
body: String, body: String,
} }
impl ShortCode { impl ShortCode {
pub fn new(name: &str, args: HashMap<String, String>) -> ShortCode { pub fn new(name: &str, args: HashMap<String, Value>) -> ShortCode {
ShortCode { ShortCode {
name: name.to_string(), name: name.to_string(),
args: args, args,
body: String::new(), body: String::new(),
} }
} }
@ -45,7 +45,7 @@ impl ShortCode {
} }
/// Parse a shortcode without a body /// Parse a shortcode without a body
pub fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) { pub fn parse_shortcode(input: &str) -> (String, HashMap<String, Value>) {
let mut args = HashMap::new(); let mut args = HashMap::new();
let caps = SHORTCODE_RE.captures(input).unwrap(); let caps = SHORTCODE_RE.captures(input).unwrap();
// caps[0] is the full match // caps[0] is the full match
@ -54,7 +54,39 @@ pub fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) {
if let Some(arg_list) = caps.get(2) { if let Some(arg_list) = caps.get(2) {
for arg in arg_list.as_str().split(',') { for arg in arg_list.as_str().split(',') {
let bits = arg.split('=').collect::<Vec<_>>(); let bits = arg.split('=').collect::<Vec<_>>();
args.insert(bits[0].trim().to_string(), bits[1].replace("\"", "")); let arg_name = bits[0].trim().to_string();
let arg_val = bits[1].replace("\"", "");
// Regex captures will be str so we need to figure out if they are
// actually str or bool/number
if input.contains(&format!("{}=\"{}\"", arg_name, arg_val)) {
// that's a str, just add it
args.insert(arg_name, to_value(arg_val).unwrap());
continue;
}
if input.contains(&format!("{}=true", arg_name)) {
args.insert(arg_name, to_value(true).unwrap());
continue;
}
if input.contains(&format!("{}=false", arg_name)) {
args.insert(arg_name, to_value(false).unwrap());
continue;
}
// Not a string or a bool, a number then?
if arg_val.contains('.') {
if let Ok(float) = arg_val.parse::<f64>() {
args.insert(arg_name, to_value(float).unwrap());
}
continue;
}
// must be an integer
if let Ok(int) = arg_val.parse::<i64>() {
args.insert(arg_name, to_value(int).unwrap());
}
} }
} }
@ -62,7 +94,7 @@ pub fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) {
} }
/// Renders a shortcode or return an error /// Renders a shortcode or return an error
pub fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, String>) -> Result<String> { pub fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, Value>) -> Result<String> {
let mut context = Context::new(); let mut context = Context::new();
for (key, value) in args.iter() { for (key, value) in args.iter() {
context.add(key, value); context.add(key, value);
@ -117,7 +149,7 @@ mod tests {
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#); let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#);
assert_eq!(name, "youtube"); assert_eq!(name, "youtube");
assert_eq!(args["id"], "w7Ft2ymGmfc"); assert_eq!(args["id"], "w7Ft2ymGmfc");
assert_eq!(args["autoplay"], "true"); assert_eq!(args["autoplay"], true);
} }
#[test] #[test]
@ -125,6 +157,15 @@ mod tests {
let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#); let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#);
assert_eq!(name, "youtube"); assert_eq!(name, "youtube");
assert_eq!(args["id"], "w7Ft2ymGmfc"); assert_eq!(args["id"], "w7Ft2ymGmfc");
assert_eq!(args["autoplay"], "true"); assert_eq!(args["autoplay"], true);
}
#[test]
fn can_parse_shortcode_number() {
let (name, args) = parse_shortcode(r#"{% test(int=42, float=42.0, autoplay=true) %}"#);
assert_eq!(name, "test");
assert_eq!(args["int"], 42);
assert_eq!(args["float"], 42.0);
assert_eq!(args["autoplay"], true);
} }
} }

View file

@ -47,6 +47,15 @@ Lastly, a shortcode name (and thus the corresponding `.html` file) as well as th
can only contain numbers, letters and underscores, or in Regex terms the following: `[0-9A-Za-z_]`. can only contain numbers, letters and underscores, or in Regex terms the following: `[0-9A-Za-z_]`.
While theoretically an argument name could be a number, it will not be possible to use it in the template in that case. While theoretically an argument name could be a number, it will not be possible to use it in the template in that case.
Argument values can be of 4 types:
- string: surrounded by double quotes `"..."`
- bool: `true` or `false`
- float: a number with a `.` in it
- integer: a number without a `.` in it
Malformed values will be silently ignored.
### Shortcodes without body ### Shortcodes without body
On a new line, call the shortcode as if it was a Tera function in a variable block. All the examples below are valid On a new line, call the shortcode as if it was a Tera function in a variable block. All the examples below are valid