From 6d382f9a34f1339bec524e7b8f1adb8e3488e635 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Thu, 27 May 2021 22:52:24 +0200 Subject: [PATCH] Add `list_variables` method --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ast.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 11 +++++++++ src/main.rs | 41 +++++++++++++++++++++------------ src/parser.rs | 8 +++---- 6 files changed, 104 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1bde10..a65cb1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ dependencies = [ [[package]] name = "blueprint" -version = "0.3.0" +version = "0.4.0" dependencies = [ "nom", "structopt", diff --git a/Cargo.toml b/Cargo.toml index dd26cf0..633ed03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blueprint" -version = "0.3.0" +version = "0.4.0" authors = ["Joakim Hulthe "] description = "A simple templating library" edition = "2018" diff --git a/src/ast.rs b/src/ast.rs index 86e2307..50d8124 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -53,10 +53,23 @@ impl Template<'_> { } 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<'_> { - 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 { Part::Text(text) => w.write_all(text.as_bytes())?, Part::If { @@ -81,10 +94,34 @@ impl Part<'_> { } 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<'_> { - pub fn write(&self, env: &Env, w: &mut impl Write) -> io::Result { + fn write(&self, env: &Env, w: &mut impl Write) -> io::Result { let cond = self.cond.eval(env); let cond = match &*cond { &Value::Bool(cond) => cond, @@ -100,10 +137,18 @@ impl IfThen<'_> { 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<'_> { - pub fn eval<'a>(&self, env: &'a Env) -> Cow<'a, Value> { + fn eval<'a>(&self, env: &'a Env) -> Cow<'a, Value> { match self { Expr::Variable(var) => Cow::Borrowed(env.get(&**var).unwrap_or(&Value::Null)), 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(), + } + } } diff --git a/src/lib.rs b/src/lib.rs index f320d69..999f2fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,4 +173,15 @@ mod test { 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); + } } diff --git a/src/main.rs b/src/main.rs index e1856fd..f86690f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,23 +5,36 @@ use std::path::PathBuf; use structopt::StructOpt; #[derive(StructOpt)] -struct Opt { - template: PathBuf, - env: Vec, +enum Opt { + Eval { template: PathBuf, env: Vec }, + ListVars { template: PathBuf }, } fn main() { let opt = Opt::from_args(); - let file = read_to_string(&opt.template).expect("failed to load file"); - let template = blueprint::parse_template(&file).expect("failed to parse"); - let env = opt - .env - .into_iter() - .map(|key| (key, Value::Bool(true))) - .collect(); - let mut stdout = io::stdout(); - template - .write(&env, &mut stdout) - .expect("failed to write output"); + 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 env = env + .into_iter() + .map(|key| (key, Value::Bool(true))) + .collect(); + + let mut stdout = io::stdout(); + template + .write(&env, &mut stdout) + .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); + } + } + } } diff --git a/src/parser.rs b/src/parser.rs index 60c1e5c..7f46d9d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -86,7 +86,7 @@ fn text_part(i: &str) -> IResult<&str, Part> { //eprintln!("text_part 2 \"{}\"", i); let (i, o) = r.unwrap_or(("", i)); //eprintln!("text_part 3 \"{}\"", i); - if o == "" { + if o.is_empty() { //eprintln!("text_part 4A \"{}\"", i); Err(nom::Err::Error(nom::error::Error::new( i, @@ -168,11 +168,11 @@ pub fn parse_template(template: &str) -> Result { Ok((rest, parts)) => { //eprintln!("{:#?}", parts); - if rest != "" { + if rest.is_empty() { + Ok(Template { parts }) + } else { //panic!("failed to parse input. remainder: \"{}\"", rest); Err(Error::ParseError) - } else { - Ok(Template { parts }) } } Err(_e) => {