Merge pull request #299 from federico-terzi/dev

Version 0.6.1
This commit is contained in:
Federico Terzi 2020-05-29 22:29:44 +02:00 committed by GitHub
commit 195edbe9d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 121 deletions

2
Cargo.lock generated
View File

@ -366,7 +366,7 @@ dependencies = [
[[package]] [[package]]
name = "espanso" name = "espanso"
version = "0.6.0" version = "0.6.1"
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)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "espanso" name = "espanso"
version = "0.6.0" version = "0.6.1"
authors = ["Federico Terzi <federicoterzi96@gmail.com>"] authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
license = "GPL-3.0" license = "GPL-3.0"
description = "Cross-platform Text Expander written in Rust" description = "Cross-platform Text Expander written in Rust"

View File

@ -1,5 +1,5 @@
name: espanso name: espanso
version: 0.6.0 version: 0.6.1
summary: A Cross-platform Text Expander written in Rust summary: A Cross-platform Text Expander written in Rust
description: | description: |
espanso is a Cross-platform, Text Expander written in Rust. espanso is a Cross-platform, Text Expander written in Rust.

View File

@ -31,7 +31,7 @@ use std::fs;
use std::fs::{create_dir_all, File}; use std::fs::{create_dir_all, File};
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use walkdir::WalkDir; use walkdir::{DirEntry, WalkDir};
pub(crate) mod runtime; pub(crate) mod runtime;
@ -409,7 +409,7 @@ impl Configs {
} }
} }
fn merge_config(&mut self, new_config: Configs) { fn merge_overwrite(&mut self, new_config: Configs) {
// Merge matches // Merge matches
let mut merged_matches = new_config.matches; let mut merged_matches = new_config.matches;
let mut match_trigger_set = HashSet::new(); let mut match_trigger_set = HashSet::new();
@ -447,7 +447,7 @@ impl Configs {
self.global_vars = merged_global_vars; self.global_vars = merged_global_vars;
} }
fn merge_default(&mut self, default: &Configs) { fn merge_no_overwrite(&mut self, default: &Configs) {
// Merge matches // Merge matches
let mut match_trigger_set = HashSet::new(); let mut match_trigger_set = HashSet::new();
self.matches.iter().for_each(|m| { self.matches.iter().for_each(|m| {
@ -503,7 +503,7 @@ impl ConfigSet {
eprintln!("Warning: Using Auto backend is only supported on Linux, falling back to Inject backend."); eprintln!("Warning: Using Auto backend is only supported on Linux, falling back to Inject backend.");
} }
// Analyze which config files has to be loaded // Analyze which config files have to be loaded
let mut target_files = Vec::new(); let mut target_files = Vec::new();
@ -513,20 +513,26 @@ impl ConfigSet {
target_files.extend(dir_entry); target_files.extend(dir_entry);
} }
if package_dir.exists() { let package_files = if package_dir.exists() {
let dir_entry = WalkDir::new(package_dir); let dir_entry = WalkDir::new(package_dir);
target_files.extend(dir_entry); dir_entry.into_iter().collect()
} } else {
vec![]
};
// Load the user defined config files // Load the user defined config files
let mut name_set = HashSet::new(); let mut name_set = HashSet::new();
let mut children_map: HashMap<String, Vec<Configs>> = HashMap::new(); let mut children_map: HashMap<String, Vec<Configs>> = HashMap::new();
let mut package_map: HashMap<String, Vec<Configs>> = HashMap::new();
let mut root_configs = Vec::new(); let mut root_configs = Vec::new();
root_configs.push(default); root_configs.push(default);
for entry in target_files { let mut file_loader = |entry: walkdir::Result<DirEntry>,
if let Ok(entry) = entry { dest_map: &mut HashMap<String, Vec<Configs>>|
-> Result<(), ConfigLoadError> {
match entry {
Ok(entry) => {
let path = entry.path(); let path = entry.path();
// Skip non-yaml config files // Skip non-yaml config files
@ -537,7 +543,7 @@ impl ConfigSet {
.unwrap_or_default() .unwrap_or_default()
!= "yml" != "yml"
{ {
continue; return Ok(());
} }
// Skip hidden files // Skip hidden files
@ -548,7 +554,7 @@ impl ConfigSet {
.unwrap_or_default() .unwrap_or_default()
.starts_with(".") .starts_with(".")
{ {
continue; return Ok(());
} }
let mut config = Configs::load_config(&path)?; let mut config = Configs::load_config(&path)?;
@ -574,21 +580,41 @@ impl ConfigSet {
root_configs.push(config); root_configs.push(config);
} else { } else {
// Children config // Children config
let children_vec = children_map.entry(config.parent.clone()).or_default(); let children_vec = dest_map.entry(config.parent.clone()).or_default();
children_vec.push(config); children_vec.push(config);
} }
} else { }
eprintln!( Err(e) => {
"Warning: Unable to read config file: {}", eprintln!("Warning: Unable to read config file: {}", e);
entry.unwrap_err()
)
} }
} }
Ok(())
};
// Load the default and user specific configs
for entry in target_files {
file_loader(entry, &mut children_map)?;
}
// Load the package related configs
for entry in package_files {
file_loader(entry, &mut package_map)?;
}
// Merge the children config files // Merge the children config files
let mut configs = Vec::new(); let mut configs_without_packages = Vec::new();
for root_config in root_configs { for root_config in root_configs {
let config = ConfigSet::reduce_configs(root_config, &children_map); let config = ConfigSet::reduce_configs(root_config, &children_map, true);
configs_without_packages.push(config);
}
// Merge package files
// Note: we need two different steps as the packages have a lower priority
// than configs.
let mut configs = Vec::new();
for root_config in configs_without_packages {
let config = ConfigSet::reduce_configs(root_config, &package_map, false);
configs.push(config); configs.push(config);
} }
@ -599,7 +625,7 @@ impl ConfigSet {
// Add default entries to specific configs when needed // Add default entries to specific configs when needed
for config in specific.iter_mut() { for config in specific.iter_mut() {
if !config.exclude_default_entries { if !config.exclude_default_entries {
config.merge_default(&default); config.merge_no_overwrite(&default);
} }
} }
@ -618,12 +644,21 @@ impl ConfigSet {
Ok(ConfigSet { default, specific }) Ok(ConfigSet { default, specific })
} }
fn reduce_configs(target: Configs, children_map: &HashMap<String, Vec<Configs>>) -> Configs { fn reduce_configs(
target: Configs,
children_map: &HashMap<String, Vec<Configs>>,
higher_priority: bool,
) -> Configs {
if children_map.contains_key(&target.name) { if children_map.contains_key(&target.name) {
let mut target = target; let mut target = target;
for children in children_map.get(&target.name).unwrap() { for children in children_map.get(&target.name).unwrap() {
let children = Self::reduce_configs(children.clone(), children_map); let children =
target.merge_config(children); Self::reduce_configs(children.clone(), children_map, higher_priority);
if higher_priority {
target.merge_overwrite(children);
} else {
target.merge_no_overwrite(&children);
}
} }
target target
} else { } else {
@ -1480,6 +1515,40 @@ mod tests {
.any(|m| m.triggers[0] == "harry")); .any(|m| m.triggers[0] == "harry"));
} }
#[test]
fn test_config_set_package_configs_lower_priority_than_user() {
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
r###"
matches:
- trigger: hasta
replace: Hasta la vista
"###,
);
create_package_file(
package_dir.path(),
"package1",
"package.yml",
r###"
parent: default
matches:
- trigger: "hasta"
replace: "potter"
"###,
);
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
assert_eq!(config_set.specific.len(), 0);
assert_eq!(config_set.default.matches.len(), 1);
if let MatchContentType::Text(content) = config_set.default.matches[0].content.clone() {
assert_eq!(config_set.default.matches[0].triggers[0], "hasta");
assert_eq!(content.replace, "Hasta la vista")
} else {
panic!("invalid content");
}
}
#[test] #[test]
fn test_config_set_package_configs_without_merge() { fn test_config_set_package_configs_without_merge() {
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content( let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(

View File

@ -75,10 +75,15 @@ impl super::Extension for ScriptExtension {
} }
}); });
let mut command = Command::new(&str_args[0]);
// Inject the $CONFIG variable
command.env("CONFIG", crate::context::get_config_dir());
let output = if str_args.len() > 1 { let output = if str_args.len() > 1 {
Command::new(&str_args[0]).args(&str_args[1..]).output() command.args(&str_args[1..]).output()
} else { } else {
Command::new(&str_args[0]).output() command.output()
}; };
match output { match output {

View File

@ -40,15 +40,38 @@ pub enum Shell {
impl Shell { impl Shell {
fn execute_cmd(&self, cmd: &str) -> std::io::Result<Output> { fn execute_cmd(&self, cmd: &str) -> std::io::Result<Output> {
match self { let mut command = match self {
Shell::Cmd => Command::new("cmd").args(&["/C", &cmd]).output(), Shell::Cmd => {
Shell::Powershell => Command::new("powershell") let mut command = Command::new("cmd");
.args(&["-Command", &cmd]) command.args(&["/C", &cmd]);
.output(), command
Shell::WSL => Command::new("wsl").args(&["bash", "-c", &cmd]).output(), },
Shell::Bash => Command::new("bash").args(&["-c", &cmd]).output(), Shell::Powershell => {
Shell::Sh => Command::new("sh").args(&["-c", &cmd]).output(), let mut command = Command::new("powershell");
} command.args(&["-Command", &cmd]);
command
},
Shell::WSL => {
let mut command = Command::new("wsl");
command.args(&["bash", "-c", &cmd]);
command
},
Shell::Bash => {
let mut command = Command::new("bash");
command.args(&["-c", &cmd]);
command
},
Shell::Sh => {
let mut command = Command::new("sh");
command.args(&["-c", &cmd]);
command
},
};
// Inject the $CONFIG variable
command.env("CONFIG", crate::context::get_config_dir());
command.output()
} }
fn from_string(shell: &str) -> Option<Shell> { fn from_string(shell: &str) -> Option<Shell> {
@ -143,14 +166,16 @@ impl super::Extension for ShellExtension {
// If specified, trim the output // If specified, trim the output
let trim_opt = params.get(&Value::from("trim")); let trim_opt = params.get(&Value::from("trim"));
if let Some(value) = trim_opt { let should_trim = if let Some(value) = trim_opt {
let val = value.as_bool(); let val = value.as_bool();
if let Some(val) = val { val.unwrap_or(true)
if val { }else{
true
};
if should_trim {
output_str = output_str.trim().to_owned() output_str = output_str.trim().to_owned()
} }
}
}
Some(output_str) Some(output_str)
} }
@ -168,9 +193,10 @@ mod tests {
use crate::extension::Extension; use crate::extension::Extension;
#[test] #[test]
fn test_shell_basic() { fn test_shell_not_trimmed() {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert(Value::from("cmd"), Value::from("echo \"hello world\"")); params.insert(Value::from("cmd"), Value::from("echo \"hello world\""));
params.insert(Value::from("trim"), Value::from(false));
let extension = ShellExtension::new(); let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![]);
@ -185,10 +211,9 @@ mod tests {
} }
#[test] #[test]
fn test_shell_trimmed() { fn test_shell_basic() {
let mut params = Mapping::new(); let mut params = Mapping::new();
params.insert(Value::from("cmd"), Value::from("echo \"hello world\"")); params.insert(Value::from("cmd"), Value::from("echo \"hello world\""));
params.insert(Value::from("trim"), Value::from(true));
let extension = ShellExtension::new(); let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![]);
@ -205,8 +230,6 @@ mod tests {
Value::from("echo \" hello world \""), Value::from("echo \" hello world \""),
); );
params.insert(Value::from("trim"), Value::from(true));
let extension = ShellExtension::new(); let extension = ShellExtension::new();
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![]);
@ -224,11 +247,7 @@ mod tests {
let output = extension.calculate(&params, &vec![]); let output = extension.calculate(&params, &vec![]);
assert!(output.is_some()); assert!(output.is_some());
if cfg!(target_os = "windows") { assert_eq!(output.unwrap(), "hello world");
assert_eq!(output.unwrap(), "hello world\r\n");
} else {
assert_eq!(output.unwrap(), "hello world\n");
}
} }
#[test] #[test]
@ -256,7 +275,7 @@ mod tests {
assert!(output.is_some()); assert!(output.is_some());
assert_eq!(output.unwrap(), "hello\n"); assert_eq!(output.unwrap(), "hello");
} }
#[test] #[test]
@ -270,6 +289,6 @@ mod tests {
assert!(output.is_some()); assert!(output.is_some());
assert_eq!(output.unwrap(), "hello\r\n"); assert_eq!(output.unwrap(), "hello");
} }
} }

View File

@ -381,10 +381,31 @@ fn daemon_main(config_set: ConfigSet) {
info!("spawning worker process..."); info!("spawning worker process...");
let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location"); let espanso_path = std::env::current_exe().expect("unable to obtain espanso path location");
crate::process::spawn_process( let mut child = crate::process::spawn_process(
&espanso_path.to_string_lossy().to_string(), &espanso_path.to_string_lossy().to_string(),
&vec!["worker".to_owned()], &vec!["worker".to_owned()],
)
.expect("unable to create worker process");
// Create a monitor thread that will exit with the same non-zero code if
// the worker thread exits
thread::Builder::new()
.name("worker monitor".to_string())
.spawn(move || {
let result = child.wait();
if let Ok(status) = result {
if let Some(code) = status.code() {
if code != 0 {
error!(
"worker process exited with non-zero code: {}, exiting",
code
); );
std::process::exit(code);
}
}
}
})
.expect("Unable to spawn worker monitor thread");
std::thread::sleep(Duration::from_millis(200)); std::thread::sleep(Duration::from_millis(200));

View File

@ -18,29 +18,19 @@
*/ */
use log::warn; use log::warn;
use widestring::WideCString; use std::io;
use std::process::{Child, Command, Stdio};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn spawn_process(cmd: &str, args: &Vec<String>) { pub fn spawn_process(cmd: &str, args: &Vec<String>) -> io::Result<Child> {
let quoted_args: Vec<String> = args.iter().map(|arg| format!("\"{}\"", arg)).collect(); use std::os::windows::process::CommandExt;
let quoted_args = quoted_args.join(" "); Command::new(cmd)
let final_cmd = format!("\"{}\" {}", cmd, quoted_args); .creation_flags(0x00000008) // Detached Process
unsafe { .args(args)
let cmd_wstr = WideCString::from_str(&final_cmd); .spawn()
if let Ok(string) = cmd_wstr {
let res = crate::bridge::windows::start_process(string.as_ptr());
if res < 0 {
warn!("unable to start process: {}", final_cmd);
}
} else {
warn!("unable to convert process string into wide format")
}
}
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn spawn_process(cmd: &str, args: &Vec<String>) { pub fn spawn_process(cmd: &str, args: &Vec<String>) -> io::Result<Child> {
use std::process::{Command, Stdio}; Command::new(cmd).args(args).spawn()
Command::new(cmd).args(args).spawn();
} }