/* * This file is part of espanso. * * Copyright (C) 2019-2021 Federico Terzi * * espanso is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * espanso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with espanso. If not, see . */ use std::{cmp::Ordering, collections::HashMap, path::PathBuf}; 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"); let yaml_matches = yaml_get_vec(yaml, "matches"); let yaml_global_vars = yaml_get_vec(yaml, "global_vars"); let yaml_parent = yaml_get_string(yaml, "parent"); let yaml_name = yaml_get_string(yaml, "name"); let should_generate_match = yaml_matches.is_some() || yaml_global_vars.is_some(); let match_file_path_if_unlisted = 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.clone()).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); } } if should_underscore { Some(match_output_path) } else { None } } else { None }; 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"); copy_field_if_present(yaml, "enable_active", &mut output_yaml, "enable"); // TODO: warn if passive mode parameters are used // TODO: copy other config fields: https://github.com/federico-terzi/espanso/blob/master/src/config/mod.rs#L169 // Link any unlisted match file (the ones starting with the _ underscore, which are excluded by the // default.yml config) explicitly, if present. if let Some(match_file_path) = match_file_path_if_unlisted { let yaml_exclude_default_entries = yaml_get_bool(yaml, "exclude_default_entries").unwrap_or(false); let key_name = if yaml_exclude_default_entries { "includes" } else { "extra_includes" }; let includes = vec![Yaml::String(format!("../{}", match_file_path))]; output_yaml.insert(Yaml::String(key_name.to_string()), Yaml::Array(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); // } output_files } fn sort_input_files(input_files: &HashMap) -> Vec { let mut files: Vec = input_files.iter().map(|(key, _)| key.clone()).collect(); files.sort_by(|f1, f2| { let f1_slashes = f1.matches("/").count(); let f2_slashes = f2.matches("/").count(); if f1_slashes > f2_slashes { Ordering::Greater } else if f1_slashes < f2_slashes { Ordering::Less } else { f1.cmp(f2) } }); files } // 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 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 yaml_get_bool<'a>(yaml: &'a Hash, name: &str) -> Option { yaml .get(&Yaml::String(name.to_string())) .and_then(|v| v.as_bool()) } 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()); } }