Refactor extensions to allow them to stop the expansion process. Fix #475

This commit is contained in:
Federico Terzi 2020-11-14 21:58:54 +01:00
parent c5c2a4ab90
commit cc72f10398
11 changed files with 88 additions and 59 deletions

View File

@ -22,6 +22,8 @@ use crate::extension::ExtensionResult;
use serde_yaml::Mapping;
use std::collections::HashMap;
use super::ExtensionOut;
pub struct ClipboardExtension {
clipboard_manager: Box<dyn ClipboardManager>,
}
@ -42,11 +44,11 @@ impl super::Extension for ClipboardExtension {
_: &Mapping,
_: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> ExtensionOut {
if let Some(clipboard) = self.clipboard_manager.get_clipboard() {
Some(ExtensionResult::Single(clipboard))
Ok(Some(ExtensionResult::Single(clipboard)))
} else {
None
Ok(None)
}
}
}

View File

@ -22,6 +22,8 @@ use chrono::{DateTime, Duration, Local};
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
use super::ExtensionOut;
pub struct DateExtension {}
impl DateExtension {
@ -40,7 +42,7 @@ impl super::Extension for DateExtension {
params: &Mapping,
_: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> ExtensionOut {
let mut now: DateTime<Local> = Local::now();
// Compute the given offset
@ -59,6 +61,6 @@ impl super::Extension for DateExtension {
now.to_rfc2822()
};
Some(ExtensionResult::Single(date))
Ok(Some(ExtensionResult::Single(date)))
}
}

View File

@ -43,15 +43,15 @@ impl super::Extension for DummyExtension {
params: &Mapping,
_: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let echo = params.get(&Value::from("echo"));
if let Some(echo) = echo {
Some(ExtensionResult::Single(
Ok(Some(ExtensionResult::Single(
echo.as_str().unwrap_or_default().to_owned(),
))
)))
} else {
None
Ok(None)
}
}
}

View File

@ -43,13 +43,13 @@ impl super::Extension for FormExtension {
params: &Mapping,
_: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let layout = params.get(&Value::from("layout"));
let layout = if let Some(value) = layout {
value.as_str().unwrap_or_default().to_string()
} else {
error!("invoking form extension without specifying a layout");
return None;
return Err(super::ExtensionError::Internal);
};
let mut form_config = Mapping::new();
@ -81,16 +81,22 @@ impl super::Extension for FormExtension {
let json: Result<HashMap<String, String>, _> = serde_json::from_str(&output);
match json {
Ok(json) => {
return Some(ExtensionResult::Multiple(json));
// Check if the JSON is empty. In those cases, it means the user exited
// the form before submitting it, therefore the expansion should stop
if json.is_empty() {
return Err(super::ExtensionError::Aborted);
}
return Ok(Some(ExtensionResult::Multiple(json)));
}
Err(error) => {
error!("modulo json parsing error: {}", error);
return None;
return Err(super::ExtensionError::Internal);
}
}
} else {
error!("modulo form didn't return any output");
return None;
return Err(super::ExtensionError::Internal);
}
}
}

View File

@ -38,6 +38,17 @@ pub enum ExtensionResult {
Multiple(HashMap<String, String>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExtensionError {
// Returned by an extension if an internal process occurred
Internal,
// Returned by an extension if the user aborted the expansion
// for example when pressing ESC inside a FormExtension.
Aborted,
}
pub type ExtensionOut = Result<Option<ExtensionResult>, ExtensionError>;
pub trait Extension {
fn name(&self) -> String;
fn calculate(
@ -45,7 +56,7 @@ pub trait Extension {
params: &Mapping,
args: &Vec<String>,
current_vars: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult>;
) -> ExtensionOut;
}
pub fn get_extensions(

View File

@ -39,7 +39,7 @@ impl super::Extension for MultiEchoExtension {
params: &Mapping,
_: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let mut output: HashMap<String, String> = HashMap::new();
for (key, value) in params.iter() {
if let Some(key) = key.as_str() {
@ -48,6 +48,6 @@ impl super::Extension for MultiEchoExtension {
}
}
}
Some(ExtensionResult::Multiple(output))
Ok(Some(ExtensionResult::Multiple(output)))
}
}

View File

@ -41,11 +41,11 @@ impl super::Extension for RandomExtension {
params: &Mapping,
args: &Vec<String>,
_: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let choices = params.get(&Value::from("choices"));
if choices.is_none() {
warn!("No 'choices' parameter specified for random variable");
return None;
return Ok(None);
}
let choices = choices.unwrap().as_sequence();
if let Some(choices) = choices {
@ -62,17 +62,17 @@ impl super::Extension for RandomExtension {
// Render arguments
let output = crate::render::utils::render_args(output, args);
return Some(ExtensionResult::Single(output));
return Ok(Some(ExtensionResult::Single(output)));
}
None => {
error!("Could not select a random choice.");
return None;
return Err(super::ExtensionError::Internal)
}
}
}
error!("choices array have an invalid format '{:?}'", choices);
None
Err(super::ExtensionError::Internal)
}
}
@ -88,7 +88,7 @@ mod tests {
params.insert(Value::from("choices"), Value::from(choices.clone()));
let extension = RandomExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
@ -106,7 +106,7 @@ mod tests {
params.insert(Value::from("choices"), Value::from(choices.clone()));
let extension = RandomExtension::new();
let output = extension.calculate(&params, &vec!["test".to_owned()], &HashMap::new());
let output = extension.calculate(&params, &vec!["test".to_owned()], &HashMap::new()).unwrap();
assert!(output.is_some());

View File

@ -42,11 +42,11 @@ impl super::Extension for ScriptExtension {
params: &Mapping,
user_args: &Vec<String>,
vars: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let args = params.get(&Value::from("args"));
if args.is_none() {
warn!("No 'args' parameter specified for script variable");
return None;
return Err(super::ExtensionError::Internal);
}
let args = args.unwrap().as_sequence();
if let Some(args) = args {
@ -145,17 +145,17 @@ impl super::Extension for ScriptExtension {
output_str = output_str.trim().to_owned()
}
return Some(ExtensionResult::Single(output_str));
return Ok(Some(ExtensionResult::Single(output_str)));
}
Err(e) => {
error!("Could not execute script '{:?}', error: {}", args, e);
return None;
return Err(super::ExtensionError::Internal);
}
}
}
error!("Could not execute script with args '{:?}'", args);
None
Err(super::ExtensionError::Internal)
}
}

View File

@ -164,11 +164,11 @@ impl super::Extension for ShellExtension {
params: &Mapping,
args: &Vec<String>,
vars: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let cmd = params.get(&Value::from("cmd"));
if cmd.is_none() {
warn!("No 'cmd' parameter specified for shell variable");
return None;
return Err(super::ExtensionError::Internal);
}
let inject_args = params
@ -186,7 +186,7 @@ impl super::Extension for ShellExtension {
if shell.is_none() {
error!("Invalid shell parameter, please select a valid one.");
return None;
return Err(super::ExtensionError::Internal);
}
shell.unwrap()
@ -257,11 +257,11 @@ impl super::Extension for ShellExtension {
output_str = output_str.trim().to_owned()
}
Some(ExtensionResult::Single(output_str))
Ok(Some(ExtensionResult::Single(output_str)))
}
Err(e) => {
error!("Could not execute cmd '{}', error: {}", cmd, e);
None
Err(super::ExtensionError::Internal)
}
}
}
@ -279,7 +279,7 @@ mod tests {
params.insert(Value::from("trim"), Value::from(false));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
@ -302,7 +302,7 @@ mod tests {
params.insert(Value::from("cmd"), Value::from("echo \"hello world\""));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
assert_eq!(
@ -320,7 +320,7 @@ mod tests {
);
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
assert_eq!(
@ -336,7 +336,7 @@ mod tests {
params.insert(Value::from("trim"), Value::from("error"));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
assert_eq!(
@ -353,7 +353,7 @@ mod tests {
params.insert(Value::from("trim"), Value::from(true));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![], &HashMap::new());
let output = extension.calculate(&params, &vec![], &HashMap::new()).unwrap();
assert!(output.is_some());
assert_eq!(
@ -370,7 +370,7 @@ mod tests {
params.insert(Value::from("inject_args"), Value::from(true));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new());
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new()).unwrap();
assert!(output.is_some());
@ -384,7 +384,7 @@ mod tests {
params.insert(Value::from("cmd"), Value::from("echo 'hey friend' | awk '{ print $2 }'"));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new());
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new()).unwrap();
assert!(output.is_some());
@ -399,7 +399,7 @@ mod tests {
params.insert(Value::from("inject_args"), Value::from(true));
let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new());
let output = extension.calculate(&params, &vec!["hello".to_owned()], &HashMap::new()).unwrap();
assert!(output.is_some());
@ -422,7 +422,7 @@ mod tests {
"var1".to_owned(),
ExtensionResult::Single("hello".to_owned()),
);
let output = extension.calculate(&params, &vec![], &vars);
let output = extension.calculate(&params, &vec![], &vars).unwrap();
assert!(output.is_some());
assert_eq!(output.unwrap(), ExtensionResult::Single("hello".to_owned()));
@ -443,7 +443,7 @@ mod tests {
let mut subvars = HashMap::new();
subvars.insert("name".to_owned(), "John".to_owned());
vars.insert("form1".to_owned(), ExtensionResult::Multiple(subvars));
let output = extension.calculate(&params, &vec![], &vars);
let output = extension.calculate(&params, &vec![], &vars).unwrap();
assert!(output.is_some());
assert_eq!(output.unwrap(), ExtensionResult::Single("John".to_owned()));

View File

@ -39,14 +39,14 @@ impl super::Extension for VarDummyExtension {
params: &Mapping,
_: &Vec<String>,
vars: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> {
) -> super::ExtensionOut {
let target = params.get(&Value::from("target"));
if let Some(target) = target {
let value = vars.get(target.as_str().unwrap_or_default());
Some(value.unwrap().clone())
Ok(Some(value.unwrap().clone()))
} else {
None
Ok(None)
}
}
}

View File

@ -182,8 +182,10 @@ impl super::Renderer for DefaultRenderer {
// Normal extension variables
let extension = self.extension_map.get(&variable.var_type);
if let Some(extension) = extension {
let ext_out =
let ext_res =
extension.calculate(&variable.params, &args, &output_map);
match ext_res {
Ok(ext_out) => {
if let Some(output) = ext_out {
output_map.insert(variable.name.clone(), output);
} else {
@ -196,6 +198,12 @@ impl super::Renderer for DefaultRenderer {
variable.name
);
}
}
Err(_) => {
return RenderResult::Error
}
}
} else {
error!(
"No extension found for variable type: {}",