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