Fix many shortcode parsing issues

Closes #228
Closes #229
This commit is contained in:
Vincent Prouillet 2018-03-28 19:31:28 +02:00
parent e40e97711f
commit d67211bfd6
3 changed files with 47 additions and 27 deletions

View file

@ -12,6 +12,7 @@ to the public directory
- Update Tera: now has `break` and `continue` in loops - Update Tera: now has `break` and `continue` in loops
- Gutenberg now creates an anchor link at the position of the `<!-- more -->` tag if you - Gutenberg now creates an anchor link at the position of the `<!-- more -->` tag if you
want to link directly to it want to link directly to it
- Fix many shortcode parsing issues
## 0.3.2 (2018-03-05) ## 0.3.2 (2018-03-05)

View file

@ -6,7 +6,15 @@ use tera::{Tera, Context, Value, to_value};
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
lazy_static!{ lazy_static!{
pub static ref SHORTCODE_RE: Regex = Regex::new(r#"\{(?:%|\{)\s+([[:word:]]+?)\(([[:word:]]+?="?.+?"?)?\)\s+(?:%|\})\}"#).unwrap(); // Does this look like a shortcode?
pub static ref SHORTCODE_RE: Regex = Regex::new(
r#"\{(?:%|\{)\s+(\w+?)\((\w+?="?(?:.|\n)+?"?)?\)\s+(?:%|\})\}"#
).unwrap();
// Parse the shortcode args with capture groups named after their type
pub static ref SHORTCODE_ARGS_RE: Regex = Regex::new(
r#"(?P<name>\w+)=\s*((?P<str>".*?")|(?P<float>[-+]?[0-9]+\.[0-9]+)|(?P<int>[-+]?[0-9]+)|(?P<bool>true|false))"#
).unwrap();
} }
/// A shortcode that has a body /// A shortcode that has a body
@ -52,41 +60,28 @@ pub fn parse_shortcode(input: &str) -> (String, HashMap<String, Value>) {
let name = &caps[1]; let name = &caps[1];
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_cap in SHORTCODE_ARGS_RE.captures_iter(arg_list.as_str()) {
let bits = arg.split('=').collect::<Vec<_>>(); let arg_name = arg_cap["name"].trim().to_string();
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 if let Some(arg_val) = arg_cap.name("str") {
// actually str or bool/number args.insert(arg_name, to_value(arg_val.as_str().replace("\"", "")).unwrap());
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; continue;
} }
if input.contains(&format!("{}=true", arg_name)) { if let Some(arg_val) = arg_cap.name("int") {
args.insert(arg_name, to_value(true).unwrap()); args.insert(arg_name, to_value(arg_val.as_str().parse::<i64>().unwrap()).unwrap());
continue; continue;
} }
if input.contains(&format!("{}=false", arg_name)) { if let Some(arg_val) = arg_cap.name("float") {
args.insert(arg_name, to_value(false).unwrap()); args.insert(arg_name, to_value(arg_val.as_str().parse::<f64>().unwrap()).unwrap());
continue; continue;
} }
// Not a string or a bool, a number then? if let Some(arg_val) = arg_cap.name("bool") {
if arg_val.contains('.') { args.insert(arg_name, to_value(arg_val.as_str() == "true").unwrap());
if let Ok(float) = arg_val.parse::<f64>() {
args.insert(arg_name, to_value(float).unwrap());
}
continue; continue;
} }
// must be an integer
if let Ok(int) = arg_val.parse::<i64>() {
args.insert(arg_name, to_value(int).unwrap());
}
} }
} }
@ -122,6 +117,10 @@ mod tests {
"{% basic() %}", "{% basic() %}",
"{% quo_te(author=\"Bob\") %}", "{% quo_te(author=\"Bob\") %}",
"{{ quo_te(author=\"Bob\") }}", "{{ quo_te(author=\"Bob\") }}",
// https://github.com/Keats/gutenberg/issues/229
r#"{{ youtube(id="dQw4w9WgXcQ",
autoplay=true) }}"#,
]; ];
for i in inputs { for i in inputs {
@ -130,6 +129,15 @@ mod tests {
} }
} }
// https://github.com/Keats/gutenberg/issues/228
#[test]
fn doesnt_panic_on_invalid_shortcode() {
let (name, args) = parse_shortcode(r#"{{ youtube(id="dQw4w9WgXcQ", autoplay) }}"#);
assert_eq!(name, "youtube");
assert_eq!(args["id"], "dQw4w9WgXcQ");
assert!(args.get("autoplay").is_none());
}
#[test] #[test]
fn can_parse_simple_shortcode_no_arg() { fn can_parse_simple_shortcode_no_arg() {
let (name, args) = parse_shortcode(r#"{{ basic() }}"#); let (name, args) = parse_shortcode(r#"{{ basic() }}"#);
@ -162,10 +170,21 @@ mod tests {
#[test] #[test]
fn can_parse_shortcode_number() { fn can_parse_shortcode_number() {
let (name, args) = parse_shortcode(r#"{% test(int=42, float=42.0, autoplay=true) %}"#); let (name, args) = parse_shortcode(r#"{% test(int=42, float=42.0, autoplay=false) %}"#);
assert_eq!(name, "test"); assert_eq!(name, "test");
assert_eq!(args["int"], 42); assert_eq!(args["int"], 42);
assert_eq!(args["float"], 42.0); assert_eq!(args["float"], 42.0);
assert_eq!(args["autoplay"], true); assert_eq!(args["autoplay"], false);
}
// https://github.com/Keats/gutenberg/issues/249
#[test]
fn can_parse_shortcode_with_comma_in_it() {
let (name, args) = parse_shortcode(
r#"{% quote(author="C++ Standard Core Language Defect Reports and Accepted Issues, Revision 82, delete and user-written deallocation function", href="http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#348") %}"#
);
assert_eq!(name, "quote");
assert_eq!(args["author"], "C++ Standard Core Language Defect Reports and Accepted Issues, Revision 82, delete and user-written deallocation function");
assert_eq!(args["href"], "http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#348");
} }
} }