Add form translation

This commit is contained in:
Federico Terzi 2020-08-11 22:04:44 +02:00
parent 316ebbb502
commit 34e08e6ec8
3 changed files with 125 additions and 10 deletions

View File

@ -211,7 +211,7 @@ mod tests {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert( params.insert(
Value::from("args"), Value::from("args"),
Value::from(vec!["echo", "$ESPANSO_VAR1 $ESPANSO_FORM1_NAME"]), Value::from(vec!["bash", "-c", "echo $ESPANSO_VAR1 $ESPANSO_FORM1_NAME"]),
); );
let mut vars: HashMap<String, ExtensionResult> = HashMap::new(); let mut vars: HashMap<String, ExtensionResult> = HashMap::new();
@ -221,9 +221,9 @@ mod tests {
vars.insert("var1".to_owned(), ExtensionResult::Single("hello".to_owned())); vars.insert("var1".to_owned(), ExtensionResult::Single("hello".to_owned()));
let extension = ScriptExtension::new(); let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![], &vars);
assert!(output.is_some()); assert!(output.is_some());
assert_eq!(output.unwrap(), "hello Jon"); assert_eq!(output.unwrap(), ExtensionResult::Single("hello John".to_owned()));
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* This file is part of espanso. * This file is part of espans{ name: (), var_type: (), params: ()}
* *
* Copyright (C) 2019 Federico Terzi * Copyright (C) 2019 Federico Terzi
* *
@ -19,9 +19,10 @@
use crate::event::KeyEventReceiver; use crate::event::KeyEventReceiver;
use crate::event::{KeyEvent, KeyModifier}; use crate::event::{KeyEvent, KeyModifier};
use regex::Regex; use regex::{Captures, Regex};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Mapping; use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@ -47,7 +48,7 @@ pub enum MatchContentType {
Image(ImageContent), Image(ImageContent),
} }
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone, PartialEq)]
pub struct TextContent { pub struct TextContent {
pub replace: String, pub replace: String,
pub vars: Vec<MatchVariable>, pub vars: Vec<MatchVariable>,
@ -143,6 +144,40 @@ impl<'a> From<&'a AutoMatch> for Match {
_has_vars: has_vars, _has_vars: has_vars,
}; };
MatchContentType::Text(content)
} else if let Some(form) = &other.form { // Form shorthand
// Replace all the form fields with actual variables
let new_replace = VAR_REGEX.replace_all(&form, |caps: &Captures| {
let var_name = caps.get(1).unwrap().as_str();
format!("{{{{form1.{}}}}}", var_name)
});
let new_replace = new_replace.to_string();
// Convert the form data to valid variables
let mut params = Mapping::new();
if let Some(fields) = &other.form_fields {
let mut mapping_fields = Mapping::new();
fields.iter().for_each(|(key, value)| {
mapping_fields.insert(Value::from(key.to_owned()), Value::from(value.clone()));
});
params.insert(Value::from("fields"), Value::from(mapping_fields));
}
params.insert(Value::from("layout"), Value::from(form.to_owned()));
let vars = vec![
MatchVariable {
name: "form1".to_owned(),
var_type: "form".to_owned(),
params,
}
];
let content = TextContent {
replace: new_replace,
vars,
_has_vars: true,
};
MatchContentType::Text(content) MatchContentType::Text(content)
} else if let Some(image_path) = &other.image_path { } else if let Some(image_path) = &other.image_path {
// Image match // Image match
@ -173,7 +208,7 @@ impl<'a> From<&'a AutoMatch> for Match {
MatchContentType::Image(content) MatchContentType::Image(content)
} else { } else {
eprintln!("ERROR: no action specified for match {}, please specify either 'replace' or 'image_path'", other.trigger); eprintln!("ERROR: no action specified for match {}, please specify either 'replace', 'image_path' or 'form'", other.trigger);
std::process::exit(2); std::process::exit(2);
}; };
@ -204,6 +239,12 @@ struct AutoMatch {
#[serde(default = "default_image_path")] #[serde(default = "default_image_path")]
pub image_path: Option<String>, pub image_path: Option<String>,
#[serde(default = "default_form")]
pub form: Option<String>,
#[serde(default = "default_form_fields")]
pub form_fields: Option<HashMap<String, Value>>,
#[serde(default = "default_vars")] #[serde(default = "default_vars")]
pub vars: Vec<MatchVariable>, pub vars: Vec<MatchVariable>,
@ -238,6 +279,12 @@ fn default_passive_only() -> bool {
fn default_replace() -> Option<String> { fn default_replace() -> Option<String> {
None None
} }
fn default_form() -> Option<String> {
None
}
fn default_form_fields() -> Option<HashMap<String, Value>> {
None
}
fn default_image_path() -> Option<String> { fn default_image_path() -> Option<String> {
None None
} }
@ -248,7 +295,7 @@ fn default_force_clipboard() -> bool {
false false
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MatchVariable { pub struct MatchVariable {
pub name: String, pub name: String,
@ -543,4 +590,68 @@ mod tests {
assert_eq!(_match.triggers, vec![":..", ":..", ":.."]) assert_eq!(_match.triggers, vec![":..", ":..", ":.."])
} }
#[test]
fn test_match_form_translated_correctly() {
let match_str = r###"
trigger: ":test"
form: "Hey {{name}}, how are you? {{greet}}"
"###;
let _match: Match = serde_yaml::from_str(match_str).unwrap();
match _match.content {
MatchContentType::Text(content) => {
let mut mapping = Mapping::new();
mapping.insert(Value::from("layout"), Value::from("Hey {{name}}, how are you? {{greet}}"));
assert_eq!(content, TextContent {
replace: "Hey {{form1.name}}, how are you? {{form1.greet}}".to_owned(),
_has_vars: true,
vars: vec![
MatchVariable {
name: "form1".to_owned(),
var_type: "form".to_owned(),
params: mapping,
}
]
});
},
_ => panic!("wrong content")
}
}
#[test]
fn test_match_form_with_fields_translated_correctly() {
let match_str = r###"
trigger: ":test"
form: "Hey {{name}}, how are you? {{greet}}"
form_fields:
name:
multiline: true
"###;
let _match: Match = serde_yaml::from_str(match_str).unwrap();
match _match.content {
MatchContentType::Text(content) => {
let mut name_mapping = Mapping::new();
name_mapping.insert(Value::from("multiline"), Value::Bool(true));
let mut submapping = Mapping::new();
submapping.insert(Value::from("name"), Value::from(name_mapping));
let mut mapping = Mapping::new();
mapping.insert(Value::from("fields"), Value::from(submapping));
mapping.insert(Value::from("layout"), Value::from("Hey {{name}}, how are you? {{greet}}"));
assert_eq!(content, TextContent {
replace: "Hey {{form1.name}}, how are you? {{form1.greet}}".to_owned(),
_has_vars: true,
vars: vec![
MatchVariable {
name: "form1".to_owned(),
var_type: "form".to_owned(),
params: mapping,
}
]
});
},
_ => panic!("wrong content")
}
}
} }

View File

@ -1,6 +1,6 @@
use crate::config::Configs; use crate::config::Configs;
use std::process::{Command, Child, Output}; use std::process::{Command, Child, Output};
use log::{error}; use log::{error, info};
use std::io::{Error, Write}; use std::io::{Error, Write};
pub mod form; pub mod form;
@ -42,6 +42,10 @@ impl ModuloManager {
} }
} }
if let Some(ref modulo_path) = modulo_path {
info!("Using modulo at {:?}", modulo_path);
}
Self { Self {
modulo_path, modulo_path,
} }