mirror of
https://github.com/zaphar/runwhen.git
synced 2025-07-25 21:09:51 -04:00
Update clap version and cleanup some new warnings
This commit is contained in:
parent
7bce901323
commit
eb1d17407e
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "runwhen"
|
name = "runwhen"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
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"
|
||||||
@ -9,6 +9,6 @@ keywords = ["file", "watcher", "command-line", "trigger"]
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "~2.19.0"
|
clap = "~2.34"
|
||||||
humantime = "~1.0.0"
|
humantime = "~1.0.0"
|
||||||
notify = "~3.0.0"
|
notify = "~3.0.0"
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use notify;
|
use notify;
|
||||||
@ -35,6 +34,6 @@ impl fmt::Display for CommandError {
|
|||||||
|
|
||||||
impl From<notify::Error> for CommandError {
|
impl From<notify::Error> for CommandError {
|
||||||
fn from(e: notify::Error) -> CommandError {
|
fn from(e: notify::Error) -> CommandError {
|
||||||
CommandError::new(e.description().to_string())
|
CommandError::new(format!("{}", e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
src/file.rs
102
src/file.rs
@ -11,18 +11,18 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use std::thread;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use notify::{Watcher, RecursiveMode, watcher};
|
use notify::{watcher, RecursiveMode, Watcher};
|
||||||
|
|
||||||
use traits::Process;
|
|
||||||
use error::CommandError;
|
use error::CommandError;
|
||||||
use events::WatchEventType;
|
use events::WatchEventType;
|
||||||
use exec::run_cmd;
|
use exec::run_cmd;
|
||||||
|
use traits::Process;
|
||||||
|
|
||||||
pub struct FileProcess<'a> {
|
pub struct FileProcess<'a> {
|
||||||
cmd: &'a str,
|
cmd: &'a str,
|
||||||
@ -33,12 +33,13 @@ pub struct FileProcess<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FileProcess<'a> {
|
impl<'a> FileProcess<'a> {
|
||||||
pub fn new(cmd: &'a str,
|
pub fn new(
|
||||||
env: Option<Vec<&'a str>>,
|
cmd: &'a str,
|
||||||
file: &'a str,
|
env: Option<Vec<&'a str>>,
|
||||||
method: WatchEventType,
|
file: &'a str,
|
||||||
poll: Duration)
|
method: WatchEventType,
|
||||||
-> FileProcess<'a> {
|
poll: Duration,
|
||||||
|
) -> FileProcess<'a> {
|
||||||
FileProcess {
|
FileProcess {
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
env: env,
|
env: env,
|
||||||
@ -49,9 +50,20 @@ impl<'a> FileProcess<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String,
|
fn spawn_runner_thread(
|
||||||
env: Option<Vec<&str>>, poll: Duration) {
|
lock: Arc<Mutex<bool>>,
|
||||||
let copied_env = env.and_then(|v| Some(v.iter().cloned().map(|s| String::from(s)).collect::<Vec<String>>()));
|
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 {
|
let copied_env_refs: Option<Vec<&str>> = match copied_env {
|
||||||
Some(ref vec) => {
|
Some(ref vec) => {
|
||||||
@ -60,7 +72,7 @@ fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String,
|
|||||||
refs.push(s);
|
refs.push(s);
|
||||||
}
|
}
|
||||||
Some(refs)
|
Some(refs)
|
||||||
},
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
@ -68,34 +80,37 @@ fn spawn_runner_thread(lock: Arc<Mutex<bool>>, cmd: String,
|
|||||||
thread::sleep(poll);
|
thread::sleep(poll);
|
||||||
// Default to not running the command.
|
// Default to not running the command.
|
||||||
match lock.lock() {
|
match lock.lock() {
|
||||||
Ok(mut signal) => if *signal {
|
Ok(mut signal) => {
|
||||||
// set signal to false so we won't trigger on the
|
if *signal {
|
||||||
// next loop iteration unless we recieved more events.
|
// set signal to false so we won't trigger on the
|
||||||
*signal = false;
|
// next loop iteration unless we recieved more events.
|
||||||
// Run our command!
|
*signal = false;
|
||||||
println!("exec: {}", cmd);
|
// Run our command!
|
||||||
if let Err(err) = run_cmd(&cmd, &copied_env_refs) {
|
println!("exec: {}", cmd);
|
||||||
println!("{:?}", err)
|
if let Err(err) = run_cmd(&cmd, &copied_env_refs) {
|
||||||
|
println!("{:?}", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Unexpected error; {}", err);
|
println!("Unexpected error; {}", err);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_for_fs_events(lock: Arc<Mutex<bool>>,
|
fn wait_for_fs_events(
|
||||||
method: WatchEventType,
|
lock: Arc<Mutex<bool>>,
|
||||||
file: &str)
|
method: WatchEventType,
|
||||||
-> Result<(), CommandError> {
|
file: &str,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
// Notify requires a channel for communication.
|
// Notify requires a channel for communication.
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let mut watcher = try!(watcher(tx, Duration::from_secs(1)));
|
let mut watcher = watcher(tx, Duration::from_secs(1))?;
|
||||||
// TODO(jwall): Better error handling.
|
// TODO(jwall): Better error handling.
|
||||||
try!(watcher.watch(file, RecursiveMode::Recursive));
|
watcher.watch(file, RecursiveMode::Recursive)?;
|
||||||
println!("Watching {:?}", file);
|
println!("Watching {:?}", file);
|
||||||
loop {
|
loop {
|
||||||
let evt: WatchEventType = match rx.recv() {
|
let evt: WatchEventType = match rx.recv() {
|
||||||
@ -115,15 +130,13 @@ fn wait_for_fs_events(lock: Arc<Mutex<bool>>,
|
|||||||
*signal = true;
|
*signal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WatchEventType::Changed => {
|
WatchEventType::Changed => match lock.lock() {
|
||||||
match lock.lock() {
|
Ok(mut signal) => *signal = true,
|
||||||
Ok(mut signal) => *signal = true,
|
Err(err) => {
|
||||||
Err(err) => {
|
println!("Unexpected error; {}", err);
|
||||||
println!("Unexpected error; {}", err);
|
return Ok(());
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,12 +146,19 @@ impl<'a> Process for FileProcess<'a> {
|
|||||||
// NOTE(jwall): this is necessary because notify::fsEventWatcher panics
|
// NOTE(jwall): this is necessary because notify::fsEventWatcher panics
|
||||||
// if the path doesn't exist. :-(
|
// if the path doesn't exist. :-(
|
||||||
if !Path::new(self.file).exists() {
|
if !Path::new(self.file).exists() {
|
||||||
return Err(CommandError::new(format!("No such path! {0}", self.file).to_string()));
|
return Err(CommandError::new(
|
||||||
|
format!("No such path! {0}", self.file).to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// 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.env.clone(), 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
src/main.rs
47
src/main.rs
@ -20,18 +20,18 @@ extern crate notify;
|
|||||||
use std::process;
|
use std::process;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
mod traits;
|
|
||||||
mod file;
|
|
||||||
mod timer;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod events;
|
mod events;
|
||||||
mod exec;
|
mod exec;
|
||||||
|
mod file;
|
||||||
|
mod timer;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
use traits::Process;
|
use events::WatchEventType;
|
||||||
|
use exec::ExecProcess;
|
||||||
use file::FileProcess;
|
use file::FileProcess;
|
||||||
use timer::TimerProcess;
|
use timer::TimerProcess;
|
||||||
use exec::ExecProcess;
|
use traits::Process;
|
||||||
use events::WatchEventType;
|
|
||||||
|
|
||||||
fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
||||||
clap_app!(
|
clap_app!(
|
||||||
@ -68,7 +68,7 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
|||||||
"How frequently to test command (default 5s)")
|
"How frequently to test command (default 5s)")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.get_matches()
|
.get_matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -83,7 +83,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
maybe_env = Some(env_vec);
|
maybe_env = Some(env_vec);
|
||||||
}
|
}
|
||||||
let mut process: Option<Box<Process>> = None;
|
let mut process: Option<Box<dyn 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.
|
||||||
let file = matches.value_of("file").unwrap_or(".");
|
let file = matches.value_of("file").unwrap_or(".");
|
||||||
@ -94,11 +94,15 @@ 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(
|
process = Some(Box::new(FileProcess::new(
|
||||||
cmd, maybe_env, file, method, dur)));
|
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(
|
||||||
.expect("duration flag is required"));
|
matches
|
||||||
|
.value_of("duration")
|
||||||
|
.expect("duration flag is required"),
|
||||||
|
);
|
||||||
match dur {
|
match dur {
|
||||||
Ok(duration) => {
|
Ok(duration) => {
|
||||||
let max_repeat = if let Some(val) = matches.value_of("repeat") {
|
let max_repeat = if let Some(val) = matches.value_of("repeat") {
|
||||||
@ -114,7 +118,8 @@ fn main() {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
process = Some(Box::new(TimerProcess::new(
|
process = Some(Box::new(TimerProcess::new(
|
||||||
cmd, maybe_env, duration, max_repeat)));
|
cmd, maybe_env, duration, max_repeat,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
println!("Malformed duration {:?}", msg);
|
println!("Malformed duration {:?}", msg);
|
||||||
@ -127,7 +132,9 @@ fn main() {
|
|||||||
let negate = matches.is_present("not");
|
let negate = matches.is_present("not");
|
||||||
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, negate, maybe_env, duration))),
|
Ok(duration) => Some(Box::new(ExecProcess::new(
|
||||||
|
ifcmd, cmd, negate, maybe_env, duration,
|
||||||
|
))),
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
println!("Malformed poll {:?}", msg);
|
println!("Malformed poll {:?}", msg);
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
@ -135,15 +142,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match process {
|
match process {
|
||||||
Some(process) => {
|
Some(process) => match process.run() {
|
||||||
match process.run() {
|
Ok(_) => return,
|
||||||
Ok(_) => return,
|
Err(err) => {
|
||||||
Err(err) => {
|
println!("{0}", err);
|
||||||
println!("{0}", err);
|
process::exit(1)
|
||||||
process::exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
None => {
|
None => {
|
||||||
println!("You must specify a subcommand.");
|
println!("You must specify a subcommand.");
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user