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

View File

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

View File

@ -43,13 +43,13 @@ impl super::Extension for FormExtension {
params: &Mapping, params: &Mapping,
_: &Vec<String>, _: &Vec<String>,
_: &HashMap<String, ExtensionResult>, _: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult> { ) -> super::ExtensionOut {
let layout = params.get(&Value::from("layout")); let layout = params.get(&Value::from("layout"));
let layout = if let Some(value) = layout { let layout = if let Some(value) = layout {
value.as_str().unwrap_or_default().to_string() value.as_str().unwrap_or_default().to_string()
} else { } else {
error!("invoking form extension without specifying a layout"); error!("invoking form extension without specifying a layout");
return None; return Err(super::ExtensionError::Internal);
}; };
let mut form_config = Mapping::new(); 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); let json: Result<HashMap<String, String>, _> = serde_json::from_str(&output);
match json { match json {
Ok(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) => { Err(error) => {
error!("modulo json parsing error: {}", error); error!("modulo json parsing error: {}", error);
return None; return Err(super::ExtensionError::Internal);
} }
} }
} else { } else {
error!("modulo form didn't return any output"); 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>), 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 { pub trait Extension {
fn name(&self) -> String; fn name(&self) -> String;
fn calculate( fn calculate(
@ -45,7 +56,7 @@ pub trait Extension {
params: &Mapping, params: &Mapping,
args: &Vec<String>, args: &Vec<String>,
current_vars: &HashMap<String, ExtensionResult>, current_vars: &HashMap<String, ExtensionResult>,
) -> Option<ExtensionResult>; ) -> ExtensionOut;
} }
pub fn get_extensions( pub fn get_extensions(

View File

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

View File

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

View File

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

View File

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