feat(config): add backend option

This commit is contained in:
Federico Terzi 2021-03-24 20:51:15 +01:00
parent 8257e9f400
commit 65fd76c5b9
7 changed files with 130 additions and 24 deletions

View File

@ -30,6 +30,7 @@ pub(crate) mod store;
pub trait Config { pub trait Config {
fn label(&self) -> &str; fn label(&self) -> &str;
fn match_paths(&self) -> &[String]; fn match_paths(&self) -> &[String];
fn backend(&self) -> Backend;
fn is_match(&self, app: &AppProperties) -> bool; fn is_match(&self, app: &AppProperties) -> bool;
} }
@ -47,6 +48,13 @@ pub struct AppProperties<'a> {
pub exec: Option<&'a str>, pub exec: Option<&'a str>,
} }
#[derive(Debug, Copy, Clone)]
pub enum Backend {
Inject,
Clipboard,
Auto,
}
pub fn load_store(config_dir: &Path) -> Result<impl ConfigStore> { pub fn load_store(config_dir: &Path) -> Result<impl ConfigStore> {
store::DefaultConfigStore::load(config_dir) store::DefaultConfigStore::load(config_dir)
} }

View File

@ -27,6 +27,8 @@ mod yaml;
pub(crate) struct ParsedConfig { pub(crate) struct ParsedConfig {
pub label: Option<String>, pub label: Option<String>,
pub backend: Option<String>,
// Includes // Includes
pub includes: Option<Vec<String>>, pub includes: Option<Vec<String>>,
pub excludes: Option<Vec<String>>, pub excludes: Option<Vec<String>>,

View File

@ -30,6 +30,9 @@ pub(crate) struct YAMLConfig {
#[serde(default)] #[serde(default)]
pub label: Option<String>, pub label: Option<String>,
#[serde(default)]
pub backend: Option<String>,
#[serde(default)] #[serde(default)]
pub includes: Option<Vec<String>>, pub includes: Option<Vec<String>>,
@ -78,7 +81,7 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
fn try_from(yaml_config: YAMLConfig) -> Result<Self, Self::Error> { fn try_from(yaml_config: YAMLConfig) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
label: yaml_config.label, label: yaml_config.label,
backend: yaml_config.backend,
use_standard_includes: yaml_config.use_standard_includes, use_standard_includes: yaml_config.use_standard_includes,
includes: yaml_config.includes, includes: yaml_config.includes,
extra_includes: yaml_config.extra_includes, extra_includes: yaml_config.extra_includes,
@ -103,6 +106,7 @@ mod tests {
let config = YAMLConfig::parse_from_str( let config = YAMLConfig::parse_from_str(
r#" r#"
label: "test" label: "test"
backend: clipboard
use_standard_includes: true use_standard_includes: true
includes: ["test1"] includes: ["test1"]
@ -123,6 +127,9 @@ mod tests {
parsed_config, parsed_config,
ParsedConfig { ParsedConfig {
label: Some("test".to_string()), label: Some("test".to_string()),
backend: Some("clipboard".to_string()),
use_standard_includes: Some(true), use_standard_includes: Some(true),
includes: Some(vec!["test1".to_string()]), includes: Some(vec!["test1".to_string()]),
extra_includes: Some(vec!["test2".to_string()]), extra_includes: Some(vec!["test2".to_string()]),

View File

@ -17,9 +17,10 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use super::{parse::ParsedConfig, path::calculate_paths, util::os_matches, AppProperties, Config}; use super::{AppProperties, Backend, Config, parse::ParsedConfig, path::calculate_paths, util::os_matches};
use crate::merge; use crate::merge;
use anyhow::Result; use anyhow::Result;
use log::error;
use regex::Regex; use regex::Regex;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::{collections::HashSet, path::Path}; use std::{collections::HashSet, path::Path};
@ -109,6 +110,18 @@ impl Config for ResolvedConfig {
// All the filters that have been specified must be true to define a match // All the filters that have been specified must be true to define a match
is_os_match && is_exec_match && is_title_match && is_class_match is_os_match && is_exec_match && is_title_match && is_class_match
} }
fn backend(&self) -> Backend {
match self.parsed.backend.as_deref().map(|b| b.to_lowercase()).as_deref() {
Some("clipboard") => Backend::Clipboard,
Some("inject") => Backend::Inject,
Some("auto") => Backend::Auto,
err => {
error!("invalid backend specified {:?}, falling back to Auto", err);
Backend::Auto
}
}
}
} }
impl ResolvedConfig { impl ResolvedConfig {
@ -164,6 +177,7 @@ impl ResolvedConfig {
parent, parent,
// Fields // Fields
label, label,
backend,
includes, includes,
excludes, excludes,
extra_includes, extra_includes,

View File

@ -145,6 +145,10 @@ mod tests {
fn is_match(&self, _: &crate::config::AppProperties) -> bool { fn is_match(&self, _: &crate::config::AppProperties) -> bool {
self.is_match self.is_match
} }
fn backend(&self) -> crate::config::Backend {
unimplemented!()
}
} }
#[test] #[test]

View File

@ -509,7 +509,7 @@ impl LegacyConfig {
fn triggers_for_match(m: &Value) -> Vec<String> { fn triggers_for_match(m: &Value) -> Vec<String> {
if let Some(triggers) = m.get("triggers").and_then(|v| v.as_sequence()) { if let Some(triggers) = m.get("triggers").and_then(|v| v.as_sequence()) {
triggers.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect() triggers.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()
} else if let Some(trigger) = m.get("trigger").and_then(|v| v.as_str()) { } else if let Some(trigger) = m.get("trigger").and_then(|v| v.as_str()) {
vec![trigger.to_string()] vec![trigger.to_string()]
} else { } else {
@ -517,6 +517,7 @@ fn triggers_for_match(m: &Value) -> Vec<String> {
} }
} }
#[allow(dead_code)]
fn replace_for_match(m: &Value) -> String { fn replace_for_match(m: &Value) -> String {
m.get("replace").and_then(|v| v.as_str()).expect("match is missing replace field").to_string() m.get("replace").and_then(|v| v.as_str()).expect("match is missing replace field").to_string()
} }
@ -752,6 +753,7 @@ impl LegacyConfigSet {
// Error handling // Error handling
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
#[allow(dead_code)]
pub enum ConfigLoadError { pub enum ConfigLoadError {
FileNotFound, FileNotFound,
UnableToReadFile, UnableToReadFile,

View File

@ -92,9 +92,10 @@ fn split_config(config: LegacyConfig) -> (LegacyInteropConfig, LegacyMatchGroup)
struct LegacyInteropConfig { struct LegacyInteropConfig {
pub name: String, pub name: String,
match_paths: Vec<String>, match_paths: Vec<String>,
config: LegacyConfig,
filter_title: Option<Regex>, filter_title: Option<Regex>,
filter_class: Option<Regex>, filter_class: Option<Regex>,
filter_exec: Option<Regex>, filter_exec: Option<Regex>,
@ -103,6 +104,7 @@ struct LegacyInteropConfig {
impl From<config::LegacyConfig> for LegacyInteropConfig { impl From<config::LegacyConfig> for LegacyInteropConfig {
fn from(config: config::LegacyConfig) -> Self { fn from(config: config::LegacyConfig) -> Self {
Self { Self {
config: config.clone(),
name: config.name.clone(), name: config.name.clone(),
match_paths: vec![config.name], match_paths: vec![config.name],
filter_title: if !config.filter_title.is_empty() { filter_title: if !config.filter_title.is_empty() {
@ -126,7 +128,15 @@ impl From<config::LegacyConfig> for LegacyInteropConfig {
impl Config for LegacyInteropConfig { impl Config for LegacyInteropConfig {
fn label(&self) -> &str { fn label(&self) -> &str {
&self.name &self.config.name
}
fn backend(&self) -> crate::config::Backend {
match self.config.backend {
config::BackendType::Inject => crate::config::Backend::Inject,
config::BackendType::Clipboard => crate::config::Backend::Clipboard,
config::BackendType::Auto => crate::config::Backend::Auto,
}
} }
fn match_paths(&self) -> &[String] { fn match_paths(&self) -> &[String] {
@ -237,7 +247,9 @@ mod tests {
#[test] #[test]
fn load_legacy_works_correctly() { fn load_legacy_works_correctly() {
use_test_directory(|base, user, packages| { use_test_directory(|base, user, packages| {
std::fs::write(base.join("default.yml"), r#" std::fs::write(
base.join("default.yml"),
r#"
backend: Clipboard backend: Clipboard
global_vars: global_vars:
@ -247,25 +259,35 @@ mod tests {
matches: matches:
- trigger: "hello" - trigger: "hello"
replace: "world" replace: "world"
"#).unwrap(); "#,
)
.unwrap();
std::fs::write(user.join("specific.yml"), r#" std::fs::write(
user.join("specific.yml"),
r#"
name: specific name: specific
parent: default parent: default
matches: matches:
- trigger: "foo" - trigger: "foo"
replace: "bar" replace: "bar"
"#).unwrap(); "#,
)
.unwrap();
std::fs::write(user.join("separate.yml"), r#" std::fs::write(
user.join("separate.yml"),
r#"
name: separate name: separate
filter_title: "Google" filter_title: "Google"
matches: matches:
- trigger: "eren" - trigger: "eren"
replace: "mikasa" replace: "mikasa"
"#).unwrap(); "#,
)
.unwrap();
let (config_store, match_store) = load(base, packages).unwrap(); let (config_store, match_store) = load(base, packages).unwrap();
@ -286,44 +308,91 @@ mod tests {
}); });
assert_eq!(default_fallback.match_paths().len(), 1); assert_eq!(default_fallback.match_paths().len(), 1);
assert_eq!(match_store.query(default_config.match_paths()).matches.len(), 2); assert_eq!(
assert_eq!(match_store.query(default_config.match_paths()).global_vars.len(), 1); match_store
.query(default_config.match_paths())
.matches
.len(),
2
);
assert_eq!(
match_store
.query(default_config.match_paths())
.global_vars
.len(),
1
);
assert_eq!(match_store.query(active_config.match_paths()).matches.len(), 3); assert_eq!(
assert_eq!(match_store.query(active_config.match_paths()).global_vars.len(), 1); match_store.query(active_config.match_paths()).matches.len(),
3
);
assert_eq!(
match_store
.query(active_config.match_paths())
.global_vars
.len(),
1
);
assert_eq!(match_store.query(default_fallback.match_paths()).matches.len(), 2); assert_eq!(
assert_eq!(match_store.query(default_fallback.match_paths()).global_vars.len(), 1); match_store
.query(default_fallback.match_paths())
.matches
.len(),
2
);
assert_eq!(
match_store
.query(default_fallback.match_paths())
.global_vars
.len(),
1
);
}); });
} }
#[test] #[test]
fn load_legacy_with_packages() { fn load_legacy_with_packages() {
use_test_directory(|base, user, packages| { use_test_directory(|base, _, packages| {
std::fs::write(base.join("default.yml"), r#" std::fs::write(
base.join("default.yml"),
r#"
backend: Clipboard backend: Clipboard
matches: matches:
- trigger: "hello" - trigger: "hello"
replace: "world" replace: "world"
"#).unwrap(); "#,
)
.unwrap();
create_dir_all(packages.join("test-package")).unwrap(); create_dir_all(packages.join("test-package")).unwrap();
std::fs::write(packages.join("test-package").join("package.yml"), r#" std::fs::write(
packages.join("test-package").join("package.yml"),
r#"
name: test-package name: test-package
parent: default parent: default
matches: matches:
- trigger: "foo" - trigger: "foo"
replace: "bar" replace: "bar"
"#).unwrap(); "#,
)
.unwrap();
let (config_store, match_store) = load(base, packages).unwrap(); let (config_store, match_store) = load(base, packages).unwrap();
let default_config = config_store.default(); let default_config = config_store.default();
assert_eq!(default_config.match_paths().len(), 1); assert_eq!(default_config.match_paths().len(), 1);
assert_eq!(match_store.query(default_config.match_paths()).matches.len(), 2); assert_eq!(
match_store
.query(default_config.match_paths())
.matches
.len(),
2
);
}); });
} }
} }