From e3cdaa91fb52772aed1e933ee9c789a4a50db172 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 25 May 2021 22:07:26 +0200 Subject: [PATCH] feat(migrate): progress in the migration process --- espanso-migrate/src/convert.rs | 154 ++++++++++++++++++++++++++++++--- espanso-migrate/src/lib.rs | 16 +++- 2 files changed, 155 insertions(+), 15 deletions(-) diff --git a/espanso-migrate/src/convert.rs b/espanso-migrate/src/convert.rs index df03fa1..01ff066 100644 --- a/espanso-migrate/src/convert.rs +++ b/espanso-migrate/src/convert.rs @@ -18,28 +18,112 @@ */ use std::{cmp::Ordering, collections::HashMap, path::PathBuf}; -use yaml_rust::yaml::Hash; +use yaml_rust::{yaml::Hash, Yaml, YamlEmitter}; pub fn convert(input_files: HashMap) -> HashMap { let mut output_files = HashMap::new(); let sorted_input_files = sort_input_files(&input_files); + let mut config_names_to_path = HashMap::new(); + for input_path in sorted_input_files { let yaml = input_files .get(&input_path) .expect("received unexpected file in input function"); - if let Some((file_name, file_name_without_extension)) = extract_name_information(&input_path) { - println!("file: {}, {}", file_name, file_name_without_extension); + let yaml_matches = yaml_get_vec(yaml, "matches"); + let yaml_global_vars = yaml_get_vec(yaml, "global_vars"); - // TODO: execute the actual conversion + let yaml_parent = yaml_get_string(yaml, "parent"); + let yaml_name = yaml_get_string(yaml, "name"); - } else { - eprintln!("unable to extract filename from path: {}", input_path); + let should_generate_match = yaml_matches.is_some() || yaml_global_vars.is_some(); + if should_generate_match { + let should_underscore = !input_path.starts_with("default") && yaml_parent != Some("default"); + let match_output_path = calculate_output_match_path(&input_path, should_underscore); + if match_output_path.is_none() { + eprintln!( + "unable to determine output path for {}, skipping...", + input_path + ); + continue; + } + let match_output_path = match_output_path.unwrap(); + + if let Some(name) = yaml_name { + config_names_to_path.insert(name.to_string(), match_output_path.clone()); + } + + let output_yaml = output_files.entry(match_output_path).or_insert(Hash::new()); + + if let Some(global_vars) = yaml_global_vars { + let output_global_vars = output_yaml + .entry(Yaml::String("global_vars".to_string())) + .or_insert(Yaml::Array(Vec::new())); + if let Yaml::Array(out_global_vars) = output_global_vars { + out_global_vars.extend(global_vars.clone()); + } else { + eprintln!("unable to transform global_vars for file: {}", input_path); + } + } + + if let Some(matches) = yaml_matches { + let output_matches = output_yaml + .entry(Yaml::String("matches".to_string())) + .or_insert(Yaml::Array(Vec::new())); + if let Yaml::Array(out_matches) = output_matches { + out_matches.extend(matches.clone()); + } else { + eprintln!("unable to transform matches for file: {}", input_path); + } + } } + + let yaml_filter_class = yaml_get_string(yaml, "filter_class"); + let yaml_filter_title = yaml_get_string(yaml, "filter_title"); + let yaml_filter_exec = yaml_get_string(yaml, "filter_exec"); + + let should_generate_config = input_path.starts_with("default") + || yaml_filter_class.is_some() + || yaml_filter_exec.is_some() + || yaml_filter_title.is_some(); + + if should_generate_config { + let config_output_path = calculate_output_config_path(&input_path); + + let mut output_yaml = Hash::new(); + + copy_field_if_present(yaml, "filter_title", &mut output_yaml, "filter_title"); + copy_field_if_present(yaml, "filter_class", &mut output_yaml, "filter_class"); + copy_field_if_present(yaml, "filter_exec", &mut output_yaml, "filter_exec"); + + // TODO: copy other config fields: https://github.com/federico-terzi/espanso/blob/master/src/config/mod.rs#L169 + + // TODO: if a match file was created above of type "underscored", then explicitly include it here + // depending on whether "exclude_default_entries" is set, use "includes" or "extra_includes" + + output_files.insert(config_output_path, output_yaml); + } + + // TODO: create config file + + // TODO: execute the actual conversion } + // TODO: here resolve parent: name imports + + // TODO: remove this prints + for (file, content) in output_files { + let mut out_str = String::new(); + { + let mut emitter = YamlEmitter::new(&mut out_str); + emitter.dump(&Yaml::Hash(content)).unwrap(); // dump the YAML object to a String + } + println!("\n------- {} ------------\n{}", file, out_str); + } + + todo!(); output_files } @@ -59,10 +143,58 @@ fn sort_input_files(input_files: &HashMap) -> Vec { files } -fn extract_name_information(path: &str) -> Option<(String, String)> { +// TODO: test +fn calculate_output_match_path(path: &str, is_underscored: bool) -> Option { let path_buf = PathBuf::from(path); let file_name = path_buf.file_name()?.to_string_lossy().to_string(); - let extension = path_buf.extension()?.to_string_lossy().to_string(); - let file_name_without_extension = file_name.trim_end_matches(&format!(".{}", extension)).to_string(); - Some((file_name, file_name_without_extension)) -} \ No newline at end of file + + let path = if is_underscored { + path.replace(&file_name, &format!("_{}", file_name)) + } else { + path.to_string() + }; + + Some(if path.starts_with("user/") { + format!("match/{}", path.trim_start_matches("user/")) + } else if path.starts_with("packages/") { + format!("match/packages/{}", path.trim_start_matches("packages/")) + } else if path == "default.yml" { + "match/base.yml".to_string() + } else { + format!("match/{}", path) + }) +} + +// TODO: test +fn calculate_output_config_path(path: &str) -> String { + if path.starts_with("user/") { + format!("config/{}", path.trim_start_matches("user/")) + } else if path.starts_with("packages/") { + format!("config/packages/{}", path.trim_start_matches("packages/")) + } else { + format!("config/{}", path) + } +} + +fn yaml_get_vec<'a>(yaml: &'a Hash, name: &str) -> Option<&'a Vec> { + yaml + .get(&Yaml::String(name.to_string())) + .and_then(|v| v.as_vec()) +} + +fn yaml_get_string<'a>(yaml: &'a Hash, name: &str) -> Option<&'a str> { + yaml + .get(&Yaml::String(name.to_string())) + .and_then(|v| v.as_str()) +} + +fn copy_field_if_present( + input_yaml: &Hash, + input_field_name: &str, + output_yaml: &mut Hash, + output_field_name: &str, +) { + if let Some(value) = input_yaml.get(&Yaml::String(input_field_name.to_string())) { + output_yaml.insert(Yaml::String(output_field_name.to_string()), value.clone()); + } +} diff --git a/espanso-migrate/src/lib.rs b/espanso-migrate/src/lib.rs index ac1116a..c4f4e20 100644 --- a/espanso-migrate/src/lib.rs +++ b/espanso-migrate/src/lib.rs @@ -57,21 +57,29 @@ mod load; // TODO: test also with non-lowercase file names -// TODO: test packages in another directory +// TODO: test packages in another directory (a possible strategy is to copy +// the packages dir and the config dir into a temporary one, with the packages +// as a directory at the same level of user/) + +// TODO: when dumping the output file, remove the front-matter at the top (generated by YamlEmitter) +// and insert a comment with "Automatically generated from {{file_name}} by migration tool" #[cfg(test)] mod tests { use std::path::PathBuf; -use super::*; - use test_case::test_case; + use super::*; use include_dir::{include_dir, Dir}; + use test_case::test_case; static BASE_CASE: Dir = include_dir!("test/base"); #[test_case(&BASE_CASE; "base case")] fn test_migration(test_data: &Dir) { - let input_files = load::load(&PathBuf::from(r"")).unwrap(); + let input_files = load::load(&PathBuf::from( + r"", + )) + .unwrap(); convert::convert(input_files); // TODO