|
|
@ -1,12 +1,12 @@
|
|
|
|
use std::collections::{HashMap, VecDeque};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
|
|
|
|
use fancy_regex::{CaptureMatches, Captures, Regex};
|
|
|
|
use fancy_regex::{CaptureMatches, Captures, Regex};
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use mdbook::errors::Result;
|
|
|
|
use mdbook::errors::Result;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use crate::FileReader;
|
|
|
|
|
|
|
|
|
|
|
|
const ESCAPE_CHAR: char = '\\';
|
|
|
|
const ESCAPE_CHAR: char = '\\';
|
|
|
|
const LINE_BREAKS: &[char] = &['\n', '\r'];
|
|
|
|
const LINE_BREAKS: &[char] = &['\n', '\r'];
|
|
|
|
|
|
|
|
|
|
|
@ -14,7 +14,7 @@ lazy_static! {
|
|
|
|
// https://stackoverflow.com/questions/22871602/optimizing-regex-to-fine-key-value-pairs-space-delimited
|
|
|
|
// https://stackoverflow.com/questions/22871602/optimizing-regex-to-fine-key-value-pairs-space-delimited
|
|
|
|
static ref TEMPLATE_ARGS: Regex = Regex::new(r"(?<=\s|\A)([^\s=]+)=(.*?)(?=(?:\s[^\s=]+=|$))").unwrap();
|
|
|
|
static ref TEMPLATE_ARGS: Regex = Regex::new(r"(?<=\s|\A)([^\s=]+)=(.*?)(?=(?:\s[^\s=]+=|$))").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// r"(?x)\\\{\{\#.*\}\}|\{\{\s*\#(template)\s+([a-zA-Z0-9_^'<>().:*+|\\\/?-]+)\s+([^}]+)\}\}"
|
|
|
|
// r"(?x)\\\{\{\#.*\}\}|\{\{\s*\#(template)\s+([\S]+)\s*\}\}|\{\{\s*\#(template)\s+([\S]+)\s+([^}]+)\}\}"
|
|
|
|
static ref TEMPLATE: Regex = Regex::new(
|
|
|
|
static ref TEMPLATE: Regex = Regex::new(
|
|
|
|
r"(?x) # enable insignificant whitespace mode
|
|
|
|
r"(?x) # enable insignificant whitespace mode
|
|
|
|
|
|
|
|
|
|
|
@ -22,32 +22,49 @@ lazy_static! {
|
|
|
|
\#.* # match any character
|
|
|
|
\#.* # match any character
|
|
|
|
\}\} # escaped link closing parens
|
|
|
|
\}\} # escaped link closing parens
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # or
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\{\{\s* # link opening parens and whitespace(s)
|
|
|
|
|
|
|
|
\#(template) # link type - template
|
|
|
|
|
|
|
|
\s+ # separating whitespace
|
|
|
|
|
|
|
|
([\S]+) # relative path to template file
|
|
|
|
|
|
|
|
\s* # optional separating whitespaces(s)
|
|
|
|
|
|
|
|
\}\} # link closing parens
|
|
|
|
|
|
|
|
|
|
|
|
| # or
|
|
|
|
| # or
|
|
|
|
|
|
|
|
|
|
|
|
\{\{\s* # link opening parens and whitespace(s)
|
|
|
|
\{\{\s* # link opening parens and whitespace(s)
|
|
|
|
\#(template) # link type - template
|
|
|
|
\#(template) # link type - template
|
|
|
|
\s+ # separating whitespace
|
|
|
|
\s+ # separating whitespace
|
|
|
|
([\w'<>.:^\-\(\)\*\+\|\\\/\?]+) # relative path to template file
|
|
|
|
([\S]+) # relative path to template file
|
|
|
|
\s+ # separating whitespace(s)
|
|
|
|
\s+ # separating whitespace(s)
|
|
|
|
([^}]+) # get all template arguments
|
|
|
|
([^}]+) # get all template arguments
|
|
|
|
\}\} # link closing parens"
|
|
|
|
\}\} # link closing parens"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// r"(?x)\\\{\{\#.*\}\}|\{\{\s*\#([\w'<>.:^\-\(\)\*\+\|\\\/\?]+)\s*\}\}|\{\{\s*\#([\w'<>.:^\-\(\)\*\+\|\\\/\?]+)\s+([^}]+)\}\}"
|
|
|
|
// r"(?x)\\\[\[.*\]\]|\[\[\s*\#([\S]+)\s*\]\]|\[\[\s*\#([\S]+)\s+([^]]+)\]\]"
|
|
|
|
static ref ARGS: Regex = Regex::new(
|
|
|
|
static ref ARGS: Regex = Regex::new(
|
|
|
|
r"(?x) # enable insignificant whitespace mode
|
|
|
|
r"(?x) # enable insignificant whitespace mode
|
|
|
|
|
|
|
|
|
|
|
|
\\\{\{ # escaped link opening parens
|
|
|
|
\\\[\[ # escaped link opening square brackets
|
|
|
|
\#.* # match any character
|
|
|
|
\#.* # match any character
|
|
|
|
\}\} # escaped link closing parens
|
|
|
|
\]\] # escaped link closing parens
|
|
|
|
|
|
|
|
|
|
|
|
| # or
|
|
|
|
| # or
|
|
|
|
|
|
|
|
|
|
|
|
\{\{\s* # link opening parens and whitespace(s)
|
|
|
|
\[\[\s* # link opening parens and whitespace(s)
|
|
|
|
\#([\w'<>.:^\-\(\)\*\+\|\\\/\?]+) # arg name
|
|
|
|
\#([\S]+) # arg name
|
|
|
|
\s* # optional separating whitespace(s)
|
|
|
|
\s* # optional separating whitespace(s)
|
|
|
|
\}\} # link closing parens"
|
|
|
|
\]\] # link closing parens
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # or
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\[\[\s* # link opening parens and whitespace(s)
|
|
|
|
|
|
|
|
\#([\S]+) # arg name
|
|
|
|
|
|
|
|
\s+ # optional separating whitespace(s)
|
|
|
|
|
|
|
|
([^]]+) # match everything after space
|
|
|
|
|
|
|
|
\]\] # link closing parens"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -65,55 +82,61 @@ impl<'a> Link<'a> {
|
|
|
|
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
|
|
|
|
fn from_capture(cap: Captures<'a>) -> Option<Link<'a>> {
|
|
|
|
let mut all_args = HashMap::with_capacity(20);
|
|
|
|
let mut all_args = HashMap::with_capacity(20);
|
|
|
|
|
|
|
|
|
|
|
|
let link_type = match (cap.get(0), cap.get(1), cap.get(2), cap.get(3)) {
|
|
|
|
// https://regex101.com/r/OBywLv/1
|
|
|
|
(Some(mat), _, _, _) if mat.as_str().contains(LINE_BREAKS) => {
|
|
|
|
let link_type = match (
|
|
|
|
/*
|
|
|
|
cap.get(0),
|
|
|
|
Given a template string that looks like:
|
|
|
|
cap.get(1),
|
|
|
|
{{#template
|
|
|
|
cap.get(2),
|
|
|
|
footer.md
|
|
|
|
cap.get(3),
|
|
|
|
path=../images
|
|
|
|
cap.get(4),
|
|
|
|
author=Hazel
|
|
|
|
cap.get(5),
|
|
|
|
}}
|
|
|
|
) {
|
|
|
|
|
|
|
|
// This looks like {{#template <file>}}
|
|
|
|
The resulting args: <VecDeque<&str> will look like:
|
|
|
|
(_, _, Some(file), None, None, None) => {
|
|
|
|
["{{#template", "footer.md", "path=../images", "author=Hazel", "}}"]
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
let mut args = mat
|
|
|
|
|
|
|
|
.as_str()
|
|
|
|
|
|
|
|
.lines()
|
|
|
|
|
|
|
|
.map(|line| {
|
|
|
|
|
|
|
|
line.trim_end_matches(LINE_BREAKS)
|
|
|
|
|
|
|
|
.trim_start_matches(LINE_BREAKS)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect::<VecDeque<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remove {{#template
|
|
|
|
|
|
|
|
args.pop_front();
|
|
|
|
|
|
|
|
// Remove ending }}
|
|
|
|
|
|
|
|
args.pop_back();
|
|
|
|
|
|
|
|
// Store relative path of template file
|
|
|
|
|
|
|
|
let file = args.pop_front().unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let split_args = args
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.map(|arg| {
|
|
|
|
|
|
|
|
let mut split_n = arg.splitn(2, '=');
|
|
|
|
|
|
|
|
let key = split_n.next().unwrap().trim();
|
|
|
|
|
|
|
|
let value = split_n.next().unwrap();
|
|
|
|
|
|
|
|
(key, value)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
all_args.extend(split_args);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Some(LinkType::Template(PathBuf::from(file.trim())))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
(_, _, Some(file), Some(args)) => {
|
|
|
|
|
|
|
|
all_args.extend(extract_template_args(args.as_str()).collect::<Vec<_>>());
|
|
|
|
|
|
|
|
Some(LinkType::Template(PathBuf::from(file.as_str())))
|
|
|
|
Some(LinkType::Template(PathBuf::from(file.as_str())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(Some(mat), _, _, _) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
|
|
|
// This looks like \{{#<whatever string>}}
|
|
|
|
|
|
|
|
(Some(mat), _, _, _, _, _) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
|
|
|
Some(LinkType::Escaped)
|
|
|
|
Some(LinkType::Escaped)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(_, None, None, _, Some(file), Some(args)) => {
|
|
|
|
|
|
|
|
let split_args = match args.as_str().contains(LINE_BREAKS) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
This looks like
|
|
|
|
|
|
|
|
{{#template
|
|
|
|
|
|
|
|
<file>
|
|
|
|
|
|
|
|
<args>
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
true => args
|
|
|
|
|
|
|
|
.as_str()
|
|
|
|
|
|
|
|
.split(LINE_BREAKS)
|
|
|
|
|
|
|
|
.map(|str| str.trim())
|
|
|
|
|
|
|
|
.filter(|trimmed| !trimmed.is_empty())
|
|
|
|
|
|
|
|
.map(|mat| {
|
|
|
|
|
|
|
|
let mut split_n = mat.splitn(2, '=');
|
|
|
|
|
|
|
|
let key = split_n.next().unwrap().trim();
|
|
|
|
|
|
|
|
let value = split_n.next().unwrap();
|
|
|
|
|
|
|
|
(key, value)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This looks like {{#template <file> <args>}}
|
|
|
|
|
|
|
|
false => TEMPLATE_ARGS
|
|
|
|
|
|
|
|
.captures_iter(args.as_str())
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.map(|mat| {
|
|
|
|
|
|
|
|
let mut split_n = mat.unwrap().get(0).unwrap().as_str().splitn(2, '=');
|
|
|
|
|
|
|
|
let key = split_n.next().unwrap().trim();
|
|
|
|
|
|
|
|
let value = split_n.next().unwrap();
|
|
|
|
|
|
|
|
(key, value)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
all_args.extend(split_args);
|
|
|
|
|
|
|
|
Some(LinkType::Template(PathBuf::from(file.as_str())))
|
|
|
|
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -128,20 +151,16 @@ impl<'a> Link<'a> {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) fn replace_args<P: AsRef<Path>>(&self, base: P) -> Result<String> {
|
|
|
|
pub(crate) fn replace_args<P, FR>(&self, base: P, file_reader: &FR) -> Result<String>
|
|
|
|
|
|
|
|
where
|
|
|
|
|
|
|
|
P: AsRef<Path>,
|
|
|
|
|
|
|
|
FR: FileReader,
|
|
|
|
|
|
|
|
{
|
|
|
|
match self.link_type {
|
|
|
|
match self.link_type {
|
|
|
|
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
|
|
|
LinkType::Escaped => Ok((&self.link_text[1..]).to_owned()),
|
|
|
|
LinkType::Template(ref pat) => {
|
|
|
|
LinkType::Template(ref pat) => {
|
|
|
|
let target = base.as_ref().join(pat);
|
|
|
|
let target = base.as_ref().join(pat);
|
|
|
|
|
|
|
|
let contents = file_reader.read_to_string(&target, self.link_text)?;
|
|
|
|
let contents = fs::read_to_string(&target).with_context(|| {
|
|
|
|
|
|
|
|
format!(
|
|
|
|
|
|
|
|
"Could not read template file {} ({})",
|
|
|
|
|
|
|
|
self.link_text,
|
|
|
|
|
|
|
|
target.display(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Args::replace(contents.as_str(), &self.args))
|
|
|
|
Ok(Args::replace(contents.as_str(), &self.args))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -188,26 +207,6 @@ pub(crate) fn extract_template_links(contents: &str) -> LinkIter<'_> {
|
|
|
|
LinkIter(TEMPLATE.captures_iter(contents))
|
|
|
|
LinkIter(TEMPLATE.captures_iter(contents))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct TemplateArgsIter<'a>(CaptureMatches<'a, 'a>);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> Iterator for TemplateArgsIter<'a> {
|
|
|
|
|
|
|
|
type Item = (&'a str, &'a str);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
|
|
|
if let Some(cap) = (&mut self.0).next() {
|
|
|
|
|
|
|
|
let mut split_args = cap.unwrap().get(0).unwrap().as_str().splitn(2, '=');
|
|
|
|
|
|
|
|
let key = split_args.next().unwrap().trim();
|
|
|
|
|
|
|
|
let value = split_args.next().unwrap();
|
|
|
|
|
|
|
|
return Some((key, value));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn extract_template_args(contents: &str) -> TemplateArgsIter<'_> {
|
|
|
|
|
|
|
|
TemplateArgsIter(TEMPLATE_ARGS.captures_iter(contents))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
|
|
struct Args<'a> {
|
|
|
|
struct Args<'a> {
|
|
|
|
start_index: usize,
|
|
|
|
start_index: usize,
|
|
|
@ -231,12 +230,10 @@ impl<'a> Args<'a> {
|
|
|
|
None => {}
|
|
|
|
None => {}
|
|
|
|
Some(value) => replaced.push_str(value),
|
|
|
|
Some(value) => replaced.push_str(value),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ArgsType::Default(argument, default_value) => {
|
|
|
|
ArgsType::Default(argument, default_value) => match all_args.get(argument) {
|
|
|
|
// [TEM #2]
|
|
|
|
None => replaced.push_str(default_value),
|
|
|
|
// check if captured_arg exists within hashmap
|
|
|
|
Some(value) => replaced.push_str(value),
|
|
|
|
// if so, replace arg with corresponding value and push to replaced string
|
|
|
|
},
|
|
|
|
// if not, replace arg with default value and push to replaced string
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
previous_end_index = captured_arg.end_index;
|
|
|
|
previous_end_index = captured_arg.end_index;
|
|
|
@ -247,18 +244,16 @@ impl<'a> Args<'a> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn from_capture(cap: Captures<'a>) -> Option<Args<'a>> {
|
|
|
|
fn from_capture(cap: Captures<'a>) -> Option<Args<'a>> {
|
|
|
|
let arg_type = match (cap.get(0), cap.get(1), cap.get(2)) {
|
|
|
|
// https://regex101.com/r/lKSOOl/4
|
|
|
|
(_, Some(argument), None) => {
|
|
|
|
let arg_type = match (cap.get(0), cap.get(1), cap.get(2), cap.get(3)) {
|
|
|
|
println!("Argument -> {:?}", argument);
|
|
|
|
// This looks like [[#path]]
|
|
|
|
Some(ArgsType::Plain(argument.as_str()))
|
|
|
|
(_, Some(argument), None, None) => Some(ArgsType::Plain(argument.as_str())),
|
|
|
|
}
|
|
|
|
// This looks like [[#path ../images]]
|
|
|
|
(_, Some(argument), Some(default_value)) => {
|
|
|
|
(_, _, Some(argument), Some(default_value)) => {
|
|
|
|
println!("Argument -> {:?}", argument);
|
|
|
|
|
|
|
|
println!("Default Value -> {:?}", default_value);
|
|
|
|
|
|
|
|
Some(ArgsType::Default(argument.as_str(), default_value.as_str()))
|
|
|
|
Some(ArgsType::Default(argument.as_str(), default_value.as_str()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(Some(mat), _, _) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
|
|
|
// This looks like \[[#any string]]
|
|
|
|
println!("Escaped -> {}", mat.as_str());
|
|
|
|
(Some(mat), _, _, _) if mat.as_str().starts_with(ESCAPE_CHAR) => {
|
|
|
|
Some(ArgsType::Escaped)
|
|
|
|
Some(ArgsType::Escaped)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
_ => None,
|
|
|
@ -303,27 +298,10 @@ fn extract_args(contents: &str) -> ArgsIter<'_> {
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
#[cfg(test)]
|
|
|
|
mod link_tests {
|
|
|
|
mod link_tests {
|
|
|
|
use std::any::Any;
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
|
|
use crate::links::{extract_args, extract_template_links, Args, ArgsType, Link, LinkType};
|
|
|
|
use crate::links::{extract_args, extract_template_links, Args, ArgsType, Link, LinkType};
|
|
|
|
use crate::replace_template;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_escaped_template_link() {
|
|
|
|
|
|
|
|
let start = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
```hbs
|
|
|
|
|
|
|
|
\{{#template template.md}} << an escaped link!
|
|
|
|
|
|
|
|
```";
|
|
|
|
|
|
|
|
let end = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
```hbs
|
|
|
|
|
|
|
|
{{#template template.md}} << an escaped link!
|
|
|
|
|
|
|
|
```";
|
|
|
|
|
|
|
|
assert_eq!(replace_template(start, "", "", 0), end);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_zero_template_links() {
|
|
|
|
fn test_extract_zero_template_links() {
|
|
|
@ -331,12 +309,6 @@ mod link_tests {
|
|
|
|
assert_eq!(extract_template_links(s).collect::<Vec<_>>(), vec![])
|
|
|
|
assert_eq!(extract_template_links(s).collect::<Vec<_>>(), vec![])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_zero_template_links_without_args() {
|
|
|
|
|
|
|
|
let s = "{{#template templates/footer.md}}";
|
|
|
|
|
|
|
|
assert_eq!(extract_template_links(s).collect::<Vec<_>>(), vec![])
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_template_links_partial_match() {
|
|
|
|
fn test_extract_template_links_partial_match() {
|
|
|
|
let s = "Some random text with {{#template...";
|
|
|
|
let s = "Some random text with {{#template...";
|
|
|
@ -351,7 +323,7 @@ mod link_tests {
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_template_links_empty() {
|
|
|
|
fn test_extract_template_links_empty() {
|
|
|
|
let s = "Some random text with {{#template}} and {{#template }} {{}} {{#}}...";
|
|
|
|
let s = "Some random text with {{}} {{#}}...";
|
|
|
|
assert_eq!(extract_template_links(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_template_links(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -361,6 +333,24 @@ mod link_tests {
|
|
|
|
assert!(extract_template_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
assert!(extract_template_links(s).collect::<Vec<_>>() == vec![]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_zero_template_links_without_args() {
|
|
|
|
|
|
|
|
let s = "{{#template templates/footer.md}}";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_template_links(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
res,
|
|
|
|
|
|
|
|
vec![Link {
|
|
|
|
|
|
|
|
start_index: 0,
|
|
|
|
|
|
|
|
end_index: 33,
|
|
|
|
|
|
|
|
link_type: LinkType::Template(PathBuf::from("templates/footer.md")),
|
|
|
|
|
|
|
|
link_text: "{{#template templates/footer.md}}",
|
|
|
|
|
|
|
|
args: HashMap::new()
|
|
|
|
|
|
|
|
},]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_template_links_simple() {
|
|
|
|
fn test_extract_template_links_simple() {
|
|
|
|
let s =
|
|
|
|
let s =
|
|
|
@ -370,13 +360,22 @@ mod link_tests {
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
assert_eq!(
|
|
|
|
res,
|
|
|
|
res,
|
|
|
|
vec![Link {
|
|
|
|
vec![
|
|
|
|
start_index: 48,
|
|
|
|
Link {
|
|
|
|
end_index: 79,
|
|
|
|
start_index: 22,
|
|
|
|
link_type: LinkType::Template(PathBuf::from("test.rs")),
|
|
|
|
end_index: 43,
|
|
|
|
link_text: "{{#template test.rs lang=rust}}",
|
|
|
|
link_type: LinkType::Template(PathBuf::from("file.rs")),
|
|
|
|
args: HashMap::from([("lang", "rust")])
|
|
|
|
link_text: "{{#template file.rs}}",
|
|
|
|
},]
|
|
|
|
args: HashMap::new()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Link {
|
|
|
|
|
|
|
|
start_index: 48,
|
|
|
|
|
|
|
|
end_index: 79,
|
|
|
|
|
|
|
|
link_type: LinkType::Template(PathBuf::from("test.rs")),
|
|
|
|
|
|
|
|
link_text: "{{#template test.rs lang=rust}}",
|
|
|
|
|
|
|
|
args: HashMap::from([("lang", "rust")])
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
]
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -498,6 +497,26 @@ year=2022
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_template_links_with_newlines_malformed() {
|
|
|
|
|
|
|
|
let s = "{{#template test.rs
|
|
|
|
|
|
|
|
lang=rust
|
|
|
|
|
|
|
|
year=2022}}";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_template_links(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
res,
|
|
|
|
|
|
|
|
vec![Link {
|
|
|
|
|
|
|
|
start_index: 0,
|
|
|
|
|
|
|
|
end_index: 58,
|
|
|
|
|
|
|
|
link_type: LinkType::Template(PathBuf::from("test.rs")),
|
|
|
|
|
|
|
|
link_text: "{{#template test.rs \n lang=rust\n year=2022}}",
|
|
|
|
|
|
|
|
args: HashMap::from([("lang", "rust"), ("year", "2022")]),
|
|
|
|
|
|
|
|
},]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_zero_args() {
|
|
|
|
fn test_extract_zero_args() {
|
|
|
|
let s = "This is some text without any template links";
|
|
|
|
let s = "This is some text without any template links";
|
|
|
@ -506,25 +525,25 @@ year=2022
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_partial_match() {
|
|
|
|
fn test_extract_args_partial_match() {
|
|
|
|
let s = "Some random text with {{#height...";
|
|
|
|
let s = "Some random text with [[#height...";
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
let s = "Some random text with {{#image ferris.png...";
|
|
|
|
let s = "Some random text with [[#image ferris.png...";
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
let s = "Some random text with {{#width 550...";
|
|
|
|
let s = "Some random text with [[#width 550...";
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
let s = "Some random text with \\{{#title...";
|
|
|
|
let s = "Some random text with \\[[#title...";
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_empty() {
|
|
|
|
fn test_extract_args_empty() {
|
|
|
|
let s = "Some random text with {{}} {{#}}...";
|
|
|
|
let s = "Some random text with [[]] [[#]]...";
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
assert_eq!(extract_args(s).collect::<Vec<_>>(), vec![]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_simple() {
|
|
|
|
fn test_extract_args_simple() {
|
|
|
|
let s = "This is some random text with {{#path}} and then some more random text";
|
|
|
|
let s = "This is some random text with [[#path]] and then some more random text";
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_args(s).collect::<Vec<_>>();
|
|
|
|
let res = extract_args(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
@ -534,7 +553,7 @@ year=2022
|
|
|
|
start_index: 30,
|
|
|
|
start_index: 30,
|
|
|
|
end_index: 39,
|
|
|
|
end_index: 39,
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_text: "{{#path}}"
|
|
|
|
args_text: "[[#path]]"
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -543,36 +562,20 @@ year=2022
|
|
|
|
fn test_extract_args_escaped() {
|
|
|
|
fn test_extract_args_escaped() {
|
|
|
|
let start = r"
|
|
|
|
let start = r"
|
|
|
|
Example Text
|
|
|
|
Example Text
|
|
|
|
\{{#height 200px}} << an escaped argument!
|
|
|
|
\[[#height 200px]] << an escaped argument!
|
|
|
|
";
|
|
|
|
";
|
|
|
|
let end = r"
|
|
|
|
let end = r"
|
|
|
|
Example Text
|
|
|
|
Example Text
|
|
|
|
{{#height 200px}} << an escaped argument!
|
|
|
|
[[#height 200px]] << an escaped argument!
|
|
|
|
";
|
|
|
|
";
|
|
|
|
assert_eq!(Args::replace(start, &HashMap::<&str, &str>::new()), end);
|
|
|
|
assert_eq!(Args::replace(start, &HashMap::<&str, &str>::new()), end);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_replace_args_simple() {
|
|
|
|
|
|
|
|
let start = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
{{#height}} << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
let end = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
200px << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
Args::replace(start, &HashMap::from([("height", "200px")])),
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_with_spaces() {
|
|
|
|
fn test_extract_args_with_spaces() {
|
|
|
|
let s1 = "This is some random text with {{ #path }}";
|
|
|
|
let s1 = "This is some random text with [[ #path ]]";
|
|
|
|
let s2 = "This is some random text with {{#path }}";
|
|
|
|
let s2 = "This is some random text with [[#path ]]";
|
|
|
|
let s3 = "This is some random text with {{ #path}}";
|
|
|
|
let s3 = "This is some random text with [[ #path]]";
|
|
|
|
|
|
|
|
|
|
|
|
let res1 = extract_args(s1).collect::<Vec<_>>();
|
|
|
|
let res1 = extract_args(s1).collect::<Vec<_>>();
|
|
|
|
let res2 = extract_args(s2).collect::<Vec<_>>();
|
|
|
|
let res2 = extract_args(s2).collect::<Vec<_>>();
|
|
|
@ -584,7 +587,7 @@ year=2022
|
|
|
|
start_index: 30,
|
|
|
|
start_index: 30,
|
|
|
|
end_index: 51,
|
|
|
|
end_index: 51,
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_text: "{{ #path }}"
|
|
|
|
args_text: "[[ #path ]]"
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
@ -594,7 +597,7 @@ year=2022
|
|
|
|
start_index: 30,
|
|
|
|
start_index: 30,
|
|
|
|
end_index: 46,
|
|
|
|
end_index: 46,
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_text: "{{#path }}"
|
|
|
|
args_text: "[[#path ]]"
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
@ -604,14 +607,105 @@ year=2022
|
|
|
|
start_index: 30,
|
|
|
|
start_index: 30,
|
|
|
|
end_index: 44,
|
|
|
|
end_index: 44,
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_type: ArgsType::Plain("path"),
|
|
|
|
args_text: "{{ #path}}"
|
|
|
|
args_text: "[[ #path]]"
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_args_with_default_value() {
|
|
|
|
|
|
|
|
let s = "This is some random text with [[#path 200px]] and then some more random text";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_args(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
res,
|
|
|
|
|
|
|
|
vec![Args {
|
|
|
|
|
|
|
|
start_index: 30,
|
|
|
|
|
|
|
|
end_index: 45,
|
|
|
|
|
|
|
|
args_type: ArgsType::Default("path", "200px"),
|
|
|
|
|
|
|
|
args_text: "[[#path 200px]]"
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_args_with_default_value_and_spaces() {
|
|
|
|
|
|
|
|
let s =
|
|
|
|
|
|
|
|
"This is some random text with [[ #path 400px ]] and then some more random text";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_args(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
res,
|
|
|
|
|
|
|
|
vec![Args {
|
|
|
|
|
|
|
|
start_index: 30,
|
|
|
|
|
|
|
|
end_index: 52,
|
|
|
|
|
|
|
|
args_type: ArgsType::Default("path", "400px "),
|
|
|
|
|
|
|
|
args_text: "[[ #path 400px ]]"
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_extract_args_with_multiple_spaced_default_value() {
|
|
|
|
|
|
|
|
let s = "[[#title An Amazing Title]]";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = extract_args(s).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
res,
|
|
|
|
|
|
|
|
vec![Args {
|
|
|
|
|
|
|
|
start_index: 0,
|
|
|
|
|
|
|
|
end_index: 27,
|
|
|
|
|
|
|
|
args_type: ArgsType::Default("title", "An Amazing Title"),
|
|
|
|
|
|
|
|
args_text: "[[#title An Amazing Title]]"
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_with_default_value() {}
|
|
|
|
fn test_replace_args_simple() {
|
|
|
|
|
|
|
|
let start = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
[[#height]] << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
let end = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
200px << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
Args::replace(start, &HashMap::from([("height", "200px")])),
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_replace_args_with_default() {
|
|
|
|
|
|
|
|
let start = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
[[#height 300px]] << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
let end = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
300px << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
assert_eq!(Args::replace(start, &HashMap::<&str, &str>::new()), end);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
#[test]
|
|
|
|
fn test_extract_args_with_default_value_and_spaces() {}
|
|
|
|
fn test_replace_args_overriding_default() {
|
|
|
|
|
|
|
|
let start = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
[[#height 300px]] << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
let end = r"
|
|
|
|
|
|
|
|
Example Text
|
|
|
|
|
|
|
|
200px << an argument!
|
|
|
|
|
|
|
|
";
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
Args::replace(start, &HashMap::from([("height", "200px")])),
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|