Add extension mechanism and date extension
This commit is contained in:
		
							parent
							
								
									43dc66c25e
								
							
						
					
					
						commit
						b70d99c7ab
					
				
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -226,10 +226,12 @@ name = "espanso"
 | 
			
		|||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
 "log-panics 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,8 @@ fs2 = "0.4.3"
 | 
			
		|||
serde_json = "1.0.40"
 | 
			
		||||
log-panics = {version = "2.0.0", features = ["with-backtrace"]}
 | 
			
		||||
backtrace = "0.3.37"
 | 
			
		||||
chrono = "0.4.9"
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
 | 
			
		||||
[target.'cfg(unix)'.dependencies]
 | 
			
		||||
libc = "0.2.62"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,15 @@ use crate::keyboard::KeyboardManager;
 | 
			
		|||
use crate::config::ConfigManager;
 | 
			
		||||
use crate::config::BackendType;
 | 
			
		||||
use crate::clipboard::ClipboardManager;
 | 
			
		||||
use log::{info};
 | 
			
		||||
use log::{info, warn, error};
 | 
			
		||||
use crate::ui::{UIManager, MenuItem, MenuItemType};
 | 
			
		||||
use crate::event::{ActionEventReceiver, ActionType};
 | 
			
		||||
use crate::extension::Extension;
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::process::exit;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use serde_yaml::Mapping;
 | 
			
		||||
use regex::{Regex, Captures};
 | 
			
		||||
 | 
			
		||||
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
 | 
			
		||||
                  U: UIManager> {
 | 
			
		||||
| 
						 | 
				
			
			@ -15,14 +19,32 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
 | 
			
		|||
    clipboard_manager: &'a C,
 | 
			
		||||
    config_manager: &'a M,
 | 
			
		||||
    ui_manager: &'a U,
 | 
			
		||||
 | 
			
		||||
    extension_map: HashMap<String, Box<dyn Extension>>,
 | 
			
		||||
 | 
			
		||||
    enabled: RefCell<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
 | 
			
		||||
    Engine<'a, S, C, M, U> {
 | 
			
		||||
    pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C, config_manager: &'a M, ui_manager: &'a U) -> Engine<'a, S, C, M, U> {
 | 
			
		||||
    pub fn new(keyboard_manager: &'a S, clipboard_manager: &'a C,
 | 
			
		||||
               config_manager: &'a M, ui_manager: &'a U,
 | 
			
		||||
               extensions: Vec<Box<dyn Extension>>) -> Engine<'a, S, C, M, U> {
 | 
			
		||||
        // Register all the extensions
 | 
			
		||||
        let mut extension_map = HashMap::new();
 | 
			
		||||
        for extension in extensions.into_iter() {
 | 
			
		||||
            extension_map.insert(extension.name(), extension);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let enabled = RefCell::new(true);
 | 
			
		||||
        Engine{keyboard_manager, clipboard_manager, config_manager, ui_manager, enabled }
 | 
			
		||||
 | 
			
		||||
        Engine{keyboard_manager,
 | 
			
		||||
            clipboard_manager,
 | 
			
		||||
            config_manager,
 | 
			
		||||
            ui_manager,
 | 
			
		||||
            extension_map,
 | 
			
		||||
            enabled
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn build_menu(&self) -> Vec<MenuItem> {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +78,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    static ref VarRegex: Regex = Regex::new("\\{\\{\\s*(?P<name>\\w+)\\s*\\}\\}").unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
 | 
			
		||||
    MatchReceiver for Engine<'a, S, C, M, U>{
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -68,16 +94,47 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
 | 
			
		|||
 | 
			
		||||
        self.keyboard_manager.delete_string(m.trigger.len() as i32);
 | 
			
		||||
 | 
			
		||||
        let target_string = if m._has_vars {
 | 
			
		||||
            //self.extension_map.get("date").unwrap().calculate(Mapping::new()).unwrap()
 | 
			
		||||
            let mut output_map = HashMap::new();
 | 
			
		||||
 | 
			
		||||
            for variable in m.vars.iter() {
 | 
			
		||||
                let extension = self.extension_map.get(&variable.var_type);
 | 
			
		||||
                if let Some(extension) = extension {
 | 
			
		||||
                    let ext_out = extension.calculate(&variable.params);
 | 
			
		||||
                    if let Some(output) = ext_out {
 | 
			
		||||
                        output_map.insert(variable.name.clone(), output);
 | 
			
		||||
                    }else{
 | 
			
		||||
                        output_map.insert(variable.name.clone(), "".to_owned());
 | 
			
		||||
                        warn!("Could not generate output for variable: {}", variable.name);
 | 
			
		||||
                    }
 | 
			
		||||
                }else{
 | 
			
		||||
                    error!("No extension found for variable type: {}", variable.var_type);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Replace the variables
 | 
			
		||||
            let result = VarRegex.replace_all(&m.replace, |caps: &Captures| {
 | 
			
		||||
                let var_name = caps.name("name").unwrap().as_str();
 | 
			
		||||
                let output = output_map.get(var_name);
 | 
			
		||||
                output.unwrap()
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            result.to_string()
 | 
			
		||||
        }else{  // No variables, simple text substitution
 | 
			
		||||
            m.replace.clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match config.backend {
 | 
			
		||||
            BackendType::Inject => {
 | 
			
		||||
                // Send the expected string. On linux, newlines are managed automatically
 | 
			
		||||
                // while on windows and macos, we need to emulate a Enter key press.
 | 
			
		||||
 | 
			
		||||
                if cfg!(target_os = "linux") {
 | 
			
		||||
                    self.keyboard_manager.send_string(m.replace.as_str());
 | 
			
		||||
                    self.keyboard_manager.send_string(&target_string);
 | 
			
		||||
                }else{
 | 
			
		||||
                    // To handle newlines, substitute each "\n" char with an Enter key press.
 | 
			
		||||
                    let splits = m.replace.lines();
 | 
			
		||||
                    let splits = target_string.lines();
 | 
			
		||||
 | 
			
		||||
                    for (i, split) in splits.enumerate() {
 | 
			
		||||
                        if i > 0 {
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +146,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
 | 
			
		|||
                }
 | 
			
		||||
            },
 | 
			
		||||
            BackendType::Clipboard => {
 | 
			
		||||
                self.clipboard_manager.set_clipboard(m.replace.as_str());
 | 
			
		||||
                self.clipboard_manager.set_clipboard(&target_string);
 | 
			
		||||
                self.keyboard_manager.trigger_paste();
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								src/extension/date.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/extension/date.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
use serde_yaml::{Mapping, Value};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
 | 
			
		||||
pub struct DateExtension {}
 | 
			
		||||
 | 
			
		||||
impl DateExtension {
 | 
			
		||||
    pub fn new() -> DateExtension {
 | 
			
		||||
        DateExtension{}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl super::Extension for DateExtension {
 | 
			
		||||
    fn name(&self) -> String {
 | 
			
		||||
        String::from("date")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn calculate(&self, params: &Mapping) -> Option<String> {
 | 
			
		||||
        let now: DateTime<Utc> = Utc::now();
 | 
			
		||||
 | 
			
		||||
        let format = params.get(&Value::from("format"));
 | 
			
		||||
 | 
			
		||||
        let date = if let Some(format) = format {
 | 
			
		||||
            now.format(format.as_str().unwrap()).to_string()
 | 
			
		||||
        }else{
 | 
			
		||||
            now.to_rfc2822()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Some(date)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/extension/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/extension/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
use serde_yaml::Mapping;
 | 
			
		||||
 | 
			
		||||
mod date;
 | 
			
		||||
 | 
			
		||||
pub trait Extension {
 | 
			
		||||
    fn name(&self) -> String;
 | 
			
		||||
    fn calculate(&self, params: &Mapping) -> Option<String>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_extensions() -> Vec<Box<dyn Extension>> {
 | 
			
		||||
    vec![
 | 
			
		||||
        Box::new(date::DateExtension::new()),
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/main.rs
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,3 +1,6 @@
 | 
			
		|||
#[macro_use]
 | 
			
		||||
extern crate lazy_static;
 | 
			
		||||
 | 
			
		||||
use std::thread;
 | 
			
		||||
use std::fs::{File, OpenOptions};
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +36,7 @@ mod matcher;
 | 
			
		|||
mod keyboard;
 | 
			
		||||
mod protocol;
 | 
			
		||||
mod clipboard;
 | 
			
		||||
mod extension;
 | 
			
		||||
 | 
			
		||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
 | 
			
		||||
const LOG_FILE: &str = "espanso.log";
 | 
			
		||||
| 
						 | 
				
			
			@ -223,12 +227,15 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet) {
 | 
			
		|||
 | 
			
		||||
    let clipboard_manager = clipboard::get_manager();
 | 
			
		||||
 | 
			
		||||
    let manager = keyboard::get_manager();
 | 
			
		||||
    let keyboard_manager = keyboard::get_manager();
 | 
			
		||||
 | 
			
		||||
    let engine = Engine::new(&manager,
 | 
			
		||||
    let extensions = extension::get_extensions();
 | 
			
		||||
 | 
			
		||||
    let engine = Engine::new(&keyboard_manager,
 | 
			
		||||
                             &clipboard_manager,
 | 
			
		||||
                             &config_manager,
 | 
			
		||||
                             &ui_manager
 | 
			
		||||
                             &ui_manager,
 | 
			
		||||
                             extensions,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let matcher = ScrollingMatcher::new(&config_manager, &engine);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,68 @@
 | 
			
		|||
use serde::{Serialize, Deserialize};
 | 
			
		||||
use serde::{Serialize, Deserialize, Deserializer};
 | 
			
		||||
use crate::event::{KeyEvent, KeyModifier};
 | 
			
		||||
use crate::event::KeyEventReceiver;
 | 
			
		||||
use serde_yaml::Mapping;
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
 | 
			
		||||
pub(crate) mod scrolling;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
#[derive(Debug, Serialize, Clone)]
 | 
			
		||||
pub struct Match {
 | 
			
		||||
    pub trigger: String,
 | 
			
		||||
    pub replace: String
 | 
			
		||||
    pub replace: String,
 | 
			
		||||
    pub vars: Vec<MatchVariable>,
 | 
			
		||||
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub _has_vars: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <'de> serde::Deserialize<'de> for Match {
 | 
			
		||||
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
 | 
			
		||||
        D: Deserializer<'de> {
 | 
			
		||||
 | 
			
		||||
        let auto_match = AutoMatch::deserialize(deserializer)?;
 | 
			
		||||
        Ok(Match::from(&auto_match))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> From<&'a AutoMatch> for Match{
 | 
			
		||||
    fn from(other: &'a AutoMatch) -> Self {
 | 
			
		||||
        lazy_static! {
 | 
			
		||||
            static ref VarRegex: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if the match contains variables
 | 
			
		||||
        let has_vars = VarRegex.is_match(&other.replace);
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            trigger: other.trigger.clone(),
 | 
			
		||||
            replace: other.replace.clone(),
 | 
			
		||||
            vars: other.vars.clone(),
 | 
			
		||||
            _has_vars: has_vars,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Used to deserialize the Match struct before applying some custom elaboration.
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
struct AutoMatch {
 | 
			
		||||
    pub trigger: String,
 | 
			
		||||
    pub replace: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(default = "default_vars")]
 | 
			
		||||
    pub vars: Vec<MatchVariable>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_vars() -> Vec<MatchVariable> {Vec::new()}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
pub struct MatchVariable {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(rename = "type")]
 | 
			
		||||
    pub var_type: String,
 | 
			
		||||
 | 
			
		||||
    pub params: Mapping,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait MatchReceiver {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user