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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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 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)",
|
"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"
|
serde_json = "1.0.40"
|
||||||
log-panics = {version = "2.0.0", features = ["with-backtrace"]}
|
log-panics = {version = "2.0.0", features = ["with-backtrace"]}
|
||||||
backtrace = "0.3.37"
|
backtrace = "0.3.37"
|
||||||
|
chrono = "0.4.9"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.62"
|
libc = "0.2.62"
|
||||||
|
|
|
@ -3,11 +3,15 @@ use crate::keyboard::KeyboardManager;
|
||||||
use crate::config::ConfigManager;
|
use crate::config::ConfigManager;
|
||||||
use crate::config::BackendType;
|
use crate::config::BackendType;
|
||||||
use crate::clipboard::ClipboardManager;
|
use crate::clipboard::ClipboardManager;
|
||||||
use log::{info};
|
use log::{info, warn, error};
|
||||||
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
use crate::ui::{UIManager, MenuItem, MenuItemType};
|
||||||
use crate::event::{ActionEventReceiver, ActionType};
|
use crate::event::{ActionEventReceiver, ActionType};
|
||||||
|
use crate::extension::Extension;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::process::exit;
|
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>,
|
pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>,
|
||||||
U: UIManager> {
|
U: UIManager> {
|
||||||
|
@ -15,14 +19,32 @@ pub struct Engine<'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<
|
||||||
clipboard_manager: &'a C,
|
clipboard_manager: &'a C,
|
||||||
config_manager: &'a M,
|
config_manager: &'a M,
|
||||||
ui_manager: &'a U,
|
ui_manager: &'a U,
|
||||||
|
|
||||||
|
extension_map: HashMap<String, Box<dyn Extension>>,
|
||||||
|
|
||||||
enabled: RefCell<bool>,
|
enabled: RefCell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
||||||
Engine<'a, S, C, M, 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) -> 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);
|
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> {
|
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>
|
impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager>
|
||||||
MatchReceiver for Engine<'a, S, C, M, U>{
|
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);
|
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 {
|
match config.backend {
|
||||||
BackendType::Inject => {
|
BackendType::Inject => {
|
||||||
// Send the expected string. On linux, newlines are managed automatically
|
// Send the expected string. On linux, newlines are managed automatically
|
||||||
// while on windows and macos, we need to emulate a Enter key press.
|
// while on windows and macos, we need to emulate a Enter key press.
|
||||||
|
|
||||||
if cfg!(target_os = "linux") {
|
if cfg!(target_os = "linux") {
|
||||||
self.keyboard_manager.send_string(m.replace.as_str());
|
self.keyboard_manager.send_string(&target_string);
|
||||||
}else{
|
}else{
|
||||||
// To handle newlines, substitute each "\n" char with an Enter key press.
|
// 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() {
|
for (i, split) in splits.enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -89,7 +146,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BackendType::Clipboard => {
|
BackendType::Clipboard => {
|
||||||
self.clipboard_manager.set_clipboard(m.replace.as_str());
|
self.clipboard_manager.set_clipboard(&target_string);
|
||||||
self.keyboard_manager.trigger_paste();
|
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::thread;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -33,6 +36,7 @@ mod matcher;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
|
mod extension;
|
||||||
|
|
||||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
const LOG_FILE: &str = "espanso.log";
|
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 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,
|
&clipboard_manager,
|
||||||
&config_manager,
|
&config_manager,
|
||||||
&ui_manager
|
&ui_manager,
|
||||||
|
extensions,
|
||||||
);
|
);
|
||||||
|
|
||||||
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
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::{KeyEvent, KeyModifier};
|
||||||
use crate::event::KeyEventReceiver;
|
use crate::event::KeyEventReceiver;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
pub(crate) mod scrolling;
|
pub(crate) mod scrolling;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
pub struct Match {
|
pub struct Match {
|
||||||
pub trigger: String,
|
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 {
|
pub trait MatchReceiver {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user