Add list_variables method

This commit is contained in:
2021-05-27 22:52:24 +02:00
parent 0af5d72005
commit 6d382f9a34
6 changed files with 104 additions and 23 deletions

2
Cargo.lock generated
View File

@ -46,7 +46,7 @@ dependencies = [
[[package]] [[package]]
name = "blueprint" name = "blueprint"
version = "0.3.0" version = "0.4.0"
dependencies = [ dependencies = [
"nom", "nom",
"structopt", "structopt",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "blueprint" name = "blueprint"
version = "0.3.0" version = "0.4.0"
authors = ["Joakim Hulthe <joakim@hulthe.net>"] authors = ["Joakim Hulthe <joakim@hulthe.net>"]
description = "A simple templating library" description = "A simple templating library"
edition = "2018" edition = "2018"

View File

@ -53,10 +53,23 @@ impl Template<'_> {
} }
Ok(()) Ok(())
} }
pub fn list_variables(&self) -> Vec<&str> {
let mut vars: Vec<_> = self
.parts
.iter()
.flat_map(|part| part.list_variables())
.collect();
vars.sort_unstable();
vars.dedup();
vars
}
} }
impl Part<'_> { impl Part<'_> {
pub fn write(&self, env: &Env, w: &mut impl Write) -> io::Result<()> { fn write(&self, env: &Env, w: &mut impl Write) -> io::Result<()> {
match self { match self {
Part::Text(text) => w.write_all(text.as_bytes())?, Part::Text(text) => w.write_all(text.as_bytes())?,
Part::If { Part::If {
@ -81,10 +94,34 @@ impl Part<'_> {
} }
Ok(()) Ok(())
} }
fn list_variables(&self) -> Vec<&str> {
match self {
Part::Text(_) => vec![],
Part::If {
if_then,
elifs,
else_part,
} => {
let if_then = if_then.list_variables().into_iter();
let elifs = elifs.iter().flat_map(|elif| elif.list_variables());
if let Some(else_part) = else_part {
if_then
.chain(elifs)
.chain(else_part.list_variables())
.collect()
} else {
if_then.chain(elifs).collect()
}
}
}
}
} }
impl IfThen<'_> { impl IfThen<'_> {
pub fn write(&self, env: &Env, w: &mut impl Write) -> io::Result<bool> { fn write(&self, env: &Env, w: &mut impl Write) -> io::Result<bool> {
let cond = self.cond.eval(env); let cond = self.cond.eval(env);
let cond = match &*cond { let cond = match &*cond {
&Value::Bool(cond) => cond, &Value::Bool(cond) => cond,
@ -100,10 +137,18 @@ impl IfThen<'_> {
Ok(cond) Ok(cond)
} }
fn list_variables(&self) -> Vec<&str> {
self.parts
.iter()
.flat_map(|part| part.list_variables())
.chain(self.cond.list_variables().into_iter())
.collect()
}
} }
impl Expr<'_> { impl Expr<'_> {
pub fn eval<'a>(&self, env: &'a Env) -> Cow<'a, Value> { fn eval<'a>(&self, env: &'a Env) -> Cow<'a, Value> {
match self { match self {
Expr::Variable(var) => Cow::Borrowed(env.get(&**var).unwrap_or(&Value::Null)), Expr::Variable(var) => Cow::Borrowed(env.get(&**var).unwrap_or(&Value::Null)),
Expr::Str(s) => Cow::Owned(Value::Str(s.to_string())), Expr::Str(s) => Cow::Owned(Value::Str(s.to_string())),
@ -136,4 +181,16 @@ impl Expr<'_> {
} }
} }
} }
fn list_variables(&self) -> Vec<&str> {
match self {
Expr::Variable(var) => vec![var],
Expr::Str(_) => vec![],
Expr::Cmp { expr1, expr2, .. } => expr1
.list_variables()
.into_iter()
.chain(expr2.list_variables())
.collect(),
}
}
} }

View File

@ -173,4 +173,15 @@ mod test {
assert_eq!(ast, expected); assert_eq!(ast, expected);
} }
#[test]
fn test_list_vars() {
let input = "one {% if foo %} two {% if bar %} three {% end %} four {% end %} five";
let ast = parse_template(&input).expect("failed to parse");
let expected = vec!["bar", "foo"];
let actual = ast.list_variables();
assert_eq!(actual, expected);
}
} }

View File

@ -5,17 +5,19 @@ use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(StructOpt)] #[derive(StructOpt)]
struct Opt { enum Opt {
template: PathBuf, Eval { template: PathBuf, env: Vec<String> },
env: Vec<String>, ListVars { template: PathBuf },
} }
fn main() { fn main() {
let opt = Opt::from_args(); let opt = Opt::from_args();
let file = read_to_string(&opt.template).expect("failed to load file");
match opt {
Opt::Eval { template, env } => {
let file = read_to_string(template).expect("failed to load file");
let template = blueprint::parse_template(&file).expect("failed to parse"); let template = blueprint::parse_template(&file).expect("failed to parse");
let env = opt let env = env
.env
.into_iter() .into_iter()
.map(|key| (key, Value::Bool(true))) .map(|key| (key, Value::Bool(true)))
.collect(); .collect();
@ -25,3 +27,14 @@ fn main() {
.write(&env, &mut stdout) .write(&env, &mut stdout)
.expect("failed to write output"); .expect("failed to write output");
} }
Opt::ListVars { template } => {
let file = read_to_string(template).expect("failed to load file");
let template = blueprint::parse_template(&file).expect("failed to parse");
for var in template.list_variables() {
println!("{}", var);
}
}
}
}

View File

@ -86,7 +86,7 @@ fn text_part(i: &str) -> IResult<&str, Part> {
//eprintln!("text_part 2 \"{}\"", i); //eprintln!("text_part 2 \"{}\"", i);
let (i, o) = r.unwrap_or(("", i)); let (i, o) = r.unwrap_or(("", i));
//eprintln!("text_part 3 \"{}\"", i); //eprintln!("text_part 3 \"{}\"", i);
if o == "" { if o.is_empty() {
//eprintln!("text_part 4A \"{}\"", i); //eprintln!("text_part 4A \"{}\"", i);
Err(nom::Err::Error(nom::error::Error::new( Err(nom::Err::Error(nom::error::Error::new(
i, i,
@ -168,11 +168,11 @@ pub fn parse_template(template: &str) -> Result<Template, Error> {
Ok((rest, parts)) => { Ok((rest, parts)) => {
//eprintln!("{:#?}", parts); //eprintln!("{:#?}", parts);
if rest != "" { if rest.is_empty() {
Ok(Template { parts })
} else {
//panic!("failed to parse input. remainder: \"{}\"", rest); //panic!("failed to parse input. remainder: \"{}\"", rest);
Err(Error::ParseError) Err(Error::ParseError)
} else {
Ok(Template { parts })
} }
} }
Err(_e) => { Err(_e) => {