├── CHANGES.md ├── .gitignore ├── .github └── workflows │ └── rust.yml ├── tests ├── niladic.rs └── define.rs ├── Cargo.toml ├── LICENSE ├── src ├── lib.rs ├── template.rs ├── error.rs ├── utils.rs ├── printf.rs ├── print_verb.rs ├── node.rs ├── lexer.rs ├── funcs.rs ├── exec.rs └── parse.rs └── README.md /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## [0.6.0] - 2021-06-07 4 | ### Added 5 | - Maximum template depth 6 | ### Changed 7 | - Move from `String` to `thiserror` for errors 8 | - `Context::from` returns `Context` instead of `Result` 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | .idea 9 | .vscode -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Run fmt 20 | run: cargo fmt -- --check 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose --all-features 25 | - name: Run clippy 26 | run: cargo clippy --all-features -- -D warnings 27 | -------------------------------------------------------------------------------- /tests/niladic.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use gtmpl::{Func, FuncError, Value}; 3 | use gtmpl_derive::Gtmpl; 4 | 5 | fn plus_one(args: &[Value]) -> Result { 6 | if let Value::Object(ref o) = &args[0] { 7 | if let Some(Value::Number(ref n)) = o.get("num") { 8 | if let Some(i) = n.as_i64() { 9 | return Ok((i + 1).into()); 10 | } 11 | } 12 | } 13 | Err(anyhow!("integer required, got: {:?}", args).into()) 14 | } 15 | 16 | #[derive(Gtmpl)] 17 | struct AddMe { 18 | num: u8, 19 | plus_one: Func, 20 | } 21 | 22 | #[test] 23 | fn simple_niladic_method() { 24 | let add_me = AddMe { num: 42, plus_one }; 25 | let output = gtmpl::template("The answer is: {{ .plus_one }}", add_me); 26 | assert_eq!(&output.unwrap(), "The answer is: 43"); 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gtmpl" 3 | version = "0.7.1" 4 | authors = ["Florian Dieminger "] 5 | description = "The Golang Templating Language for Rust" 6 | license = "MIT" 7 | repository = "https://github.com/fiji-flo/gtmpl-rust" 8 | documentation = "https://docs.rs/crate/gtmpl" 9 | keywords = ["golang", "template", "templating"] 10 | categories = ["template-engine"] 11 | readme = "README.md" 12 | include = ["Cargo.toml", "src/**/*.rs", "tests/**/*.rs", "README.md", "LICENSE"] 13 | edition = "2018" 14 | 15 | [badges] 16 | maintenance = { status = "passively-maintained" } 17 | 18 | [features] 19 | gtmpl_dynamic_template = [] 20 | 21 | [dependencies] 22 | lazy_static = "1" 23 | percent-encoding = "2" 24 | gtmpl_value = "0.5" 25 | anyhow = "1" 26 | thiserror = "1" 27 | 28 | [dev-dependencies] 29 | gtmpl_derive = "0.5" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Florian Merz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The Golang Templating Language for Rust. 2 | //! 3 | //! ## Example 4 | //! ```rust 5 | //! use gtmpl; 6 | //! 7 | //! let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl"); 8 | //! assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust"); 9 | //! ``` 10 | pub mod error; 11 | mod exec; 12 | pub mod funcs; 13 | mod lexer; 14 | mod node; 15 | mod parse; 16 | mod print_verb; 17 | mod printf; 18 | mod template; 19 | mod utils; 20 | 21 | #[doc(inline)] 22 | pub use crate::template::Template; 23 | 24 | #[doc(inline)] 25 | pub use crate::exec::Context; 26 | 27 | #[doc(inline)] 28 | pub use gtmpl_value::Func; 29 | 30 | pub use gtmpl_value::FuncError; 31 | 32 | #[doc(inline)] 33 | pub use gtmpl_value::from_value; 34 | 35 | pub use error::TemplateError; 36 | pub use gtmpl_value::Value; 37 | 38 | /// Provides simple basic templating given just a template sting and context. 39 | /// 40 | /// ## Example 41 | /// ```rust 42 | /// let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl"); 43 | /// assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust"); 44 | /// ``` 45 | pub fn template>(template_str: &str, context: T) -> Result { 46 | let mut tmpl = Template::default(); 47 | tmpl.parse(template_str)?; 48 | tmpl.render(&Context::from(context)).map_err(Into::into) 49 | } 50 | -------------------------------------------------------------------------------- /src/template.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::error::{ParseError, TemplateError}; 4 | use crate::funcs::BUILTINS; 5 | use crate::parse::{parse, Tree}; 6 | 7 | use gtmpl_value::Func; 8 | 9 | /// The main template structure. 10 | pub struct Template { 11 | pub name: String, 12 | pub text: String, 13 | pub funcs: HashMap, 14 | pub tree_set: HashMap, 15 | } 16 | 17 | impl Default for Template { 18 | fn default() -> Template { 19 | Template { 20 | name: String::default(), 21 | text: String::from(""), 22 | funcs: BUILTINS.iter().map(|&(k, v)| (k.to_owned(), v)).collect(), 23 | tree_set: HashMap::default(), 24 | } 25 | } 26 | } 27 | 28 | impl Template { 29 | /// Creates a new empty template with a given `name`. 30 | pub fn with_name>(name: T) -> Template { 31 | Template { 32 | name: name.into(), 33 | ..Default::default() 34 | } 35 | } 36 | 37 | /// Adds a single custom function to the template. 38 | /// 39 | /// ## Example 40 | /// 41 | /// ```rust 42 | /// use gtmpl::{Context, Func, FuncError, Value}; 43 | /// 44 | /// fn hello_world(_args: &[Value]) -> Result { 45 | /// Ok(Value::from("Hello World!")) 46 | /// } 47 | /// 48 | /// let mut tmpl = gtmpl::Template::default(); 49 | /// tmpl.add_func("helloWorld", hello_world); 50 | /// tmpl.parse("{{ helloWorld }}").unwrap(); 51 | /// let output = tmpl.render(&Context::empty()); 52 | /// assert_eq!(&output.unwrap(), "Hello World!"); 53 | /// ``` 54 | pub fn add_func(&mut self, name: &str, func: Func) { 55 | self.funcs.insert(name.to_owned(), func); 56 | } 57 | 58 | /// Adds custom functions to the template. 59 | /// 60 | /// ## Example 61 | /// 62 | /// ```rust 63 | /// use std::collections::HashMap; 64 | /// 65 | /// use gtmpl::{Context, Func, FuncError, Value}; 66 | /// 67 | /// fn hello_world(_args: &[Value]) -> Result { 68 | /// Ok(Value::from("Hello World!")) 69 | /// } 70 | /// 71 | /// let funcs = vec![("helloWorld", hello_world as Func)]; 72 | /// let mut tmpl = gtmpl::Template::default(); 73 | /// tmpl.add_funcs(&funcs); 74 | /// tmpl.parse("{{ helloWorld }}").unwrap(); 75 | /// let output = tmpl.render(&Context::empty()); 76 | /// assert_eq!(&output.unwrap(), "Hello World!"); 77 | /// ``` 78 | pub fn add_funcs + Clone>(&mut self, funcs: &[(T, Func)]) { 79 | self.funcs 80 | .extend(funcs.iter().cloned().map(|(k, v)| (k.into(), v))); 81 | } 82 | 83 | /// Parse the given `text` as template body. 84 | /// 85 | /// ## Example 86 | /// 87 | /// ```rust 88 | /// let mut tmpl = gtmpl::Template::default(); 89 | /// tmpl.parse("Hello World!").unwrap(); 90 | /// ``` 91 | pub fn parse>(&mut self, text: T) -> Result<(), ParseError> { 92 | let tree_set = parse( 93 | self.name.clone(), 94 | text.into(), 95 | self.funcs.keys().cloned().collect(), 96 | )?; 97 | self.tree_set.extend(tree_set); 98 | Ok(()) 99 | } 100 | 101 | /// Add the given `text` as a template with a `name`. 102 | /// 103 | /// ## Example 104 | /// 105 | /// ```rust 106 | /// use gtmpl::Context; 107 | /// 108 | /// let mut tmpl = gtmpl::Template::default(); 109 | /// tmpl.add_template("fancy", "{{ . }}"); 110 | /// tmpl.parse(r#"{{ template "fancy" . }}!"#).unwrap(); 111 | /// let output = tmpl.render(&Context::from("Hello World")); 112 | /// assert_eq!(&output.unwrap(), "Hello World!"); 113 | /// ``` 114 | pub fn add_template, T: Into>( 115 | &mut self, 116 | name: N, 117 | text: T, 118 | ) -> Result<(), TemplateError> { 119 | let tree_set = parse( 120 | name.into(), 121 | text.into(), 122 | self.funcs.keys().cloned().collect(), 123 | )?; 124 | self.tree_set.extend(tree_set); 125 | Ok(()) 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests_mocked { 131 | use super::*; 132 | 133 | #[test] 134 | fn test_parse() { 135 | let mut t = Template::with_name("foo"); 136 | assert!(t.parse(r#"{{ if eq "bar" "bar" }} 2000 {{ end }}"#).is_ok()); 137 | assert!(t.tree_set.contains_key("foo")); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::node::{ChainNode, CommandNode, Nodes, PipeNode}; 2 | use gtmpl_value::{FuncError, Value}; 3 | use std::{fmt, num::ParseIntError, string::FromUtf8Error}; 4 | use thiserror::Error; 5 | 6 | #[derive(Debug)] 7 | pub struct ErrorContext { 8 | pub name: String, 9 | pub line: usize, 10 | } 11 | 12 | impl fmt::Display for ErrorContext { 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | write!(f, "{}:{}", self.name, self.line) 15 | } 16 | } 17 | 18 | #[derive(Error, Debug)] 19 | pub enum ParseError { 20 | #[error("unexpected {0} in define clause")] 21 | UnexpectedInDefineClause(Nodes), 22 | #[error("unexpected end")] 23 | UnexpectedEnd, 24 | #[error("template: {0}:{1}")] 25 | WithContext(ErrorContext, String), 26 | #[error("no tree")] 27 | NoTree, 28 | #[error(transparent)] 29 | NodeError(#[from] NodeError), 30 | #[error("enable gtmpl_dynamic_template to use a pipeline as name")] 31 | NoDynamicTemplate, 32 | #[error("unable to parse string: {0}")] 33 | UnableToParseString(String), 34 | } 35 | 36 | impl ParseError { 37 | pub fn with_context(name: impl ToString, line: usize, msg: impl ToString) -> Self { 38 | Self::WithContext( 39 | ErrorContext { 40 | name: name.to_string(), 41 | line, 42 | }, 43 | msg.to_string(), 44 | ) 45 | } 46 | } 47 | 48 | #[derive(Error, Debug)] 49 | pub enum NodeError { 50 | #[error("unable to unquote")] 51 | UnquoteError, 52 | #[error("NaN")] 53 | NaN, 54 | #[error("not a tree node")] 55 | NaTN, 56 | } 57 | 58 | #[derive(Error, Debug)] 59 | pub enum PrintError { 60 | #[error("unable to process verb: {0}")] 61 | UnableToProcessVerb(String), 62 | #[error("{0:X} is not a valid char")] 63 | NotAValidChar(i128), 64 | #[error("unable to format {0} as {1}")] 65 | UnableToFormat(Value, char), 66 | #[error("unable to terminate format arg: {0}")] 67 | UnableToTerminateFormatArg(String), 68 | #[error("missing ] in {0}")] 69 | MissingClosingBracket(String), 70 | #[error("unable to parse index: {0}")] 71 | UnableToParseIndex(ParseIntError), 72 | #[error("unable to parse width: {0}")] 73 | UnableToParseWidth(ParseIntError), 74 | #[error("width after index (e.g. %[3]2d)")] 75 | WithAfterIndex, 76 | #[error("precision after index (e.g. %[3].2d)")] 77 | PrecisionAfterIndex, 78 | } 79 | 80 | #[derive(Error, Debug)] 81 | pub enum ExecError { 82 | #[error("{0} is an incomplete or empty template")] 83 | IncompleteTemplate(String), 84 | #[error("{0}")] 85 | IOError(#[from] std::io::Error), 86 | #[error("unknown node: {0}")] 87 | UnknownNode(Nodes), 88 | #[error("expected if or with node, got {0}")] 89 | ExpectedIfOrWith(Nodes), 90 | #[error("unable to convert output to uft-8: {0}")] 91 | Utf8ConversionFailed(FromUtf8Error), 92 | #[error("empty var stack")] 93 | EmptyStack, 94 | #[error("var context smaller than {0}")] 95 | VarContextToSmall(usize), 96 | #[error("invalid range {0:?}")] 97 | InvalidRange(Value), 98 | #[error("pipeline must yield a String")] 99 | PipelineMustYieldString, 100 | #[error("template {0} not defined")] 101 | TemplateNotDefined(String), 102 | #[error("exceeded max template depth")] 103 | MaxTemplateDepth, 104 | #[error("error evaluating pipe: {0}")] 105 | ErrorEvaluatingPipe(PipeNode), 106 | #[error("no arguments for command node: {0}")] 107 | NoArgsForCommandNode(CommandNode), 108 | #[error("cannot evaluate command: {0}")] 109 | CannotEvaluateCommand(Nodes), 110 | #[error("field chain without fields :/")] 111 | FieldChainWithoutFields, 112 | #[error("{0} has arguments but cannot be invoked as function")] 113 | NotAFunctionButArguments(String), 114 | #[error("no fields in eval_chain_node")] 115 | NoFieldsInEvalChainNode, 116 | #[error("indirection through explicit nul in {0}")] 117 | NullInChain(ChainNode), 118 | #[error("cannot handle {0} as argument")] 119 | InvalidArgument(Nodes), 120 | #[error("{0} is not a defined function")] 121 | UndefinedFunction(String), 122 | #[error(transparent)] 123 | FuncError(#[from] FuncError), 124 | #[error("can't give argument to non-function {0}")] 125 | ArgumentForNonFunction(Nodes), 126 | #[error("only maps and objects have fields")] 127 | OnlyMapsAndObjectsHaveFields, 128 | #[error("no field {0} for {1}")] 129 | NoFiledFor(String, Value), 130 | #[error("variable {0} not found")] 131 | VariableNotFound(String), 132 | } 133 | 134 | #[derive(Error, Debug)] 135 | pub enum TemplateError { 136 | #[error(transparent)] 137 | ExecError(#[from] ExecError), 138 | #[error(transparent)] 139 | ParseError(#[from] ParseError), 140 | } 141 | -------------------------------------------------------------------------------- /tests/define.rs: -------------------------------------------------------------------------------- 1 | use gtmpl::{Context, Template}; 2 | use gtmpl_derive::Gtmpl; 3 | 4 | #[test] 5 | fn simple_define() { 6 | let mut template = Template::default(); 7 | template 8 | .parse(r#"{{ define "tmpl"}} some {{ end -}} there is {{- template "tmpl" -}} template"#) 9 | .unwrap(); 10 | 11 | let context = Context::empty(); 12 | 13 | let output = template.render(&context); 14 | assert!(output.is_ok()); 15 | assert_eq!(output.unwrap(), "there is some template".to_string()); 16 | } 17 | 18 | #[test] 19 | fn range_and_define() { 20 | let mut template = Template::default(); 21 | template 22 | .parse( 23 | r#"{{ define "foo" }}{{ $ }}{{ end -}} 24 | {{ range $x := . -}}{{ template "foo" . }}{{- end }}"#, 25 | ) 26 | .unwrap(); 27 | 28 | let context = Context::from(vec![1, 2]); 29 | 30 | let output = template.render(&context); 31 | assert!(output.is_ok()); 32 | assert_eq!(output.unwrap(), "12".to_string()); 33 | 34 | let mut template = Template::default(); 35 | template 36 | .parse( 37 | r#"{{ define "foo" }}{{ . }}{{ end -}} 38 | {{ range $x := . -}}{{ template "foo" . }}{{- end }}"#, 39 | ) 40 | .unwrap(); 41 | 42 | let context = Context::from(vec![1, 2]); 43 | 44 | let output = template.render(&context); 45 | assert!(output.is_ok()); 46 | assert_eq!(output.unwrap(), "12".to_string()); 47 | 48 | let mut template = Template::default(); 49 | template 50 | .parse( 51 | r#"{{ define "foo" }}{{ $ }}{{ end -}} 52 | {{ range $x := . -}}{{ template "foo" }}{{- end }}"#, 53 | ) 54 | .unwrap(); 55 | 56 | let context = Context::from(vec![1, 2]); 57 | 58 | let output = template.render(&context); 59 | assert!(output.is_ok()); 60 | assert_eq!(output.unwrap(), "".to_string()); 61 | 62 | let mut template = Template::default(); 63 | template 64 | .parse( 65 | r#"{{ define "foo" }}{{ . }}{{ end -}} 66 | {{ range $x := . -}}{{ template "foo" }}{{- end }}"#, 67 | ) 68 | .unwrap(); 69 | 70 | let context = Context::from(vec![1, 2]); 71 | 72 | let output = template.render(&context); 73 | assert!(output.is_ok()); 74 | assert_eq!(output.unwrap(), "".to_string()); 75 | } 76 | 77 | #[test] 78 | fn simple_define_context() { 79 | let mut template = Template::default(); 80 | template 81 | .parse(r#"{{ define "tmpl"}} {{.}} {{ end -}} there is {{- template "tmpl" -}} template"#) 82 | .unwrap(); 83 | 84 | let context = Context::from("some"); 85 | 86 | let output = template.render(&context); 87 | assert!(output.is_ok()); 88 | assert_eq!(output.unwrap(), "there is template".to_string()); 89 | 90 | let mut template = Template::default(); 91 | template 92 | .parse(r#"{{ define "tmpl"}} some {{ end -}} there is {{- template "tmpl" . -}} template"#) 93 | .unwrap(); 94 | 95 | let context = Context::from("some"); 96 | 97 | let output = template.render(&context); 98 | assert!(output.is_ok()); 99 | assert_eq!(output.unwrap(), "there is some template".to_string()); 100 | } 101 | 102 | #[test] 103 | fn other_define_context() { 104 | #[derive(Gtmpl)] 105 | struct Other { 106 | pub foo: String, 107 | } 108 | let mut template = Template::default(); 109 | template 110 | .parse(r#"{{ define "tmpl"}} some {{ end -}} there is {{- template "tmpl" . -}} template"#) 111 | .unwrap(); 112 | 113 | let context = Context::from(Other { 114 | foo: "some".to_owned(), 115 | }); 116 | 117 | let output = template.render(&context); 118 | assert!(output.is_ok()); 119 | assert_eq!(output.unwrap(), "there is some template".to_string()); 120 | } 121 | 122 | #[test] 123 | fn multiple_defines() { 124 | let mut template = Template::default(); 125 | template 126 | .parse( 127 | r#"{{ define "tmpl1"}} some {{ end -}} {{- define "tmpl2"}} some other {{ end -}} 128 | there is {{- template "tmpl2" -}} template"#, 129 | ) 130 | .unwrap(); 131 | 132 | let context = Context::empty(); 133 | 134 | let output = template.render(&context); 135 | assert!(output.is_ok()); 136 | assert_eq!(output.unwrap(), "there is some other template".to_string()); 137 | } 138 | 139 | #[cfg(feature = "gtmpl_dynamic_template")] 140 | #[test] 141 | fn dynamic_template() { 142 | let mut template = Template::default(); 143 | template 144 | .parse( 145 | r#" 146 | {{- define "tmpl1"}} some {{ end -}} 147 | {{- define "tmpl2"}} some other {{ end -}} 148 | there is {{- template (.) -}} template"#, 149 | ) 150 | .unwrap(); 151 | 152 | let context = Context::from("tmpl2"); 153 | 154 | let output = template.render(&context); 155 | assert!(output.is_ok()); 156 | assert_eq!(output.unwrap(), "there is some other template".to_string()); 157 | } 158 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use gtmpl_value::Value; 2 | use std::char; 3 | 4 | pub fn unquote_char(s: &str, quote: char) -> Option { 5 | if s.len() < 2 || !s.starts_with(quote) || !s.ends_with(quote) { 6 | return None; 7 | } 8 | let raw = &s[1..s.len() - 1]; 9 | match unqote(raw) { 10 | Some((c, l)) => { 11 | if l == raw.len() { 12 | c.chars().next() 13 | } else { 14 | None 15 | } 16 | } 17 | _ => None, 18 | } 19 | } 20 | 21 | pub fn unquote_str(s: &str) -> Option { 22 | if s.len() < 2 { 23 | return None; 24 | } 25 | let quote = &s[0..1]; 26 | if !s.ends_with(quote) { 27 | return None; 28 | } 29 | let mut r = String::new(); 30 | let raw = &s[1..s.len() - 1]; 31 | let mut i = 0; 32 | while i < raw.len() { 33 | match unqote(&raw[i..]) { 34 | Some((c, len)) => { 35 | r += &c; 36 | i += len; 37 | } 38 | None => return None, 39 | } 40 | } 41 | Some(r) 42 | } 43 | 44 | fn unqote(raw: &str) -> Option<(String, usize)> { 45 | if raw.starts_with('\\') { 46 | match &raw[..2] { 47 | r"\x" => extract_bytes_x(raw), 48 | r"\U" => extract_bytes_u32(raw), 49 | r"\u" => extract_bytes_u16(raw), 50 | r"\b" => Some(('\u{0008}'.to_string(), 2)), 51 | r"\f" => Some(('\u{000C}'.to_string(), 2)), 52 | r"\n" => Some(('\n'.to_string(), 2)), 53 | r"\r" => Some(('\r'.to_string(), 2)), 54 | r"\t" => Some(('\t'.to_string(), 2)), 55 | r"\'" => Some(('\''.to_string(), 2)), 56 | r#"\""# => Some(('\"'.to_string(), 2)), 57 | r#"\\"# => Some(('\\'.to_string(), 2)), 58 | _ => None, 59 | } 60 | } else { 61 | get_char(raw) 62 | } 63 | } 64 | 65 | fn get_char(s: &str) -> Option<(String, usize)> { 66 | s.char_indices() 67 | .next() 68 | .map(|(i, c)| (c.to_string(), i + c.len_utf8())) 69 | } 70 | 71 | fn extract_bytes_u32(s: &str) -> Option<(String, usize)> { 72 | if s.len() != 10 { 73 | return None; 74 | } 75 | u32::from_str_radix(&s[2..10], 16) 76 | .ok() 77 | .and_then(char::from_u32) 78 | .map(|c| (c.to_string(), 10)) 79 | } 80 | 81 | fn extract_bytes_u16(s: &str) -> Option<(String, usize)> { 82 | let mut bytes = vec![]; 83 | let mut i = 0; 84 | while s.len() > i && s.starts_with(r"\u") && s[i..].len() >= 6 { 85 | match u16::from_str_radix(&s[(i + 2)..(i + 6)], 16) { 86 | Ok(x) => bytes.push(x), 87 | _ => { 88 | return None; 89 | } 90 | }; 91 | i += 6; 92 | } 93 | String::from_utf16(&bytes).ok().map(|s| (s, i)) 94 | } 95 | 96 | fn extract_bytes_x(s: &str) -> Option<(String, usize)> { 97 | let mut bytes = vec![]; 98 | let mut i = 0; 99 | while s.len() > i && s.starts_with(r"\x") && s[i..].len() >= 4 { 100 | match u8::from_str_radix(&s[(i + 2)..(i + 4)], 16) { 101 | Ok(x) => bytes.push(x), 102 | _ => { 103 | return None; 104 | } 105 | }; 106 | i += 4; 107 | } 108 | String::from_utf8(bytes).ok().map(|s| (s, i)) 109 | } 110 | 111 | /// Returns 112 | pub fn is_true(val: &Value) -> bool { 113 | match *val { 114 | Value::Bool(ref b) => *b, 115 | Value::String(ref s) => !s.is_empty(), 116 | Value::Array(ref a) => !a.is_empty(), 117 | Value::Object(ref o) => !o.is_empty(), 118 | Value::Map(ref m) => !m.is_empty(), 119 | Value::Function(_) => true, 120 | Value::NoValue | Value::Nil => false, 121 | Value::Number(ref n) => n.as_u64().map(|u| u != 0).unwrap_or_else(|| true), 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | 129 | #[test] 130 | fn test_unquote_char() { 131 | let s = "'→'"; 132 | let c = unquote_char(s, '\''); 133 | assert_eq!(c, Some('→')); 134 | let s = "'→←'"; 135 | let c = unquote_char(s, '\''); 136 | assert_eq!(c, None); 137 | let s = r"'\xf0\x9f\x92\xa9'"; 138 | let c = unquote_char(s, '\''); 139 | assert_eq!(c, Some('💩')); 140 | let s = r"'\xf0\x9f\x92\xa'"; 141 | let c = unquote_char(s, '\''); 142 | assert_eq!(c, None); 143 | let s = r"'\xf0\x9f\x92\xa99'"; 144 | let c = unquote_char(s, '\''); 145 | assert_eq!(c, None); 146 | let s = r"'\u263a'"; 147 | let c = unquote_char(s, '\''); 148 | assert_eq!(c, Some('☺')); 149 | let s = r"'\uD83D\uDCA9'"; 150 | let c = unquote_char(s, '\''); 151 | assert_eq!(c, Some('💩')); 152 | let s = r"'\uD83\uDCA9'"; 153 | let c = unquote_char(s, '\''); 154 | assert_eq!(c, None); 155 | let s = r"'\uD83D\uDCA9B'"; 156 | let c = unquote_char(s, '\''); 157 | assert_eq!(c, None); 158 | let s = r"'\U0001F4A9'"; 159 | let c = unquote_char(s, '\''); 160 | assert_eq!(c, Some('💩')); 161 | let s = r"'\U0001F4A'"; 162 | let c = unquote_char(s, '\''); 163 | assert_eq!(c, None); 164 | let s = r"'\U0001F4A99'"; 165 | let c = unquote_char(s, '\''); 166 | assert_eq!(c, None); 167 | } 168 | 169 | #[test] 170 | fn test_unquote_str() { 171 | let s = r#""Fran & Freddie's Diner""#; 172 | let u = unquote_str(s); 173 | assert_eq!(u, Some("Fran & Freddie's Diner".to_owned())); 174 | let s = r#""Fran & Freddie's Diner\t\u263a""#; 175 | let u = unquote_str(s); 176 | assert_eq!(u, Some("Fran & Freddie's Diner\t☺".to_owned())); 177 | } 178 | 179 | #[test] 180 | fn test_is_true() { 181 | let t = Value::from(1i8); 182 | assert!(is_true(&t)); 183 | let t = Value::from(0u32); 184 | assert!(!is_true(&t)); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtmpl-rust – Golang Templates for Rust 2 | 3 | [![Latest Version]][crates.io] 4 | 5 | [Latest Version]: https://img.shields.io/crates/v/gtmpl.svg 6 | [crates.io]: https://crates.io/crates/gtmpl 7 | 8 | --- 9 | 10 | [gtmpl-rust] provides the [Golang text/template] engine for Rust. This enables 11 | seamless integration of Rust application into the world of devops tools around 12 | [kubernetes], [docker] and whatnot. 13 | 14 | ## Getting Started 15 | 16 | Add the following dependency to your Cargo manifest… 17 | ```toml 18 | [dependencies] 19 | gtmpl = "0.7" 20 | ``` 21 | 22 | and look at the docs: 23 | * [gtmpl at crates.io](https://crates.io/crates/gtmpl) 24 | * [gtmpl documentation](https://docs.rs/crate/gtmpl) 25 | * [golang documentation](https://golang.org/pkg/text/template/) 26 | 27 | 28 | It's not perfect, yet. Help and feedback is more than welcome. 29 | 30 | ## Some Examples 31 | 32 | Basic template: 33 | ```rust 34 | use gtmpl; 35 | 36 | fn main() { 37 | let output = gtmpl::template("Finally! Some {{ . }} for Rust", "gtmpl"); 38 | assert_eq!(&output.unwrap(), "Finally! Some gtmpl for Rust"); 39 | } 40 | ``` 41 | 42 | Adding custom functions: 43 | ```rust 44 | use gtmpl_value::Function; 45 | use gtmpl::{FuncError, gtmpl_fn, template, Value}; 46 | 47 | fn main() { 48 | gtmpl_fn!( 49 | fn add(a: u64, b: u64) -> Result { 50 | Ok(a + b) 51 | }); 52 | let equal = template(r#"{{ call . 1 2 }}"#, Value::Function(Function { f: add })); 53 | assert_eq!(&equal.unwrap(), "3"); 54 | } 55 | ``` 56 | 57 | Passing a struct as context: 58 | ```rust 59 | use gtmpl_derive::Gtmpl; 60 | 61 | #[derive(Gtmpl)] 62 | struct Foo { 63 | bar: u8 64 | } 65 | 66 | fn main() { 67 | let foo = Foo { bar: 42 }; 68 | let output = gtmpl::template("The answer is: {{ .bar }}", foo); 69 | assert_eq!(&output.unwrap(), "The answer is: 42"); 70 | } 71 | ``` 72 | 73 | Invoking a *method* on a context: 74 | ```rust 75 | 76 | use gtmpl_derive::Gtmpl; 77 | use gtmpl::{Func, FuncError, Value}; 78 | 79 | fn plus_one(args: &[Value]) -> Result { 80 | if let Value::Object(ref o) = &args[0] { 81 | if let Some(Value::Number(ref n)) = o.get("num") { 82 | if let Some(i) = n.as_i64() { 83 | return Ok((i +1).into()) 84 | } 85 | } 86 | } 87 | Err(anyhow!("integer required, got: {:?}", args)) 88 | } 89 | 90 | #[derive(Gtmpl)] 91 | struct AddMe { 92 | num: u8, 93 | plus_one: Func 94 | } 95 | 96 | fn main() { 97 | let add_me = AddMe { num: 42, plus_one }; 98 | let output = gtmpl::template("The answer is: {{ .plus_one }}", add_me); 99 | assert_eq!(&output.unwrap(), "The answer is: 43"); 100 | } 101 | ``` 102 | 103 | ## Current Limitations 104 | 105 | This is work in progress. Currently the following features are not supported: 106 | 107 | * complex numbers 108 | * the following functions have not been implemented: 109 | * `html`, `js` 110 | * `printf` is not yet fully stable, but should support all *sane* input 111 | 112 | ## Enhancements 113 | 114 | Even though it was never intended to extend the syntax of Golang text/template 115 | there might be some convenient additions: 116 | 117 | ### Dynamic Template 118 | 119 | Enable `gtmpl_dynamic_template` in your `Cargo.toml`: 120 | ```toml 121 | [dependencies.gtmpl] 122 | version = "0.7" 123 | features = ["gtmpl_dynamic_template"] 124 | 125 | ``` 126 | 127 | Now you can have dynamic template names for the `template` action. 128 | 129 | #### Example 130 | 131 | ```rust 132 | use gtmpl::{Context, Template}; 133 | 134 | fn main() { 135 | let mut template = Template::default(); 136 | template 137 | .parse( 138 | r#" 139 | {{- define "tmpl1"}} some {{ end -}} 140 | {{- define "tmpl2"}} some other {{ end -}} 141 | there is {{- template (.) -}} template 142 | "#, 143 | ) 144 | .unwrap(); 145 | 146 | let context = Context::from("tmpl2"); 147 | 148 | let output = template.render(&context); 149 | assert_eq!(output.unwrap(), "there is some other template".to_string()); 150 | } 151 | ``` 152 | 153 | The following syntax is used: 154 | ``` 155 | {{template (pipeline)}} 156 | The template with the name evaluated from the pipeline (parenthesized) is 157 | executed with nil data. 158 | 159 | {{template (pipeline) pipeline}} 160 | The template with the name evaluated from the first pipeline (parenthesized) 161 | is executed with dot set to the value of the second pipeline. 162 | ``` 163 | 164 | ## Context 165 | 166 | We use [gtmpl_value]'s Value as internal data type. [gtmpl_derive] provides a 167 | handy `derive` macro to generate the `From` implementation for `Value`. 168 | 169 | See: 170 | 171 | * [gtmpl_value at crates.io](https://crates.io/crate/gtmpl_value) 172 | * [gtmpl_value documentation](https://docs.rs/crate/gtmpl_value) 173 | * [gtmpl_derive at crates.io](https://crates.io/crate/gtmpl_derive) 174 | * [gtmpl_derive documentation](https://docs.rs/crate/gtmpl_derive) 175 | 176 | ## Why do we need this? 177 | 178 | Why? Dear god, why? I can already imagine the question coming up why anyone would 179 | ever do this. I wasn't a big fan of Golang templates when i first had to write 180 | some custom formatting strings for **docker**. Learning a new template language 181 | usually isn't something one is looking forward to. Most people avoid it 182 | completely. However, it's really useful for automation if you're looking for 183 | something more lightweight than a full blown DSL. 184 | 185 | The main motivation for this is to make it easier to write devops tools in Rust 186 | that feel native. [docker] and [helm] ([kubernetes]) use golang templates and 187 | it feels more native if tooling around them uses the same. 188 | 189 | [gtmpl-rust]: https://github.com/fiji-flo/gtmpl-rust 190 | [Golang text/template]: https://golang.org/pkg/text/template/ 191 | [kubernetes]: https://kubernetes.io 192 | [helm]: https://github.com/kubernetes/helm/blob/master/docs/chart_best_practices/templates.md 193 | [docker]: https://docker.com 194 | [gtmpl_value]: https://github.com/fiji-flo/gtmpl_value 195 | [gtmpl_derive]: https://github.com/fiji-flo/gtmpl_derive 196 | -------------------------------------------------------------------------------- /src/printf.rs: -------------------------------------------------------------------------------- 1 | use std::char; 2 | 3 | use gtmpl_value::{FromValue, Value}; 4 | 5 | use crate::error::PrintError; 6 | use crate::print_verb::print; 7 | 8 | pub fn sprintf(s: &str, args: &[Value]) -> Result { 9 | let tokens = tokenize(s)?; 10 | let mut fmt = String::new(); 11 | let mut i = 0; 12 | let mut index = 0; 13 | for t in tokens { 14 | fmt.push_str(&s[i..t.start]); 15 | let (s, idx) = process_verb(&s[t.start + 1..t.end], t.typ, args, index)?; 16 | fmt.push_str(&s); 17 | index = idx; 18 | i = t.end + 1; 19 | } 20 | fmt.push_str(&s[i..]); 21 | Ok(fmt) 22 | } 23 | 24 | struct FormatArg { 25 | pub start: usize, 26 | pub end: usize, 27 | pub typ: char, 28 | } 29 | 30 | static TYPS: &str = "vVtTbcdoqxXUeEfFgGsp"; 31 | 32 | #[derive(Default)] 33 | pub struct FormatParams { 34 | pub sharp: bool, 35 | pub zero: bool, 36 | pub plus: bool, 37 | pub minus: bool, 38 | pub space: bool, 39 | pub width: usize, 40 | pub precision: Option, 41 | } 42 | 43 | fn process_verb( 44 | s: &str, 45 | typ: char, 46 | args: &[Value], 47 | mut index: usize, 48 | ) -> Result<(String, usize), PrintError> { 49 | let mut params = FormatParams::default(); 50 | let mut complex = false; 51 | let mut pos = 0; 52 | for (i, c) in s.chars().enumerate() { 53 | match c { 54 | '#' => params.sharp = true, 55 | '0' => params.zero = true, 56 | '+' => params.plus = true, 57 | '-' => { 58 | params.minus = true; 59 | // Golang does not pad with zeros to the right. 60 | params.zero = false; 61 | } 62 | ' ' => params.space = true, 63 | _ => { 64 | pos = i; 65 | complex = true; 66 | break; 67 | } 68 | } 69 | } 70 | if complex { 71 | let mut after_index = false; 72 | let arg_num = parse_index(&s[pos..])?.map(|(i, till)| { 73 | pos += till; 74 | after_index = true; 75 | index = i; 76 | i 77 | }); 78 | if s[pos..].starts_with('*') { 79 | pos += 1; 80 | let arg_num = arg_num.unwrap_or_else(|| { 81 | let i = index; 82 | index += 1; 83 | i 84 | }); 85 | if let Some(width) = args.get(arg_num).and_then(|v| i64::from_value(v)) { 86 | if width < 0 { 87 | params.minus = true; 88 | // Golang does not pad with zeros to the right. 89 | params.zero = false; 90 | } 91 | params.width = width.abs() as usize; 92 | } 93 | after_index = false; 94 | } else if let Some((width, till)) = parse_num(&s[pos..])? { 95 | if after_index { 96 | return Err(PrintError::WithAfterIndex); 97 | } 98 | pos += till; 99 | params.width = width; 100 | } 101 | 102 | if pos + 1 < s.len() && s[pos..].starts_with('.') { 103 | pos += 1; 104 | if after_index { 105 | return Err(PrintError::PrecisionAfterIndex); 106 | } 107 | 108 | let arg_num = parse_index(&s[pos..])?.map(|(i, till)| { 109 | pos += till; 110 | after_index = true; 111 | index = i; 112 | i 113 | }); 114 | if s[pos..].starts_with('*') { 115 | pos += 1; 116 | let arg_num = arg_num.unwrap_or_else(|| { 117 | let i = index; 118 | index += 1; 119 | i 120 | }); 121 | if let Some(prec) = args.get(arg_num).and_then(|v| i64::from_value(v)) { 122 | if prec < 0 { 123 | params.precision = None; 124 | } 125 | params.precision = Some(prec.abs() as usize); 126 | } 127 | } else if let Some((prec, till)) = parse_num(&s[pos..])? { 128 | if after_index { 129 | return Err(PrintError::PrecisionAfterIndex); 130 | } 131 | pos += till; 132 | params.precision = Some(prec); 133 | } 134 | } 135 | } 136 | 137 | let arg_num = if let Some((i, _)) = parse_index(&s[pos..])? { 138 | index = i; 139 | i 140 | } else { 141 | let i = index; 142 | index += 1; 143 | i 144 | }; 145 | 146 | if arg_num < args.len() { 147 | return print(¶ms, typ, &args[arg_num]).map(|s| (s, index)); 148 | } 149 | Err(PrintError::UnableToProcessVerb(s.to_string())) 150 | } 151 | 152 | fn parse_index(s: &str) -> Result, PrintError> { 153 | if s.starts_with('[') { 154 | let till = s 155 | .find(']') 156 | .ok_or_else(|| PrintError::MissingClosingBracket(s.to_string()))?; 157 | s[1..till] 158 | .parse::() 159 | .map(|u| Some((u - 1, till + 1))) 160 | .map_err(PrintError::UnableToParseIndex) 161 | } else { 162 | Ok(None) 163 | } 164 | } 165 | 166 | fn parse_num(s: &str) -> Result, PrintError> { 167 | let till = s.find(|c: char| !c.is_digit(10)).unwrap_or_else(|| s.len()); 168 | if till > 0 { 169 | s[..till] 170 | .parse() 171 | .map(|u| Some((u, till))) 172 | .map_err(PrintError::UnableToParseWidth) 173 | } else { 174 | Ok(None) 175 | } 176 | } 177 | 178 | fn tokenize(s: &str) -> Result, PrintError> { 179 | let mut iter = s.char_indices().peekable(); 180 | let mut args = Vec::new(); 181 | loop { 182 | let from = match iter.next() { 183 | None => break, 184 | Some((i, '%')) => i, 185 | _ => continue, 186 | }; 187 | 188 | if let Some(&(_, '%')) = iter.peek() { 189 | iter.next(); 190 | continue; 191 | } 192 | 193 | loop { 194 | match iter.next() { 195 | None => { 196 | return Err(PrintError::UnableToTerminateFormatArg( 197 | s[from..].to_string(), 198 | )) 199 | } 200 | Some((i, t)) if TYPS.contains(t) => { 201 | args.push(FormatArg { 202 | start: from, 203 | end: i, 204 | typ: t, 205 | }); 206 | break; 207 | } 208 | _ => continue, 209 | }; 210 | } 211 | } 212 | Ok(args) 213 | } 214 | 215 | pub fn params_to_chars(params: &FormatParams) -> (char, char, char, char, char) { 216 | ( 217 | if params.sharp { '#' } else { '_' }, 218 | if params.zero { '0' } else { '_' }, 219 | if params.plus { '+' } else { '_' }, 220 | if params.minus { '-' } else { '_' }, 221 | if params.space { ' ' } else { '_' }, 222 | ) 223 | } 224 | 225 | #[cfg(test)] 226 | mod test { 227 | use std::collections::HashMap; 228 | 229 | use super::*; 230 | 231 | #[test] 232 | fn test_sprinttf_to_format() { 233 | let s = sprintf("foo%v2000", &["bar".into()]); 234 | assert!(s.is_ok()); 235 | let s = s.unwrap(); 236 | assert_eq!(s, r"foobar2000"); 237 | 238 | let s = sprintf("%+0v", &[1.into()]); 239 | assert!(s.is_ok()); 240 | let s = s.unwrap(); 241 | assert_eq!(s, r"+1"); 242 | } 243 | 244 | #[test] 245 | fn test_sprintf_fancy() { 246 | let s = sprintf("%+-#10c", &[10000.into()]); 247 | assert!(s.is_ok()); 248 | let s = s.unwrap(); 249 | assert_eq!(s, r"✐ "); 250 | 251 | let s = sprintf("%+-#10q", &[10000.into()]); 252 | assert!(s.is_ok()); 253 | let s = s.unwrap(); 254 | assert_eq!(s, r"'\u2710' "); 255 | } 256 | 257 | #[test] 258 | fn test_sprintf_string_to_hex() { 259 | let s = sprintf("%x", &["foobar2000".into()]); 260 | assert!(s.is_ok()); 261 | let s = s.unwrap(); 262 | assert_eq!(s, r"666f6f62617232303030"); 263 | 264 | let s = sprintf("%X", &["foobar2000".into()]); 265 | assert!(s.is_ok()); 266 | let s = s.unwrap(); 267 | assert_eq!(s, r"666F6F62617232303030"); 268 | } 269 | 270 | #[test] 271 | fn test_sprintf_string_prec() { 272 | let s = sprintf("%.6s", &["foobar2000".into()]); 273 | assert!(s.is_ok()); 274 | let s = s.unwrap(); 275 | assert_eq!(s, r"foobar"); 276 | } 277 | 278 | #[test] 279 | fn test_sprintf_index() { 280 | let s = sprintf("%[1]v %v", &["foo".into(), "bar".into(), 2000.into()]); 281 | assert!(s.is_ok()); 282 | let s = s.unwrap(); 283 | assert_eq!(s, r"foo bar"); 284 | 285 | let s = sprintf( 286 | "%[2]v %v%[1]v %v%[1]v", 287 | &["!".into(), "wtf".into(), "golang".into()], 288 | ); 289 | assert!(s.is_ok()); 290 | let s = s.unwrap(); 291 | assert_eq!(s, r"wtf golang! wtf!"); 292 | } 293 | 294 | #[test] 295 | fn test_sprintf_number() { 296 | let s = sprintf("foobar%d", &[2000.into()]); 297 | assert!(s.is_ok()); 298 | let s = s.unwrap(); 299 | assert_eq!(s, r"foobar2000"); 300 | 301 | let s = sprintf("%+0d", &[1.into()]); 302 | assert!(s.is_ok()); 303 | let s = s.unwrap(); 304 | assert_eq!(s, r"+1"); 305 | 306 | let s = sprintf("%+0b", &[5.into()]); 307 | assert!(s.is_ok()); 308 | let s = s.unwrap(); 309 | assert_eq!(s, r"+101"); 310 | } 311 | 312 | #[test] 313 | fn test_sprintf_array() { 314 | let values: Vec = vec!["hello".into(), "world".into()]; 315 | let s = sprintf("foo %v", &[Value::Array(values)]); 316 | assert!(s.is_ok()); 317 | let s = s.unwrap(); 318 | assert_eq!(s, r"foo [hello world]"); 319 | 320 | let values: Vec = vec![42.into(), 100.into()]; 321 | let s = sprintf("foo %v", &[Value::Array(values)]); 322 | assert!(s.is_ok()); 323 | let s = s.unwrap(); 324 | assert_eq!(s, r"foo [42 100]"); 325 | } 326 | 327 | #[test] 328 | fn test_sprintf_map() { 329 | let mut values: HashMap = HashMap::new(); 330 | values.insert("hello".into(), "world".into()); 331 | values.insert("number".into(), 42.into()); 332 | let s = sprintf("foo %v", &[Value::Map(values)]); 333 | assert!(s.is_ok()); 334 | let s = s.unwrap(); 335 | // The print order is unpredictable, we can't write 336 | // a straight comparison 337 | assert!(s == "foo map[number:42 hello:world]" || s == "foo map[hello:world number:42]"); 338 | 339 | let mut values: HashMap = HashMap::new(); 340 | values.insert("float".into(), 4.2.into()); 341 | let s = sprintf("%v", &[Value::Map(values)]); 342 | assert!(s.is_ok()); 343 | let s = s.unwrap(); 344 | assert_eq!(s, r"map[float:4.2]"); 345 | } 346 | 347 | #[test] 348 | fn test_tokenize() { 349 | let t = tokenize("foobar%6.2ffoobar"); 350 | assert!(t.is_ok()); 351 | let t = t.unwrap(); 352 | assert_eq!(t.len(), 1); 353 | let a = &t[0]; 354 | assert_eq!(a.start, 6); 355 | assert_eq!(a.end, 10); 356 | assert_eq!(a.typ, 'f'); 357 | } 358 | 359 | #[test] 360 | fn test_tokenize_err() { 361 | let t = tokenize(" %6.2 "); 362 | assert!(t.is_err()); 363 | } 364 | 365 | #[test] 366 | fn test_tokenize_none() { 367 | let t = tokenize(" foo %% bar "); 368 | assert!(t.is_ok()); 369 | let t = t.unwrap(); 370 | assert!(t.is_empty()); 371 | } 372 | 373 | #[test] 374 | fn test_parse_index() { 375 | let x = parse_index("[12]"); 376 | assert!(x.is_ok()); 377 | let x = x.unwrap(); 378 | // Go starts with 1 in stead of 0 379 | assert_eq!(x, Some((11, 4))); 380 | 381 | let x = parse_index("[12"); 382 | assert!(x.is_err()); 383 | 384 | let x = parse_index("*[12]"); 385 | assert!(x.is_ok()); 386 | let x = x.unwrap(); 387 | assert_eq!(x, None); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/print_verb.rs: -------------------------------------------------------------------------------- 1 | use std::char; 2 | use std::fmt; 3 | 4 | use crate::error::PrintError; 5 | use crate::printf::{params_to_chars, FormatParams}; 6 | 7 | use gtmpl_value::Value; 8 | 9 | /// Print a verb like golang's printf. 10 | pub fn print(p: &FormatParams, typ: char, val: &Value) -> Result { 11 | match *val { 12 | Value::Number(ref n) if n.as_u64().is_some() => { 13 | let u = n.as_u64().unwrap(); 14 | Ok(match typ { 15 | 'b' => printf_b(p, u), 16 | 'd' | 'v' => printf_generic(p, u), 17 | 'o' => printf_o(p, u), 18 | 'c' => { 19 | let c = char::from_u32(u as u32).ok_or(PrintError::NotAValidChar(u as i128))?; 20 | printf_generic(p, c) 21 | } 22 | 'q' => { 23 | let c = char::from_u32(u as u32).ok_or(PrintError::NotAValidChar(u as i128))?; 24 | printf_generic(p, format!("'{}'", escape_char(c))) 25 | } 26 | 'x' => printf_x(p, u), 27 | 'X' => printf_xx(p, u), 28 | 'U' => printf_generic(p, format!("U+{:X}", u)), 29 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 30 | }) 31 | } 32 | Value::Number(ref n) if n.as_i64().is_some() => { 33 | let i = n.as_i64().unwrap(); 34 | Ok(match typ { 35 | 'b' => printf_b(p, i), 36 | 'd' => printf_generic(p, i), 37 | 'o' => printf_o(p, i), 38 | 'c' => { 39 | let c = char::from_u32(i as u32).ok_or(PrintError::NotAValidChar(i as i128))?; 40 | printf_generic(p, c) 41 | } 42 | 'q' => { 43 | let c = char::from_u32(i as u32).ok_or(PrintError::NotAValidChar(i as i128))?; 44 | printf_generic(p, format!("'{}'", escape_char(c))) 45 | } 46 | 'x' => printf_x(p, i), 47 | 'X' => printf_xx(p, i), 48 | 'U' => printf_generic(p, format!("U+{:X}", i)), 49 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 50 | }) 51 | } 52 | Value::Number(ref n) if n.as_f64().is_some() => { 53 | let f = n.as_f64().unwrap(); 54 | Ok(match typ { 55 | 'e' => printf_e(p, f), 56 | 'E' => printf_ee(p, f), 57 | 'f' | 'F' => printf_generic(p, f), 58 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 59 | }) 60 | } 61 | Value::Bool(ref b) => Ok(match typ { 62 | 'v' | 't' => printf_generic(p, b), 63 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 64 | }), 65 | Value::String(ref s) => Ok(match typ { 66 | 's' | 'v' => printf_generic(p, s), 67 | 'x' => printf_x(p, Hexer::from(s.as_str())), 68 | 'X' => printf_xx(p, Hexer::from(s.as_str())), 69 | 'q' => { 70 | let s = s 71 | .chars() 72 | .map(|c| c.escape_default().to_string()) 73 | .collect::(); 74 | printf_generic(p, s) 75 | } 76 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 77 | }), 78 | Value::Array(ref a) => Ok(match typ { 79 | 'v' => { 80 | let values: Vec = a.iter().map(|v| printf_generic(p, v)).collect(); 81 | let res = format!("[{}]", values.join(" ")); 82 | printf_generic(p, res) 83 | } 84 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 85 | }), 86 | Value::Map(ref m) => Ok(match typ { 87 | 'v' => { 88 | let values: Vec = m 89 | .iter() 90 | .map(|(k, v)| { 91 | let v_str = printf_generic(p, v); 92 | format!("{}:{}", k, v_str) 93 | }) 94 | .collect(); 95 | let res = format!("map[{}]", values.join(" ")); 96 | printf_generic(p, res) 97 | } 98 | _ => return Err(PrintError::UnableToFormat(val.clone(), typ)), 99 | }), 100 | _ => Err(PrintError::UnableToFormat(val.clone(), typ)), 101 | } 102 | } 103 | 104 | fn printf_b(p: &FormatParams, u: B) -> String { 105 | match params_to_chars(p) { 106 | ('#', '_', '+', '_', _) => format!("{:+#width$b}", u, width = p.width), 107 | ('_', '_', '+', '_', _) => format!("{:+width$b}", u, width = p.width), 108 | ('#', '_', '_', '_', _) => format!("{:#width$b}", u, width = p.width), 109 | ('#', '0', '+', '_', _) => format!("{:+#0width$b}", u, width = p.width), 110 | ('_', '0', '+', '_', _) => format!("{:+0width$b}", u, width = p.width), 111 | ('#', '0', '_', '_', _) => format!("{:#0width$b}", u, width = p.width), 112 | ('#', '_', '+', '-', _) => format!("{:<+#width$b}", u, width = p.width), 113 | ('_', '_', '+', '-', _) => format!("{:<+width$b}", u, width = p.width), 114 | ('#', '_', '_', '-', _) => format!("{:<#width$b}", u, width = p.width), 115 | ('#', '0', '+', '-', _) => format!("{:<+#0width$b}", u, width = p.width), 116 | ('_', '0', '+', '-', _) => format!("{:<+0width$b}", u, width = p.width), 117 | ('#', '0', '_', '-', _) => format!("{:<#0width$b}", u, width = p.width), 118 | (_, _, _, _, _) => format!("{:width$b}", u, width = p.width), 119 | } 120 | } 121 | 122 | fn printf_o(p: &FormatParams, u: B) -> String { 123 | match params_to_chars(p) { 124 | ('#', '_', '+', '_', _) => format!("{:+#width$o}", u, width = p.width), 125 | ('_', '_', '+', '_', _) => format!("{:+width$o}", u, width = p.width), 126 | ('#', '_', '_', '_', _) => format!("{:#width$o}", u, width = p.width), 127 | ('#', '0', '+', '_', _) => format!("{:+#0width$o}", u, width = p.width), 128 | ('_', '0', '+', '_', _) => format!("{:+0width$o}", u, width = p.width), 129 | ('#', '0', '_', '_', _) => format!("{:#0width$o}", u, width = p.width), 130 | ('#', '_', '+', '-', _) => format!("{:<+#width$o}", u, width = p.width), 131 | ('_', '_', '+', '-', _) => format!("{:<+width$o}", u, width = p.width), 132 | ('#', '_', '_', '-', _) => format!("{:<#width$o}", u, width = p.width), 133 | ('#', '0', '+', '-', _) => format!("{:<+#0width$o}", u, width = p.width), 134 | ('_', '0', '+', '-', _) => format!("{:<+0width$o}", u, width = p.width), 135 | ('#', '0', '_', '-', _) => format!("{:<#0width$o}", u, width = p.width), 136 | (_, _, _, _, _) => format!("{:width$o}", u, width = p.width), 137 | } 138 | } 139 | 140 | fn printf_x(p: &FormatParams, u: B) -> String { 141 | match params_to_chars(p) { 142 | ('#', '_', '+', '_', _) => format!("{:+#width$x}", u, width = p.width), 143 | ('_', '_', '+', '_', _) => format!("{:+width$x}", u, width = p.width), 144 | ('#', '_', '_', '_', _) => format!("{:#width$x}", u, width = p.width), 145 | ('#', '0', '+', '_', _) => format!("{:+#0width$x}", u, width = p.width), 146 | ('_', '0', '+', '_', _) => format!("{:+0width$x}", u, width = p.width), 147 | ('#', '0', '_', '_', _) => format!("{:#0width$x}", u, width = p.width), 148 | ('#', '_', '+', '-', _) => format!("{:<+#width$x}", u, width = p.width), 149 | ('_', '_', '+', '-', _) => format!("{:<+width$x}", u, width = p.width), 150 | ('#', '_', '_', '-', _) => format!("{:<#width$x}", u, width = p.width), 151 | ('#', '0', '+', '-', _) => format!("{:<+#0width$x}", u, width = p.width), 152 | ('_', '0', '+', '-', _) => format!("{:<+0width$x}", u, width = p.width), 153 | ('#', '0', '_', '-', _) => format!("{:<#0width$x}", u, width = p.width), 154 | (_, _, _, _, _) => format!("{:width$x}", u, width = p.width), 155 | } 156 | } 157 | 158 | fn printf_xx(p: &FormatParams, u: B) -> String { 159 | match params_to_chars(p) { 160 | ('#', '_', '+', '_', _) => format!("{:+#width$X}", u, width = p.width), 161 | ('_', '_', '+', '_', _) => format!("{:+width$X}", u, width = p.width), 162 | ('#', '_', '_', '_', _) => format!("{:#width$X}", u, width = p.width), 163 | ('#', '0', '+', '_', _) => format!("{:+#0width$X}", u, width = p.width), 164 | ('_', '0', '+', '_', _) => format!("{:+0width$X}", u, width = p.width), 165 | ('#', '0', '_', '_', _) => format!("{:#0width$X}", u, width = p.width), 166 | ('#', '_', '+', '-', _) => format!("{:<+#width$X}", u, width = p.width), 167 | ('_', '_', '+', '-', _) => format!("{:<+width$X}", u, width = p.width), 168 | ('#', '_', '_', '-', _) => format!("{:<#width$X}", u, width = p.width), 169 | ('#', '0', '+', '-', _) => format!("{:<+#0width$X}", u, width = p.width), 170 | ('_', '0', '+', '-', _) => format!("{:<+0width$X}", u, width = p.width), 171 | ('#', '0', '_', '-', _) => format!("{:<#0width$X}", u, width = p.width), 172 | (_, _, _, _, _) => format!("{:width$X}", u, width = p.width), 173 | } 174 | } 175 | 176 | fn printf_generic(p: &FormatParams, c: D) -> String { 177 | if let Some(pr) = p.precision { 178 | match params_to_chars(p) { 179 | ('#', '_', '+', '_', _) => format!("{:+#width$.pr$}", c, width = p.width, pr = pr), 180 | ('_', '_', '+', '_', _) => format!("{:+width$.pr$}", c, width = p.width, pr = pr), 181 | ('#', '_', '_', '_', _) => format!("{:#width$.pr$}", c, width = p.width, pr = pr), 182 | ('#', '0', '+', '_', _) => format!("{:+#0width$.pr$}", c, width = p.width, pr = pr), 183 | ('_', '0', '+', '_', _) => format!("{:+0width$.pr$}", c, width = p.width, pr = pr), 184 | ('#', '0', '_', '_', _) => format!("{:#0width$.pr$}", c, width = p.width, pr = pr), 185 | ('#', '_', '+', '-', _) => format!("{:<+#width$.pr$}", c, width = p.width, pr = pr), 186 | ('_', '_', '+', '-', _) => format!("{:<+width$.pr$}", c, width = p.width, pr = pr), 187 | ('#', '_', '_', '-', _) => format!("{:<#width$.pr$}", c, width = p.width, pr = pr), 188 | ('#', '0', '+', '-', _) => format!("{:<+#0width$.pr$}", c, width = p.width, pr = pr), 189 | ('_', '0', '+', '-', _) => format!("{:<+0width$.pr$}", c, width = p.width, pr = pr), 190 | ('#', '0', '_', '-', _) => format!("{:<#0width$.pr$}", c, width = p.width, pr = pr), 191 | (_, _, _, _, _) => format!("{:width$.pr$}", c, width = p.width, pr = pr), 192 | } 193 | } else { 194 | match params_to_chars(p) { 195 | ('#', '_', '+', '_', _) => format!("{:+#width$}", c, width = p.width), 196 | ('_', '_', '+', '_', _) => format!("{:+width$}", c, width = p.width), 197 | ('#', '_', '_', '_', _) => format!("{:#width$}", c, width = p.width), 198 | ('#', '0', '+', '_', _) => format!("{:+#0width$}", c, width = p.width), 199 | ('_', '0', '+', '_', _) => format!("{:+0width$}", c, width = p.width), 200 | ('#', '0', '_', '_', _) => format!("{:#0width$}", c, width = p.width), 201 | ('#', '_', '+', '-', _) => format!("{:<+#width$}", c, width = p.width), 202 | ('_', '_', '+', '-', _) => format!("{:<+width$}", c, width = p.width), 203 | ('#', '_', '_', '-', _) => format!("{:<#width$}", c, width = p.width), 204 | ('#', '0', '+', '-', _) => format!("{:<+#0width$}", c, width = p.width), 205 | ('_', '0', '+', '-', _) => format!("{:<+0width$}", c, width = p.width), 206 | ('#', '0', '_', '-', _) => format!("{:<#0width$}", c, width = p.width), 207 | (_, _, _, _, _) => format!("{:width$}", c, width = p.width), 208 | } 209 | } 210 | } 211 | 212 | fn printf_e(p: &FormatParams, f: E) -> String { 213 | if let Some(pr) = p.precision { 214 | match params_to_chars(p) { 215 | ('#', '_', '+', '_', _) => format!("{:+#width$.pr$e}", f, width = p.width, pr = pr), 216 | ('_', '_', '+', '_', _) => format!("{:+width$.pr$e}", f, width = p.width, pr = pr), 217 | ('#', '_', '_', '_', _) => format!("{:#width$.pr$e}", f, width = p.width, pr = pr), 218 | ('#', '0', '+', '_', _) => format!("{:+#0width$.pr$e}", f, width = p.width, pr = pr), 219 | ('_', '0', '+', '_', _) => format!("{:+0width$.pr$e}", f, width = p.width, pr = pr), 220 | ('#', '0', '_', '_', _) => format!("{:#0width$.pr$e}", f, width = p.width, pr = pr), 221 | ('#', '_', '+', '-', _) => format!("{:<+#width$.pr$e}", f, width = p.width, pr = pr), 222 | ('_', '_', '+', '-', _) => format!("{:<+width$.pr$e}", f, width = p.width, pr = pr), 223 | ('#', '_', '_', '-', _) => format!("{:<#width$.pr$e}", f, width = p.width, pr = pr), 224 | ('#', '0', '+', '-', _) => format!("{:<+#0width$.pr$e}", f, width = p.width, pr = pr), 225 | ('_', '0', '+', '-', _) => format!("{:<+0width$.pr$e}", f, width = p.width, pr = pr), 226 | ('#', '0', '_', '-', _) => format!("{:<#0width$.pr$e}", f, width = p.width, pr = pr), 227 | (_, _, _, _, _) => format!("{:width$.pr$e}", f, width = p.width, pr = pr), 228 | } 229 | } else { 230 | match params_to_chars(p) { 231 | ('#', '_', '+', '_', _) => format!("{:+#width$e}", f, width = p.width), 232 | ('_', '_', '+', '_', _) => format!("{:+width$e}", f, width = p.width), 233 | ('#', '_', '_', '_', _) => format!("{:#width$e}", f, width = p.width), 234 | ('#', '0', '+', '_', _) => format!("{:+#0width$e}", f, width = p.width), 235 | ('_', '0', '+', '_', _) => format!("{:+0width$e}", f, width = p.width), 236 | ('#', '0', '_', '_', _) => format!("{:#0width$e}", f, width = p.width), 237 | ('#', '_', '+', '-', _) => format!("{:<+#width$e}", f, width = p.width), 238 | ('_', '_', '+', '-', _) => format!("{:<+width$e}", f, width = p.width), 239 | ('#', '_', '_', '-', _) => format!("{:<#width$e}", f, width = p.width), 240 | ('#', '0', '+', '-', _) => format!("{:<+#0width$e}", f, width = p.width), 241 | ('_', '0', '+', '-', _) => format!("{:<+0width$e}", f, width = p.width), 242 | ('#', '0', '_', '-', _) => format!("{:<#0width$e}", f, width = p.width), 243 | (_, _, _, _, _) => format!("{:width$e}", f, width = p.width), 244 | } 245 | } 246 | } 247 | 248 | fn printf_ee(p: &FormatParams, f: E) -> String { 249 | if let Some(pr) = p.precision { 250 | match params_to_chars(p) { 251 | ('#', '_', '+', '_', _) => format!("{:+#width$.pr$E}", f, width = p.width, pr = pr), 252 | ('_', '_', '+', '_', _) => format!("{:+width$.pr$E}", f, width = p.width, pr = pr), 253 | ('#', '_', '_', '_', _) => format!("{:#width$.pr$E}", f, width = p.width, pr = pr), 254 | ('#', '0', '+', '_', _) => format!("{:+#0width$.pr$E}", f, width = p.width, pr = pr), 255 | ('_', '0', '+', '_', _) => format!("{:+0width$.pr$E}", f, width = p.width, pr = pr), 256 | ('#', '0', '_', '_', _) => format!("{:#0width$.pr$E}", f, width = p.width, pr = pr), 257 | ('#', '_', '+', '-', _) => format!("{:<+#width$.pr$E}", f, width = p.width, pr = pr), 258 | ('_', '_', '+', '-', _) => format!("{:<+width$.pr$E}", f, width = p.width, pr = pr), 259 | ('#', '_', '_', '-', _) => format!("{:<#width$.pr$E}", f, width = p.width, pr = pr), 260 | ('#', '0', '+', '-', _) => format!("{:<+#0width$.pr$E}", f, width = p.width, pr = pr), 261 | ('_', '0', '+', '-', _) => format!("{:<+0width$.pr$E}", f, width = p.width, pr = pr), 262 | ('#', '0', '_', '-', _) => format!("{:<#0width$.pr$E}", f, width = p.width, pr = pr), 263 | (_, _, _, _, _) => format!("{:width$.pr$E}", f, width = p.width, pr = pr), 264 | } 265 | } else { 266 | match params_to_chars(p) { 267 | ('#', '_', '+', '_', _) => format!("{:+#width$E}", f, width = p.width), 268 | ('_', '_', '+', '_', _) => format!("{:+width$E}", f, width = p.width), 269 | ('#', '_', '_', '_', _) => format!("{:#width$E}", f, width = p.width), 270 | ('#', '0', '+', '_', _) => format!("{:+#0width$E}", f, width = p.width), 271 | ('_', '0', '+', '_', _) => format!("{:+0width$E}", f, width = p.width), 272 | ('#', '0', '_', '_', _) => format!("{:#0width$E}", f, width = p.width), 273 | ('#', '_', '+', '-', _) => format!("{:<+#width$E}", f, width = p.width), 274 | ('_', '_', '+', '-', _) => format!("{:<+width$E}", f, width = p.width), 275 | ('#', '_', '_', '-', _) => format!("{:<#width$E}", f, width = p.width), 276 | ('#', '0', '+', '-', _) => format!("{:<+#0width$E}", f, width = p.width), 277 | ('_', '0', '+', '-', _) => format!("{:<+0width$E}", f, width = p.width), 278 | ('#', '0', '_', '-', _) => format!("{:<#0width$E}", f, width = p.width), 279 | (_, _, _, _, _) => format!("{:width$E}", f, width = p.width), 280 | } 281 | } 282 | } 283 | 284 | fn escape_char(c: char) -> String { 285 | let mut s = c.escape_default().to_string(); 286 | if s.starts_with(r"\u") { 287 | s = s.replace("{", "").replace("}", ""); 288 | } 289 | s 290 | } 291 | 292 | struct Hexer<'a> { 293 | s: &'a str, 294 | } 295 | 296 | impl<'a> From<&'a str> for Hexer<'a> { 297 | fn from(s: &'a str) -> Self { 298 | Hexer { s } 299 | } 300 | } 301 | 302 | impl<'a> fmt::UpperHex for Hexer<'a> { 303 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 304 | for u in self.s.as_bytes() { 305 | write!(f, "{:X}", u)? 306 | } 307 | Ok(()) 308 | } 309 | } 310 | 311 | impl<'a> fmt::LowerHex for Hexer<'a> { 312 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 313 | for u in self.s.as_bytes() { 314 | write!(f, "{:x}", u)? 315 | } 316 | Ok(()) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use crate::error::NodeError; 4 | use crate::lexer::ItemType; 5 | use crate::utils::unquote_char; 6 | 7 | use gtmpl_value::Value; 8 | 9 | macro_rules! nodes { 10 | ($($node:ident, $name:ident),*) => { 11 | #[derive(Debug)] 12 | #[derive(Clone)] 13 | #[derive(PartialEq)] 14 | pub enum NodeType { 15 | $($name,)* 16 | } 17 | 18 | #[derive(Clone)] 19 | #[derive(Debug)] 20 | pub enum Nodes { 21 | $($name($node),)* 22 | } 23 | 24 | impl Display for Nodes { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 26 | match *self { 27 | $(Nodes::$name(ref t) => t.fmt(f),)* 28 | } 29 | } 30 | } 31 | 32 | impl Nodes { 33 | pub fn typ(&self) -> &NodeType { 34 | match *self { 35 | $(Nodes::$name(ref t) => t.typ(),)* 36 | } 37 | } 38 | pub fn pos(&self) -> Pos { 39 | match *self { 40 | $(Nodes::$name(ref t) => t.pos(),)* 41 | } 42 | } 43 | pub fn tree(&self) -> TreeId { 44 | match *self { 45 | $(Nodes::$name(ref t) => t.tree(),)* 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | nodes!( 53 | ListNode, 54 | List, 55 | TextNode, 56 | Text, 57 | PipeNode, 58 | Pipe, 59 | ActionNode, 60 | Action, 61 | CommandNode, 62 | Command, 63 | IdentifierNode, 64 | Identifier, 65 | VariableNode, 66 | Variable, 67 | DotNode, 68 | Dot, 69 | NilNode, 70 | Nil, 71 | FieldNode, 72 | Field, 73 | ChainNode, 74 | Chain, 75 | BoolNode, 76 | Bool, 77 | NumberNode, 78 | Number, 79 | StringNode, 80 | String, 81 | EndNode, 82 | End, 83 | ElseNode, 84 | Else, 85 | IfNode, 86 | If, 87 | WithNode, 88 | With, 89 | RangeNode, 90 | Range, 91 | TemplateNode, 92 | Template 93 | ); 94 | 95 | pub type Pos = usize; 96 | 97 | pub type TreeId = usize; 98 | 99 | pub trait Node: Display { 100 | fn typ(&self) -> &NodeType; 101 | fn pos(&self) -> Pos; 102 | fn tree(&self) -> TreeId; 103 | } 104 | 105 | macro_rules! node { 106 | ($name:ident { 107 | $($field:ident : $typ:ty),* $(,)* 108 | }) => { 109 | #[derive(Clone)] 110 | #[derive(Debug)] 111 | pub struct $name { 112 | typ: NodeType, 113 | pos: Pos, 114 | tr: TreeId, 115 | $(pub $field: $typ,)* 116 | } 117 | impl Node for $name { 118 | fn typ(&self) -> &NodeType { 119 | &self.typ 120 | } 121 | fn pos(&self) -> Pos { 122 | self.pos 123 | } 124 | fn tree(&self) -> TreeId { 125 | self.tr 126 | } 127 | } 128 | } 129 | } 130 | 131 | impl Nodes { 132 | pub fn is_empty_tree(&self) -> Result { 133 | match *self { 134 | Nodes::List(ref n) => n.is_empty_tree(), 135 | Nodes::Text(ref n) => Ok(n.text.is_empty()), 136 | Nodes::Action(_) 137 | | Nodes::If(_) 138 | | Nodes::Range(_) 139 | | Nodes::Template(_) 140 | | Nodes::With(_) => Ok(false), 141 | _ => Err(NodeError::NaTN), 142 | } 143 | } 144 | } 145 | 146 | node!( 147 | ListNode { 148 | nodes: Vec 149 | } 150 | ); 151 | 152 | impl ListNode { 153 | pub fn append(&mut self, n: Nodes) { 154 | self.nodes.push(n); 155 | } 156 | pub fn new(tr: TreeId, pos: Pos) -> ListNode { 157 | ListNode { 158 | typ: NodeType::List, 159 | pos, 160 | tr, 161 | nodes: vec![], 162 | } 163 | } 164 | pub fn is_empty_tree(&self) -> Result { 165 | for n in &self.nodes { 166 | match n.is_empty_tree() { 167 | Ok(true) => {} 168 | Ok(false) => return Ok(false), 169 | Err(s) => return Err(s), 170 | } 171 | } 172 | Ok(true) 173 | } 174 | } 175 | 176 | impl Display for ListNode { 177 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 178 | for n in &self.nodes { 179 | if let Err(e) = n.fmt(f) { 180 | return Err(e); 181 | } 182 | } 183 | Ok(()) 184 | } 185 | } 186 | 187 | node!(TextNode { text: String }); 188 | 189 | impl TextNode { 190 | pub fn new(tr: TreeId, pos: Pos, text: String) -> TextNode { 191 | TextNode { 192 | typ: NodeType::Text, 193 | pos, 194 | tr, 195 | text, 196 | } 197 | } 198 | } 199 | 200 | impl Display for TextNode { 201 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 202 | write!(f, "{}", self.text) 203 | } 204 | } 205 | 206 | node!( 207 | PipeNode { 208 | decl: Vec, 209 | cmds: Vec 210 | } 211 | ); 212 | 213 | impl PipeNode { 214 | pub fn new(tr: TreeId, pos: Pos, decl: Vec) -> PipeNode { 215 | PipeNode { 216 | typ: NodeType::Pipe, 217 | tr, 218 | pos, 219 | decl, 220 | cmds: vec![], 221 | } 222 | } 223 | 224 | pub fn append(&mut self, cmd: CommandNode) { 225 | self.cmds.push(cmd); 226 | } 227 | } 228 | 229 | impl Display for PipeNode { 230 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 231 | let decl = if self.decl.is_empty() { 232 | Ok(()) 233 | } else { 234 | write!( 235 | f, 236 | "{} := ", 237 | self.decl 238 | .iter() 239 | .map(|n| n.to_string()) 240 | .collect::>() 241 | .join(", ") 242 | ) 243 | }; 244 | decl.and_then(|_| { 245 | write!( 246 | f, 247 | "{}", 248 | self.cmds 249 | .iter() 250 | .map(|cmd| cmd.to_string()) 251 | .collect::>() 252 | .join(" | ") 253 | ) 254 | }) 255 | } 256 | } 257 | 258 | node!(ActionNode { pipe: PipeNode }); 259 | 260 | impl ActionNode { 261 | pub fn new(tr: TreeId, pos: Pos, pipe: PipeNode) -> ActionNode { 262 | ActionNode { 263 | typ: NodeType::Action, 264 | tr, 265 | pos, 266 | pipe, 267 | } 268 | } 269 | } 270 | 271 | impl Display for ActionNode { 272 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 273 | write!(f, "{{{{{}}}}}", self.pipe) 274 | } 275 | } 276 | 277 | node!( 278 | CommandNode { 279 | args: Vec 280 | } 281 | ); 282 | 283 | impl CommandNode { 284 | pub fn new(tr: TreeId, pos: Pos) -> CommandNode { 285 | CommandNode { 286 | typ: NodeType::Command, 287 | pos, 288 | tr, 289 | args: vec![], 290 | } 291 | } 292 | 293 | pub fn append(&mut self, node: Nodes) { 294 | self.args.push(node); 295 | } 296 | } 297 | 298 | impl Display for CommandNode { 299 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 300 | let s = self 301 | .args 302 | .iter() 303 | .map(|n| 304 | // Handle PipeNode. 305 | n.to_string()) 306 | .collect::>() 307 | .join(" "); 308 | write!(f, "{}", s) 309 | } 310 | } 311 | 312 | node!(IdentifierNode { ident: String }); 313 | 314 | impl IdentifierNode { 315 | pub fn new(ident: String) -> IdentifierNode { 316 | IdentifierNode { 317 | typ: NodeType::Identifier, 318 | tr: 0, 319 | pos: 0, 320 | ident, 321 | } 322 | } 323 | 324 | pub fn set_pos(&mut self, pos: Pos) -> &IdentifierNode { 325 | self.pos = pos; 326 | self 327 | } 328 | 329 | pub fn set_tree(&mut self, tr: TreeId) -> &IdentifierNode { 330 | self.tr = tr; 331 | self 332 | } 333 | } 334 | 335 | impl Display for IdentifierNode { 336 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 337 | write!(f, "{}", self.ident) 338 | } 339 | } 340 | 341 | node!( 342 | VariableNode { 343 | ident: Vec 344 | } 345 | ); 346 | 347 | impl VariableNode { 348 | pub fn new(tr: TreeId, pos: Pos, ident: &str) -> VariableNode { 349 | VariableNode { 350 | typ: NodeType::Variable, 351 | tr, 352 | pos, 353 | ident: ident.split('.').map(|s| s.to_owned()).collect(), 354 | } 355 | } 356 | } 357 | 358 | impl Display for VariableNode { 359 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 360 | write!(f, "{}", self.ident.join(".")) 361 | } 362 | } 363 | 364 | node!(DotNode {}); 365 | 366 | impl DotNode { 367 | pub fn new(tr: TreeId, pos: Pos) -> DotNode { 368 | DotNode { 369 | typ: NodeType::Dot, 370 | tr, 371 | pos, 372 | } 373 | } 374 | } 375 | 376 | impl Display for DotNode { 377 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 378 | write!(f, ".") 379 | } 380 | } 381 | 382 | node!(NilNode {}); 383 | 384 | impl Display for NilNode { 385 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 386 | write!(f, "nil") 387 | } 388 | } 389 | 390 | impl NilNode { 391 | pub fn new(tr: TreeId, pos: Pos) -> NilNode { 392 | NilNode { 393 | typ: NodeType::Nil, 394 | tr, 395 | pos, 396 | } 397 | } 398 | } 399 | 400 | node!( 401 | FieldNode { 402 | ident: Vec 403 | } 404 | ); 405 | 406 | impl FieldNode { 407 | pub fn new(tr: TreeId, pos: Pos, ident: &str) -> FieldNode { 408 | FieldNode { 409 | typ: NodeType::Field, 410 | tr, 411 | pos, 412 | ident: ident[..] 413 | .split('.') 414 | .filter_map(|s| { 415 | if s.is_empty() { 416 | None 417 | } else { 418 | Some(s.to_owned()) 419 | } 420 | }) 421 | .collect(), 422 | } 423 | } 424 | } 425 | 426 | impl Display for FieldNode { 427 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 428 | write!(f, "{}", self.ident.join(".")) 429 | } 430 | } 431 | 432 | node!( 433 | ChainNode { 434 | node: Box, 435 | field: Vec 436 | } 437 | ); 438 | 439 | impl ChainNode { 440 | pub fn new(tr: TreeId, pos: Pos, node: Nodes) -> ChainNode { 441 | ChainNode { 442 | typ: NodeType::Chain, 443 | tr, 444 | pos, 445 | node: Box::new(node), 446 | field: vec![], 447 | } 448 | } 449 | 450 | pub fn add(&mut self, val: &str) { 451 | let val = val.trim_start_matches('.').to_owned(); 452 | self.field.push(val); 453 | } 454 | } 455 | 456 | impl Display for ChainNode { 457 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 458 | if let Err(e) = { 459 | // Handle PipeNode. 460 | write!(f, "{}", self.node) 461 | } { 462 | return Err(e); 463 | } 464 | for field in &self.field { 465 | if let Err(e) = write!(f, ".{}", field) { 466 | return Err(e); 467 | } 468 | } 469 | Ok(()) 470 | } 471 | } 472 | 473 | node!(BoolNode { value: Value }); 474 | 475 | impl BoolNode { 476 | pub fn new(tr: TreeId, pos: Pos, val: bool) -> BoolNode { 477 | BoolNode { 478 | typ: NodeType::Bool, 479 | tr, 480 | pos, 481 | value: Value::from(val), 482 | } 483 | } 484 | } 485 | 486 | impl Display for BoolNode { 487 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 488 | write!(f, "{}", self.value) 489 | } 490 | } 491 | 492 | #[derive(Clone, Debug)] 493 | pub enum NumberType { 494 | U64, 495 | I64, 496 | Float, 497 | Char, 498 | } 499 | 500 | node!(NumberNode { 501 | is_i64: bool, 502 | is_u64: bool, 503 | is_f64: bool, 504 | text: String, 505 | number_typ: NumberType, 506 | value: Value, 507 | }); 508 | 509 | impl NumberNode { 510 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp))] 511 | pub fn new( 512 | tr: TreeId, 513 | pos: Pos, 514 | text: String, 515 | item_typ: &ItemType, 516 | ) -> Result { 517 | match *item_typ { 518 | ItemType::ItemCharConstant => unquote_char(&text, '\'') 519 | .map(|c| NumberNode { 520 | typ: NodeType::Number, 521 | tr, 522 | pos, 523 | is_i64: true, 524 | is_u64: true, 525 | is_f64: true, 526 | text, 527 | number_typ: NumberType::Char, 528 | value: Value::from(c as u64), 529 | }) 530 | .ok_or(NodeError::UnquoteError), 531 | _ => { 532 | let mut number_typ = NumberType::Float; 533 | 534 | // TODO: Deal with hex. 535 | let (mut as_i64, mut is_i64) = text 536 | .parse::() 537 | .map(|i| (i, true)) 538 | .unwrap_or((0i64, false)); 539 | 540 | if is_i64 { 541 | number_typ = NumberType::I64; 542 | } 543 | 544 | let (mut as_u64, mut is_u64) = text 545 | .parse::() 546 | .map(|i| (i, true)) 547 | .unwrap_or((0u64, false)); 548 | 549 | if is_u64 { 550 | number_typ = NumberType::U64; 551 | } 552 | 553 | if is_i64 && as_i64 == 0 { 554 | // In case of -0. 555 | as_u64 = 0; 556 | is_u64 = true; 557 | } 558 | 559 | let (as_f64, is_f64) = match text.parse::() { 560 | Err(_) => (0.0_f64, false), 561 | Ok(f) => { 562 | let frac = text.contains(|c| { 563 | matches! { 564 | c, '.' | 'e' | 'E' } 565 | }); 566 | if frac { 567 | (f, true) 568 | } else { 569 | (f, false) 570 | } 571 | } 572 | }; 573 | if !is_i64 && ((as_f64 as i64) as f64) == as_f64 { 574 | as_i64 = as_f64 as i64; 575 | is_i64 = true; 576 | } 577 | if !is_u64 && ((as_f64 as u64) as f64) == as_f64 { 578 | as_u64 = as_f64 as u64; 579 | is_u64 = true; 580 | } 581 | if !is_u64 && !is_i64 && !is_f64 { 582 | return Err(NodeError::NaN); 583 | } 584 | 585 | let value = if is_u64 { 586 | Value::from(as_u64) 587 | } else if is_i64 { 588 | Value::from(as_i64) 589 | } else { 590 | Value::from(as_f64) 591 | }; 592 | 593 | Ok(NumberNode { 594 | typ: NodeType::Number, 595 | tr, 596 | pos, 597 | is_i64, 598 | is_u64, 599 | is_f64, 600 | text, 601 | number_typ, 602 | value, 603 | }) 604 | } 605 | } 606 | } 607 | } 608 | 609 | impl Display for NumberNode { 610 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 611 | write!(f, "{}", self.text) 612 | } 613 | } 614 | 615 | node!(StringNode { 616 | quoted: String, 617 | value: Value, 618 | }); 619 | 620 | impl StringNode { 621 | pub fn new(tr: TreeId, pos: Pos, orig: String, text: String) -> StringNode { 622 | StringNode { 623 | typ: NodeType::String, 624 | tr, 625 | pos, 626 | quoted: orig, 627 | value: Value::from(text), 628 | } 629 | } 630 | } 631 | 632 | impl Display for StringNode { 633 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 634 | write!(f, "{}", self.quoted) 635 | } 636 | } 637 | 638 | node!(EndNode {}); 639 | 640 | impl EndNode { 641 | pub fn new(tr: TreeId, pos: Pos) -> EndNode { 642 | EndNode { 643 | typ: NodeType::End, 644 | tr, 645 | pos, 646 | } 647 | } 648 | } 649 | 650 | impl Display for EndNode { 651 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 652 | write!(f, "{{{{end}}}}") 653 | } 654 | } 655 | 656 | node!(ElseNode {}); 657 | 658 | impl ElseNode { 659 | pub fn new(tr: TreeId, pos: Pos) -> ElseNode { 660 | ElseNode { 661 | typ: NodeType::Else, 662 | tr, 663 | pos, 664 | } 665 | } 666 | } 667 | 668 | impl Display for ElseNode { 669 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 670 | write!(f, "{{{{else}}}}") 671 | } 672 | } 673 | 674 | node!( 675 | BranchNode { 676 | pipe: PipeNode, 677 | list: ListNode, 678 | else_list: Option 679 | } 680 | ); 681 | 682 | pub type IfNode = BranchNode; 683 | pub type WithNode = BranchNode; 684 | pub type RangeNode = BranchNode; 685 | 686 | impl BranchNode { 687 | pub fn new_if( 688 | tr: TreeId, 689 | pos: Pos, 690 | pipe: PipeNode, 691 | list: ListNode, 692 | else_list: Option, 693 | ) -> IfNode { 694 | IfNode { 695 | typ: NodeType::If, 696 | tr, 697 | pos, 698 | pipe, 699 | list, 700 | else_list, 701 | } 702 | } 703 | 704 | pub fn new_with( 705 | tr: TreeId, 706 | pos: Pos, 707 | pipe: PipeNode, 708 | list: ListNode, 709 | else_list: Option, 710 | ) -> WithNode { 711 | WithNode { 712 | typ: NodeType::With, 713 | tr, 714 | pos, 715 | pipe, 716 | list, 717 | else_list, 718 | } 719 | } 720 | 721 | pub fn new_range( 722 | tr: TreeId, 723 | pos: Pos, 724 | pipe: PipeNode, 725 | list: ListNode, 726 | else_list: Option, 727 | ) -> RangeNode { 728 | RangeNode { 729 | typ: NodeType::Range, 730 | tr, 731 | pos, 732 | pipe, 733 | list, 734 | else_list, 735 | } 736 | } 737 | } 738 | 739 | impl Display for BranchNode { 740 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 741 | let name = match self.typ { 742 | NodeType::If => "if", 743 | NodeType::Range => "range", 744 | NodeType::With => "with", 745 | _ => { 746 | return Err(std::fmt::Error); 747 | } 748 | }; 749 | if let Some(ref else_list) = self.else_list { 750 | return write!( 751 | f, 752 | "{{{{{} {}}}}}{}{{{{else}}}}{}{{{{end}}}}", 753 | name, self.pipe, self.list, else_list 754 | ); 755 | } 756 | write!(f, "{{{{{} {}}}}}{}{{{{end}}}}", name, self.pipe, self.list) 757 | } 758 | } 759 | 760 | node!( 761 | TemplateNode { 762 | name: PipeOrString, 763 | pipe: Option 764 | } 765 | ); 766 | 767 | impl TemplateNode { 768 | pub fn new(tr: TreeId, pos: Pos, name: PipeOrString, pipe: Option) -> TemplateNode { 769 | TemplateNode { 770 | typ: NodeType::Template, 771 | tr, 772 | pos, 773 | name, 774 | pipe, 775 | } 776 | } 777 | } 778 | 779 | impl Display for TemplateNode { 780 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 781 | match self.pipe { 782 | Some(ref pipe) => write!(f, "{{{{template {} {}}}}}", self.name, pipe), 783 | None => write!(f, "{{{{template {}}}}}", self.name), 784 | } 785 | } 786 | } 787 | 788 | #[derive(Clone, Debug)] 789 | pub enum PipeOrString { 790 | Pipe(PipeNode), 791 | String(String), 792 | } 793 | 794 | impl Display for PipeOrString { 795 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 796 | match *self { 797 | PipeOrString::Pipe(ref pipe_node) => write!(f, "{}", pipe_node), 798 | PipeOrString::String(ref s) => write!(f, "{}", s), 799 | } 800 | } 801 | } 802 | 803 | #[cfg(test)] 804 | mod tests { 805 | use super::*; 806 | 807 | #[test] 808 | fn test_clone() { 809 | let t1 = TextNode::new(1, 0, "foo".to_owned()); 810 | let mut t2 = t1.clone(); 811 | t2.text = "bar".to_owned(); 812 | assert_eq!(t1.to_string(), "foo"); 813 | assert_eq!(t2.to_string(), "bar"); 814 | } 815 | 816 | #[test] 817 | fn test_end() { 818 | let t1 = EndNode::new(1, 0); 819 | assert_eq!(t1.to_string(), "{{end}}"); 820 | } 821 | } 822 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::collections::HashMap; 3 | use std::fmt; 4 | use std::sync::mpsc::{channel, Receiver, Sender}; 5 | use std::thread; 6 | 7 | type Pos = usize; 8 | 9 | static LEFT_TRIM_MARKER: &str = "- "; 10 | static RIGHT_TRIM_MARKER: &str = " -"; 11 | static LEFT_DELIM: &str = "{{"; 12 | static RIGHT_DELIM: &str = "}}"; 13 | static LEFT_COMMENT: &str = "/*"; 14 | static RIGHT_COMMENT: &str = "*/"; 15 | 16 | lazy_static! { 17 | static ref KEY: HashMap<&'static str, ItemType> = { 18 | let mut m = HashMap::new(); 19 | m.insert(".", ItemType::ItemDot); 20 | m.insert("block", ItemType::ItemBlock); 21 | m.insert("define", ItemType::ItemDefine); 22 | m.insert("end", ItemType::ItemEnd); 23 | m.insert("else", ItemType::ItemElse); 24 | m.insert("if", ItemType::ItemIf); 25 | m.insert("range", ItemType::ItemRange); 26 | m.insert("nil", ItemType::ItemNil); 27 | m.insert("template", ItemType::ItemTemplate); 28 | m.insert("with", ItemType::ItemWith); 29 | m 30 | }; 31 | } 32 | 33 | #[allow(clippy::enum_variant_names)] 34 | #[derive(Debug, Clone, PartialEq)] 35 | pub enum ItemType { 36 | ItemError, // error occurred; value is text of error 37 | ItemBool, // boolean constant 38 | ItemChar, // printable ASCII character; grab bag for comma etc. 39 | ItemCharConstant, // character constant 40 | ItemComplex, // complex constant (1+2i); imaginary is just a number 41 | ItemColonEquals, // colon-equals (':=') introducing a declaration 42 | ItemEOF, 43 | ItemField, // alphanumeric identifier starting with '.' 44 | ItemIdentifier, // alphanumeric identifier not starting with '.' 45 | ItemLeftDelim, // left action delimiter 46 | ItemLeftParen, // '(' inside action 47 | ItemNumber, // simple number, including imaginary 48 | ItemPipe, // pipe symbol 49 | ItemRawString, // raw quoted string (includes quotes) 50 | ItemRightDelim, // right action delimiter 51 | ItemRightParen, // ')' inside action 52 | ItemSpace, // run of spaces separating arguments 53 | ItemString, // quoted string (includes quotes) 54 | ItemText, // plain text 55 | ItemVariable, // variable starting with '$', such as '$' or '$1' or '$hello' 56 | // Keywords, appear after all the rest. 57 | ItemKeyword, // used only to delimit the keywords 58 | ItemBlock, // block keyword 59 | ItemDot, // the cursor, spelled '.' 60 | ItemDefine, // define keyword 61 | ItemElse, // else keyword 62 | ItemEnd, // end keyword 63 | ItemIf, // if keyword 64 | ItemNil, // the untyped nil constant, easiest to treat as a keyword 65 | ItemRange, // range keyword 66 | ItemTemplate, // template keyword 67 | ItemWith, // with keyword 68 | } 69 | 70 | #[derive(Debug)] 71 | pub struct Item { 72 | pub typ: ItemType, 73 | pub pos: Pos, 74 | pub val: String, 75 | pub line: usize, 76 | } 77 | 78 | impl Item { 79 | pub fn new>(typ: ItemType, pos: Pos, val: T, line: usize) -> Item { 80 | Item { 81 | typ, 82 | pos, 83 | val: val.into(), 84 | line, 85 | } 86 | } 87 | } 88 | 89 | impl fmt::Display for Item { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | match self.typ { 92 | ItemType::ItemEOF => write!(f, "EOF"), 93 | ItemType::ItemKeyword => write!(f, "<{}>", self.val), 94 | _ => write!(f, "{}", self.val), 95 | } 96 | } 97 | } 98 | 99 | pub struct Lexer { 100 | last_pos: Pos, // position of most recent item returned by nextItem 101 | items_receiver: Receiver, // channel of scanned items 102 | finished: bool, // flag if lexer is finished 103 | } 104 | 105 | struct LexerStateMachine { 106 | input: String, // the string being scanned 107 | state: State, // the next lexing function to enter 108 | pos: Pos, // current position in the input 109 | start: Pos, // start position of this item 110 | width: Pos, // width of last rune read from input 111 | items_sender: Sender, // channel of scanned items 112 | paren_depth: usize, // nesting depth of ( ) exprs 113 | line: usize, // 1+number of newlines seen 114 | } 115 | 116 | #[derive(Debug)] 117 | enum State { 118 | End, 119 | LexText, 120 | LexLeftDelim, 121 | LexComment, 122 | LexRightDelim, 123 | LexInsideAction, 124 | LexSpace, 125 | LexIdentifier, 126 | LexField, 127 | LexVariable, 128 | LexChar, 129 | LexNumber, 130 | LexQuote, 131 | LexRawQuote, 132 | } 133 | 134 | impl Iterator for Lexer { 135 | type Item = Item; 136 | fn next(&mut self) -> Option { 137 | if self.finished { 138 | return None; 139 | } 140 | let item = match self.items_receiver.recv() { 141 | Ok(item) => { 142 | self.last_pos = item.pos; 143 | if item.typ == ItemType::ItemError || item.typ == ItemType::ItemEOF { 144 | self.finished = true; 145 | } 146 | item 147 | } 148 | Err(e) => { 149 | self.finished = true; 150 | Item::new(ItemType::ItemError, 0, format!("{}", e), 0) 151 | } 152 | }; 153 | Some(item) 154 | } 155 | } 156 | 157 | impl Lexer { 158 | pub fn new(input: String) -> Lexer { 159 | let (tx, rx) = channel(); 160 | let mut l = LexerStateMachine { 161 | input, 162 | state: State::LexText, 163 | pos: 0, 164 | start: 0, 165 | width: 0, 166 | items_sender: tx, 167 | paren_depth: 0, 168 | line: 1, 169 | }; 170 | thread::spawn(move || l.run()); 171 | Lexer { 172 | last_pos: 0, 173 | items_receiver: rx, 174 | finished: false, 175 | } 176 | } 177 | 178 | pub fn drain(&mut self) { 179 | for _ in self.items_receiver.iter() {} 180 | } 181 | } 182 | 183 | impl Drop for Lexer { 184 | fn drop(&mut self) { 185 | self.drain(); 186 | } 187 | } 188 | 189 | impl Iterator for LexerStateMachine { 190 | type Item = char; 191 | fn next(&mut self) -> Option { 192 | match self.input[self.pos..].chars().next() { 193 | Some(c) => { 194 | self.width = c.len_utf8(); 195 | self.pos += self.width; 196 | if c == '\n' { 197 | self.line += 1; 198 | } 199 | Some(c) 200 | } 201 | None => { 202 | self.width = 0; 203 | None 204 | } 205 | } 206 | } 207 | } 208 | 209 | impl LexerStateMachine { 210 | fn run(&mut self) { 211 | loop { 212 | self.state = match self.state { 213 | State::LexText => self.lex_text(), 214 | State::LexComment => self.lex_comment(), 215 | State::LexLeftDelim => self.lex_left_delim(), 216 | State::LexRightDelim => self.lex_right_delim(), 217 | State::LexInsideAction => self.lex_inside_action(), 218 | State::LexSpace => self.lex_space(), 219 | State::LexIdentifier => self.lex_identifier(), 220 | State::LexField => self.lex_field(), 221 | State::LexVariable => self.lex_variable(), 222 | State::LexChar => self.lex_char(), 223 | State::LexNumber => self.lex_number(), 224 | State::LexQuote => self.lex_quote(), 225 | State::LexRawQuote => self.lex_raw_quote(), 226 | State::End => { 227 | return; 228 | } 229 | } 230 | } 231 | } 232 | 233 | fn backup(&mut self) { 234 | self.pos -= 1; 235 | if self.width == 1 236 | && self.input[self.pos..] 237 | .chars() 238 | .next() 239 | .and_then(|c| if c == '\n' { Some(()) } else { None }) 240 | .is_some() 241 | { 242 | self.line -= 1; 243 | } 244 | } 245 | 246 | fn peek(&mut self) -> Option { 247 | let c = self.next(); 248 | self.backup(); 249 | c 250 | } 251 | 252 | fn emit(&mut self, t: ItemType) { 253 | let s = &self.input[self.start..self.pos]; 254 | let lines = match t { 255 | ItemType::ItemText 256 | | ItemType::ItemRawString 257 | | ItemType::ItemLeftDelim 258 | | ItemType::ItemRightDelim => 1, 259 | _ => s.chars().filter(|c| *c == '\n').count(), 260 | }; 261 | self.items_sender 262 | .send(Item::new(t, self.start, s, self.line)) 263 | .unwrap(); 264 | self.line += lines; 265 | self.start = self.pos; 266 | } 267 | 268 | fn ignore(&mut self) { 269 | self.start = self.pos; 270 | } 271 | 272 | fn accept(&mut self, valid: &str) -> bool { 273 | if self.next().map(|s| valid.contains(s)).unwrap_or_default() { 274 | return true; 275 | } 276 | self.backup(); 277 | false 278 | } 279 | 280 | fn accept_run(&mut self, valid: &str) { 281 | while self.accept(valid) {} 282 | } 283 | 284 | fn errorf(&mut self, msg: &str) -> State { 285 | self.items_sender 286 | .send(Item::new(ItemType::ItemError, self.start, msg, self.line)) 287 | .unwrap(); 288 | State::End 289 | } 290 | 291 | fn lex_text(&mut self) -> State { 292 | self.width = 0; 293 | let x = self.input[self.pos..].find(&LEFT_DELIM); 294 | match x { 295 | Some(x) => { 296 | self.pos += x; 297 | let ld = self.pos + LEFT_DELIM.len(); 298 | let trim = if self.input[ld..].starts_with(LEFT_TRIM_MARKER) { 299 | rtrim_len(&self.input[self.start..self.pos]) 300 | } else { 301 | 0 302 | }; 303 | self.pos -= trim; 304 | if self.pos > self.start { 305 | self.emit(ItemType::ItemText); 306 | } 307 | self.pos += trim; 308 | self.ignore(); 309 | State::LexLeftDelim 310 | } 311 | None => { 312 | self.pos = self.input.len(); 313 | if self.pos > self.start { 314 | self.emit(ItemType::ItemText); 315 | } 316 | self.emit(ItemType::ItemEOF); 317 | State::End 318 | } 319 | } 320 | } 321 | 322 | fn at_right_delim(&mut self) -> (bool, bool) { 323 | if self.input[self.pos..].starts_with(&RIGHT_DELIM) { 324 | return (true, false); 325 | } 326 | if self.input[self.pos..].starts_with(&format!("{}{}", RIGHT_TRIM_MARKER, RIGHT_DELIM)) { 327 | return (true, true); 328 | } 329 | (false, false) 330 | } 331 | 332 | fn lex_left_delim(&mut self) -> State { 333 | self.pos += LEFT_DELIM.len(); 334 | let trim = self.input[self.pos..].starts_with(LEFT_TRIM_MARKER); 335 | let after_marker = if trim { LEFT_TRIM_MARKER.len() } else { 0 }; 336 | if self.input[(self.pos + after_marker)..].starts_with(LEFT_COMMENT) { 337 | self.pos += after_marker; 338 | self.ignore(); 339 | State::LexComment 340 | } else { 341 | self.emit(ItemType::ItemLeftDelim); 342 | self.pos += after_marker; 343 | self.ignore(); 344 | self.paren_depth = 0; 345 | State::LexInsideAction 346 | } 347 | } 348 | 349 | fn lex_comment(&mut self) -> State { 350 | self.pos += LEFT_COMMENT.len(); 351 | let i = match self.input[self.pos..].find(RIGHT_COMMENT) { 352 | Some(i) => i, 353 | None => { 354 | return self.errorf("unclosed comment"); 355 | } 356 | }; 357 | 358 | self.pos += i + RIGHT_COMMENT.len(); 359 | let (delim, trim) = self.at_right_delim(); 360 | 361 | if !delim { 362 | return self.errorf("comment end before closing delimiter"); 363 | } 364 | 365 | if trim { 366 | self.pos += RIGHT_TRIM_MARKER.len(); 367 | } 368 | 369 | self.pos += RIGHT_DELIM.len(); 370 | 371 | if trim { 372 | self.pos += ltrim_len(&self.input[self.pos..]); 373 | } 374 | 375 | self.ignore(); 376 | State::LexText 377 | } 378 | 379 | fn lex_right_delim(&mut self) -> State { 380 | let trim = self.input[self.pos..].starts_with(RIGHT_TRIM_MARKER); 381 | if trim { 382 | self.pos += RIGHT_TRIM_MARKER.len(); 383 | self.ignore(); 384 | } 385 | self.pos += RIGHT_DELIM.len(); 386 | self.emit(ItemType::ItemRightDelim); 387 | if trim { 388 | self.pos += ltrim_len(&self.input[self.pos..]); 389 | self.ignore(); 390 | } 391 | State::LexText 392 | } 393 | 394 | fn lex_inside_action(&mut self) -> State { 395 | let (delim, _) = self.at_right_delim(); 396 | if delim { 397 | if self.paren_depth == 0 { 398 | return State::LexRightDelim; 399 | } 400 | return self.errorf("unclosed left paren"); 401 | } 402 | 403 | match self.next() { 404 | None | Some('\r') | Some('\n') => self.errorf("unclosed action"), 405 | Some(c) => { 406 | match c { 407 | '"' => State::LexQuote, 408 | '`' => State::LexRawQuote, 409 | '$' => State::LexVariable, 410 | '\'' => State::LexChar, 411 | '(' => { 412 | self.emit(ItemType::ItemLeftParen); 413 | self.paren_depth += 1; 414 | State::LexInsideAction 415 | } 416 | ')' => { 417 | self.emit(ItemType::ItemRightParen); 418 | if self.paren_depth == 0 { 419 | return self.errorf(&format!("unexpected right paren {}", c)); 420 | } 421 | self.paren_depth -= 1; 422 | State::LexInsideAction 423 | } 424 | ':' => match self.next() { 425 | Some('=') => { 426 | self.emit(ItemType::ItemColonEquals); 427 | State::LexInsideAction 428 | } 429 | _ => self.errorf("expected :="), 430 | }, 431 | '|' => { 432 | self.emit(ItemType::ItemPipe); 433 | State::LexInsideAction 434 | } 435 | '.' => match self.input[self.pos..].chars().next() { 436 | Some('0'..='9') => { 437 | self.backup(); 438 | State::LexNumber 439 | } 440 | _ => State::LexField, 441 | }, 442 | '+' | '-' | '0'..='9' => { 443 | self.backup(); 444 | State::LexNumber 445 | } 446 | _ if c.is_whitespace() => State::LexSpace, 447 | _ if c.is_alphanumeric() || c == '_' => { 448 | self.backup(); 449 | State::LexIdentifier 450 | } 451 | _ if c.is_ascii() => { 452 | // figure out a way to check for unicode.isPrint ?! 453 | self.emit(ItemType::ItemChar); 454 | State::LexInsideAction 455 | } 456 | _ => self.errorf(&format!("unrecognized character in action {}", c)), 457 | } 458 | } 459 | } 460 | } 461 | 462 | fn lex_space(&mut self) -> State { 463 | while self.peek().map(|c| c.is_whitespace()).unwrap_or_default() { 464 | self.next(); 465 | } 466 | self.emit(ItemType::ItemSpace); 467 | State::LexInsideAction 468 | } 469 | 470 | fn lex_identifier(&mut self) -> State { 471 | let c = self.find(|c| !(c.is_alphanumeric() || *c == '_')); 472 | self.backup(); 473 | if !self.at_terminator() { 474 | return self.errorf(&format!("bad character {}", c.unwrap_or_default())); 475 | } 476 | let item_type = match &self.input[self.start..self.pos] { 477 | "true" | "false" => ItemType::ItemBool, 478 | word if KEY.contains_key(word) => (*KEY.get(word).unwrap()).clone(), 479 | word if word.starts_with('.') => ItemType::ItemField, 480 | _ => ItemType::ItemIdentifier, 481 | }; 482 | self.emit(item_type); 483 | State::LexInsideAction 484 | } 485 | 486 | fn lex_field(&mut self) -> State { 487 | self.lex_field_or_variable(ItemType::ItemField) 488 | } 489 | 490 | fn lex_variable(&mut self) -> State { 491 | self.lex_field_or_variable(ItemType::ItemVariable) 492 | } 493 | 494 | fn lex_field_or_variable(&mut self, typ: ItemType) -> State { 495 | if self.at_terminator() { 496 | self.emit(match typ { 497 | ItemType::ItemVariable => ItemType::ItemVariable, 498 | _ => ItemType::ItemDot, 499 | }); 500 | return State::LexInsideAction; 501 | } 502 | let c = self.find(|c| !(c.is_alphanumeric() || *c == '_')); 503 | self.backup(); 504 | 505 | if !self.at_terminator() { 506 | return self.errorf(&format!("bad character {}", c.unwrap_or_default())); 507 | } 508 | self.emit(typ); 509 | State::LexInsideAction 510 | } 511 | 512 | fn at_terminator(&mut self) -> bool { 513 | match self.peek() { 514 | Some(c) => { 515 | match c { 516 | '.' | ',' | '|' | ':' | ')' | '(' | ' ' | '\t' | '\r' | '\n' => true, 517 | // this is what golang does to detect a delimiter 518 | _ => RIGHT_DELIM.starts_with(c), 519 | } 520 | } 521 | None => false, 522 | } 523 | } 524 | 525 | fn lex_char(&mut self) -> State { 526 | let mut escaped = false; 527 | loop { 528 | let c = self.next(); 529 | match c { 530 | Some('\\') => { 531 | escaped = true; 532 | continue; 533 | } 534 | Some('\n') | None => { 535 | return self.errorf("unterminated character constant"); 536 | } 537 | Some('\'') if !escaped => { 538 | break; 539 | } 540 | _ => {} 541 | }; 542 | escaped = false; 543 | } 544 | self.emit(ItemType::ItemCharConstant); 545 | State::LexInsideAction 546 | } 547 | 548 | fn lex_number(&mut self) -> State { 549 | if self.scan_number() { 550 | // Let's ingnore complex numbers here. 551 | self.emit(ItemType::ItemNumber); 552 | State::LexInsideAction 553 | } else { 554 | let msg = &format!("bad number syntax: {}", &self.input[self.start..self.pos]); 555 | self.errorf(msg) 556 | } 557 | } 558 | 559 | fn scan_number(&mut self) -> bool { 560 | self.accept("+-"); 561 | if self.accept("0") && self.accept("xX") { 562 | let digits = "0123456789abcdefABCDEF"; 563 | self.accept_run(digits); 564 | } else { 565 | let digits = "0123456789"; 566 | self.accept_run(digits); 567 | if self.accept(".") { 568 | self.accept_run(digits); 569 | } 570 | if self.accept("eE") { 571 | self.accept("+-"); 572 | self.accept_run(digits); 573 | } 574 | } 575 | // Let's ignore imaginary numbers for now. 576 | if self.peek().map(|c| c.is_alphanumeric()).unwrap_or(true) { 577 | self.next(); 578 | return false; 579 | } 580 | true 581 | } 582 | 583 | fn lex_quote(&mut self) -> State { 584 | let mut escaped = false; 585 | loop { 586 | let c = self.next(); 587 | match c { 588 | Some('\\') => { 589 | escaped = true; 590 | continue; 591 | } 592 | Some('\n') | None => { 593 | return self.errorf("unterminated quoted string"); 594 | } 595 | Some('"') if !escaped => { 596 | break; 597 | } 598 | _ => {} 599 | }; 600 | escaped = false; 601 | } 602 | self.emit(ItemType::ItemString); 603 | State::LexInsideAction 604 | } 605 | 606 | fn lex_raw_quote(&mut self) -> State { 607 | let start_line = self.line; 608 | if !self.any(|c| c == '`') { 609 | self.line = start_line; 610 | return self.errorf("unterminated raw quoted string"); 611 | } 612 | self.emit(ItemType::ItemRawString); 613 | State::LexInsideAction 614 | } 615 | } 616 | 617 | fn rtrim_len(s: &str) -> usize { 618 | match s.rfind(|c: char| !c.is_whitespace()) { 619 | Some(i) => s.len() - 1 - i, 620 | None => s.len(), 621 | } 622 | } 623 | 624 | fn ltrim_len(s: &str) -> usize { 625 | let l = s.len(); 626 | s.find(|c: char| !c.is_whitespace()).unwrap_or(l) 627 | } 628 | 629 | #[cfg(test)] 630 | mod tests { 631 | use super::*; 632 | 633 | #[test] 634 | fn lexer_run() { 635 | let mut l = Lexer::new("abc".to_owned()); 636 | let i1 = l.next().unwrap(); 637 | assert_eq!(i1.typ, ItemType::ItemText); 638 | assert_eq!(&i1.val, "abc"); 639 | } 640 | 641 | #[test] 642 | fn lex_simple() { 643 | let s = r#"something {{ if eq "foo" "bar" }}"#; 644 | let l = Lexer::new(s.to_owned()); 645 | assert_eq!(l.count(), 13); 646 | } 647 | 648 | #[test] 649 | fn test_whitespace() { 650 | let s = r#"something {{ .foo }}"#; 651 | let l = Lexer::new(s.to_owned()); 652 | let s_ = l.map(|i| i.val).collect::>().join(""); 653 | assert_eq!(s_, s); 654 | } 655 | 656 | #[test] 657 | fn test_input() { 658 | let s = r#"something {{ .foo }}"#; 659 | let l = Lexer::new(s.to_owned()); 660 | let s_ = l.map(|i| i.val).collect::>().join(""); 661 | assert_eq!(s_, s); 662 | } 663 | 664 | #[test] 665 | fn test_underscore() { 666 | let s = r#"something {{ .foo_bar }}"#; 667 | let l = Lexer::new(s.to_owned()); 668 | let s_ = l.map(|i| i.val).collect::>().join(""); 669 | assert_eq!(s_, s); 670 | } 671 | 672 | #[test] 673 | fn test_trim() { 674 | let s = r#"something {{- .foo -}} 2000"#; 675 | let l = Lexer::new(s.to_owned()); 676 | let s_ = l.map(|i| i.val).collect::>().join(""); 677 | assert_eq!(s_, r#"something{{.foo}}2000"#); 678 | } 679 | 680 | #[test] 681 | fn test_comment() { 682 | let s = r#"something {{- /* foo */ -}} 2000"#; 683 | let l = Lexer::new(s.to_owned()); 684 | let s_ = l.map(|i| i.val).collect::>().join(""); 685 | assert_eq!(s_, r#"something2000"#); 686 | } 687 | } 688 | -------------------------------------------------------------------------------- /src/funcs.rs: -------------------------------------------------------------------------------- 1 | //! Builtin functions. 2 | use std::cmp::Ordering; 3 | use std::fmt::Write; 4 | 5 | use gtmpl_value::{Func, FuncError, Value}; 6 | use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; 7 | 8 | use crate::printf::sprintf; 9 | use crate::utils::is_true; 10 | 11 | const QUERY_ENCODE: &AsciiSet = &CONTROLS 12 | .add(b' ') 13 | .add(b'"') 14 | .add(b'<') 15 | .add(b'>') 16 | .add(b'#') 17 | .add(b'`') 18 | .add(b'`') 19 | .add(b'?') 20 | .add(b'{') 21 | .add(b'}'); 22 | 23 | pub static BUILTINS: &[(&str, Func)] = &[ 24 | ("eq", eq as Func), 25 | ("ne", ne as Func), 26 | ("lt", lt as Func), 27 | ("le", le as Func), 28 | ("gt", gt as Func), 29 | ("ge", ge as Func), 30 | ("len", len as Func), 31 | ("and", and as Func), 32 | ("or", or as Func), 33 | ("not", not as Func), 34 | ("urlquery", urlquery as Func), 35 | ("print", print as Func), 36 | ("println", println as Func), 37 | ("printf", printf as Func), 38 | ("index", index as Func), 39 | ("call", call as Func), 40 | ]; 41 | 42 | macro_rules! val { 43 | ($x:expr) => { 44 | Value::from($x) 45 | }; 46 | } 47 | 48 | /// Help to write new functions for gtmpl. 49 | #[macro_export] 50 | macro_rules! gtmpl_fn { 51 | ( 52 | $(#[$outer:meta])* 53 | fn $name:ident() -> Result<$otyp:ty, FuncError> 54 | { $($body:tt)* } 55 | ) => { 56 | $(#[$outer])* 57 | pub fn $name(args: &[$crate::Value]) -> Result<$crate::Value, FuncError> { 58 | fn inner() -> Result<$otyp, FuncError> { 59 | $($body)* 60 | } 61 | Ok($crate::Value::from(inner()?)) 62 | } 63 | }; 64 | ( 65 | $(#[$outer:meta])* 66 | fn $name:ident($arg0:ident : $typ0:ty) -> Result<$otyp:ty, FuncError> 67 | { $($body:tt)* } 68 | ) => { 69 | $(#[$outer])* 70 | pub fn $name( 71 | args: &[$crate::Value] 72 | ) -> Result<$crate::Value, FuncError> { 73 | if args.is_empty() { 74 | return Err(FuncError::AtLeastXArgs(stringify!($name).into(), 1)); 75 | } 76 | let x = &args[0]; 77 | let $arg0: $typ0 = $crate::from_value(x) 78 | .ok_or(FuncError::UnableToConvertFromValue)?; 79 | fn inner($arg0 : $typ0) -> Result<$otyp, FuncError> { 80 | $($body)* 81 | } 82 | let ret: $crate::Value = inner($arg0)?.into(); 83 | Ok(ret) 84 | } 85 | }; 86 | ( 87 | $(#[$outer:meta])* 88 | fn $name:ident($arg0:ident : $typ0:ty$(, $arg:ident : $typ:ty)*) -> Result<$otyp:ty, FuncError> 89 | { $($body:tt)* } 90 | ) => { 91 | $(#[$outer])* 92 | pub fn $name( 93 | args: &[$crate::Value] 94 | ) -> Result<$crate::Value, FuncError> { 95 | #[allow(unused_mut)] 96 | let mut args = args; 97 | if args.is_empty() { 98 | return Err(FuncError::AtLeastXArgs(stringify!($name).into(), 1)); 99 | } 100 | let x = &args[0]; 101 | let $arg0: $typ0 = $crate::from_value(x) 102 | .ok_or(FuncError::UnableToConvertFromValue)?; 103 | $(args = &args[1..]; 104 | let x = &args[0]; 105 | let $arg: $typ = $crate::from_value(x) 106 | .ok_or(FuncError::UnableToConvertFromValue)?;)* 107 | fn inner($arg0 : $typ0, $($arg : $typ,)*) -> Result<$otyp, FuncError> { 108 | $($body)* 109 | } 110 | let ret: $crate::Value = inner($arg0, $($arg),*)?.into(); 111 | Ok(ret) 112 | } 113 | } 114 | } 115 | 116 | macro_rules! gn { 117 | ( 118 | $(#[$outer:meta])* 119 | $name:ident($arg1:ident : ref Value, $arg2:ident : ref Value) -> 120 | Result 121 | { $($body:tt)* } 122 | ) => { 123 | $(#[$outer])* 124 | pub fn $name(args: &[Value]) -> Result { 125 | if args.len() != 2 { 126 | return Err(FuncError::AtLeastXArgs(stringify!($name).into(), 2)); 127 | } 128 | let $arg1 = &args[0]; 129 | let $arg2 = &args[1]; 130 | fn inner($arg1: &Value, $arg2: &Value) -> Result { 131 | $($body)* 132 | } 133 | inner($arg1, $arg2) 134 | } 135 | } 136 | } 137 | 138 | /// Returns the boolean OR of its arguments by returning the 139 | /// first non-empty argument or the last argument, that is, 140 | /// "or x y" behaves as "if x then x else y". All the 141 | /// arguments are evaluated. 142 | /// 143 | /// # Example 144 | /// ``` 145 | /// use gtmpl::template; 146 | /// let equal = template("{{ or 1 2.0 false . }}", "foo"); 147 | /// assert_eq!(&equal.unwrap(), "1"); 148 | /// ``` 149 | pub fn or(args: &[Value]) -> Result { 150 | for arg in args { 151 | if is_true(arg) { 152 | return Ok(arg.clone()); 153 | } 154 | } 155 | args.iter() 156 | .cloned() 157 | .last() 158 | .ok_or_else(|| FuncError::AtLeastXArgs("or".into(), 1)) 159 | } 160 | 161 | /// Returns the boolean AND of its arguments by returning the 162 | /// first empty argument or the last argument, that is, 163 | /// "and x y" behaves as "if x then y else x". All the 164 | /// arguments are evaluated. 165 | /// 166 | /// # Example 167 | /// ``` 168 | /// use gtmpl::template; 169 | /// let equal = template("{{ and 1 2.0 true . }}", "foo"); 170 | /// assert_eq!(&equal.unwrap(), "foo"); 171 | /// ``` 172 | pub fn and(args: &[Value]) -> Result { 173 | for arg in args { 174 | if !is_true(arg) { 175 | return Ok(arg.clone()); 176 | } 177 | } 178 | args.iter() 179 | .cloned() 180 | .last() 181 | .ok_or_else(|| FuncError::AtLeastXArgs("and".into(), 1)) 182 | } 183 | 184 | /// Returns the boolean negation of its single argument. 185 | /// 186 | /// # Example 187 | /// ``` 188 | /// use gtmpl::template; 189 | /// let equal = template("{{ not 0 }}", ""); 190 | /// assert_eq!(&equal.unwrap(), "true"); 191 | /// ``` 192 | pub fn not(args: &[Value]) -> Result { 193 | if args.len() != 1 { 194 | Err(FuncError::ExactlyXArgs("not".into(), 1)) 195 | } else { 196 | Ok(val!(!is_true(&args[0]))) 197 | } 198 | } 199 | 200 | /// Returns the integer length of its argument. 201 | /// 202 | /// # Example 203 | /// ``` 204 | /// use gtmpl::template; 205 | /// let equal = template("{{ len . }}", "foo"); 206 | /// assert_eq!(&equal.unwrap(), "3"); 207 | /// ``` 208 | pub fn len(args: &[Value]) -> Result { 209 | if args.len() != 1 { 210 | return Err(FuncError::ExactlyXArgs("len".into(), 1)); 211 | } 212 | let arg = &args[0]; 213 | let len = match *arg { 214 | Value::String(ref s) => s.len(), 215 | Value::Array(ref a) => a.len(), 216 | Value::Object(ref o) => o.len(), 217 | _ => { 218 | return Err(FuncError::Generic(format!("unable to call len on {}", arg))); 219 | } 220 | }; 221 | 222 | Ok(val!(len)) 223 | } 224 | 225 | /// Returns the result of calling the first argument, which 226 | /// must be a function, with the remaining arguments as parameters. 227 | /// 228 | /// # Example 229 | /// ``` 230 | /// use gtmpl::{gtmpl_fn, template, Value}; 231 | /// use gtmpl_value::{FuncError, Function}; 232 | /// 233 | /// gtmpl_fn!( 234 | /// fn add(a: u64, b: u64) -> Result { 235 | /// Ok(a + b) 236 | /// }); 237 | /// let equal = template(r#"{{ call . 1 2 }}"#, Value::Function(Function { f: add })); 238 | /// assert_eq!(&equal.unwrap(), "3"); 239 | /// ``` 240 | pub fn call(args: &[Value]) -> Result { 241 | if args.is_empty() { 242 | Err(FuncError::AtLeastXArgs("call".into(), 1)) 243 | } else if let Value::Function(ref f) = args[0] { 244 | (f.f)(&args[1..]) 245 | } else { 246 | Err(FuncError::Generic( 247 | "call requires the first argument to be a function".into(), 248 | )) 249 | } 250 | } 251 | 252 | /// An implementation of golang's fmt.Sprint 253 | /// 254 | /// Golang's Sprint formats using the default formats for its operands and returns the 255 | /// resulting string. Spaces are added between operands when neither is a string. 256 | /// 257 | /// # Example 258 | /// ``` 259 | /// use gtmpl::template; 260 | /// let equal = template(r#"{{ print "Hello " . "!" }}"#, "world"); 261 | /// assert_eq!(&equal.unwrap(), "Hello world!"); 262 | /// ``` 263 | pub fn print(args: &[Value]) -> Result { 264 | let mut no_space = true; 265 | let mut s = String::new(); 266 | for val in args { 267 | if let Value::String(ref v) = *val { 268 | no_space = true; 269 | s.push_str(v); 270 | } else { 271 | if no_space { 272 | s += &val.to_string(); 273 | } else { 274 | s += &format!(" {}", val.to_string()) 275 | } 276 | no_space = false; 277 | } 278 | } 279 | Ok(val!(s)) 280 | } 281 | 282 | /// An implementation of golang's fmt.Sprintln 283 | /// 284 | /// Sprintln formats using the default formats for its operands and returns the 285 | /// resulting string. Spaces are always added between operands and a newline is appended. 286 | /// 287 | /// # Example 288 | /// ``` 289 | /// use gtmpl::template; 290 | /// let equal = template(r#"{{ println "Hello" . "!" }}"#, "world"); 291 | /// assert_eq!(&equal.unwrap(), "Hello world !\n"); 292 | /// ``` 293 | pub fn println(args: &[Value]) -> Result { 294 | let mut iter = args.iter(); 295 | let s = match iter.next() { 296 | None => String::from("\n"), 297 | Some(first_elt) => { 298 | let (lower, _) = iter.size_hint(); 299 | let mut result = String::with_capacity(lower + 1); 300 | if let Value::String(ref v) = *first_elt { 301 | result.push_str(v); 302 | } else { 303 | write!(&mut result, "{}", first_elt).unwrap(); 304 | } 305 | for elt in iter { 306 | result.push(' '); 307 | if let Value::String(ref v) = *elt { 308 | result.push_str(v); 309 | } else { 310 | write!(&mut result, "{}", elt).unwrap(); 311 | } 312 | } 313 | result.push('\n'); 314 | result 315 | } 316 | }; 317 | Ok(val!(s)) 318 | } 319 | 320 | /// An implementation of golang's fmt.Sprintf 321 | /// Limitations: 322 | /// - float: 323 | /// * `g`, `G`, and `b` are weired and not implement yet 324 | /// - pretty sure there are more 325 | /// 326 | /// # Example 327 | /// ``` 328 | /// use gtmpl::template; 329 | /// let equal = template(r#"{{ printf "%v %s %v" "Hello" . "!" }}"#, "world"); 330 | /// assert_eq!(&equal.unwrap(), "Hello world !"); 331 | /// ``` 332 | pub fn printf(args: &[Value]) -> Result { 333 | if args.is_empty() { 334 | return Err(FuncError::AtLeastXArgs("printf".into(), 1)); 335 | } 336 | if let Value::String(ref s) = args[0] { 337 | let s = sprintf(s, &args[1..]).map_err(|e| FuncError::Other(e.into()))?; 338 | Ok(val!(s)) 339 | } else { 340 | Err(FuncError::Generic("printf requires a format string".into())) 341 | } 342 | } 343 | 344 | /// Returns the result of indexing its first argument by the 345 | /// following arguments. Thus "index x 1 2 3" is, in Go syntax, 346 | /// x[1][2][3]. Each indexed item must be a map, slice or array. 347 | /// 348 | /// # Example 349 | /// ``` 350 | /// use gtmpl::template; 351 | /// let ctx = vec![23, 42, 7]; 352 | /// let index = template("{{ index . 1 }}", ctx); 353 | /// assert_eq!(&index.unwrap(), "42"); 354 | /// ``` 355 | pub fn index(args: &[Value]) -> Result { 356 | if args.len() < 2 { 357 | return Err(FuncError::AtLeastXArgs("index".into(), 2)); 358 | } 359 | let mut col = &args[0]; 360 | for val in &args[1..] { 361 | col = get_item(col, val)?; 362 | } 363 | 364 | Ok(col.clone()) 365 | } 366 | 367 | fn get_item<'a>(col: &'a Value, key: &Value) -> Result<&'a Value, FuncError> { 368 | let ret = match (col, key) { 369 | (&Value::Array(ref a), &Value::Number(ref n)) => { 370 | if let Some(i) = n.as_u64() { 371 | a.get(i as usize) 372 | } else { 373 | None 374 | } 375 | } 376 | (&Value::Object(ref o), &Value::Number(ref n)) 377 | | (&Value::Map(ref o), &Value::Number(ref n)) => o.get(&n.to_string()), 378 | (&Value::Object(ref o), &Value::String(ref s)) 379 | | (&Value::Map(ref o), &Value::String(ref s)) => o.get(s), 380 | _ => None, 381 | }; 382 | match *col { 383 | Value::Map(_) => Ok(ret.unwrap_or(&Value::NoValue)), 384 | _ => ret.ok_or_else(|| FuncError::Generic(format!("unable to get {} in {}", key, col))), 385 | } 386 | } 387 | 388 | /// Returns the escaped value of the textual representation of 389 | /// its arguments in a form suitable for embedding in a URL query. 390 | /// 391 | /// # Example 392 | /// ``` 393 | /// use gtmpl::template; 394 | /// let url = template(r#"{{ urlquery "foo bar?" }}"#, 0); 395 | /// assert_eq!(&url.unwrap(), "foo%20bar%3F"); 396 | /// ``` 397 | pub fn urlquery(args: &[Value]) -> Result { 398 | if args.len() != 1 { 399 | return Err(FuncError::ExactlyXArgs("urlquery".into(), 1)); 400 | } 401 | let val = &args[0]; 402 | match *val { 403 | Value::String(ref s) => Ok(val!(utf8_percent_encode(s, QUERY_ENCODE).to_string())), 404 | _ => Err(FuncError::Generic( 405 | "Arguments need to be of type String".into(), 406 | )), 407 | } 408 | } 409 | 410 | /// Returns the boolean truth of arg1 == arg2 [== arg3 ...] 411 | /// 412 | /// # Example 413 | /// ``` 414 | /// use gtmpl::template; 415 | /// let equal = template("{{ eq 1 1 . }}", 1); 416 | /// assert_eq!(&equal.unwrap(), "true"); 417 | /// ``` 418 | pub fn eq(args: &[Value]) -> Result { 419 | if args.len() < 2 { 420 | return Err(FuncError::AtLeastXArgs("eq".into(), 2)); 421 | } 422 | let first = &args[0]; 423 | Ok(Value::from(args.iter().skip(1).all(|x| *x == *first))) 424 | } 425 | 426 | gn!( 427 | #[doc=" 428 | Returns the boolean truth of arg1 != arg2 429 | 430 | # Example 431 | ``` 432 | use gtmpl::template; 433 | let not_equal = template(\"{{ ne 2 . }}\", 1); 434 | assert_eq!(¬_equal.unwrap(), \"true\"); 435 | ``` 436 | "] 437 | ne(a: ref Value, b: ref Value) -> Result { 438 | Ok(Value::from(a != b)) 439 | }); 440 | 441 | gn!( 442 | #[doc=" 443 | Returns the boolean truth of arg1 < arg2 444 | 445 | # Example 446 | ``` 447 | use gtmpl::template; 448 | let less_than = template(\"{{ lt 0 . }}\", 1); 449 | assert_eq!(&less_than.unwrap(), \"true\"); 450 | ``` 451 | "] 452 | lt(a: ref Value, b: ref Value) -> Result { 453 | let ret = match cmp(a, b) { 454 | None => return Err(FuncError::Generic(format!("unable to compare {} and {}", a, b))), 455 | Some(Ordering::Less) => true, 456 | _ => false, 457 | }; 458 | Ok(Value::from(ret)) 459 | }); 460 | 461 | gn!( 462 | #[doc=" 463 | Returns the boolean truth of arg1 <= arg2 464 | 465 | # Example 466 | ``` 467 | use gtmpl::template; 468 | let less_or_equal = template(\"{{ le 1.4 . }}\", 1.4); 469 | assert_eq!(less_or_equal.unwrap(), \"true\"); 470 | 471 | let less_or_equal = template(\"{{ le 0.2 . }}\", 1.4); 472 | assert_eq!(&less_or_equal.unwrap(), \"true\"); 473 | ``` 474 | "] 475 | le(a: ref Value, b: ref Value) -> Result { 476 | let ret = match cmp(a, b) { 477 | None => return Err(FuncError::Generic(format!("unable to compare {} and {}", a, b))), 478 | Some(Ordering::Less) | Some(Ordering::Equal) => true, 479 | _ => false, 480 | }; 481 | Ok(Value::from(ret)) 482 | }); 483 | 484 | gn!( 485 | #[doc=" 486 | Returns the boolean truth of arg1 > arg2 487 | 488 | # Example 489 | ``` 490 | use gtmpl::template; 491 | let greater_than = template(\"{{ gt 1.4 . }}\", 1.2); 492 | assert_eq!(&greater_than.unwrap(), \"true\"); 493 | ``` 494 | "] 495 | gt(a: ref Value, b: ref Value) -> Result { 496 | let ret = match cmp(a, b) { 497 | None => return Err(FuncError::Generic(format!("unable to compare {} and {}", a, b))), 498 | Some(Ordering::Greater) => true, 499 | _ => false, 500 | }; 501 | Ok(Value::from(ret)) 502 | }); 503 | 504 | gn!( 505 | #[doc=" 506 | Returns the boolean truth of arg1 >= arg2 507 | 508 | # Example 509 | ``` 510 | use gtmpl::template; 511 | let greater_or_equal = template(\"{{ ge 1.4 1.3 }}\", 1.2); 512 | assert_eq!(greater_or_equal.unwrap(), \"true\"); 513 | 514 | let greater_or_equal = template(\"{{ ge 1.4 . }}\", 0.2); 515 | assert_eq!(&greater_or_equal.unwrap(), \"true\"); 516 | ``` 517 | "] 518 | ge(a: ref Value, b: ref Value) -> Result { 519 | let ret = match cmp(a, b) { 520 | None => return Err(FuncError::Generic(format!("unable to compare {} and {}", a, b))), 521 | Some(Ordering::Greater) | Some(Ordering::Equal) => true, 522 | _ => false, 523 | }; 524 | Ok(Value::from(ret)) 525 | }); 526 | 527 | fn cmp(left: &Value, right: &Value) -> Option { 528 | match (left, right) { 529 | (&Value::Number(ref l), &Value::Number(ref r)) => { 530 | if let (Some(lf), Some(rf)) = (l.as_f64(), r.as_f64()) { 531 | return lf.partial_cmp(&rf); 532 | } 533 | if let (Some(li), Some(ri)) = (l.as_i64(), r.as_i64()) { 534 | return li.partial_cmp(&ri); 535 | } 536 | if let (Some(lu), Some(ru)) = (l.as_u64(), r.as_u64()) { 537 | return lu.partial_cmp(&ru); 538 | } 539 | None 540 | } 541 | (&Value::Bool(ref l), &Value::Bool(ref r)) => l.partial_cmp(r), 542 | (&Value::String(ref l), &Value::String(ref r)) => l.partial_cmp(r), 543 | (&Value::Array(ref l), &Value::Array(ref r)) => l.len().partial_cmp(&r.len()), 544 | _ => None, 545 | } 546 | } 547 | 548 | #[cfg(test)] 549 | mod tests_mocked { 550 | use super::*; 551 | use std::collections::HashMap; 552 | 553 | #[test] 554 | fn test_macro() { 555 | gtmpl_fn!( 556 | fn f1(i: i64) -> Result { 557 | Ok(i + 1) 558 | } 559 | ); 560 | let vals: Vec = vec![val!(1i64)]; 561 | let ret = f1(&vals); 562 | assert_eq!(ret.unwrap(), Value::from(2i64)); 563 | 564 | gtmpl_fn!( 565 | fn f3(i: i64, j: i64, k: i64) -> Result { 566 | Ok(i + j + k) 567 | } 568 | ); 569 | let vals: Vec = vec![val!(1i64), val!(2i64), val!(3i64)]; 570 | let ret = f3(&vals); 571 | assert_eq!(ret.unwrap(), Value::from(6i64)); 572 | } 573 | 574 | #[test] 575 | fn test_eq() { 576 | let vals: Vec = vec![val!("foo".to_owned()), val!("foo".to_owned())]; 577 | let ret = eq(&vals); 578 | assert_eq!(ret.unwrap(), Value::Bool(true)); 579 | let vals: Vec = vec![val!(1u32), val!(1u32), val!(1i8)]; 580 | let ret = eq(&vals); 581 | assert_eq!(ret.unwrap(), Value::Bool(true)); 582 | let vals: Vec = vec![val!(false), val!(false), val!(false)]; 583 | let ret = eq(&vals); 584 | assert_eq!(ret.unwrap(), Value::Bool(true)); 585 | } 586 | 587 | #[test] 588 | fn test_and() { 589 | let vals: Vec = vec![val!(0i32), val!(1u8)]; 590 | let ret = and(&vals); 591 | assert_eq!(ret.unwrap(), Value::from(0i32)); 592 | 593 | let vals: Vec = vec![val!(1i32), val!(2u8)]; 594 | let ret = and(&vals); 595 | assert_eq!(ret.unwrap(), Value::from(2u8)); 596 | } 597 | 598 | #[test] 599 | fn test_or() { 600 | let vals: Vec = vec![val!(0i32), val!(1u8)]; 601 | let ret = or(&vals); 602 | assert_eq!(ret.unwrap(), Value::from(1u8)); 603 | 604 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 605 | let ret = or(&vals); 606 | assert_eq!(ret.unwrap(), Value::from(0u8)); 607 | } 608 | 609 | #[test] 610 | fn test_ne() { 611 | let vals: Vec = vec![val!(0i32), val!(1u8)]; 612 | let ret = ne(&vals); 613 | assert_eq!(ret.unwrap(), Value::from(true)); 614 | 615 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 616 | let ret = ne(&vals); 617 | assert_eq!(ret.unwrap(), Value::from(false)); 618 | 619 | let vals: Vec = vec![val!("foo"), val!("bar")]; 620 | let ret = ne(&vals); 621 | assert_eq!(ret.unwrap(), Value::from(true)); 622 | 623 | let vals: Vec = vec![val!("foo"), val!("foo")]; 624 | let ret = ne(&vals); 625 | assert_eq!(ret.unwrap(), Value::from(false)); 626 | } 627 | 628 | #[test] 629 | fn test_lt() { 630 | let vals: Vec = vec![val!(-1i32), val!(1u8)]; 631 | let ret = lt(&vals); 632 | assert_eq!(ret.unwrap(), Value::from(true)); 633 | 634 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 635 | let ret = lt(&vals); 636 | assert_eq!(ret.unwrap(), Value::from(false)); 637 | 638 | let vals: Vec = vec![val!(1i32), val!(0u8)]; 639 | let ret = lt(&vals); 640 | assert_eq!(ret.unwrap(), Value::from(false)); 641 | } 642 | 643 | #[test] 644 | fn test_le() { 645 | let vals: Vec = vec![val!(-1i32), val!(1u8)]; 646 | let ret = le(&vals); 647 | assert_eq!(ret.unwrap(), Value::from(true)); 648 | 649 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 650 | let ret = le(&vals); 651 | assert_eq!(ret.unwrap(), Value::from(true)); 652 | 653 | let vals: Vec = vec![val!(1i32), val!(0u8)]; 654 | let ret = le(&vals); 655 | assert_eq!(ret.unwrap(), Value::from(false)); 656 | } 657 | 658 | #[test] 659 | fn test_gt() { 660 | let vals: Vec = vec![val!(-1i32), val!(1u8)]; 661 | let ret = gt(&vals); 662 | assert_eq!(ret.unwrap(), Value::from(false)); 663 | 664 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 665 | let ret = gt(&vals); 666 | assert_eq!(ret.unwrap(), Value::from(false)); 667 | 668 | let vals: Vec = vec![val!(1i32), val!(0u8)]; 669 | let ret = gt(&vals); 670 | assert_eq!(ret.unwrap(), Value::from(true)); 671 | } 672 | 673 | #[test] 674 | fn test_ge() { 675 | let vals: Vec = vec![val!(-1i32), val!(1u8)]; 676 | let ret = ge(&vals); 677 | assert_eq!(ret.unwrap(), Value::from(false)); 678 | 679 | let vals: Vec = vec![val!(0i32), val!(0u8)]; 680 | let ret = ge(&vals); 681 | assert_eq!(ret.unwrap(), Value::from(true)); 682 | 683 | let vals: Vec = vec![val!(1i32), val!(0u8)]; 684 | let ret = ge(&vals); 685 | assert_eq!(ret.unwrap(), Value::from(true)); 686 | } 687 | 688 | #[test] 689 | fn test_print() { 690 | let vals: Vec = vec![val!("foo"), val!(1u8)]; 691 | let ret = print(&vals); 692 | assert_eq!(ret.unwrap(), Value::from("foo1")); 693 | 694 | let vals: Vec = vec![val!("foo"), val!(1u8), val!(2)]; 695 | let ret = print(&vals); 696 | assert_eq!(ret.unwrap(), Value::from("foo1 2")); 697 | 698 | let vals: Vec = vec![val!(true), val!(1), val!("foo"), val!(2)]; 699 | let ret = print(&vals); 700 | assert_eq!(ret.unwrap(), Value::from("true 1foo2")); 701 | } 702 | 703 | #[test] 704 | fn test_println() { 705 | let vals: Vec = vec![val!("foo"), val!(1u8)]; 706 | let ret = println(&vals); 707 | assert_eq!(ret.unwrap(), Value::from("foo 1\n")); 708 | 709 | let vals: Vec = vec![]; 710 | let ret = println(&vals); 711 | assert_eq!(ret.unwrap(), Value::from("\n")); 712 | } 713 | 714 | #[test] 715 | fn test_index() { 716 | let vals: Vec = vec![val!(vec![vec![1, 2], vec![3, 4]]), val!(1), val!(0)]; 717 | let ret = index(&vals); 718 | assert_eq!(ret.unwrap(), Value::from(3)); 719 | 720 | let mut o = HashMap::new(); 721 | o.insert(String::from("foo"), vec![String::from("bar")]); 722 | let col = Value::from(o); 723 | let vals: Vec = vec![col, val!("foo"), val!(0)]; 724 | let ret = index(&vals); 725 | assert_eq!(ret.unwrap(), Value::from("bar")); 726 | 727 | let mut o = HashMap::new(); 728 | o.insert(String::from("foo"), String::from("bar")); 729 | let col = Value::from(o); 730 | let vals: Vec = vec![col, val!("foo2")]; 731 | let ret = index(&vals); 732 | assert_eq!(ret.unwrap(), Value::NoValue); 733 | } 734 | 735 | #[test] 736 | fn test_builtins() { 737 | let vals: Vec = vec![val!("foo".to_owned()), val!("foo".to_owned())]; 738 | let builtin_eq = BUILTINS 739 | .iter() 740 | .find(|&&(n, _)| n == "eq") 741 | .map(|&(_, f)| f) 742 | .unwrap(); 743 | let ret = builtin_eq(&vals); 744 | assert_eq!(ret.unwrap(), Value::Bool(true)); 745 | } 746 | 747 | #[test] 748 | fn test_gtmpl_fn() { 749 | gtmpl_fn!( 750 | fn add(a: u64, b: u64) -> Result { 751 | Ok(a + b) 752 | } 753 | ); 754 | let vals: Vec = vec![val!(1u32), val!(2u32)]; 755 | let ret = add(&vals); 756 | assert_eq!(ret.unwrap(), Value::from(3u32)); 757 | 758 | gtmpl_fn!( 759 | fn has_prefix(s: String, prefix: String) -> Result { 760 | Ok(s.starts_with(&prefix)) 761 | } 762 | ); 763 | let vals: Vec = vec![val!("foobar"), val!("foo")]; 764 | let ret = has_prefix(&vals); 765 | assert_eq!(ret.unwrap(), Value::from(true)); 766 | } 767 | } 768 | -------------------------------------------------------------------------------- /src/exec.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::io::Write; 3 | 4 | use crate::error::ExecError; 5 | use crate::node::*; 6 | use crate::template::Template; 7 | use crate::utils::is_true; 8 | 9 | use gtmpl_value::{Func, Value}; 10 | 11 | const MAX_TEMPLATE_DEPTH: usize = 100_000; 12 | 13 | struct Variable { 14 | name: String, 15 | value: Value, 16 | } 17 | 18 | struct State<'a, 'b, T: Write> { 19 | template: &'a Template, 20 | writer: &'b mut T, 21 | node: Option<&'a Nodes>, 22 | vars: VecDeque>, 23 | depth: usize, 24 | } 25 | 26 | /// A Context for the template. Passed to the template exectution. 27 | pub struct Context { 28 | dot: Value, 29 | } 30 | 31 | impl Context { 32 | pub fn empty() -> Context { 33 | Context { dot: Value::Nil } 34 | } 35 | 36 | pub fn from(value: T) -> Context 37 | where 38 | T: Into, 39 | { 40 | let serialized: Value = value.into(); 41 | Context { dot: serialized } 42 | } 43 | } 44 | 45 | impl<'b> Template { 46 | pub fn execute(&self, writer: &'b mut T, data: &Context) -> Result<(), ExecError> { 47 | let mut vars: VecDeque> = VecDeque::new(); 48 | let mut dot = VecDeque::new(); 49 | dot.push_back(Variable { 50 | name: "$".to_owned(), 51 | value: data.dot.clone(), 52 | }); 53 | vars.push_back(dot); 54 | 55 | let mut state = State { 56 | template: self, 57 | writer, 58 | node: None, 59 | vars, 60 | depth: 0, 61 | }; 62 | 63 | let root = self 64 | .tree_set 65 | .get(&self.name) 66 | .and_then(|tree| tree.root.as_ref()) 67 | .ok_or_else(|| ExecError::IncompleteTemplate(self.name.clone()))?; 68 | state.walk(data, root)?; 69 | 70 | Ok(()) 71 | } 72 | 73 | pub fn render(&self, data: &Context) -> Result { 74 | let mut w: Vec = vec![]; 75 | self.execute(&mut w, data)?; 76 | String::from_utf8(w).map_err(ExecError::Utf8ConversionFailed) 77 | } 78 | } 79 | 80 | impl<'a, 'b, T: Write> State<'a, 'b, T> { 81 | fn set_kth_last_var_value(&mut self, k: usize, value: Value) -> Result<(), ExecError> { 82 | if let Some(last_vars) = self.vars.back_mut() { 83 | let i = last_vars.len() - k; 84 | if let Some(kth_last_var) = last_vars.get_mut(i) { 85 | kth_last_var.value = value; 86 | return Ok(()); 87 | } 88 | return Err(ExecError::VarContextToSmall(k)); 89 | } 90 | Err(ExecError::EmptyStack) 91 | } 92 | 93 | fn var_value(&self, key: &str) -> Result { 94 | for context in self.vars.iter().rev() { 95 | for var in context.iter().rev() { 96 | if var.name == key { 97 | return Ok(var.value.clone()); 98 | } 99 | } 100 | } 101 | Err(ExecError::VariableNotFound(key.to_string())) 102 | } 103 | 104 | fn walk_list(&mut self, ctx: &Context, node: &'a ListNode) -> Result<(), ExecError> { 105 | for n in &node.nodes { 106 | self.walk(ctx, n)?; 107 | } 108 | Ok(()) 109 | } 110 | 111 | // Top level walk function. Steps through the major parts for the template strcuture and 112 | // writes to the output. 113 | fn walk(&mut self, ctx: &Context, node: &'a Nodes) -> Result<(), ExecError> { 114 | self.node = Some(node); 115 | match *node { 116 | Nodes::Action(ref n) => { 117 | let val = self.eval_pipeline(ctx, &n.pipe)?; 118 | if n.pipe.decl.is_empty() { 119 | self.print_value(&val)?; 120 | } 121 | Ok(()) 122 | } 123 | Nodes::If(_) | Nodes::With(_) => self.walk_if_or_with(node, ctx), 124 | Nodes::Range(ref n) => self.walk_range(ctx, n), 125 | Nodes::List(ref n) => self.walk_list(ctx, n), 126 | Nodes::Text(ref n) => write!(self.writer, "{}", n).map_err(ExecError::IOError), 127 | Nodes::Template(ref n) => self.walk_template(ctx, n), 128 | _ => Err(ExecError::UnknownNode(node.clone())), 129 | } 130 | } 131 | 132 | fn walk_template(&mut self, ctx: &Context, template: &TemplateNode) -> Result<(), ExecError> { 133 | let name = match template.name { 134 | PipeOrString::String(ref name) => name.to_owned(), 135 | PipeOrString::Pipe(ref pipe) => { 136 | if let Value::String(s) = self.eval_pipeline(ctx, pipe)? { 137 | s 138 | } else { 139 | return Err(ExecError::PipelineMustYieldString); 140 | } 141 | } 142 | }; 143 | if self.depth >= MAX_TEMPLATE_DEPTH { 144 | return Err(ExecError::MaxTemplateDepth); 145 | } 146 | let tree = self.template.tree_set.get(&name); 147 | if let Some(tree) = tree { 148 | if let Some(ref root) = tree.root { 149 | let mut vars = VecDeque::new(); 150 | let mut dot = VecDeque::new(); 151 | let value = if let Some(ref pipe) = template.pipe { 152 | self.eval_pipeline(ctx, pipe)? 153 | } else { 154 | Value::NoValue 155 | }; 156 | dot.push_back(Variable { 157 | name: "$".to_owned(), 158 | value: value.clone(), 159 | }); 160 | vars.push_back(dot); 161 | let mut new_state = State { 162 | template: self.template, 163 | writer: self.writer, 164 | node: None, 165 | vars, 166 | depth: self.depth + 1, 167 | }; 168 | return new_state.walk(&Context::from(value), root); 169 | } 170 | } 171 | Err(ExecError::TemplateNotDefined(name)) 172 | } 173 | 174 | fn eval_pipeline(&mut self, ctx: &Context, pipe: &PipeNode) -> Result { 175 | let mut val: Option = None; 176 | for cmd in &pipe.cmds { 177 | val = Some(self.eval_command(ctx, cmd, &val)?); 178 | // TODO 179 | } 180 | let val = val.ok_or_else(|| ExecError::ErrorEvaluatingPipe(pipe.clone()))?; 181 | for var in &pipe.decl { 182 | self.vars 183 | .back_mut() 184 | .map(|v| { 185 | v.push_back(Variable { 186 | name: var.ident[0].clone(), 187 | value: val.clone(), 188 | }) 189 | }) 190 | .ok_or(ExecError::EmptyStack)?; 191 | } 192 | Ok(val) 193 | } 194 | 195 | fn eval_command( 196 | &mut self, 197 | ctx: &Context, 198 | cmd: &CommandNode, 199 | val: &Option, 200 | ) -> Result { 201 | let first_word = &cmd 202 | .args 203 | .first() 204 | .ok_or_else(|| ExecError::NoArgsForCommandNode(cmd.clone()))?; 205 | 206 | match *(*first_word) { 207 | Nodes::Field(ref n) => return self.eval_field_node(ctx, n, &cmd.args, val), 208 | Nodes::Variable(ref n) => return self.eval_variable_node(n, &cmd.args, val), 209 | Nodes::Pipe(ref n) => return self.eval_pipeline(ctx, n), 210 | Nodes::Chain(ref n) => return self.eval_chain_node(ctx, n, &cmd.args, val), 211 | Nodes::Identifier(ref n) => return self.eval_function(ctx, n, &cmd.args, val), 212 | _ => {} 213 | } 214 | not_a_function(&cmd.args, val)?; 215 | match *(*first_word) { 216 | Nodes::Bool(ref n) => Ok(n.value.clone()), 217 | Nodes::Dot(_) => Ok(ctx.dot.clone()), 218 | Nodes::Number(ref n) => Ok(n.value.clone()), 219 | Nodes::String(ref n) => Ok(n.value.clone()), 220 | _ => Err(ExecError::CannotEvaluateCommand((*first_word).clone())), 221 | } 222 | } 223 | 224 | fn eval_function( 225 | &mut self, 226 | ctx: &Context, 227 | ident: &IdentifierNode, 228 | args: &[Nodes], 229 | fin: &Option, 230 | ) -> Result { 231 | let name = &ident.ident; 232 | let function = self 233 | .template 234 | .funcs 235 | .get(name.as_str()) 236 | .ok_or_else(|| ExecError::UndefinedFunction(name.to_string()))?; 237 | self.eval_call(ctx, *function, args, fin) 238 | } 239 | 240 | fn eval_call( 241 | &mut self, 242 | ctx: &Context, 243 | function: Func, 244 | args: &[Nodes], 245 | fin: &Option, 246 | ) -> Result { 247 | let mut arg_vals = vec![]; 248 | if !args.is_empty() { 249 | for arg in &args[1..] { 250 | let val = self.eval_arg(ctx, arg)?; 251 | arg_vals.push(val); 252 | } 253 | } 254 | if let Some(ref f) = *fin { 255 | arg_vals.push(f.clone()); 256 | } 257 | 258 | function(&arg_vals).map_err(Into::into) 259 | } 260 | 261 | fn eval_chain_node( 262 | &mut self, 263 | ctx: &Context, 264 | chain: &ChainNode, 265 | args: &[Nodes], 266 | fin: &Option, 267 | ) -> Result { 268 | if chain.field.is_empty() { 269 | return Err(ExecError::NoFieldsInEvalChainNode); 270 | } 271 | if let Nodes::Nil(_) = *chain.node { 272 | return Err(ExecError::NullInChain(chain.clone())); 273 | } 274 | let pipe = self.eval_arg(ctx, &*chain.node)?; 275 | self.eval_field_chain(&pipe, &chain.field, args, fin) 276 | } 277 | 278 | fn eval_arg(&mut self, ctx: &Context, node: &Nodes) -> Result { 279 | match *node { 280 | Nodes::Dot(_) => Ok(ctx.dot.clone()), 281 | //Nodes::Nil 282 | Nodes::Field(ref n) => self.eval_field_node(ctx, n, &[], &None), // args? 283 | Nodes::Variable(ref n) => self.eval_variable_node(n, &[], &None), 284 | Nodes::Pipe(ref n) => self.eval_pipeline(ctx, n), 285 | // Nodes::Identifier 286 | Nodes::Identifier(ref n) => self.eval_function(ctx, n, &[], &None), 287 | Nodes::Chain(ref n) => self.eval_chain_node(ctx, n, &[], &None), 288 | Nodes::String(ref n) => Ok(n.value.clone()), 289 | Nodes::Bool(ref n) => Ok(n.value.clone()), 290 | Nodes::Number(ref n) => Ok(n.value.clone()), 291 | _ => Err(ExecError::InvalidArgument(node.clone())), 292 | } 293 | } 294 | 295 | fn eval_field_node( 296 | &mut self, 297 | ctx: &Context, 298 | field: &FieldNode, 299 | args: &[Nodes], 300 | fin: &Option, 301 | ) -> Result { 302 | self.eval_field_chain(&ctx.dot, &field.ident, args, fin) 303 | } 304 | 305 | fn eval_field_chain( 306 | &mut self, 307 | receiver: &Value, 308 | ident: &[String], 309 | args: &[Nodes], 310 | fin: &Option, 311 | ) -> Result { 312 | let n = ident.len(); 313 | if n < 1 { 314 | return Err(ExecError::FieldChainWithoutFields); 315 | } 316 | // TODO clean shit up 317 | let mut r: Value = Value::from(0); 318 | for (i, id) in ident.iter().enumerate().take(n - 1) { 319 | r = self.eval_field(if i == 0 { receiver } else { &r }, id, &[], &None)?; 320 | } 321 | self.eval_field(if n == 1 { receiver } else { &r }, &ident[n - 1], args, fin) 322 | } 323 | 324 | fn eval_field( 325 | &mut self, 326 | receiver: &Value, 327 | field_name: &str, 328 | args: &[Nodes], 329 | fin: &Option, 330 | ) -> Result { 331 | let has_args = args.len() > 1 || fin.is_some(); 332 | if has_args { 333 | return Err(ExecError::NotAFunctionButArguments(field_name.to_string())); 334 | } 335 | let ret = match *receiver { 336 | Value::Object(ref o) => o 337 | .get(field_name) 338 | .cloned() 339 | .ok_or_else(|| ExecError::NoFiledFor(field_name.to_string(), receiver.clone())), 340 | Value::Map(ref o) => Ok(o.get(field_name).cloned().unwrap_or(Value::NoValue)), 341 | _ => Err(ExecError::OnlyMapsAndObjectsHaveFields), 342 | }; 343 | if let Ok(Value::Function(ref f)) = ret { 344 | return (f.f)(&[receiver.clone()]).map_err(Into::into); 345 | } 346 | ret 347 | } 348 | 349 | fn eval_variable_node( 350 | &mut self, 351 | variable: &VariableNode, 352 | args: &[Nodes], 353 | fin: &Option, 354 | ) -> Result { 355 | let val = self.var_value(&variable.ident[0])?; 356 | if variable.ident.len() == 1 { 357 | not_a_function(args, fin)?; 358 | return Ok(val); 359 | } 360 | self.eval_field_chain(&val, &variable.ident[1..], args, fin) 361 | } 362 | 363 | // Walks an `if` or `with` node. They behave the same, except that `with` sets dot. 364 | fn walk_if_or_with(&mut self, node: &'a Nodes, ctx: &Context) -> Result<(), ExecError> { 365 | let pipe = match *node { 366 | Nodes::If(ref n) | Nodes::With(ref n) => &n.pipe, 367 | _ => return Err(ExecError::ExpectedIfOrWith(node.clone())), 368 | }; 369 | let val = self.eval_pipeline(ctx, pipe)?; 370 | let truth = is_true(&val); 371 | if truth { 372 | match *node { 373 | Nodes::If(ref n) => self.walk_list(ctx, &n.list)?, 374 | Nodes::With(ref n) => { 375 | let ctx = Context { dot: val }; 376 | self.walk_list(&ctx, &n.list)?; 377 | } 378 | _ => {} 379 | } 380 | } else { 381 | match *node { 382 | Nodes::If(ref n) | Nodes::With(ref n) => { 383 | if let Some(ref otherwise) = n.else_list { 384 | self.walk_list(ctx, otherwise)?; 385 | } 386 | } 387 | _ => {} 388 | } 389 | } 390 | Ok(()) 391 | } 392 | 393 | fn one_iteration( 394 | &mut self, 395 | key: Value, 396 | val: Value, 397 | range: &'a RangeNode, 398 | ) -> Result<(), ExecError> { 399 | if !range.pipe.decl.is_empty() { 400 | self.set_kth_last_var_value(1, val.clone())?; 401 | } 402 | if range.pipe.decl.len() > 1 { 403 | self.set_kth_last_var_value(2, key)?; 404 | } 405 | let vars = VecDeque::new(); 406 | self.vars.push_back(vars); 407 | let ctx = Context { dot: val }; 408 | self.walk_list(&ctx, &range.list)?; 409 | self.vars.pop_back(); 410 | Ok(()) 411 | } 412 | 413 | fn walk_range(&mut self, ctx: &Context, range: &'a RangeNode) -> Result<(), ExecError> { 414 | let val = self.eval_pipeline(ctx, &range.pipe)?; 415 | match val { 416 | Value::Object(ref map) | Value::Map(ref map) => { 417 | for (k, v) in map.clone() { 418 | self.one_iteration(Value::from(k), v, range)?; 419 | } 420 | } 421 | Value::Array(ref vec) => { 422 | for (k, v) in vec.iter().enumerate() { 423 | self.one_iteration(Value::from(k), v.clone(), range)?; 424 | } 425 | } 426 | _ => return Err(ExecError::InvalidRange(val)), 427 | } 428 | if let Some(ref else_list) = range.else_list { 429 | self.walk_list(ctx, else_list)?; 430 | } 431 | Ok(()) 432 | } 433 | 434 | fn print_value(&mut self, val: &Value) -> Result<(), ExecError> { 435 | write!(self.writer, "{}", val).map_err(ExecError::IOError)?; 436 | Ok(()) 437 | } 438 | } 439 | 440 | fn not_a_function(args: &[Nodes], val: &Option) -> Result<(), ExecError> { 441 | if args.len() > 1 || val.is_some() { 442 | return Err(ExecError::ArgumentForNonFunction(args[0].clone())); 443 | } 444 | Ok(()) 445 | } 446 | 447 | #[cfg(test)] 448 | mod tests_mocked { 449 | use super::*; 450 | use anyhow::anyhow; 451 | use gtmpl_derive::Gtmpl; 452 | use gtmpl_value::FuncError; 453 | use std::collections::HashMap; 454 | 455 | #[test] 456 | fn simple_template() { 457 | let data = Context::from(1); 458 | let mut w: Vec = vec![]; 459 | let mut t = Template::default(); 460 | assert!(t.parse(r#"{{ if false }} 2000 {{ end }}"#).is_ok()); 461 | let out = t.execute(&mut w, &data); 462 | assert!(out.is_ok()); 463 | assert_eq!(String::from_utf8(w).unwrap(), ""); 464 | 465 | let data = Context::from(1); 466 | let mut w: Vec = vec![]; 467 | let mut t = Template::default(); 468 | assert!(t.parse(r#"{{ if true }} 2000 {{ end }}"#).is_ok()); 469 | let out = t.execute(&mut w, &data); 470 | assert!(out.is_ok()); 471 | assert_eq!(String::from_utf8(w).unwrap(), " 2000 "); 472 | 473 | let data = Context::from(1); 474 | let mut w: Vec = vec![]; 475 | let mut t = Template::default(); 476 | assert!(t.parse(r#"{{ if true -}} 2000 {{- end }}"#).is_ok()); 477 | let out = t.execute(&mut w, &data); 478 | assert!(out.is_ok()); 479 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 480 | 481 | let data = Context::from(1); 482 | let mut w: Vec = vec![]; 483 | let mut t = Template::default(); 484 | assert!(t 485 | .parse(r#"{{ if false -}} 2000 {{- else -}} 3000 {{- end }}"#) 486 | .is_ok()); 487 | let out = t.execute(&mut w, &data); 488 | assert!(out.is_ok()); 489 | assert_eq!(String::from_utf8(w).unwrap(), "3000"); 490 | } 491 | 492 | #[test] 493 | fn test_dot() { 494 | let data = Context::from(1); 495 | let mut w: Vec = vec![]; 496 | let mut t = Template::default(); 497 | assert!(t 498 | .parse(r#"{{ if . -}} 2000 {{- else -}} 3000 {{- end }}"#) 499 | .is_ok()); 500 | let out = t.execute(&mut w, &data); 501 | assert!(out.is_ok()); 502 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 503 | 504 | let data = Context::from(false); 505 | let mut w: Vec = vec![]; 506 | let mut t = Template::default(); 507 | assert!(t 508 | .parse(r#"{{ if . -}} 2000 {{- else -}} 3000 {{- end }}"#) 509 | .is_ok()); 510 | let out = t.execute(&mut w, &data); 511 | assert!(out.is_ok()); 512 | assert_eq!(String::from_utf8(w).unwrap(), "3000"); 513 | } 514 | 515 | #[test] 516 | fn test_sub() { 517 | let data = Context::from(1u8); 518 | let mut w: Vec = vec![]; 519 | let mut t = Template::default(); 520 | assert!(t.parse(r#"{{.}}"#).is_ok()); 521 | let out = t.execute(&mut w, &data); 522 | assert!(out.is_ok()); 523 | assert_eq!(String::from_utf8(w).unwrap(), "1"); 524 | 525 | #[derive(Gtmpl)] 526 | struct Foo { 527 | foo: u8, 528 | } 529 | let f = Foo { foo: 1 }; 530 | let data = Context::from(f); 531 | let mut w: Vec = vec![]; 532 | let mut t = Template::default(); 533 | assert!(t.parse(r#"{{.foo}}"#).is_ok()); 534 | let out = t.execute(&mut w, &data); 535 | assert!(out.is_ok()); 536 | assert_eq!(String::from_utf8(w).unwrap(), "1"); 537 | } 538 | 539 | #[test] 540 | fn test_novalue() { 541 | #[derive(Gtmpl)] 542 | struct Foo { 543 | foo: u8, 544 | } 545 | let f = Foo { foo: 1 }; 546 | let data = Context::from(f); 547 | let mut w: Vec = vec![]; 548 | let mut t = Template::default(); 549 | assert!(t.parse(r#"{{.foobar}}"#).is_ok()); 550 | let out = t.execute(&mut w, &data); 551 | assert!(out.is_err()); 552 | 553 | let map: HashMap = [("foo".to_owned(), 23u64)].iter().cloned().collect(); 554 | let data = Context::from(map); 555 | let mut w: Vec = vec![]; 556 | let mut t = Template::default(); 557 | assert!(t.parse(r#"{{.foo2}}"#).is_ok()); 558 | let out = t.execute(&mut w, &data); 559 | assert!(out.is_ok()); 560 | assert_eq!(String::from_utf8(w).unwrap(), Value::NoValue.to_string()); 561 | } 562 | 563 | #[test] 564 | fn test_dollar_dot() { 565 | #[derive(Gtmpl, Clone)] 566 | struct Foo { 567 | foo: u8, 568 | } 569 | let data = Context::from(Foo { foo: 1u8 }); 570 | let mut w: Vec = vec![]; 571 | let mut t = Template::default(); 572 | assert!(t.parse(r#"{{$.foo}}"#).is_ok()); 573 | let out = t.execute(&mut w, &data); 574 | assert!(out.is_ok()); 575 | assert_eq!(String::from_utf8(w).unwrap(), "1"); 576 | } 577 | 578 | #[test] 579 | fn test_function_via_dot() { 580 | #[derive(Gtmpl)] 581 | struct Foo { 582 | foo: Func, 583 | } 584 | fn foo(_: &[Value]) -> Result { 585 | Ok(Value::from("foobar")) 586 | } 587 | let data = Context::from(Foo { foo }); 588 | let mut w: Vec = vec![]; 589 | let mut t = Template::default(); 590 | assert!(t.parse(r#"{{.foo}}"#).is_ok()); 591 | let out = t.execute(&mut w, &data); 592 | assert!(out.is_ok()); 593 | assert_eq!(String::from_utf8(w).unwrap(), "foobar"); 594 | 595 | fn plus_one(args: &[Value]) -> Result { 596 | if let Value::Object(ref o) = &args[0] { 597 | if let Some(Value::Number(ref n)) = o.get("num") { 598 | if let Some(i) = n.as_i64() { 599 | return Ok((i + 1).into()); 600 | } 601 | } 602 | } 603 | Err(anyhow!("integer required, got: {:?}", args).into()) 604 | } 605 | 606 | #[derive(Gtmpl)] 607 | struct AddMe { 608 | num: u8, 609 | plus_one: Func, 610 | } 611 | let data = Context::from(AddMe { num: 42, plus_one }); 612 | let mut w: Vec = vec![]; 613 | let mut t = Template::default(); 614 | assert!(t.parse(r#"{{.plus_one}}"#).is_ok()); 615 | let out = t.execute(&mut w, &data); 616 | assert!(out.is_ok()); 617 | assert_eq!(String::from_utf8(w).unwrap(), "43"); 618 | } 619 | 620 | #[test] 621 | fn test_function_ret_map() { 622 | fn map(_: &[Value]) -> Result { 623 | let mut h = HashMap::new(); 624 | h.insert("field".to_owned(), 1); 625 | Ok(h.into()) 626 | } 627 | 628 | let data = Context::empty(); 629 | let mut w: Vec = vec![]; 630 | let mut t = Template::default(); 631 | t.add_func("map", map); 632 | assert!(t.parse(r#"{{map.field}}"#).is_ok()); 633 | let out = t.execute(&mut w, &data); 634 | assert!(out.is_ok()); 635 | assert_eq!(String::from_utf8(w).unwrap(), "1"); 636 | } 637 | 638 | #[test] 639 | fn test_dot_value() { 640 | #[derive(Gtmpl, Clone)] 641 | struct Foo { 642 | foo: u8, 643 | } 644 | #[derive(Gtmpl)] 645 | struct Bar { 646 | bar: Foo, 647 | } 648 | let f = Foo { foo: 1 }; 649 | let data = Context::from(f); 650 | let mut w: Vec = vec![]; 651 | let mut t = Template::default(); 652 | assert!(t 653 | .parse(r#"{{ if .foo -}} 2000 {{- else -}} 3000 {{- end }}"#) 654 | .is_ok()); 655 | let out = t.execute(&mut w, &data); 656 | assert!(out.is_ok()); 657 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 658 | 659 | let f = Foo { foo: 0 }; 660 | let data = Context::from(f); 661 | let mut w: Vec = vec![]; 662 | let mut t = Template::default(); 663 | assert!(t 664 | .parse(r#"{{ if .foo -}} 2000 {{- else -}} 3000 {{- end }}"#) 665 | .is_ok()); 666 | let out = t.execute(&mut w, &data); 667 | assert!(out.is_ok()); 668 | assert_eq!(String::from_utf8(w).unwrap(), "3000"); 669 | 670 | let bar = Bar { 671 | bar: Foo { foo: 1 }, 672 | }; 673 | let data = Context::from(bar); 674 | let mut w: Vec = vec![]; 675 | let mut t = Template::default(); 676 | assert!(t 677 | .parse(r#"{{ if .bar.foo -}} 2000 {{- else -}} 3000 {{- end }}"#) 678 | .is_ok()); 679 | let out = t.execute(&mut w, &data); 680 | assert!(out.is_ok()); 681 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 682 | 683 | let bar = Bar { 684 | bar: Foo { foo: 0 }, 685 | }; 686 | let data = Context::from(bar); 687 | let mut w: Vec = vec![]; 688 | let mut t = Template::default(); 689 | assert!(t 690 | .parse(r#"{{ if .bar.foo -}} 2000 {{- else -}} 3000 {{- end }}"#) 691 | .is_ok()); 692 | let out = t.execute(&mut w, &data); 693 | assert!(out.is_ok()); 694 | assert_eq!(String::from_utf8(w).unwrap(), "3000"); 695 | } 696 | 697 | #[test] 698 | fn test_with() { 699 | #[derive(Gtmpl)] 700 | struct Foo { 701 | foo: u16, 702 | } 703 | let f = Foo { foo: 1000 }; 704 | let data = Context::from(f); 705 | let mut w: Vec = vec![]; 706 | let mut t = Template::default(); 707 | assert!(t 708 | .parse(r#"{{ with .foo -}} {{.}} {{- else -}} 3000 {{- end }}"#) 709 | .is_ok()); 710 | let out = t.execute(&mut w, &data); 711 | assert!(out.is_ok()); 712 | assert_eq!(String::from_utf8(w).unwrap(), "1000"); 713 | } 714 | 715 | fn to_sorted_string(buf: Vec) -> String { 716 | let mut chars: Vec = String::from_utf8(buf).unwrap().chars().collect(); 717 | chars.sort_unstable(); 718 | chars.iter().cloned().collect::() 719 | } 720 | 721 | #[test] 722 | fn test_range() { 723 | let mut map = HashMap::new(); 724 | map.insert("a".to_owned(), 1); 725 | map.insert("b".to_owned(), 2); 726 | let data = Context::from(map); 727 | let mut w: Vec = vec![]; 728 | let mut t = Template::default(); 729 | assert!(t.parse(r#"{{ range . -}} {{.}} {{- end }}"#).is_ok()); 730 | let out = t.execute(&mut w, &data); 731 | assert!(out.is_ok()); 732 | assert_eq!(to_sorted_string(w), "12"); 733 | 734 | let vec = vec!["foo", "bar", "2000"]; 735 | let data = Context::from(vec); 736 | let mut w: Vec = vec![]; 737 | let mut t = Template::default(); 738 | assert!(t.parse(r#"{{ range . -}} {{.}} {{- end }}"#).is_ok()); 739 | let out = t.execute(&mut w, &data); 740 | assert!(out.is_ok()); 741 | assert_eq!(String::from_utf8(w).unwrap(), "foobar2000"); 742 | } 743 | 744 | #[test] 745 | fn test_proper_range() { 746 | let vec = vec!["a".to_string(), "b".to_string()]; 747 | let data = Context::from(vec); 748 | let mut w: Vec = vec![]; 749 | let mut t = Template::default(); 750 | assert!(t 751 | .parse(r#"{{ range $k, $v := . -}} {{ $k }}{{ $v }} {{- end }}"#) 752 | .is_ok()); 753 | let out = t.execute(&mut w, &data); 754 | assert!(out.is_ok()); 755 | assert_eq!(String::from_utf8(w).unwrap(), "0a1b"); 756 | 757 | let mut map = HashMap::new(); 758 | map.insert("a".to_owned(), 1); 759 | map.insert("b".to_owned(), 2); 760 | let data = Context::from(map); 761 | let mut w: Vec = vec![]; 762 | let mut t = Template::default(); 763 | assert!(t 764 | .parse(r#"{{ range $k, $v := . -}} {{ $v }} {{- end }}"#) 765 | .is_ok()); 766 | let out = t.execute(&mut w, &data); 767 | assert!(out.is_ok()); 768 | assert_eq!(to_sorted_string(w), "12"); 769 | 770 | let mut map = HashMap::new(); 771 | map.insert("a".to_owned(), "b"); 772 | map.insert("c".to_owned(), "d"); 773 | let data = Context::from(map); 774 | let mut w: Vec = vec![]; 775 | let mut t = Template::default(); 776 | assert!(t 777 | .parse(r#"{{ range $k, $v := . -}} {{ $k }}{{ $v }} {{- end }}"#) 778 | .is_ok()); 779 | let out = t.execute(&mut w, &data); 780 | assert!(out.is_ok()); 781 | assert_eq!(to_sorted_string(w), "abcd"); 782 | 783 | let mut map = HashMap::new(); 784 | map.insert("a".to_owned(), 1); 785 | map.insert("b".to_owned(), 2); 786 | let data = Context::from(map); 787 | let mut w: Vec = vec![]; 788 | let mut t = Template::default(); 789 | assert!(t 790 | .parse(r#"{{ range $k, $v := . -}} {{ $k }}{{ $v }} {{- end }}"#) 791 | .is_ok()); 792 | let out = t.execute(&mut w, &data); 793 | assert!(out.is_ok()); 794 | assert_eq!(to_sorted_string(w), "12ab"); 795 | 796 | let mut map = HashMap::new(); 797 | map.insert("a".to_owned(), 1); 798 | map.insert("b".to_owned(), 2); 799 | #[derive(Gtmpl)] 800 | struct Foo { 801 | foo: HashMap, 802 | } 803 | let f = Foo { foo: map }; 804 | let data = Context::from(f); 805 | let mut w: Vec = vec![]; 806 | let mut t = Template::default(); 807 | assert!(t 808 | .parse(r#"{{ range $k, $v := .foo -}} {{ $v }} {{- end }}"#) 809 | .is_ok()); 810 | let out = t.execute(&mut w, &data); 811 | assert!(out.is_ok()); 812 | assert_eq!(to_sorted_string(w), "12"); 813 | 814 | let mut map = HashMap::new(); 815 | #[derive(Gtmpl, Clone)] 816 | struct Bar { 817 | bar: i32, 818 | } 819 | map.insert("a".to_owned(), Bar { bar: 1 }); 820 | map.insert("b".to_owned(), Bar { bar: 2 }); 821 | let data = Context::from(map); 822 | let mut w: Vec = vec![]; 823 | let mut t = Template::default(); 824 | assert!(t 825 | .parse(r#"{{ range $k, $v := . -}} {{ $v.bar }} {{- end }}"#) 826 | .is_ok()); 827 | let out = t.execute(&mut w, &data); 828 | assert!(out.is_ok()); 829 | assert_eq!(to_sorted_string(w), "12"); 830 | } 831 | 832 | #[test] 833 | fn test_len() { 834 | let mut w: Vec = vec![]; 835 | let mut t = Template::default(); 836 | assert!(t.parse(r#"my len is {{ len . }}"#).is_ok()); 837 | let data = Context::from(vec![1, 2, 3]); 838 | let out = t.execute(&mut w, &data); 839 | assert!(out.is_ok()); 840 | assert_eq!(String::from_utf8(w).unwrap(), "my len is 3"); 841 | 842 | let mut w: Vec = vec![]; 843 | let mut t = Template::default(); 844 | assert!(t.parse(r#"{{ len . }}"#).is_ok()); 845 | let data = Context::from("hello".to_owned()); 846 | let out = t.execute(&mut w, &data); 847 | assert!(out.is_ok()); 848 | assert_eq!(String::from_utf8(w).unwrap(), "5"); 849 | } 850 | 851 | #[test] 852 | fn test_pipeline_function() { 853 | let mut w: Vec = vec![]; 854 | let mut t = Template::default(); 855 | assert!(t.parse(r#"{{ if ( 1 | eq . ) -}} 2000 {{- end }}"#).is_ok()); 856 | let data = Context::from(1); 857 | let out = t.execute(&mut w, &data); 858 | assert!(out.is_ok()); 859 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 860 | } 861 | 862 | #[test] 863 | fn test_function() { 864 | let mut w: Vec = vec![]; 865 | let mut t = Template::default(); 866 | assert!(t.parse(r#"{{ if eq . . -}} 2000 {{- end }}"#).is_ok()); 867 | let data = Context::from(1); 868 | let out = t.execute(&mut w, &data); 869 | assert!(out.is_ok()); 870 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 871 | } 872 | 873 | #[test] 874 | fn test_eq() { 875 | let mut w: Vec = vec![]; 876 | let mut t = Template::default(); 877 | assert!(t.parse(r#"{{ if eq "a" "a" -}} 2000 {{- end }}"#).is_ok()); 878 | let data = Context::from(1); 879 | let out = t.execute(&mut w, &data); 880 | assert!(out.is_ok()); 881 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 882 | 883 | let mut w: Vec = vec![]; 884 | let mut t = Template::default(); 885 | assert!(t.parse(r#"{{ if eq "a" "b" -}} 2000 {{- end }}"#).is_ok()); 886 | let data = Context::from(1); 887 | let out = t.execute(&mut w, &data); 888 | assert!(out.is_ok()); 889 | assert_eq!(String::from_utf8(w).unwrap(), ""); 890 | 891 | let mut w: Vec = vec![]; 892 | let mut t = Template::default(); 893 | assert!(t.parse(r#"{{ if eq true true -}} 2000 {{- end }}"#).is_ok()); 894 | let data = Context::from(1); 895 | let out = t.execute(&mut w, &data); 896 | assert!(out.is_ok()); 897 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 898 | 899 | let mut w: Vec = vec![]; 900 | let mut t = Template::default(); 901 | assert!(t 902 | .parse(r#"{{ if eq true false -}} 2000 {{- end }}"#) 903 | .is_ok()); 904 | let data = Context::from(1); 905 | let out = t.execute(&mut w, &data); 906 | assert!(out.is_ok()); 907 | assert_eq!(String::from_utf8(w).unwrap(), ""); 908 | 909 | let mut w: Vec = vec![]; 910 | let mut t = Template::default(); 911 | assert!(t 912 | .parse(r#"{{ if eq 23.42 23.42 -}} 2000 {{- end }}"#) 913 | .is_ok()); 914 | let data = Context::from(1); 915 | let out = t.execute(&mut w, &data); 916 | assert!(out.is_ok()); 917 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 918 | 919 | let mut w: Vec = vec![]; 920 | let mut t = Template::default(); 921 | assert!(t.parse(r#"{{ if eq 1 . -}} 2000 {{- end }}"#).is_ok()); 922 | let data = Context::from(1); 923 | let out = t.execute(&mut w, &data); 924 | assert!(out.is_ok()); 925 | assert_eq!(String::from_utf8(w).unwrap(), "2000"); 926 | } 927 | 928 | #[test] 929 | fn test_block() { 930 | let mut w: Vec = vec![]; 931 | let mut t = Template::default(); 932 | assert!(t 933 | .parse(r#"{{ block "foobar" true -}} {{ $ }} {{- end }}"#) 934 | .is_ok()); 935 | let data = Context::from(2000); 936 | let out = t.execute(&mut w, &data); 937 | assert!(out.is_ok()); 938 | assert_eq!(String::from_utf8(w).unwrap(), "true"); 939 | } 940 | 941 | #[test] 942 | fn test_assign_string() { 943 | let mut w: Vec = vec![]; 944 | let mut t = Template::default(); 945 | assert!(t 946 | .parse(r#"{{ with $foo := "bar" }}{{ $foo }}{{ end }}"#) 947 | .is_ok()); 948 | let data = Context::from(1); 949 | let out = t.execute(&mut w, &data); 950 | assert!(out.is_ok()); 951 | assert_eq!(String::from_utf8(w).unwrap(), "bar"); 952 | } 953 | } 954 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet, VecDeque}; 2 | 3 | use crate::error::ParseError; 4 | use crate::lexer::{Item, ItemType, Lexer}; 5 | use crate::node::*; 6 | use crate::utils::*; 7 | 8 | pub struct Parser { 9 | name: String, 10 | pub funcs: HashSet, 11 | lex: Option, 12 | line: usize, 13 | token: VecDeque, 14 | peek_count: usize, 15 | pub tree_set: HashMap, 16 | tree_id: TreeId, 17 | tree: Option, 18 | tree_stack: VecDeque, 19 | max_tree_id: TreeId, 20 | } 21 | 22 | pub struct Tree { 23 | name: String, 24 | id: TreeId, 25 | pub root: Option, 26 | vars: Vec, 27 | } 28 | 29 | impl Parser { 30 | pub fn new(name: String) -> Parser { 31 | Parser { 32 | name, 33 | funcs: HashSet::new(), 34 | lex: None, 35 | line: 0, 36 | token: VecDeque::new(), 37 | peek_count: 0, 38 | tree_set: HashMap::new(), 39 | tree_id: 0, 40 | tree: None, 41 | tree_stack: VecDeque::new(), 42 | max_tree_id: 0, 43 | } 44 | } 45 | } 46 | 47 | impl Tree { 48 | fn new(name: String, id: TreeId) -> Tree { 49 | Tree { 50 | name, 51 | id, 52 | root: None, 53 | vars: vec![], 54 | } 55 | } 56 | 57 | pub fn pop_vars(&mut self, n: usize) { 58 | self.vars.truncate(n); 59 | } 60 | } 61 | 62 | pub fn parse( 63 | name: String, 64 | text: String, 65 | funcs: HashSet, 66 | ) -> Result, ParseError> { 67 | let mut p = Parser::new(name); 68 | p.funcs = funcs; 69 | p.lex = Some(Lexer::new(text)); 70 | p.parse_tree()?; 71 | Ok(p.tree_set) 72 | } 73 | 74 | impl Parser { 75 | fn next_from_lex(&mut self) -> Option { 76 | match self.lex { 77 | Some(ref mut l) => l.next(), 78 | None => None, 79 | } 80 | } 81 | 82 | fn backup(&mut self, t: Item) { 83 | self.token.push_front(t); 84 | self.peek_count += 1; 85 | } 86 | 87 | fn backup2(&mut self, t0: Item, t1: Item) { 88 | self.token.push_front(t1); 89 | self.token.push_front(t0); 90 | self.peek_count += 2; 91 | } 92 | 93 | fn backup3(&mut self, t0: Item, t1: Item, t2: Item) { 94 | self.token.push_front(t2); 95 | self.token.push_front(t1); 96 | self.token.push_front(t0); 97 | self.peek_count += 3; 98 | } 99 | 100 | fn next_must(&mut self, context: &str) -> Result { 101 | self.next() 102 | .ok_or_else(|| self.error_msg(&format!("unexpected end in {}", context))) 103 | } 104 | 105 | fn next_non_space(&mut self) -> Option { 106 | self.find(|c| c.typ != ItemType::ItemSpace) 107 | } 108 | 109 | fn next_non_space_must(&mut self, context: &str) -> Result { 110 | self.next_non_space() 111 | .ok_or_else(|| self.unexpected("end", context)) 112 | } 113 | 114 | fn peek_non_space_must(&mut self, context: &str) -> Result<&Item, ParseError> { 115 | if let Some(t) = self.next_non_space() { 116 | self.backup(t); 117 | return Ok(self.token.front().unwrap()); 118 | } 119 | self.error(&format!("unexpected end in {}", context)) 120 | } 121 | 122 | fn peek(&mut self) -> Option<&Item> { 123 | if let Some(t) = self.next() { 124 | self.backup(t); 125 | return self.token.front(); 126 | } 127 | None 128 | } 129 | 130 | fn peek_must(&mut self, context: &str) -> Result<&Item, ParseError> { 131 | if let Some(t) = self.next_non_space() { 132 | self.backup(t); 133 | return Ok(self.token.front().unwrap()); 134 | } 135 | self.error(&format!("unexpected end in {}", context)) 136 | } 137 | 138 | fn start_parse(&mut self, name: String, id: TreeId) { 139 | if let Some(t) = self.tree.take() { 140 | self.tree_stack.push_back(t); 141 | } 142 | self.tree_id = id; 143 | let t = Tree::new(name, id); 144 | self.tree = Some(t); 145 | } 146 | 147 | fn stop_parse(&mut self) -> Result<(), ParseError> { 148 | self.add_to_tree_set()?; 149 | self.tree = self.tree_stack.pop_back(); 150 | self.tree_id = self.tree.as_ref().map(|t| t.id).unwrap_or(0); 151 | Ok(()) 152 | } 153 | 154 | // top level parser 155 | fn parse_tree(&mut self) -> Result<(), ParseError> { 156 | let name = self.name.clone(); 157 | self.start_parse(name, 1); 158 | self.parse()?; 159 | self.stop_parse()?; 160 | Ok(()) 161 | } 162 | 163 | fn add_tree(&mut self, name: String, t: Tree) { 164 | self.tree_set.insert(name, t); 165 | } 166 | 167 | fn error(&self, msg: &str) -> Result { 168 | Err(self.error_msg(msg)) 169 | } 170 | 171 | fn error_msg(&self, msg: &str) -> ParseError { 172 | let name = if let Some(t) = self.tree.as_ref() { 173 | &t.name 174 | } else { 175 | &self.name 176 | }; 177 | ParseError::with_context(name, self.line, msg) 178 | } 179 | 180 | fn expect(&mut self, expected: &ItemType, context: &str) -> Result { 181 | let token = self.next_non_space_must(context)?; 182 | if token.typ != *expected { 183 | return Err(self.unexpected(&token, context)); 184 | } 185 | Ok(token) 186 | } 187 | 188 | fn unexpected( 189 | &self, 190 | token: impl std::fmt::Display, 191 | context: impl std::fmt::Display, 192 | ) -> ParseError { 193 | self.error_msg(&format!("unexpected {} in {}", token, context)) 194 | } 195 | 196 | fn add_var(&mut self, name: String) -> Result<(), ParseError> { 197 | let mut tree = self.tree.take().ok_or_else(|| self.error_msg("no tree"))?; 198 | tree.vars.push(name); 199 | self.tree = Some(tree); 200 | Ok(()) 201 | } 202 | 203 | fn add_to_tree_set(&mut self) -> Result<(), ParseError> { 204 | let tree = self.tree.take().ok_or_else(|| self.error_msg("no tree"))?; 205 | if let Some(t) = self.tree_set.get(tree.name.as_str()) { 206 | if let Some(ref r) = t.root { 207 | match r.is_empty_tree() { 208 | Err(e) => return Err(e.into()), 209 | Ok(false) => { 210 | let err = 211 | format!("template multiple definitions of template {}", &tree.name); 212 | return self.error(&err); 213 | } 214 | Ok(true) => {} 215 | } 216 | } 217 | } 218 | self.add_tree(tree.name.clone(), tree); 219 | Ok(()) 220 | } 221 | 222 | fn has_func(&self, name: &str) -> bool { 223 | self.funcs.contains(name) 224 | } 225 | 226 | fn parse(&mut self) -> Result<(), ParseError> { 227 | if self.tree.is_none() { 228 | return self.error("no tree"); 229 | } 230 | let id = self.tree_id; 231 | let mut t = match self.next() { 232 | None => return self.error(&format!("unable to peek for tree {}", id)), 233 | Some(t) => t, 234 | }; 235 | if let Some(tree) = self.tree.as_mut() { 236 | tree.root = Some(Nodes::List(ListNode::new(id, t.pos))); 237 | } 238 | while t.typ != ItemType::ItemEOF { 239 | if t.typ == ItemType::ItemLeftDelim { 240 | let nns = self.next_non_space(); 241 | match nns { 242 | Some(ref item) if item.typ == ItemType::ItemDefine => { 243 | self.parse_definition()?; 244 | t = match self.next() { 245 | None => return self.error(&format!("unable to peek for tree {}", id)), 246 | Some(t) => t, 247 | }; 248 | continue; 249 | } 250 | _ => {} 251 | }; 252 | if let Some(t2) = nns { 253 | self.backup2(t, t2); 254 | } else { 255 | self.backup(t); 256 | } 257 | } else { 258 | self.backup(t); 259 | } 260 | let node = match self.text_or_action() { 261 | Ok(Nodes::Else(node)) => return self.error(&format!("unexpected {}", node)), 262 | Ok(Nodes::End(node)) => return self.error(&format!("unexpected {}", node)), 263 | Ok(node) => node, 264 | Err(e) => return Err(e), 265 | }; 266 | self.tree 267 | .as_mut() 268 | .and_then(|tree| { 269 | tree.root.as_mut().and_then(|r| match *r { 270 | Nodes::List(ref mut r) => { 271 | r.append(node); 272 | Some(()) 273 | } 274 | _ => None, 275 | }) 276 | }) 277 | .ok_or_else(|| self.error_msg("invalid root node"))?; 278 | 279 | t = match self.next() { 280 | None => return self.error(&format!("unable to peek for tree {}", id)), 281 | Some(t) => t, 282 | }; 283 | } 284 | self.backup(t); 285 | Ok(()) 286 | } 287 | 288 | fn parse_definition(&mut self) -> Result<(), ParseError> { 289 | let context = "define clause"; 290 | let id = self.tree_id; 291 | let token = self.next_non_space_must(context)?; 292 | let name = self.parse_template_name(&token, context)?; 293 | self.expect(&ItemType::ItemRightDelim, "define end")?; 294 | self.start_parse(name, id + 1); 295 | let (list, end) = self.item_list()?; 296 | if *end.typ() != NodeType::End { 297 | return Err(self.unexpected(&end, context)); 298 | } 299 | if let Some(tree) = self.tree.as_mut() { 300 | tree.root = Some(Nodes::List(list)); 301 | } 302 | self.stop_parse() 303 | } 304 | 305 | fn item_list(&mut self) -> Result<(ListNode, Nodes), ParseError> { 306 | let pos = self.peek_non_space_must("item list")?.pos; 307 | let mut list = ListNode::new(self.tree_id, pos); 308 | while self.peek_non_space_must("item list")?.typ != ItemType::ItemEOF { 309 | let node = self.text_or_action()?; 310 | match *node.typ() { 311 | NodeType::End | NodeType::Else => return Ok((list, node)), 312 | _ => list.append(node), 313 | } 314 | } 315 | self.error("unexpected EOF") 316 | } 317 | 318 | fn text_or_action(&mut self) -> Result { 319 | match self.next_non_space() { 320 | Some(ref item) if item.typ == ItemType::ItemText => Ok(Nodes::Text(TextNode::new( 321 | self.tree_id, 322 | item.pos, 323 | item.val.clone(), 324 | ))), 325 | Some(ref item) if item.typ == ItemType::ItemLeftDelim => self.action(), 326 | Some(ref item) => Err(self.unexpected(item, "input")), 327 | _ => self.error("unexpected end of input"), 328 | } 329 | } 330 | 331 | fn action(&mut self) -> Result { 332 | let token = self.next_non_space_must("action")?; 333 | match token.typ { 334 | ItemType::ItemBlock => return self.block_control(), 335 | ItemType::ItemElse => return self.else_control(), 336 | ItemType::ItemEnd => return self.end_control(), 337 | ItemType::ItemIf => return self.if_control(), 338 | ItemType::ItemRange => return self.range_control(), 339 | ItemType::ItemTemplate => return self.template_control(), 340 | ItemType::ItemWith => return self.with_control(), 341 | _ => {} 342 | } 343 | let pos = token.pos; 344 | self.backup(token); 345 | Ok(Nodes::Action(ActionNode::new( 346 | self.tree_id, 347 | pos, 348 | self.pipeline("command")?, 349 | ))) 350 | } 351 | 352 | fn parse_control( 353 | &mut self, 354 | allow_else_if: bool, 355 | context: &str, 356 | ) -> Result<(Pos, PipeNode, ListNode, Option), ParseError> { 357 | let vars_len = self 358 | .tree 359 | .as_ref() 360 | .map(|t| t.vars.len()) 361 | .ok_or(ParseError::NoTree)?; 362 | let pipe = self.pipeline(context)?; 363 | let (list, next) = self.item_list()?; 364 | let else_list = match *next.typ() { 365 | NodeType::End => None, 366 | NodeType::Else => { 367 | if allow_else_if && self.peek_must("else if")?.typ == ItemType::ItemIf { 368 | self.next_must("else if")?; 369 | let mut else_list = ListNode::new(self.tree_id, next.pos()); 370 | else_list.append(self.if_control()?); 371 | Some(else_list) 372 | } else { 373 | let (else_list, next) = self.item_list()?; 374 | if *next.typ() != NodeType::End { 375 | return self.error(&format!("expected end; found {}", next)); 376 | } 377 | Some(else_list) 378 | } 379 | } 380 | _ => return self.error(&format!("expected end; found {}", next)), 381 | }; 382 | if let Some(t) = self.tree.as_mut() { 383 | t.pop_vars(vars_len); 384 | } 385 | Ok((pipe.pos(), pipe, list, else_list)) 386 | } 387 | 388 | fn if_control(&mut self) -> Result { 389 | let (pos, pipe, list, else_list) = self.parse_control(true, "if")?; 390 | Ok(Nodes::If(IfNode::new_if( 391 | self.tree_id, 392 | pos, 393 | pipe, 394 | list, 395 | else_list, 396 | ))) 397 | } 398 | 399 | fn range_control(&mut self) -> Result { 400 | let (pos, pipe, list, else_list) = self.parse_control(false, "range")?; 401 | Ok(Nodes::Range(RangeNode::new_range( 402 | self.tree_id, 403 | pos, 404 | pipe, 405 | list, 406 | else_list, 407 | ))) 408 | } 409 | 410 | fn with_control(&mut self) -> Result { 411 | let (pos, pipe, list, else_list) = self.parse_control(false, "with")?; 412 | Ok(Nodes::With(WithNode::new_with( 413 | self.tree_id, 414 | pos, 415 | pipe, 416 | list, 417 | else_list, 418 | ))) 419 | } 420 | 421 | fn end_control(&mut self) -> Result { 422 | Ok(Nodes::End(EndNode::new( 423 | self.tree_id, 424 | self.expect(&ItemType::ItemRightDelim, "end")?.pos, 425 | ))) 426 | } 427 | 428 | fn else_control(&mut self) -> Result { 429 | if self.peek_non_space_must("else")?.typ == ItemType::ItemIf { 430 | let peek = self.peek_non_space_must("else")?; 431 | return Ok(Nodes::Else(ElseNode::new(peek.pos, peek.line))); 432 | } 433 | let token = self.expect(&ItemType::ItemRightDelim, "else")?; 434 | Ok(Nodes::Else(ElseNode::new(token.pos, token.line))) 435 | } 436 | 437 | fn block_control(&mut self) -> Result { 438 | let context = "block clause"; 439 | let token = self.next_non_space_must(context)?; 440 | let name = self.parse_template_name(&token, context)?; 441 | let pipe = self.pipeline(context)?; 442 | 443 | self.max_tree_id += 1; 444 | let tree_id = self.max_tree_id; 445 | self.start_parse(name.clone(), tree_id); 446 | let (root, end) = self.item_list()?; 447 | if let Some(tree) = self.tree.as_mut() { 448 | tree.root = Some(Nodes::List(root)); 449 | } 450 | if end.typ() != &NodeType::End { 451 | return self.error(&format!("unexpected {} in {}", end, context)); 452 | } 453 | self.stop_parse()?; 454 | Ok(Nodes::Template(TemplateNode::new( 455 | self.tree_id, 456 | token.pos, 457 | PipeOrString::String(name), 458 | Some(pipe), 459 | ))) 460 | } 461 | 462 | fn template_control(&mut self) -> Result { 463 | let context = "template clause"; 464 | let token = self.next_non_space().ok_or(ParseError::UnexpectedEnd)?; 465 | let name = if let ItemType::ItemLeftParen = token.typ { 466 | #[cfg(feature = "gtmpl_dynamic_template")] 467 | { 468 | let pipe = self.pipeline(context)?; 469 | self.next_must("template name pipeline end")?; 470 | PipeOrString::Pipe(pipe) 471 | } 472 | #[cfg(not(feature = "gtmpl_dynamic_template"))] 473 | return Err(ParseError::NoDynamicTemplate.into()); 474 | } else { 475 | PipeOrString::String(self.parse_template_name(&token, context)?) 476 | }; 477 | let next = self.next_non_space().ok_or(ParseError::UnexpectedEnd)?; 478 | let pipe = if next.typ != ItemType::ItemRightDelim { 479 | self.backup(next); 480 | Some(self.pipeline(context)?) 481 | } else { 482 | None 483 | }; 484 | Ok(Nodes::Template(TemplateNode::new( 485 | self.tree_id, 486 | token.pos, 487 | name, 488 | pipe, 489 | ))) 490 | } 491 | 492 | fn pipeline(&mut self, context: &str) -> Result { 493 | let mut decl = vec![]; 494 | let mut token = self.next_non_space_must("pipeline")?; 495 | let pos = token.pos; 496 | // TODO: test this hard! 497 | if token.typ == ItemType::ItemVariable { 498 | while token.typ == ItemType::ItemVariable { 499 | let token_after_var = self.next_must("variable")?; 500 | let next = if token_after_var.typ == ItemType::ItemSpace { 501 | let next = self.next_non_space_must("variable")?; 502 | if next.typ != ItemType::ItemColonEquals 503 | && !(next.typ == ItemType::ItemChar && next.val == ",") 504 | { 505 | self.backup3(token, token_after_var, next); 506 | break; 507 | } 508 | next 509 | } else { 510 | token_after_var 511 | }; 512 | if next.typ == ItemType::ItemColonEquals 513 | || (next.typ == ItemType::ItemChar && next.val == ",") 514 | { 515 | let variable = VariableNode::new(self.tree_id, token.pos, &token.val); 516 | self.add_var(token.val.clone())?; 517 | decl.push(variable); 518 | if next.typ == ItemType::ItemChar && next.val == "," { 519 | if context == "range" && decl.len() < 2 { 520 | token = self.next_non_space_must("variable")?; 521 | continue; 522 | } 523 | return self.error(&format!("to many decalarations in {}", context)); 524 | } 525 | } else { 526 | self.backup2(token, next); 527 | } 528 | break; 529 | } 530 | } else { 531 | self.backup(token); 532 | } 533 | let mut pipe = PipeNode::new(self.tree_id, pos, decl); 534 | let mut token = self.next_non_space_must("pipeline")?; 535 | loop { 536 | match token.typ { 537 | ItemType::ItemRightDelim | ItemType::ItemRightParen => { 538 | self.check_pipeline(&mut pipe, context)?; 539 | if token.typ == ItemType::ItemRightParen { 540 | self.backup(token); 541 | } 542 | return Ok(pipe); 543 | } 544 | ItemType::ItemBool 545 | | ItemType::ItemCharConstant 546 | | ItemType::ItemDot 547 | | ItemType::ItemField 548 | | ItemType::ItemIdentifier 549 | | ItemType::ItemNumber 550 | | ItemType::ItemNil 551 | | ItemType::ItemRawString 552 | | ItemType::ItemString 553 | | ItemType::ItemVariable 554 | | ItemType::ItemLeftParen => { 555 | self.backup(token); 556 | pipe.append(self.command()?); 557 | } 558 | _ => return Err(self.unexpected(&token, context)), 559 | } 560 | token = self.next_non_space_must("pipeline")?; 561 | } 562 | } 563 | 564 | fn check_pipeline(&mut self, pipe: &mut PipeNode, context: &str) -> Result<(), ParseError> { 565 | if pipe.cmds.is_empty() { 566 | return self.error(&format!("missing value for {}", context)); 567 | } 568 | for (i, c) in pipe.cmds.iter().enumerate().skip(1) { 569 | match c.args.first() { 570 | Some(n) => match *n.typ() { 571 | NodeType::Bool 572 | | NodeType::Dot 573 | | NodeType::Nil 574 | | NodeType::Number 575 | | NodeType::String => { 576 | return self.error(&format!( 577 | "non executable command in pipeline stage {}", 578 | i + 2 579 | )) 580 | } 581 | _ => {} 582 | }, 583 | None => { 584 | return self.error(&format!( 585 | "non executable command in pipeline stage {}", 586 | i + 2 587 | )) 588 | } 589 | } 590 | } 591 | Ok(()) 592 | } 593 | 594 | fn command(&mut self) -> Result { 595 | let mut cmd = CommandNode::new(self.tree_id, self.peek_non_space_must("command")?.pos); 596 | loop { 597 | self.peek_non_space_must("operand")?; 598 | if let Some(operand) = self.operand()? { 599 | cmd.append(operand); 600 | } 601 | let token = self.next_must("command")?; 602 | match token.typ { 603 | ItemType::ItemSpace => continue, 604 | ItemType::ItemError => return self.error(&token.val), 605 | ItemType::ItemRightDelim | ItemType::ItemRightParen => self.backup(token), 606 | ItemType::ItemPipe => {} 607 | _ => return self.error(&format!("unexpected {} in operand", token)), 608 | }; 609 | break; 610 | } 611 | if cmd.args.is_empty() { 612 | return self.error("empty command"); 613 | } 614 | Ok(cmd) 615 | } 616 | 617 | fn operand(&mut self) -> Result, ParseError> { 618 | let node = self.term()?; 619 | match node { 620 | None => Ok(None), 621 | Some(n) => { 622 | let next = self.next_must("operand")?; 623 | if next.typ == ItemType::ItemField { 624 | let typ = n.typ().clone(); 625 | match typ { 626 | NodeType::Bool 627 | | NodeType::String 628 | | NodeType::Number 629 | | NodeType::Nil 630 | | NodeType::Dot => { 631 | return self 632 | .error(&format!("unexpected . after term {}", n.to_string())); 633 | } 634 | _ => {} 635 | }; 636 | let mut chain = ChainNode::new(self.tree_id, next.pos, n); 637 | chain.add(&next.val); 638 | while self 639 | .peek() 640 | .map(|p| p.typ == ItemType::ItemField) 641 | .unwrap_or(false) 642 | { 643 | let field = self.next().unwrap(); 644 | chain.add(&field.val); 645 | } 646 | let n = match typ { 647 | NodeType::Field => Nodes::Field(FieldNode::new( 648 | self.tree_id, 649 | chain.pos(), 650 | &chain.to_string(), 651 | )), 652 | NodeType::Variable => Nodes::Variable(VariableNode::new( 653 | self.tree_id, 654 | chain.pos(), 655 | &chain.to_string(), 656 | )), 657 | _ => Nodes::Chain(chain), 658 | }; 659 | Ok(Some(n)) 660 | } else { 661 | self.backup(next); 662 | Ok(Some(n)) 663 | } 664 | } 665 | } 666 | } 667 | 668 | fn term(&mut self) -> Result, ParseError> { 669 | let token = self.next_non_space_must("token")?; 670 | let node = match token.typ { 671 | ItemType::ItemError => return self.error(&token.val), 672 | ItemType::ItemIdentifier => { 673 | if !self.has_func(&token.val) { 674 | return self.error(&format!("function {} not defined", token.val)); 675 | } 676 | let mut node = IdentifierNode::new(token.val); 677 | node.set_pos(token.pos); 678 | node.set_tree(self.tree_id); 679 | Nodes::Identifier(node) 680 | } 681 | ItemType::ItemDot => Nodes::Dot(DotNode::new(self.tree_id, token.pos)), 682 | ItemType::ItemNil => Nodes::Nil(NilNode::new(self.tree_id, token.pos)), 683 | ItemType::ItemVariable => { 684 | Nodes::Variable(self.use_var(self.tree_id, token.pos, &token.val)?) 685 | } 686 | ItemType::ItemField => { 687 | Nodes::Field(FieldNode::new(self.tree_id, token.pos, &token.val)) 688 | } 689 | ItemType::ItemBool => { 690 | Nodes::Bool(BoolNode::new(self.tree_id, token.pos, token.val == "true")) 691 | } 692 | ItemType::ItemCharConstant | ItemType::ItemNumber => { 693 | match NumberNode::new(self.tree_id, token.pos, token.val, &token.typ) { 694 | Ok(n) => Nodes::Number(n), 695 | Err(e) => return self.error(&e.to_string()), 696 | } 697 | } 698 | ItemType::ItemLeftParen => { 699 | let pipe = self.pipeline("parenthesized pipeline")?; 700 | let next = self.next_must("parenthesized pipeline")?; 701 | if next.typ != ItemType::ItemRightParen { 702 | return self.error(&format!("unclosed right paren: unexpected {}", next)); 703 | } 704 | Nodes::Pipe(pipe) 705 | } 706 | ItemType::ItemString | ItemType::ItemRawString => { 707 | if let Some(s) = unquote_str(&token.val) { 708 | Nodes::String(StringNode::new(self.tree_id, token.pos, token.val, s)) 709 | } else { 710 | return self.error(&format!("unable to unqote string: {}", token.val)); 711 | } 712 | } 713 | 714 | _ => { 715 | self.backup(token); 716 | return Ok(None); 717 | } 718 | }; 719 | Ok(Some(node)) 720 | } 721 | 722 | fn use_var(&self, tree_id: TreeId, pos: Pos, name: &str) -> Result { 723 | if name == "$" { 724 | return Ok(VariableNode::new(tree_id, pos, name)); 725 | } 726 | self.tree 727 | .as_ref() 728 | .and_then(|t| { 729 | t.vars 730 | .iter() 731 | .find(|&v| v == name) 732 | .map(|_| VariableNode::new(tree_id, pos, name)) 733 | }) 734 | .ok_or_else(|| self.error_msg(&format!("undefined variable {}", name))) 735 | } 736 | 737 | fn parse_template_name(&self, token: &Item, context: &str) -> Result { 738 | match token.typ { 739 | ItemType::ItemString | ItemType::ItemRawString => unquote_str(&token.val) 740 | .ok_or_else(|| ParseError::UnableToParseString(token.val.clone())), 741 | _ => Err(self.unexpected(token, context)), 742 | } 743 | } 744 | } 745 | 746 | impl Iterator for Parser { 747 | type Item = Item; 748 | fn next(&mut self) -> Option { 749 | let item = if self.peek_count > 0 { 750 | self.peek_count -= 1; 751 | self.token.pop_front() 752 | } else { 753 | self.next_from_lex() 754 | }; 755 | match item { 756 | Some(item) => { 757 | self.line = item.line; 758 | Some(item) 759 | } 760 | _ => None, 761 | } 762 | } 763 | } 764 | 765 | #[cfg(test)] 766 | mod tests_mocked { 767 | use super::*; 768 | use crate::lexer::ItemType; 769 | use gtmpl_value::Value; 770 | 771 | /* 772 | ItemText 773 | ItemLeftDelim 774 | ItemSpace 775 | ItemIf 776 | ItemSpace 777 | ItemIdentifier 778 | ItemSpace 779 | ItemString 780 | ItemSpace 781 | ItemString 782 | ItemSpace 783 | ItemRightDelim 784 | ItemEOF 785 | */ 786 | 787 | fn make_parser() -> Parser { 788 | let s = r#"something {{ if eq "foo" "bar" }}"#; 789 | make_parser_with(s) 790 | } 791 | 792 | fn make_parser_with(s: &str) -> Parser { 793 | make_parser_with_funcs(s, &[]) 794 | } 795 | 796 | fn make_parser_with_funcs<'a>(s: &str, funcs: &[&'a str]) -> Parser { 797 | let lex = Lexer::new(s.to_owned()); 798 | Parser { 799 | name: String::from("foo"), 800 | funcs: funcs.iter().map(|&k| k.to_owned()).collect(), 801 | lex: Some(lex), 802 | line: 0, 803 | token: VecDeque::new(), 804 | peek_count: 0, 805 | tree_set: HashMap::new(), 806 | tree_id: 0, 807 | tree: None, 808 | tree_stack: VecDeque::new(), 809 | max_tree_id: 0, 810 | } 811 | } 812 | 813 | #[test] 814 | fn test_iter() { 815 | let mut p = make_parser(); 816 | assert_eq!(p.next().map(|n| n.typ), Some(ItemType::ItemText)); 817 | assert_eq!(p.count(), 12); 818 | } 819 | 820 | #[test] 821 | fn test_backup() { 822 | let mut t = make_parser(); 823 | assert_eq!(t.next().map(|n| n.typ), Some(ItemType::ItemText)); 824 | let i = t.next().unwrap(); 825 | let s = i.to_string(); 826 | t.backup(i); 827 | assert_eq!(t.next().map(|n| n.to_string()), Some(s)); 828 | assert_eq!(t.last().map(|n| n.typ), Some(ItemType::ItemEOF)); 829 | } 830 | #[test] 831 | fn test_backup3() { 832 | let mut t = make_parser(); 833 | assert_eq!(t.next().map(|n| n.typ), Some(ItemType::ItemText)); 834 | let t0 = t.next().unwrap(); 835 | let t1 = t.next().unwrap(); 836 | let t2 = t.next().unwrap(); 837 | assert_eq!(t0.typ, ItemType::ItemLeftDelim); 838 | assert_eq!(t1.typ, ItemType::ItemSpace); 839 | assert_eq!(t2.typ, ItemType::ItemIf); 840 | t.backup3(t0, t1, t2); 841 | let t0 = t.next().unwrap(); 842 | let t1 = t.next().unwrap(); 843 | let t2 = t.next().unwrap(); 844 | assert_eq!(t0.typ, ItemType::ItemLeftDelim); 845 | assert_eq!(t1.typ, ItemType::ItemSpace); 846 | assert_eq!(t2.typ, ItemType::ItemIf); 847 | assert_eq!(t.last().map(|n| n.typ), Some(ItemType::ItemEOF)); 848 | } 849 | 850 | #[test] 851 | fn test_next_non_space() { 852 | let mut t = make_parser(); 853 | t.next(); 854 | let i = t.next().unwrap(); 855 | let typ = i.typ; 856 | assert_eq!(typ, ItemType::ItemLeftDelim); 857 | assert_eq!(t.next_non_space().map(|n| n.typ), Some(ItemType::ItemIf)); 858 | assert_eq!(t.last().map(|n| n.typ), Some(ItemType::ItemEOF)); 859 | } 860 | 861 | #[test] 862 | fn test_peek_non_space() { 863 | let mut t = make_parser(); 864 | t.next(); 865 | let i = t.next().unwrap(); 866 | let typ = i.typ; 867 | assert_eq!(typ, ItemType::ItemLeftDelim); 868 | assert_eq!( 869 | t.peek_non_space_must("").map(|n| &n.typ).unwrap(), 870 | &ItemType::ItemIf 871 | ); 872 | assert_eq!(t.next().map(|n| n.typ), Some(ItemType::ItemIf)); 873 | assert_eq!(t.last().map(|n| n.typ), Some(ItemType::ItemEOF)); 874 | } 875 | 876 | #[test] 877 | fn test_display() { 878 | let raw = r#"{{if .}}2000{{else}} 3000 {{end}}"#; 879 | let mut ts = parse(String::default(), String::from(raw), HashSet::default()).unwrap(); 880 | let tree = ts.get_mut("").unwrap(); 881 | if let Some(ref root) = tree.root { 882 | assert_eq!(raw, format!("{}", root)) 883 | } else { 884 | panic!() 885 | } 886 | } 887 | 888 | #[test] 889 | fn parse_basic_tree() { 890 | let mut p = make_parser_with(r#"{{ if eq .foo "bar" }} 2000 {{ end }}"#); 891 | let r = p.parse_tree(); 892 | assert_eq!( 893 | r.err().unwrap().to_string(), 894 | "template: foo:2:function eq not defined" 895 | ); 896 | let funcs = &["eq"]; 897 | let mut p = make_parser_with_funcs(r#"{{ if eq .foo "bar" }} 2000 {{ end }}"#, funcs); 898 | let r = p.parse_tree(); 899 | assert!(r.is_ok()); 900 | let funcs = &["eq"]; 901 | let mut p = make_parser_with_funcs(r#"{{ if eq 1 2 }} 2000 {{ end }}"#, funcs); 902 | let r = p.parse_tree(); 903 | assert!(r.is_ok()); 904 | } 905 | 906 | #[test] 907 | fn test_pipeline_simple() { 908 | let mut p = make_parser_with(r#" $foo, $bar := yay | blub "2000" }}"#); 909 | let pipe = p.pipeline("range"); 910 | // broken for now 911 | assert!(pipe.is_err()); 912 | } 913 | 914 | #[test] 915 | fn test_assign_string() { 916 | let mut p = make_parser_with(r#"{{ with $bar := "foo" }}{{ $bar }}{{ end }}"#); 917 | let r = p.parse_tree(); 918 | assert!(r.is_ok()); 919 | } 920 | 921 | #[test] 922 | fn test_term() { 923 | let mut p = make_parser_with(r#"{{true}}"#); 924 | p.next(); 925 | let t = p.term(); 926 | // broken for now 927 | assert!(t.is_ok()); 928 | let t = t.unwrap(); 929 | assert!(t.is_some()); 930 | let t = t.unwrap(); 931 | assert_eq!(t.typ(), &NodeType::Bool); 932 | if let Nodes::Bool(n) = t { 933 | assert_eq!(n.value, Value::from(true)); 934 | } else { 935 | panic!() 936 | } 937 | 938 | let mut p = make_parser_with(r#"{{ false }}"#); 939 | p.next(); 940 | let t = p.term(); 941 | // broken for now 942 | assert!(t.is_ok()); 943 | let t = t.unwrap(); 944 | assert!(t.is_some()); 945 | let t = t.unwrap(); 946 | assert_eq!(t.typ(), &NodeType::Bool); 947 | assert_eq!(t.typ(), &NodeType::Bool); 948 | if let Nodes::Bool(n) = t { 949 | assert_eq!(n.value, Value::from(false)); 950 | } else { 951 | panic!() 952 | } 953 | } 954 | } 955 | --------------------------------------------------------------------------------