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 {
fn label(&self) -> &str;
fn match_paths(&self) -> &[String];
fn backend(&self) -> Backend;
fn is_match(&self, app: &AppProperties) -> bool;
}
@ -47,6 +48,13 @@ pub struct AppProperties<'a> {
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> {
store::DefaultConfigStore::load(config_dir)
}

View File

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

View File

@ -30,6 +30,9 @@ pub(crate) struct YAMLConfig {
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub backend: Option<String>,
#[serde(default)]
pub includes: Option<Vec<String>>,
@ -78,7 +81,7 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
fn try_from(yaml_config: YAMLConfig) -> Result<Self, Self::Error> {
Ok(Self {
label: yaml_config.label,
backend: yaml_config.backend,
use_standard_includes: yaml_config.use_standard_includes,
includes: yaml_config.includes,
extra_includes: yaml_config.extra_includes,
@ -103,6 +106,7 @@ mod tests {
let config = YAMLConfig::parse_from_str(
r#"
label: "test"
backend: clipboard
use_standard_includes: true
includes: ["test1"]
@ -123,6 +127,9 @@ mod tests {
parsed_config,
ParsedConfig {
label: Some("test".to_string()),
backend: Some("clipboard".to_string()),
use_standard_includes: Some(true),
includes: Some(vec!["test1".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/>.
*/
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 anyhow::Result;
use log::error;
use regex::Regex;
use std::iter::FromIterator;
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
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 {
@ -164,6 +177,7 @@ impl ResolvedConfig {
parent,
// Fields
label,
backend,
includes,
excludes,
extra_includes,

View File

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

View File

@ -509,7 +509,7 @@ impl LegacyConfig {
fn triggers_for_match(m: &Value) -> Vec<String> {
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()) {
vec![trigger.to_string()]
} else {
@ -517,6 +517,7 @@ fn triggers_for_match(m: &Value) -> Vec<String> {
}
}
#[allow(dead_code)]
fn replace_for_match(m: &Value) -> 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
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
pub enum ConfigLoadError {
FileNotFound,
UnableToReadFile,

View File

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