#[macro_use]
extern crate structopt;
extern crate humantime;
extern crate atty;
extern crate regex;
extern crate subprocess;
extern crate tempfile;
use std::env;
use std::f64;
use std::io::prelude::*;
use std::io::{self, BufRead, SeekFrom};
use std::thread;
use std::time::{Duration, Instant, SystemTime};
use humantime::{parse_duration, parse_rfc3339_weak};
use regex::Regex;
use subprocess::{Exec, ExitStatus, Redirection};
use structopt::StructOpt;
static UNKONWN_EXIT_CODE: u32 = 99;
fn main() {
let opt = Opt::from_args();
let count_precision = Opt::clap()
.get_matches()
.value_of("count_by")
.map(precision_of)
.unwrap_or(0);
let program_start = Instant::now();
let mut items = if let Some(items) = opt.ffor { items.clone() } else { vec![] };
if opt.stdin || atty::isnt(atty::Stream::Stdin) {
let stdin = io::stdin();
for line in stdin.lock().lines() {
items.push(line.unwrap().to_owned())
}
}
let joined_input = &opt.input.join(" ");
if joined_input == "" {
println!("No command supplied, exiting.");
return;
}
let num = if let Some(num) = opt.num {
num
} else if !items.is_empty() {
items.len() as f64
} else {
f64::INFINITY
};
let mut has_matched = false;
let mut tmpfile = tempfile::tempfile().unwrap();
let mut summary = Summary { successes: 0, failures: Vec::new() };
let mut previous_stdout = None;
let counter = Counter {
start: opt.offset - opt.count_by,
iters: 0.0,
end: num,
step_by: opt.count_by
};
for (count, actual_count) in counter.enumerate() {
let loop_start = Instant::now();
env::set_var("ACTUALCOUNT", count.to_string());
env::set_var("COUNT", format!("{:.*}", count_precision, actual_count));
if let Some(item) = items.get(count) {
env::set_var("ITEM", item);
}
tmpfile.seek(SeekFrom::Start(0)).ok();
tmpfile.set_len(0).ok();
let result = Exec::shell(joined_input)
.stdout(Redirection::File(tmpfile.try_clone().unwrap()))
.stderr(Redirection::Merge)
.capture().unwrap();
let mut stdout = String::new();
tmpfile.seek(SeekFrom::Start(0)).ok();
tmpfile.read_to_string(&mut stdout).ok();
for line in stdout.lines() {
if !opt.only_last {
println!("{}", line);
}
if let Some(string) = &opt.until_contains {
if line.contains(string){
has_matched = true;
}
}
if let Some(regex) = &opt.until_match {
if regex.captures(&line).is_some() {
has_matched = true;
}
}
}
if let Some(error_code) = &opt.until_error {
match error_code {
ErrorCode::Any => if !result.exit_status.success() {
has_matched = true;
},
ErrorCode::Code(code) => {
if result.exit_status == ExitStatus::Exited(*code) {
has_matched = true;
}
}
}
}
if opt.until_success && result.exit_status.success() {
has_matched = true;
}
if opt.until_fail && !(result.exit_status.success()) {
has_matched = true;
}
if opt.summary {
match result.exit_status {
ExitStatus::Exited(0) => summary.successes += 1,
ExitStatus::Exited(n) => summary.failures.push(n),
_ => summary.failures.push(UNKONWN_EXIT_CODE),
}
}
if has_matched {
break;
}
if let Some(duration) = opt.for_duration {
let since = Instant::now().duration_since(program_start);
if since >= duration {
break;
}
}
if let Some(until_time) = opt.until_time {
if SystemTime::now().duration_since(until_time).is_ok() {
break;
}
}
if let Some(ref previous_stdout) = previous_stdout {
if opt.until_changes {
if *previous_stdout != stdout {
break;
}
}
if opt.until_same {
if *previous_stdout == stdout {
break;
}
}
} else {
previous_stdout = Some(stdout);
}
let since = Instant::now().duration_since(loop_start);
if let Some(time) = opt.every.checked_sub(since) {
thread::sleep(time);
}
}
if opt.only_last {
let mut stdout = String::new();
tmpfile.seek(SeekFrom::Start(0)).ok();
tmpfile.read_to_string(&mut stdout).ok();
for line in stdout.lines() {
println!("{}", line);
}
}
if opt.summary {
summary.print()
}
}
#[derive(StructOpt, Debug)]
#[structopt(name = "loop", author = "Rich Jones <miserlou@gmail.com>",
about = "UNIX's missing `loop` command")]
struct Opt {
#[structopt(short = "n", long = "num")]
num: Option<f64>,
#[structopt(short = "b", long = "count-by", default_value = "1")]
count_by: f64,
#[structopt(short = "o", long = "offset", default_value = "0")]
offset: f64,
#[structopt(short = "e", long = "every", default_value = "1us",
parse(try_from_str = "parse_duration"))]
every: Duration,
#[structopt(long = "for", parse(from_str = "get_values"))]
ffor: Option<Vec<String>>,
#[structopt(short = "d", long = "for-duration", parse(try_from_str = "parse_duration"))]
for_duration: Option<Duration>,
#[structopt(short = "c", long = "until-contains")]
until_contains: Option<String>,
#[structopt(short = "C", long = "until-changes")]
until_changes: bool,
#[structopt(short = "S", long = "until-same")]
until_same: bool,
#[structopt(short = "m", long = "until-match", parse(try_from_str = "Regex::new"))]
until_match: Option<Regex>,
#[structopt(short = "t", long = "until-time", parse(try_from_str = "parse_rfc3339_weak"))]
until_time: Option<SystemTime>,
#[structopt(short = "r", long = "until-error", parse(from_str = "get_error_code"))]
until_error: Option<ErrorCode>,
#[structopt(short = "s", long = "until-success")]
until_success: bool,
#[structopt(short = "f", long = "until-fail")]
until_fail: bool,
#[structopt(short = "l", long = "only-last")]
only_last: bool,
#[structopt(short = "i", long = "stdin")]
stdin: bool,
#[structopt(long = "summary")]
summary: bool,
#[structopt(raw(multiple="true"))]
input: Vec<String>
}
fn precision_of(s: &str) -> usize {
let after_point = match s.find('.') {
Some(point) => point + 1,
None => return 0,
};
let exp = match s.find(&['e', 'E'][..]) {
Some(exp) => exp,
None => s.len(),
};
exp - after_point
}
#[derive(Debug)]
enum ErrorCode {
Any,
Code(u32),
}
fn get_error_code(input: &&str) -> ErrorCode {
if let Ok(code) = input.parse::<u32>() {
ErrorCode::Code(code)
} else {
ErrorCode::Any
}
}
fn get_values(input: &&str) -> Vec<String> {
if input.contains('\n'){
input.split('\n').map(String::from).collect()
} else if input.contains(','){
input.split(',').map(String::from).collect()
} else {
input.split(' ').map(String::from).collect()
}
}
struct Counter {
start: f64,
iters: f64,
end: f64,
step_by: f64,
}
#[derive(Debug)]
struct Summary {
successes: u32,
failures: Vec<u32>
}
impl Summary {
fn print(self) {
let total = self.successes + self.failures.len() as u32;
let errors = if self.failures.is_empty() {
String::from("0")
} else {
format!("{} ({})", self.failures.len(), self.failures.into_iter()
.map(|f| (-(f as i32)).to_string())
.collect::<Vec<String>>()
.join(", "))
};
println!("Total runs:\t{}", total);
println!("Successes:\t{}", self.successes);
println!("Failures:\t{}", errors);
}
}
impl Iterator for Counter {
type Item = f64;
fn next(&mut self) -> Option<Self::Item> {
self.start += self.step_by;
self.iters += 1.0;
if self.iters <= self.end {
Some(self.start)
} else {
None
}
}
}