First draft of global variables. Fix #162
This commit is contained in:
parent
a6282b1a9d
commit
0dfcb9cef7
|
@ -21,7 +21,7 @@ extern crate dirs;
|
|||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs};
|
||||
use crate::matcher::Match;
|
||||
use crate::matcher::{Match, MatchVariable};
|
||||
use std::fs::{File, create_dir_all};
|
||||
use std::io::Read;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
@ -64,12 +64,13 @@ fn default_enable_active() -> bool { true }
|
|||
fn default_action_noop_interval() -> u128 { 500 }
|
||||
fn default_backspace_limit() -> i32 { 3 }
|
||||
fn default_restore_clipboard_delay() -> i32 { 300 }
|
||||
fn default_exclude_default_matches() -> bool {false}
|
||||
fn default_exclude_default_entries() -> bool {false}
|
||||
fn default_matches() -> Vec<Match> { Vec::new() }
|
||||
fn default_global_vars() -> Vec<MatchVariable> { Vec::new() }
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Configs {
|
||||
#[serde(default = "default_name")]
|
||||
#[serde(default = "default_name")]
|
||||
pub name: String,
|
||||
|
||||
#[serde(default = "default_parent")]
|
||||
|
@ -144,11 +145,15 @@ pub struct Configs {
|
|||
#[serde(default)]
|
||||
pub backend: BackendType,
|
||||
|
||||
#[serde(default = "default_exclude_default_matches")]
|
||||
pub exclude_default_matches: bool,
|
||||
#[serde(default = "default_exclude_default_entries")]
|
||||
pub exclude_default_entries: bool,
|
||||
|
||||
#[serde(default = "default_matches")]
|
||||
pub matches: Vec<Match>
|
||||
pub matches: Vec<Match>,
|
||||
|
||||
#[serde(default = "default_global_vars")]
|
||||
pub global_vars: Vec<MatchVariable>
|
||||
|
||||
}
|
||||
|
||||
// Macro used to validate config fields
|
||||
|
@ -244,29 +249,56 @@ impl Configs {
|
|||
}
|
||||
|
||||
fn merge_config(&mut self, new_config: Configs) {
|
||||
// Merge matches
|
||||
let mut merged_matches = new_config.matches;
|
||||
let mut trigger_set = HashSet::new();
|
||||
let mut match_trigger_set = HashSet::new();
|
||||
merged_matches.iter().for_each(|m| {
|
||||
trigger_set.insert(m.trigger.clone());
|
||||
match_trigger_set.insert(m.trigger.clone());
|
||||
});
|
||||
let parent_matches : Vec<Match> = self.matches.iter().filter(|&m| {
|
||||
!trigger_set.contains(&m.trigger)
|
||||
!match_trigger_set.contains(&m.trigger)
|
||||
}).cloned().collect();
|
||||
|
||||
merged_matches.extend(parent_matches);
|
||||
self.matches = merged_matches;
|
||||
|
||||
// Merge global variables
|
||||
let mut merged_global_vars = new_config.global_vars;
|
||||
let mut vars_name_set = HashSet::new();
|
||||
merged_global_vars.iter().for_each(|m| {
|
||||
vars_name_set.insert(m.name.clone());
|
||||
});
|
||||
let parent_vars : Vec<MatchVariable> = self.global_vars.iter().filter(|&m| {
|
||||
!vars_name_set.contains(&m.name)
|
||||
}).cloned().collect();
|
||||
|
||||
merged_global_vars.extend(parent_vars);
|
||||
self.global_vars = merged_global_vars;
|
||||
}
|
||||
|
||||
fn merge_default(&mut self, default: &Configs) {
|
||||
let mut trigger_set = HashSet::new();
|
||||
// Merge matches
|
||||
let mut match_trigger_set = HashSet::new();
|
||||
self.matches.iter().for_each(|m| {
|
||||
trigger_set.insert(m.trigger.clone());
|
||||
match_trigger_set.insert(m.trigger.clone());
|
||||
});
|
||||
let default_matches : Vec<Match> = default.matches.iter().filter(|&m| {
|
||||
!trigger_set.contains(&m.trigger)
|
||||
!match_trigger_set.contains(&m.trigger)
|
||||
}).cloned().collect();
|
||||
|
||||
self.matches.extend(default_matches);
|
||||
|
||||
// Merge global variables
|
||||
let mut vars_name_set = HashSet::new();
|
||||
self.global_vars.iter().for_each(|m| {
|
||||
vars_name_set.insert(m.name.clone());
|
||||
});
|
||||
let default_vars : Vec<MatchVariable> = default.global_vars.iter().filter(|&m| {
|
||||
!vars_name_set.contains(&m.name)
|
||||
}).cloned().collect();
|
||||
|
||||
self.global_vars.extend(default_vars);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,9 +389,9 @@ impl ConfigSet {
|
|||
let default= configs.get(0).unwrap().clone();
|
||||
let mut specific = (&configs[1..]).to_vec().clone();
|
||||
|
||||
// Add default matches to specific configs when needed
|
||||
// Add default entries to specific configs when needed
|
||||
for config in specific.iter_mut() {
|
||||
if !config.exclude_default_matches {
|
||||
if !config.exclude_default_entries {
|
||||
config.merge_default(&default);
|
||||
}
|
||||
}
|
||||
|
@ -849,7 +881,7 @@ mod tests {
|
|||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific2.yml", r###"
|
||||
name: specific1
|
||||
|
||||
exclude_default_matches: true
|
||||
exclude_default_entries: true
|
||||
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
|
@ -884,7 +916,7 @@ mod tests {
|
|||
let user_defined_path2 = create_user_config_file(data_dir.path(), "specific.zzz", r###"
|
||||
name: specific1
|
||||
|
||||
exclude_default_matches: true
|
||||
exclude_default_entries: true
|
||||
|
||||
matches:
|
||||
- trigger: "hello"
|
||||
|
@ -1201,4 +1233,83 @@ mod tests {
|
|||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(ConfigSet::has_conflicts(&config_set.default, &config_set.specific), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_inherits_default_global_vars() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
global_vars:
|
||||
- name: testvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
global_vars:
|
||||
- name: specificvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 1);
|
||||
assert_eq!(config_set.default.global_vars.len(), 1);
|
||||
assert_eq!(config_set.specific[0].global_vars.len(), 2);
|
||||
assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "testvar"));
|
||||
assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "specificvar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_default_get_variables_from_specific() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
global_vars:
|
||||
- name: testvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
parent: default
|
||||
global_vars:
|
||||
- name: specificvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 0);
|
||||
assert_eq!(config_set.default.global_vars.len(), 2);
|
||||
assert!(config_set.default.global_vars.iter().any(|m| m.name == "testvar"));
|
||||
assert!(config_set.default.global_vars.iter().any(|m| m.name == "specificvar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set_specific_dont_inherits_default_global_vars_when_exclude_is_on() {
|
||||
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(r###"
|
||||
global_vars:
|
||||
- name: testvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let user_defined_path = create_user_config_file(data_dir.path(), "specific.yml", r###"
|
||||
exclude_default_entries: true
|
||||
|
||||
global_vars:
|
||||
- name: specificvar
|
||||
type: date
|
||||
params:
|
||||
format: "%m"
|
||||
"###);
|
||||
|
||||
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||
assert_eq!(config_set.specific.len(), 1);
|
||||
assert_eq!(config_set.default.global_vars.len(), 1);
|
||||
assert_eq!(config_set.specific[0].global_vars.len(), 1);
|
||||
assert!(config_set.specific[0].global_vars.iter().any(|m| m.name == "specificvar"));
|
||||
}
|
||||
}
|
44
src/extension/dummy.rs
Normal file
44
src/extension/dummy.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
pub struct DummyExtension {}
|
||||
|
||||
impl DummyExtension {
|
||||
pub fn new() -> DummyExtension {
|
||||
DummyExtension{}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Extension for DummyExtension {
|
||||
fn name(&self) -> String {
|
||||
String::from("dummy")
|
||||
}
|
||||
|
||||
fn calculate(&self, params: &Mapping, _: &Vec<String>) -> Option<String> {
|
||||
let echo = params.get(&Value::from("echo"));
|
||||
|
||||
if let Some(echo) = echo {
|
||||
Some(echo.as_str().unwrap_or_default().to_owned())
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ mod date;
|
|||
mod shell;
|
||||
mod script;
|
||||
mod random;
|
||||
mod dummy;
|
||||
|
||||
pub trait Extension {
|
||||
fn name(&self) -> String;
|
||||
|
@ -35,5 +36,6 @@ pub fn get_extensions() -> Vec<Box<dyn Extension>> {
|
|||
Box::new(shell::ShellExtension::new()),
|
||||
Box::new(script::ScriptExtension::new()),
|
||||
Box::new(random::RandomExtension::new()),
|
||||
Box::new(dummy::DummyExtension::new()),
|
||||
]
|
||||
}
|
|
@ -79,10 +79,11 @@ impl super::Renderer for DefaultRenderer {
|
|||
match &m.content {
|
||||
// Text Match
|
||||
MatchContentType::Text(content) => {
|
||||
let target_string = if content._has_vars {
|
||||
let target_string = if content._has_vars || !config.global_vars.is_empty(){
|
||||
let mut output_map = HashMap::new();
|
||||
|
||||
for variable in content.vars.iter() {
|
||||
// Cycle through both the local and global variables
|
||||
for variable in config.global_vars.iter().chain(&content.vars) {
|
||||
// In case of variables of type match, we need to recursively call
|
||||
// the render function
|
||||
if variable.var_type == "match" {
|
||||
|
@ -118,7 +119,6 @@ impl super::Renderer for DefaultRenderer {
|
|||
},
|
||||
}
|
||||
}else{ // Normal extension variables
|
||||
// TODO: pass the arguments to the extension
|
||||
let extension = self.extension_map.get(&variable.var_type);
|
||||
if let Some(extension) = extension {
|
||||
let ext_out = extension.calculate(&variable.params, &args);
|
||||
|
@ -408,4 +408,77 @@ mod tests {
|
|||
|
||||
verify_render(rendered, "Hi JonSnow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_passive_local_var() {
|
||||
let text = "this is :test";
|
||||
|
||||
let config = get_config_for(r###"
|
||||
matches:
|
||||
- trigger: ':test'
|
||||
replace: "my {{output}}"
|
||||
vars:
|
||||
- name: output
|
||||
type: dummy
|
||||
params:
|
||||
echo: "result"
|
||||
"###);
|
||||
|
||||
let renderer = get_renderer(config.clone());
|
||||
|
||||
let rendered = renderer.render_passive(text, &config);
|
||||
|
||||
verify_render(rendered, "this is my result");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_passive_global_var() {
|
||||
let text = "this is :test";
|
||||
|
||||
let config = get_config_for(r###"
|
||||
global_vars:
|
||||
- name: output
|
||||
type: dummy
|
||||
params:
|
||||
echo: "result"
|
||||
matches:
|
||||
- trigger: ':test'
|
||||
replace: "my {{output}}"
|
||||
|
||||
"###);
|
||||
|
||||
let renderer = get_renderer(config.clone());
|
||||
|
||||
let rendered = renderer.render_passive(text, &config);
|
||||
|
||||
verify_render(rendered, "this is my result");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_passive_global_var_is_overridden_by_local() {
|
||||
let text = "this is :test";
|
||||
|
||||
let config = get_config_for(r###"
|
||||
global_vars:
|
||||
- name: output
|
||||
type: dummy
|
||||
params:
|
||||
echo: "result"
|
||||
matches:
|
||||
- trigger: ':test'
|
||||
replace: "my {{output}}"
|
||||
vars:
|
||||
- name: "output"
|
||||
type: dummy
|
||||
params:
|
||||
echo: "local"
|
||||
|
||||
"###);
|
||||
|
||||
let renderer = get_renderer(config.clone());
|
||||
|
||||
let rendered = renderer.render_passive(text, &config);
|
||||
|
||||
verify_render(rendered, "this is my local");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user