espanso/src/extension/script.rs

169 lines
5.3 KiB
Rust

/*
* This file is part of espanso.
*
* Copyright (C) 2019 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 log::{error, warn};
use serde_yaml::{Mapping, Value};
use std::path::PathBuf;
use std::process::Command;
pub struct ScriptExtension {}
impl ScriptExtension {
pub fn new() -> ScriptExtension {
ScriptExtension {}
}
}
impl super::Extension for ScriptExtension {
fn name(&self) -> String {
String::from("script")
}
fn calculate(&self, params: &Mapping, user_args: &Vec<String>) -> Option<String> {
let args = params.get(&Value::from("args"));
if args.is_none() {
warn!("No 'args' parameter specified for script variable");
return None;
}
let args = args.unwrap().as_sequence();
if let Some(args) = args {
let mut str_args = args
.iter()
.map(|arg| arg.as_str().unwrap_or_default().to_string())
.collect::<Vec<String>>();
// The user has to enable argument concatenation explicitly
let inject_args = params
.get(&Value::from("inject_args"))
.unwrap_or(&Value::from(false))
.as_bool()
.unwrap_or(false);
if inject_args {
str_args.extend(user_args.clone());
}
// Replace %HOME% with current user home directory to
// create cross-platform paths. See issue #265
let home_dir = dirs::home_dir().unwrap_or_default();
str_args.iter_mut().for_each(|arg| {
if arg.contains("%HOME%") {
*arg = arg.replace("%HOME%", &home_dir.to_string_lossy().to_string());
}
// On Windows, correct paths separators
if cfg!(target_os = "windows") {
let path = PathBuf::from(&arg);
if path.exists() {
*arg = path.to_string_lossy().to_string()
}
}
});
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 {
command.args(&str_args[1..]).output()
} else {
command.output()
};
match output {
Ok(output) => {
let output_str = String::from_utf8_lossy(output.stdout.as_slice());
let error_str = String::from_utf8_lossy(output.stderr.as_slice());
let error_str = error_str.to_string();
let error_str = error_str.trim();
// Print stderror if present
if !error_str.is_empty() {
warn!("Script command reported error: \n{}", error_str);
}
return Some(output_str.into_owned());
}
Err(e) => {
error!("Could not execute script '{:?}', error: {}", args, e);
return None;
}
}
}
error!("Could not execute script with args '{:?}'", args);
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extension::Extension;
#[test]
#[cfg(not(target_os = "windows"))]
fn test_script_basic() {
let mut params = Mapping::new();
params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec![]);
assert!(output.is_some());
assert_eq!(output.unwrap(), "hello world\n");
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_script_inject_args_off() {
let mut params = Mapping::new();
params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec!["jon".to_owned()]);
assert!(output.is_some());
assert_eq!(output.unwrap(), "hello world\n");
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_script_inject_args_on() {
let mut params = Mapping::new();
params.insert(
Value::from("args"),
Value::from(vec!["echo", "hello world"]),
);
params.insert(Value::from("inject_args"), Value::from(true));
let extension = ScriptExtension::new();
let output = extension.calculate(&params, &vec!["jon".to_owned()]);
assert!(output.is_some());
assert_eq!(output.unwrap(), "hello world jon\n");
}
}