From f847d0cd810981879e586fb50c2e45263c9237b9 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Fri, 9 Apr 2021 21:29:23 +0200 Subject: [PATCH] feat(config): add id to config and decouple variable params from serde_yaml --- espanso-config/Cargo.toml | 2 + espanso-config/src/config/mod.rs | 1 + espanso-config/src/config/resolve.rs | 9 +- espanso-config/src/config/store.rs | 4 + espanso-config/src/counter.rs | 1 + espanso-config/src/legacy/mod.rs | 8 + .../src/matches/group/loader/yaml/mod.rs | 29 +-- .../src/matches/group/loader/yaml/util.rs | 167 ++++++++++++++++++ espanso-config/src/matches/mod.rs | 26 ++- 9 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 espanso-config/src/matches/group/loader/yaml/util.rs diff --git a/espanso-config/Cargo.toml b/espanso-config/Cargo.toml index e0d5e5f..2e230f8 100644 --- a/espanso-config/Cargo.toml +++ b/espanso-config/Cargo.toml @@ -15,6 +15,8 @@ regex = "1.4.3" lazy_static = "1.4.0" dunce = "1.0.1" walkdir = "2.3.1" +enum-as-inner = "0.3.3" +ordered-float = "2.0" [dev-dependencies] tempdir = "0.3.7" diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs index c73d394..53f0dab 100644 --- a/espanso-config/src/config/mod.rs +++ b/espanso-config/src/config/mod.rs @@ -28,6 +28,7 @@ mod util; pub(crate) mod store; pub trait Config { + fn id(&self) -> i32; fn label(&self) -> &str; fn match_paths(&self) -> &[String]; fn backend(&self) -> Backend; diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs index 8da4f8f..6f79f83 100644 --- a/espanso-config/src/config/resolve.rs +++ b/espanso-config/src/config/resolve.rs @@ -18,7 +18,7 @@ */ use super::{AppProperties, Backend, Config, parse::ParsedConfig, path::calculate_paths, util::os_matches}; -use crate::merge; +use crate::{counter::next_id, merge}; use anyhow::Result; use log::error; use regex::Regex; @@ -34,6 +34,7 @@ pub(crate) struct ResolvedConfig { parsed: ParsedConfig, // Generated properties + id: i32, match_paths: Vec, filter_title: Option, @@ -45,6 +46,7 @@ impl Default for ResolvedConfig { fn default() -> Self { Self { parsed: Default::default(), + id: 0, match_paths: Vec::new(), filter_title: None, filter_class: None, @@ -54,6 +56,10 @@ impl Default for ResolvedConfig { } impl Config for ResolvedConfig { + fn id(&self) -> i32 { + self.id + } + fn label(&self) -> &str { self.parsed.label.as_deref().unwrap_or("none") } @@ -162,6 +168,7 @@ impl ResolvedConfig { Ok(Self { parsed: config, + id: next_id(), match_paths, filter_title, filter_class, diff --git a/espanso-config/src/config/store.rs b/espanso-config/src/config/store.rs index c424a0d..1a7e483 100644 --- a/espanso-config/src/config/store.rs +++ b/espanso-config/src/config/store.rs @@ -134,6 +134,10 @@ mod tests { } impl Config for MockConfig { + fn id(&self) -> i32 { + 0 + } + fn label(&self) -> &str { &self.label } diff --git a/espanso-config/src/counter.rs b/espanso-config/src/counter.rs index 3cab3db..565bd04 100644 --- a/espanso-config/src/counter.rs +++ b/espanso-config/src/counter.rs @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicI32, Ordering}; thread_local! { + // TODO: if thread local, we probably don't need an atomic static STRUCT_COUNTER: AtomicI32 = AtomicI32::new(0); } diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs index 4d2e3b0..187c177 100644 --- a/espanso-config/src/legacy/mod.rs +++ b/espanso-config/src/legacy/mod.rs @@ -31,6 +31,7 @@ use crate::{ store::{MatchSet, MatchStore}, Match, Variable, }, + counter::next_id, }; use std::convert::TryInto; @@ -137,6 +138,8 @@ struct LegacyInteropConfig { pub name: String, match_paths: Vec, + id: i32, + config: LegacyConfig, filter_title: Option, @@ -147,6 +150,7 @@ struct LegacyInteropConfig { impl From for LegacyInteropConfig { fn from(config: config::LegacyConfig) -> Self { Self { + id: next_id(), config: config.clone(), name: config.name.clone(), match_paths: vec![config.name], @@ -170,6 +174,10 @@ impl From for LegacyInteropConfig { } impl Config for LegacyInteropConfig { + fn id(&self) -> i32 { + self.id + } + fn label(&self) -> &str { &self.config.name } diff --git a/espanso-config/src/matches/group/loader/yaml/mod.rs b/espanso-config/src/matches/group/loader/yaml/mod.rs index 26717f9..37053a5 100644 --- a/espanso-config/src/matches/group/loader/yaml/mod.rs +++ b/espanso-config/src/matches/group/loader/yaml/mod.rs @@ -17,21 +17,25 @@ * along with espanso. If not, see . */ -use crate::{counter::next_id, matches::{ - group::{path::resolve_imports, MatchGroup}, - Match, Variable, -}}; +use crate::{ + counter::next_id, + matches::{ + group::{path::resolve_imports, MatchGroup}, + Match, Variable, + }, +}; use anyhow::Result; use log::warn; use parse::YAMLMatchGroup; use std::convert::{TryFrom, TryInto}; -use self::parse::{YAMLMatch, YAMLVariable}; +use self::{parse::{YAMLMatch, YAMLVariable}, util::convert_params}; use crate::matches::{MatchCause, MatchEffect, TextEffect, TriggerCause}; use super::Importer; pub(crate) mod parse; +mod util; pub(crate) struct YAMLImporter {} @@ -150,7 +154,7 @@ impl TryFrom for Variable { Ok(Self { name: yaml_var.name, var_type: yaml_var.var_type, - params: yaml_var.params, + params: convert_params(yaml_var.params)?, id: next_id(), }) } @@ -159,8 +163,7 @@ impl TryFrom for Variable { #[cfg(test)] mod tests { use super::*; - use crate::{matches::Match, util::tests::use_test_directory}; - use serde_yaml::{Mapping, Value}; + use crate::{matches::{Match, Params, Value}, util::tests::use_test_directory}; use std::fs::create_dir_all; fn create_match(yaml: &str) -> Result { @@ -172,7 +175,7 @@ mod tests { if let MatchEffect::Text(e) = &mut m.effect { e.vars.iter_mut().for_each(|v| v.id = 0); } - + Ok(m) } @@ -331,8 +334,8 @@ mod tests { #[test] fn vars_maps_correctly() { - let mut params = Mapping::new(); - params.insert(Value::String("param1".to_string()), Value::Bool(true)); + let mut params = Params::new(); + params.insert("param1".to_string(), Value::Bool(true)); let vars = vec![Variable { name: "var1".to_string(), var_type: "test".to_string(), @@ -371,7 +374,7 @@ mod tests { let vars = vec![Variable { name: "var1".to_string(), var_type: "test".to_string(), - params: Mapping::new(), + params: Params::new(), ..Default::default() }]; assert_eq!( @@ -444,7 +447,7 @@ mod tests { let vars = vec![Variable { name: "var1".to_string(), var_type: "test".to_string(), - params: Mapping::new(), + params: Params::new(), ..Default::default() }]; diff --git a/espanso-config/src/matches/group/loader/yaml/util.rs b/espanso-config/src/matches/group/loader/yaml/util.rs new file mode 100644 index 0000000..a8f67a5 --- /dev/null +++ b/espanso-config/src/matches/group/loader/yaml/util.rs @@ -0,0 +1,167 @@ +/* + * 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::convert::TryInto; + +use anyhow::Result; +use serde_yaml::Mapping; +use thiserror::Error; + +use crate::matches::{Number, Params, Value}; + +pub(crate) fn convert_params(m: Mapping) -> Result { + let mut params = Params::new(); + + for (key, value) in m { + let key = key.as_str().ok_or(ConversionError::InvalidKeyFormat)?; + let value = convert_value(value)?; + params.insert(key.to_owned(), value); + } + + Ok(params) +} + +fn convert_value(value: serde_yaml::Value) -> Result { + Ok(match value { + serde_yaml::Value::Null => Value::Null, + serde_yaml::Value::Bool(val) => Value::Bool(val), + serde_yaml::Value::Number(n) => { + if n.is_i64() { + Value::Number(Number::Integer( + n.as_i64().ok_or(ConversionError::InvalidNumberFormat)?, + )) + } else if n.is_u64() { + Value::Number(Number::Integer( + n.as_u64() + .ok_or(ConversionError::InvalidNumberFormat)? + .try_into()?, + )) + } else if n.is_f64() { + Value::Number(Number::Float( + n.as_f64() + .ok_or(ConversionError::InvalidNumberFormat)? + .into(), + )) + } else { + return Err(ConversionError::InvalidNumberFormat.into()); + } + } + serde_yaml::Value::String(s) => Value::String(s), + serde_yaml::Value::Sequence(arr) => Value::Array( + arr + .into_iter() + .map(convert_value) + .collect::>>()?, + ), + serde_yaml::Value::Mapping(m) => Value::Object(convert_params(m)?), + }) +} + +#[derive(Error, Debug)] +pub enum ConversionError { + #[error("invalid key format")] + InvalidKeyFormat, + + #[error("invalid number format")] + InvalidNumberFormat, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn convert_value_null() { + assert_eq!(convert_value(serde_yaml::Value::Null).unwrap(), Value::Null); + } + + #[test] + fn convert_value_bool() { + assert_eq!( + convert_value(serde_yaml::Value::Bool(true)).unwrap(), + Value::Bool(true) + ); + assert_eq!( + convert_value(serde_yaml::Value::Bool(false)).unwrap(), + Value::Bool(false) + ); + } + + #[test] + fn convert_value_number() { + assert_eq!( + convert_value(serde_yaml::Value::Number(0.into())).unwrap(), + Value::Number(Number::Integer(0)) + ); + assert_eq!( + convert_value(serde_yaml::Value::Number((-100).into())).unwrap(), + Value::Number(Number::Integer(-100)) + ); + assert_eq!( + convert_value(serde_yaml::Value::Number(1.5.into())).unwrap(), + Value::Number(Number::Float(1.5.into())) + ); + } + #[test] + fn convert_value_string() { + assert_eq!( + convert_value(serde_yaml::Value::String("hello".to_string())).unwrap(), + Value::String("hello".to_string()) + ); + } + #[test] + fn convert_value_array() { + assert_eq!( + convert_value(serde_yaml::Value::Sequence(vec![ + serde_yaml::Value::Bool(true), + serde_yaml::Value::Null, + ])) + .unwrap(), + Value::Array(vec![Value::Bool(true), Value::Null,]) + ); + } + + #[test] + fn convert_value_params() { + let mut mapping = serde_yaml::Mapping::new(); + mapping.insert(serde_yaml::Value::String("test".to_string()), serde_yaml::Value::Null); + + let mut expected = Params::new(); + expected.insert("test".to_string(), Value::Null); + assert_eq!(convert_value(serde_yaml::Value::Mapping(mapping)).unwrap(), Value::Object(expected)); + } + + #[test] + fn convert_params_works_correctly() { + let mut mapping = serde_yaml::Mapping::new(); + mapping.insert(serde_yaml::Value::String("test".to_string()), serde_yaml::Value::Null); + + let mut expected = Params::new(); + expected.insert("test".to_string(), Value::Null); + assert_eq!(convert_params(mapping).unwrap(), expected); + } + + #[test] + fn convert_params_invalid_key_type() { + let mut mapping = serde_yaml::Mapping::new(); + mapping.insert(serde_yaml::Value::Null, serde_yaml::Value::Null); + + assert!(convert_params(mapping).is_err()); + } +} diff --git a/espanso-config/src/matches/mod.rs b/espanso-config/src/matches/mod.rs index c7c88ea..fb322f1 100644 --- a/espanso-config/src/matches/mod.rs +++ b/espanso-config/src/matches/mod.rs @@ -17,7 +17,9 @@ * along with espanso. If not, see . */ -use serde_yaml::Mapping; +use std::collections::{BTreeMap}; +use enum_as_inner::EnumAsInner; +use ordered_float::OrderedFloat; use crate::counter::{StructId}; @@ -107,7 +109,7 @@ pub struct Variable { pub id: StructId, pub name: String, pub var_type: String, - pub params: Mapping, + pub params: Params, } impl Default for Variable { @@ -116,7 +118,25 @@ impl Default for Variable { id: 0, name: String::new(), var_type: String::new(), - params: Mapping::new(), + params: Params::new(), } } } + +pub type Params = BTreeMap; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumAsInner)] +pub enum Value { + Null, + Bool(bool), + Number(Number), + String(String), + Array(Vec), + Object(Params), +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub enum Number { + Integer(i64), + Float(OrderedFloat), +} \ No newline at end of file