diff --git a/src/lib.rs b/src/lib.rs index be065c1..677af57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -370,4 +370,19 @@ mod test { r" ^^^", "\n", )); } + + #[test] + fn test_vec_map() { + let test_vec = vec!["xxxx", "foo", "bar"]; + let test_slice = &test_vec[..]; + let_assert!(Err(e) = substitute("${99}", &test_slice)); + assert!(e.to_string() == "No such variable: $99"); + assert_eq!(Ok("foo bar"), substitute("${*}", &test_slice).as_deref()); + assert_eq!(Ok("xxxx"), substitute("${0}", &test_slice).as_deref()); + assert_eq!(Ok("foo"), substitute("${1}", &test_slice).as_deref()); + assert_eq!(Ok("bar"), substitute("${2}", &test_slice).as_deref()); + assert_eq!(Ok("foo bar"), substitute("$*", &test_slice).as_deref()); + assert_eq!(Ok("foo"), substitute("$1", &test_slice).as_deref()); + assert_eq!(Ok("bar"), substitute("$2", &test_slice).as_deref()); + } } diff --git a/src/map.rs b/src/map.rs index da2d090..7c8d30c 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; use std::hash::BuildHasher; @@ -101,3 +102,27 @@ impl<'a, V: 'a, S: BuildHasher> VariableMap<'a> for HashMap { self.get(key) } } + +impl<'a, T: AsRef> VariableMap<'a> for &[T] { + type Value = Cow<'a, str>; + + #[inline] + fn get(&'a self, key: &str) -> Option { + if key == "*" { + let mut s = String::new(); + for (n, val) in self.iter().skip(1).enumerate() { + if n > 0 { + s.push(' '); + } + s.push_str(val.as_ref()); + } + Some(Cow::from(s)) + } else { + str::parse::(key) + .ok() + .filter(|index| *index < self.len()) + .map(|index| &self[index]) + .map(|s| Cow::from(s.as_ref())) + } + } +} diff --git a/src/template/raw/parse.rs b/src/template/raw/parse.rs index bad1a91..a23e157 100644 --- a/src/template/raw/parse.rs +++ b/src/template/raw/parse.rs @@ -60,6 +60,13 @@ impl Variable { } if source[finger + 1] == b'{' { Self::parse_braced(source, finger) + } else if source[finger + 1] == b'*' { + let name_end = finger + 2; + let variable = Variable { + name: finger + 1..name_end, + default: None, + }; + Ok((variable, name_end)) } else { let name_end = match source[finger + 1..] .iter() @@ -101,7 +108,7 @@ impl Variable { // Get the first sequence of alphanumeric characters and underscores for the variable name. let name_end = match source[name_start..] .iter() - .position(|&c| !c.is_ascii_alphanumeric() && c != b'_') + .position(|&c| !c.is_ascii_alphanumeric() && c != b'_' && c != b'*') { Some(0) => { return Err(error::MissingVariableName { @@ -119,6 +126,14 @@ impl Variable { return Err(error::MissingClosingBrace { position: finger + 1 }.into()); } + if name_end - name_start > 1 && source[name_start..name_end].contains(&b'*') { + return Err(error::MissingVariableName { + position: finger, + len: name_end - name_start, + } + .into()); + } + // If there is a closing brace after the name, there is no default value and we're done. if source[name_end] == b'}' { let variable = Variable { @@ -140,8 +155,8 @@ impl Variable { } // If there is no matching un-escaped closing brace, it's missing. - let end = finger + find_closing_brace(&source[finger..]) - .ok_or(error::MissingClosingBrace { position: finger + 1 })?; + let end = finger + + find_closing_brace(&source[finger..]).ok_or(error::MissingClosingBrace { position: finger + 1 })?; let variable = Variable { name: name_start..name_end,