// Copyright 2017 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // runwhen - A utility that runs commands on user defined triggers. #[macro_use] extern crate clap; extern crate humantime; extern crate notify; use std::{path::PathBuf, process, str::FromStr}; mod error; mod events; mod exec; mod file; mod timer; mod traits; use events::WatchEventType; use exec::ExecProcess; use file::FileProcess; use timer::TimerProcess; use traits::Process; #[rustfmt::skip] fn do_flags() -> clap::ArgMatches { clap::command!() .version(crate_version!()) .author(crate_authors!()) .about("Runs a command on user defined triggers.") .arg(arg!(-c --cmd).takes_value(true).help("The command to run on the trigger")) .arg(arg!(-e --env ...).takes_value(true).help("Set of environment variables to set for the command")) .subcommand( clap::Command::new("watch") .about("Trigger that fires when a file or directory changes.") .arg( arg!(-f --file) .takes_value(true).value_parser(value_parser!(PathBuf)).help("File or directory to watch for changes"), ) .arg(arg!(--touch).name("filetouch").help("Use file or directory timestamps to monitor for changes.")) .arg(arg!(--poll).value_parser(value_parser!(humantime::Duration)).help("Duration of time between polls"))) .subcommand( clap::Command::new("timer") .about("Run command on a timer") .arg(arg!(-t --duration).takes_value(true).value_parser(value_parser!(humantime::Duration)).help("Duration between runs")) .arg(arg!(-n --repeat).value_parser(value_parser!(u32))).about("Number of times to run before finishing")) .subcommand( clap::Command::new("success") .about("Run a command when a test command succeeds") .arg(arg!(--if).value_parser(value_parser!(String)).help("The command to run and check for success on")) .arg(arg!(--not).help("Negate the success of the command")) .arg(arg!(--poll).value_parser(value_parser!(humantime::Duration)).help("Duration of time between poll"))) .get_matches() } 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.to_string()); } maybe_env = Some(env_vec); } let mut proc: Box = if let Some(matches) = app.subcommand_matches("watch") { let file = match matches.values_of("file") { Some(v) => v.collect(), // The default is our current directory None => vec!["."], }; let mut method = WatchEventType::Changed; if matches.is_present("filetouch") { method = WatchEventType::Touched; } let duration = *matches .get_one::("poll") .cloned() .unwrap_or(humantime::Duration::from_str("5s").unwrap()); Box::new(FileProcess::new(cmd, maybe_env, file, method, duration)) } else if let Some(matches) = app.subcommand_matches("timer") { // TODO(jwall): This should use cancelable commands. // Unwrap because this flag is required. let duration = matches .get_one::("duration") .expect("duration flag is required") .clone(); let max_repeat = matches.get_one::("repeat").cloned(); Box::new(TimerProcess::new(cmd, maybe_env, *duration, max_repeat)) } else if let Some(matches) = app.subcommand_matches("success") { // unwrap because this is required. let ifcmd = matches.value_of("ifcmd").expect("ifcmd flag is required"); let negate = matches.is_present("not"); let duration = *matches .get_one::("poll") .cloned() .unwrap_or(humantime::Duration::from_str("5s").unwrap()); Box::new(ExecProcess::new(ifcmd, cmd, negate, maybe_env, duration)) } else { println!("You must specify a subcommand."); process::exit(1) }; match proc.run() { Ok(_) => return, Err(err) => { println!("{0}", err); process::exit(1) } } }