#![warn(missing_docs)]
#![warn(missing_doc_code_examples)]
#![allow(clippy::needless_return)]
use std::env;
use std::process::exit;
extern crate pest;
#[macro_use]
extern crate pest_derive;
mod file;
mod parser;
mod printer;
mod utils;
#[derive(Debug)]
struct Args {
raw_date: String,
debug: bool,
json: bool,
location: bool,
tag: file::Tag,
}
impl Args {
fn help(warn: bool) {
if warn {
println!("Not enough arguments provided.");
}
println!(
"A tool to manually run commands -- periodically.
Uses shell exit codes to determine control flow in shell scripts
Usage:
evry [describe duration]... <-tagname>
evry location <-tagname>
evry help
Best explained with an example:
evry 2 weeks -scrapesite && wget \"https://\" -o ....
In other words, run the wget command every 2 weeks.
evry exits with an unsuccessful exit code if the command has
been run in the last 2 weeks, which means the wget command wouldn't run.
When evry exits with a successful exit code, it saves the current time
to a metadata file for that tag (-scrapesite). That way, when evry
is run again with that tag, it can compare the current time against that file.
location prints the computed tag file location
See https://github.com/seanbreckenridge/evry for more examples."
);
exit(10)
}
fn parse_args(dir_info: &file::LocalDir) -> Self {
let args: Vec<String> = env::args().skip(1).collect();
let args_len = args.len();
if args_len >= 1 && (args[0] == "help" || args[0] == "--help") {
Args::help(false)
}
let (tag_vec, other_vec): (_, Vec<_>) =
args.into_iter().partition(|arg| arg.starts_with('-'));
if tag_vec.is_empty() || other_vec.is_empty() {
Args::help(true)
}
let tag_raw = &tag_vec[0];
let tag = tag_raw
.chars()
.next()
.map(|c| &tag_raw[c.len_utf8()..])
.expect("Error: Couldn't parse tag from arguments");
if tag.chars().count() == 0 {
eprintln!("Error: passed tag was an empty string");
}
let first_arg = &other_vec[0];
let json = env::var("EVRY_JSON").is_ok();
Args {
raw_date: other_vec.join(" "),
debug: json | env::var("EVRY_DEBUG").is_ok(),
json,
location: first_arg == "location",
tag: file::Tag::new(tag.to_string(), dir_info),
}
}
}
fn evry(dir_info: file::LocalDir, cli: Args, printer: &mut printer::Printer) -> i32 {
if cli.debug {
printer.echo("tag_name", &cli.tag.name);
let mut d = dir_info.root_dir;
d.push("data");
printer.echo("data_directory", &d.into_os_string().into_string().unwrap());
}
if cli.location {
println!("{}", cli.tag.path);
return 0;
}
let run_every = match parser::parse_time(&cli.raw_date) {
Ok(time) => time,
Err(_e) => {
printer.echo(
"error",
&format!("couldn't parse '{}' into a duration", cli.raw_date),
);
return 1; }
};
let now = utils::epoch_millis();
if cli.debug {
printer.echo(
"log",
&format!("parsed '{}' into {}ms", cli.raw_date, run_every),
);
printer.print(
printer::Message::new("duration", &format!("{}", run_every)),
Some(printer::PrinterType::Json),
);
printer.print(
printer::Message::new("duration_pretty", &utils::describe_ms(run_every)),
Some(printer::PrinterType::Json),
);
}
if !cli.tag.file_exists() {
if cli.debug {
printer.echo(
"log",
"Tag file doesn't exist, creating and exiting with code 0",
);
}
cli.tag.write(now);
return 0;
} else {
let last_ran_at = cli.tag.read_epoch_millis();
if now - last_ran_at > run_every {
if cli.debug {
printer.echo("log", &format!("Has been more than '{}' ({}ms) since last succeeded, writing to tag file, exiting with code 0", utils::describe_ms(run_every), run_every));
}
cli.tag.write(now);
return 0;
} else {
if cli.debug {
printer.echo(
"log",
&format!(
"{} ({}ms) haven't elapsed since last run, exiting with code 1",
utils::describe_ms(run_every),
run_every
),
);
let till_next_run = last_ran_at + run_every - now;
let till_next_pretty = utils::describe_ms(till_next_run);
printer.echo(
"log",
&format!(
"Will next be able to run in '{}' ({}ms)",
till_next_pretty, till_next_run
),
);
printer.print(
printer::Message::new("till_next", &format!("{}", till_next_run)),
Some(printer::PrinterType::Json),
);
printer.print(
printer::Message::new("till_next_pretty", &till_next_pretty),
Some(printer::PrinterType::Json),
);
}
return 2; }
}
}
fn main() {
let dir_info = file::LocalDir::new();
let cli = Args::parse_args(&dir_info);
let printer_type = if cli.json {
printer::PrinterType::Json
} else {
printer::PrinterType::Stderr
};
let mut printer = printer::Printer::new(printer_type);
let result = evry(dir_info, cli, &mut printer);
printer.flush();
exit(result);
}