feat(migrate): implement form syntax auto migration #856

This commit is contained in:
Federico Terzi 2021-11-07 14:21:59 +01:00
parent fa26b1ffde
commit 7244d34c7c
5 changed files with 117 additions and 2 deletions

View File

@ -17,6 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use regex::{Captures, Regex};
use std::{cmp::Ordering, collections::HashMap, path::PathBuf}; use std::{cmp::Ordering, collections::HashMap, path::PathBuf};
use yaml_rust::{yaml::Hash, Yaml}; use yaml_rust::{yaml::Hash, Yaml};
@ -69,24 +70,32 @@ pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedF
}); });
if let Some(global_vars) = yaml_global_vars { if let Some(global_vars) = yaml_global_vars {
let mut patched_global_vars: Vec<Yaml> = global_vars.clone();
patched_global_vars
.iter_mut()
.for_each(apply_form_syntax_patch_to_variable);
let output_global_vars = output_yaml let output_global_vars = output_yaml
.content .content
.entry(Yaml::String("global_vars".to_string())) .entry(Yaml::String("global_vars".to_string()))
.or_insert(Yaml::Array(Vec::new())); .or_insert(Yaml::Array(Vec::new()));
if let Yaml::Array(out_global_vars) = output_global_vars { if let Yaml::Array(out_global_vars) = output_global_vars {
out_global_vars.extend(global_vars.clone()); out_global_vars.extend(patched_global_vars);
} else { } else {
eprintln!("unable to transform global_vars for file: {}", input_path); eprintln!("unable to transform global_vars for file: {}", input_path);
} }
} }
if let Some(matches) = yaml_matches { if let Some(matches) = yaml_matches {
let mut patched_matches = matches.clone();
apply_form_syntax_patch(&mut patched_matches);
let output_matches = output_yaml let output_matches = output_yaml
.content .content
.entry(Yaml::String("matches".to_string())) .entry(Yaml::String("matches".to_string()))
.or_insert(Yaml::Array(Vec::new())); .or_insert(Yaml::Array(Vec::new()));
if let Yaml::Array(out_matches) = output_matches { if let Yaml::Array(out_matches) = output_matches {
out_matches.extend(matches.clone()); out_matches.extend(patched_matches);
} else { } else {
eprintln!("unable to transform matches for file: {}", input_path); eprintln!("unable to transform matches for file: {}", input_path);
} }
@ -324,3 +333,57 @@ fn map_field_if_present(
} }
} }
} }
// This is needed to convert the old form's {{control}} syntax to the new [[control]] one.
fn apply_form_syntax_patch(matches: &mut Vec<Yaml>) {
matches.iter_mut().for_each(|m| {
if let Yaml::Hash(fields) = m {
if let Some(Yaml::String(form_option)) = fields.get_mut(&Yaml::String("form".to_string())) {
let converted = replace_legacy_form_syntax_with_new_one(form_option);
if &converted != form_option {
form_option.clear();
form_option.push_str(&converted);
}
}
if let Some(Yaml::Array(vars)) = fields.get_mut(&Yaml::String("vars".to_string())) {
vars
.iter_mut()
.for_each(apply_form_syntax_patch_to_variable)
}
}
})
}
fn apply_form_syntax_patch_to_variable(variable: &mut Yaml) {
if let Yaml::Hash(fields) = variable {
if let Some(Yaml::String(var_type)) = fields.get(&Yaml::String("type".to_string())) {
if var_type != "form" {
return;
}
}
if let Some(Yaml::Hash(params)) = fields.get_mut(&Yaml::String("params".to_string())) {
if let Some(Yaml::String(layout)) = params.get_mut(&Yaml::String("layout".to_string())) {
let converted = replace_legacy_form_syntax_with_new_one(layout);
if &converted != layout {
layout.clear();
layout.push_str(&converted);
}
}
}
}
}
lazy_static! {
static ref LEGACY_FIELD_REGEX: Regex = Regex::new(r"\{\{(?P<name>.*?)\}\}").unwrap();
}
fn replace_legacy_form_syntax_with_new_one(layout: &str) -> String {
LEGACY_FIELD_REGEX
.replace_all(layout, |caps: &Captures| {
let field_name = caps.name("name").unwrap().as_str();
format!("[[{}]]", field_name)
})
.to_string()
}

View File

@ -200,12 +200,14 @@ mod tests {
static BASE_CASE: Dir = include_dir!("test/base"); static BASE_CASE: Dir = include_dir!("test/base");
static ALL_PARAMS_CASE: Dir = include_dir!("test/all_params"); static ALL_PARAMS_CASE: Dir = include_dir!("test/all_params");
static OTHER_DIRS_CASE: Dir = include_dir!("test/other_dirs"); static OTHER_DIRS_CASE: Dir = include_dir!("test/other_dirs");
static FORM_SYNTAX: Dir = include_dir!("test/form_syntax");
#[allow(clippy::unused_unit)] #[allow(clippy::unused_unit)]
#[test_case(&SIMPLE_CASE; "simple case")] #[test_case(&SIMPLE_CASE; "simple case")]
#[test_case(&BASE_CASE; "base case")] #[test_case(&BASE_CASE; "base case")]
#[test_case(&ALL_PARAMS_CASE; "all config parameters case")] #[test_case(&ALL_PARAMS_CASE; "all config parameters case")]
#[test_case(&OTHER_DIRS_CASE; "other directories case")] #[test_case(&OTHER_DIRS_CASE; "other directories case")]
#[test_case(&FORM_SYNTAX; "form syntax")]
fn test_migration(test_data: &Dir) { fn test_migration(test_data: &Dir) {
run_with_temp_dir(test_data, |legacy, expected| { run_with_temp_dir(test_data, |legacy, expected| {
let tmp_out_dir = TempDir::new("espanso-migrate-out").unwrap(); let tmp_out_dir = TempDir::new("espanso-migrate-out").unwrap();

View File

@ -0,0 +1,25 @@
global_vars:
- name: global_form
type: form
params:
layout: |
Reverse [[name]]
matches:
- trigger: ":greet"
form: |
Hey [[name]],
Happy Birthday!
- trigger: ":rev"
replace: "{{reversed}}"
vars:
- name: form1
type: form
params:
layout: |
Reverse [[name]]
- name: reversed
type: shell
params:
cmd: "echo $ESPANSO_FORM1_NAME | rev"

View File

@ -0,0 +1,25 @@
global_vars:
- name: global_form
type: form
params:
layout: |
Reverse {{name}}
matches:
- trigger: ":greet"
form: |
Hey {{name}},
Happy Birthday!
- trigger: ":rev"
replace: "{{reversed}}"
vars:
- name: form1
type: form
params:
layout: |
Reverse {{name}}
- name: reversed
type: shell
params:
cmd: "echo $ESPANSO_FORM1_NAME | rev"