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