mirror of
https://github.com/zaphar/runwhen.git
synced 2025-07-22 20:39:49 -04:00
Add the ability to specify environment variables.
This commit is contained in:
parent
3991f03161
commit
07120d141f
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "runwhen"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
authors = ["Jeremy Wall <jeremy@marzhillstudios.com>"]
|
||||
description = "Runs a command on user specified triggers."
|
||||
repository = "https://github.com/zaphar/runwhen"
|
||||
@ -12,4 +12,3 @@ license = "Apache-2.0"
|
||||
clap = "~2.19.0"
|
||||
humantime = "~1.0.0"
|
||||
notify = "~3.0.0"
|
||||
subprocess = "~0.1.7"
|
||||
|
@ -6,7 +6,7 @@
|
||||
Runs a command on user defined triggers.
|
||||
|
||||
USAGE:
|
||||
runwhen --cmd <cmd> [SUBCOMMAND]
|
||||
runwhen [OPTIONS] --cmd <cmd> [SUBCOMMAND]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
@ -14,6 +14,7 @@ FLAGS:
|
||||
|
||||
OPTIONS:
|
||||
-c, --cmd <cmd> Command to run on supplied triggers
|
||||
-e, --env <env>... Command to run on supplied triggers
|
||||
|
||||
SUBCOMMANDS:
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
|
@ -22,8 +22,8 @@ pub struct CommandError {
|
||||
}
|
||||
|
||||
impl CommandError {
|
||||
pub fn new(msg: String) -> CommandError {
|
||||
CommandError { msg: msg }
|
||||
pub fn new<S: Into<String>>(msg: S) -> CommandError {
|
||||
CommandError { msg: msg.into() }
|
||||
}
|
||||
}
|
||||
|
||||
|
60
src/exec.rs
60
src/exec.rs
@ -14,19 +14,53 @@
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use subprocess::{Exec, PopenError, ExitStatus};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use traits::Process;
|
||||
use error::CommandError;
|
||||
|
||||
pub fn run_cmd(cmd: &str) -> Result<(), PopenError> {
|
||||
Exec::shell(cmd).join()?;
|
||||
Ok(())
|
||||
|
||||
fn env_var_to_tuple(var: &str) -> (String, String) {
|
||||
let mut vs = var.split('=');
|
||||
if let Some(name) = vs.next() {
|
||||
return match vs.next() {
|
||||
Some(val) => (String::from(name), String::from(val)),
|
||||
None => (String::from(name), "".to_string()),
|
||||
}
|
||||
}
|
||||
("".to_string(), "".to_string())
|
||||
}
|
||||
|
||||
fn is_cmd_success(cmd: &str) -> bool {
|
||||
match Exec::shell(cmd).join() {
|
||||
Ok(ExitStatus::Exited(code)) => code == 0,
|
||||
pub fn run_cmd(cmd: &str, env: &Option<Vec<&str>>) -> Result<i32, CommandError> {
|
||||
let args = cmd.split(' ').filter(|s| !s.is_empty()).collect::<Vec<&str>>();
|
||||
if args.len() < 1 {
|
||||
return Err(CommandError::new("Empty command string passed in"));
|
||||
}
|
||||
let mut exec = Command::new(args[0]);
|
||||
if args.len() > 1 {
|
||||
exec.args(&args[1..]);
|
||||
}
|
||||
exec.stdout(Stdio::inherit());
|
||||
exec.stderr(Stdio::inherit());
|
||||
if let &Some(ref env_vars) = env {
|
||||
for var in env_vars {
|
||||
let tpl = env_var_to_tuple(var);
|
||||
exec.env(tpl.0, tpl.1);
|
||||
}
|
||||
}
|
||||
return match exec.output() {
|
||||
Ok(out) => match out.status.code() {
|
||||
Some(val) => Ok(val),
|
||||
None => Ok(0),
|
||||
},
|
||||
// TODO(jeremy): We should not swallow this error.
|
||||
Err(_) => Err(CommandError::new("Error running command")),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_cmd_success(cmd: &str, env: Option<Vec<&str>>) -> bool {
|
||||
match run_cmd(cmd, &env) {
|
||||
Ok(code) => code == 0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -34,14 +68,19 @@ fn is_cmd_success(cmd: &str) -> bool {
|
||||
pub struct ExecProcess<'a> {
|
||||
test_cmd: &'a str,
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
poll: Duration,
|
||||
}
|
||||
|
||||
impl<'a> ExecProcess<'a> {
|
||||
pub fn new(test_cmd: &'a str, cmd: &'a str, poll: Duration) -> ExecProcess<'a> {
|
||||
pub fn new(test_cmd: &'a str,
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
poll: Duration) -> ExecProcess<'a> {
|
||||
ExecProcess {
|
||||
test_cmd: test_cmd,
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
poll: poll,
|
||||
}
|
||||
}
|
||||
@ -50,8 +89,9 @@ impl<'a> ExecProcess<'a> {
|
||||
impl<'a> Process for ExecProcess<'a> {
|
||||
fn run(&self) -> Result<(), CommandError> {
|
||||
loop {
|
||||
if is_cmd_success(self.test_cmd) {
|
||||
if let Err(err) = run_cmd(self.cmd) {
|
||||
// TODO(jwall): Should we set the environment the same as the other command?
|
||||
if is_cmd_success(self.test_cmd, None) {
|
||||
if let Err(err) = run_cmd(self.cmd, &self.env) {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
}
|
||||
|
21
src/file.rs
21
src/file.rs
@ -26,6 +26,7 @@ use exec::run_cmd;
|
||||
|
||||
pub struct FileProcess<'a> {
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
file: &'a str,
|
||||
method: WatchEventType,
|
||||
poll: Duration,
|
||||
@ -33,12 +34,14 @@ pub struct FileProcess<'a> {
|
||||
|
||||
impl<'a> FileProcess<'a> {
|
||||
pub fn new(cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
file: &'a str,
|
||||
method: WatchEventType,
|
||||
poll: Duration)
|
||||
-> FileProcess<'a> {
|
||||
FileProcess {
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
file: file,
|
||||
method: method,
|
||||
poll: poll,
|
||||
@ -46,8 +49,20 @@ impl<'a> FileProcess<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String, poll: Duration) {
|
||||
fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String,
|
||||
env: Option<Vec<&str>>, poll: Duration) {
|
||||
let copied_env = env.and_then(|v| Some(v.iter().cloned().map(|s| String::from(s)).collect::<Vec<String>>()));
|
||||
thread::spawn(move || {
|
||||
let copied_env_refs: Option<Vec<&str>> = match copied_env {
|
||||
Some(ref vec) => {
|
||||
let mut refs: Vec<&str> = Vec::new();
|
||||
for s in vec.iter() {
|
||||
refs.push(s);
|
||||
}
|
||||
Some(refs)
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
loop {
|
||||
// Wait our requisit number of seconds
|
||||
thread::sleep(poll);
|
||||
@ -59,7 +74,7 @@ fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String, poll: Duration) {
|
||||
*signal = false;
|
||||
// Run our command!
|
||||
println!("exec: {}", cmd);
|
||||
if let Err(err) = run_cmd(&cmd) {
|
||||
if let Err(err) = run_cmd(&cmd, &copied_env_refs) {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
},
|
||||
@ -123,7 +138,7 @@ impl<'a> Process for FileProcess<'a> {
|
||||
// TODO(jeremy): Is this sufficent or do we want to ignore
|
||||
// any events that come in while the command is running?
|
||||
let lock = Arc::new(Mutex::new(false));
|
||||
spawn_runner_thread(lock.clone(), self.cmd.to_string(), self.poll);
|
||||
spawn_runner_thread(lock.clone(), self.cmd.to_string(), self.env.clone(), self.poll);
|
||||
wait_for_fs_events(lock, self.method.clone(), self.file)
|
||||
}
|
||||
}
|
||||
|
18
src/main.rs
18
src/main.rs
@ -16,7 +16,6 @@
|
||||
extern crate clap;
|
||||
extern crate humantime;
|
||||
extern crate notify;
|
||||
extern crate subprocess;
|
||||
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
@ -41,6 +40,7 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
||||
(author: crate_authors!())
|
||||
(about: "Runs a command on user defined triggers.")
|
||||
(@arg cmd: -c --cmd +required +takes_value "Command to run on supplied triggers")
|
||||
(@arg env: -e --env +takes_value ... "Command to run on supplied triggers")
|
||||
(@subcommand watch =>
|
||||
(about: "Trigger that fires when a file or directory changes.")
|
||||
// TODO(jeremy): We need to support filters
|
||||
@ -73,6 +73,14 @@ fn main() {
|
||||
let app = do_flags();
|
||||
// Unwrap because this flag is required.
|
||||
let cmd = app.value_of("cmd").expect("cmd flag is required");
|
||||
let mut maybe_env = None;
|
||||
if let Some(env_values) = app.values_of("env") {
|
||||
let mut env_vec = Vec::new();
|
||||
for v in env_values {
|
||||
env_vec.push(v);
|
||||
}
|
||||
maybe_env = Some(env_vec);
|
||||
}
|
||||
let mut process: Option<Box<Process>> = None;
|
||||
if let Some(matches) = app.subcommand_matches("watch") {
|
||||
// Unwrap because this flag is required.
|
||||
@ -83,7 +91,8 @@ fn main() {
|
||||
}
|
||||
let poll = matches.value_of("poll").unwrap_or("5s");
|
||||
let dur = humantime::parse_duration(poll).expect("Invalid poll value.");
|
||||
process = Some(Box::new(FileProcess::new(cmd, file, method, dur)));
|
||||
process = Some(Box::new(FileProcess::new(
|
||||
cmd, maybe_env, file, method, dur)));
|
||||
} else if let Some(matches) = app.subcommand_matches("timer") {
|
||||
// Unwrap because this flag is required.
|
||||
let dur = humantime::parse_duration(matches.value_of("duration")
|
||||
@ -102,7 +111,8 @@ fn main() {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
process = Some(Box::new(TimerProcess::new(cmd, duration, max_repeat)));
|
||||
process = Some(Box::new(TimerProcess::new(
|
||||
cmd, maybe_env, duration, max_repeat)));
|
||||
}
|
||||
Err(msg) => {
|
||||
println!("Malformed duration {:?}", msg);
|
||||
@ -114,7 +124,7 @@ fn main() {
|
||||
let ifcmd = matches.value_of("ifcmd").expect("ifcmd flag is required");
|
||||
let dur = humantime::parse_duration(matches.value_of("poll").unwrap_or("5s"));
|
||||
process = match dur {
|
||||
Ok(duration) => Some(Box::new(ExecProcess::new(ifcmd, cmd, duration))),
|
||||
Ok(duration) => Some(Box::new(ExecProcess::new(ifcmd, cmd, maybe_env, duration))),
|
||||
Err(msg) => {
|
||||
println!("Malformed poll {:?}", msg);
|
||||
process::exit(1)
|
||||
|
@ -20,14 +20,19 @@ use exec::run_cmd;
|
||||
|
||||
pub struct TimerProcess<'a> {
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
poll_duration: Duration,
|
||||
max_repeat: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> TimerProcess<'a> {
|
||||
pub fn new(cmd: &'a str, poll_duration: Duration, max_repeat: Option<u32>) -> TimerProcess<'a> {
|
||||
pub fn new(cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
poll_duration: Duration,
|
||||
max_repeat: Option<u32>) -> TimerProcess<'a> {
|
||||
TimerProcess {
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
poll_duration: poll_duration,
|
||||
max_repeat: max_repeat,
|
||||
}
|
||||
@ -41,7 +46,7 @@ impl<'a> Process for TimerProcess<'a> {
|
||||
if self.max_repeat.is_some() && counter >= self.max_repeat.unwrap() {
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(err) = run_cmd(self.cmd) {
|
||||
if let Err(err) = run_cmd(self.cmd, &self.env) {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
thread::sleep(self.poll_duration);
|
||||
|
Loading…
x
Reference in New Issue
Block a user