179 lines
5.0 KiB
Rust
179 lines
5.0 KiB
Rust
/*
|
|
* This file is part of espanso.
|
|
*
|
|
* Copyright (C) 2019-2021 Federico Terzi
|
|
*
|
|
* espanso is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* espanso is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use crate::{renderer::RendererError, ExtensionOutput, Params, Scope, Value};
|
|
use anyhow::Result;
|
|
use log::error;
|
|
use regex::Captures;
|
|
|
|
use super::VAR_REGEX;
|
|
use std::collections::HashSet;
|
|
|
|
pub(crate) fn get_body_variable_names(body: &str) -> HashSet<&str> {
|
|
let mut variables = HashSet::new();
|
|
for caps in VAR_REGEX.captures_iter(body) {
|
|
let var_name = caps.name("name").unwrap().as_str();
|
|
variables.insert(var_name);
|
|
}
|
|
variables
|
|
}
|
|
|
|
pub(crate) fn render_variables(body: &str, scope: &Scope) -> Result<String> {
|
|
let mut replacing_error = None;
|
|
let output = VAR_REGEX
|
|
.replace_all(body, |caps: &Captures| {
|
|
let var_name = caps.name("name").unwrap().as_str();
|
|
let var_subname = caps.name("subname");
|
|
match scope.get(var_name) {
|
|
Some(output) => match output {
|
|
ExtensionOutput::Single(output) => output,
|
|
ExtensionOutput::Multiple(results) => match var_subname {
|
|
Some(var_subname) => {
|
|
let var_subname = var_subname.as_str();
|
|
results.get(var_subname).map_or("", |value| &*value)
|
|
}
|
|
None => {
|
|
error!(
|
|
"nested name missing from multi-value variable: {}",
|
|
var_name
|
|
);
|
|
replacing_error = Some(RendererError::MissingVariable(format!(
|
|
"nested name missing from multi-value variable: {}",
|
|
var_name
|
|
)));
|
|
""
|
|
}
|
|
},
|
|
},
|
|
None => {
|
|
replacing_error = Some(RendererError::MissingVariable(format!(
|
|
"variable '{}' is missing",
|
|
var_name
|
|
)));
|
|
""
|
|
}
|
|
}
|
|
})
|
|
.to_string();
|
|
|
|
if let Some(error) = replacing_error {
|
|
return Err(error.into());
|
|
}
|
|
|
|
Ok(output)
|
|
}
|
|
|
|
pub(crate) fn inject_variables_into_params(params: &Params, scope: &Scope) -> Result<Params> {
|
|
let mut params = params.clone();
|
|
|
|
for (_, value) in params.iter_mut() {
|
|
inject_variables_into_value(value, scope)?;
|
|
}
|
|
|
|
Ok(params)
|
|
}
|
|
|
|
fn inject_variables_into_value(value: &mut Value, scope: &Scope) -> Result<()> {
|
|
match value {
|
|
Value::String(s_value) => {
|
|
let new_value = render_variables(s_value, scope)?;
|
|
|
|
if &new_value != s_value {
|
|
s_value.clear();
|
|
s_value.push_str(&new_value);
|
|
}
|
|
}
|
|
Value::Array(values) => {
|
|
for value in values {
|
|
inject_variables_into_value(value, scope)?;
|
|
}
|
|
}
|
|
Value::Object(fields) => {
|
|
for value in fields.values_mut() {
|
|
inject_variables_into_value(value, scope)?;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::{collections::HashMap, iter::FromIterator};
|
|
|
|
#[test]
|
|
fn get_body_variable_names_no_vars() {
|
|
assert_eq!(
|
|
get_body_variable_names("no variables"),
|
|
HashSet::from_iter(vec![]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn get_body_variable_names_multiple_vars() {
|
|
assert_eq!(
|
|
get_body_variable_names("hello {{world}} name {{greet}}"),
|
|
HashSet::from_iter(vec!["world", "greet"]),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_inject_variables_into_params() {
|
|
let mut params = Params::new();
|
|
params.insert(
|
|
"field1".to_string(),
|
|
Value::String("this contains {{first}}".to_string()),
|
|
);
|
|
params.insert("field2".to_string(), Value::Bool(true));
|
|
params.insert(
|
|
"field3".to_string(),
|
|
Value::Array(vec![Value::String("this contains {{first}}".to_string())]),
|
|
);
|
|
|
|
let mut nested = HashMap::new();
|
|
nested.insert(
|
|
"subfield1".to_string(),
|
|
Value::String("also contains {{first}}".to_string()),
|
|
);
|
|
params.insert("field4".to_string(), Value::Object(nested));
|
|
|
|
let mut scope = Scope::new();
|
|
scope.insert("first", ExtensionOutput::Single("one".to_string()));
|
|
|
|
let result = inject_variables_into_params(¶ms, &scope).unwrap();
|
|
|
|
assert_eq!(result.len(), 4);
|
|
assert_eq!(
|
|
result.get("field1").unwrap(),
|
|
&Value::String("this contains one".to_string())
|
|
);
|
|
assert_eq!(result.get("field2").unwrap(), &Value::Bool(true));
|
|
assert_eq!(
|
|
result.get("field3").unwrap(),
|
|
&Value::Array(vec![Value::String("this contains one".to_string())])
|
|
);
|
|
assert!(
|
|
matches!(result.get("field4").unwrap(), Value::Object(fields) if fields.get("subfield1").unwrap() == &Value::String("also contains one".to_string()))
|
|
);
|
|
}
|
|
}
|