From 08f8898b25262d202145c44c0192f7e34c2a8d2b Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 20:52:12 +0100 Subject: [PATCH 01/56] apply rustfmt --- Cargo.lock | 2 +- Cargo.toml | 1 + src/main.rs | 93 ++++++++++++++++++++++++++++++++++------------------- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f80144..7ce4712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "loop-rs" -version = "0.6.0" +version = "0.6.1" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 90abedb..33652df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "loop-rs" version = "0.6.1" authors = ["Rich Jones "] description = "UNIX's missing loop command" +edition = "2015" documentation = "https://github.com/Miserlou/Loop" homepage = "https://github.com/Miserlou/Loop" diff --git a/src/main.rs b/src/main.rs index 8f0fc94..0611e95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate structopt; -extern crate humantime; extern crate atty; +extern crate humantime; extern crate regex; extern crate subprocess; extern crate tempfile; @@ -16,8 +16,8 @@ use std::time::{Duration, Instant, SystemTime}; use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; -use subprocess::{Exec, ExitStatus, Redirection}; use structopt::StructOpt; +use subprocess::{Exec, ExitStatus, Redirection}; static UNKONWN_EXIT_CODE: u32 = 99; @@ -25,7 +25,6 @@ static UNKONWN_EXIT_CODE: u32 = 99; static TIMEOUT_EXIT_CODE: i32 = 124; fn main() { - // Load the CLI arguments let opt = Opt::from_args(); let count_precision = Opt::clap() @@ -40,7 +39,11 @@ fn main() { let program_start = Instant::now(); // Number of iterations - let mut items = if let Some(items) = opt.ffor { items.clone() } else { vec![] }; + let mut items = if let Some(items) = opt.ffor { + items.clone() + } else { + vec![] + }; // Get any lines from stdin if opt.stdin || atty::isnt(atty::Stream::Stdin) { @@ -66,17 +69,19 @@ fn main() { }; let mut has_matched = false; let mut tmpfile = tempfile::tempfile().unwrap(); - let mut summary = Summary { successes: 0, failures: Vec::new() }; + 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 + start: opt.offset - opt.count_by, + iters: 0.0, + end: num, + step_by: opt.count_by, }; for (count, actual_count) in counter.enumerate() { - // Time Start let loop_start = Instant::now(); @@ -116,7 +121,8 @@ fn main() { let result = Exec::shell(joined_input) .stdout(Redirection::File(tmpfile.try_clone().unwrap())) .stderr(Redirection::Merge) - .capture().unwrap(); + .capture() + .unwrap(); // Print the results let mut stdout = String::new(); @@ -133,7 +139,7 @@ fn main() { // --until-contains // We defer loop breaking until the entire result is printed. if let Some(string) = &opt.until_contains { - if line.contains(string){ + if line.contains(string) { has_matched = true; } } @@ -149,10 +155,12 @@ fn main() { // --until-error if let Some(error_code) = &opt.until_error { match error_code { - ErrorCode::Any => if !result.exit_status.success() { - has_matched = true; - }, - ErrorCode::Code(code) => { + ErrorCode::Any => { + if !result.exit_status.success() { + has_matched = true; + } + } + ErrorCode::Code(code) => { if result.exit_status == ExitStatus::Exited(*code) { has_matched = true; } @@ -162,17 +170,17 @@ fn main() { // --until-success if opt.until_success && result.exit_status.success() { - has_matched = true; + has_matched = true; } // --until-fail if opt.until_fail && !(result.exit_status.success()) { - has_matched = true; + has_matched = true; } if opt.summary { match result.exit_status { - ExitStatus::Exited(0) => summary.successes += 1, + ExitStatus::Exited(0) => summary.successes += 1, ExitStatus::Exited(n) => summary.failures.push(n), _ => summary.failures.push(UNKONWN_EXIT_CODE), } @@ -224,8 +232,11 @@ fn main() { } #[derive(StructOpt, Debug)] -#[structopt(name = "loop", author = "Rich Jones ", - about = "UNIX's missing `loop` command")] +#[structopt( + name = "loop", + author = "Rich Jones ", + about = "UNIX's missing `loop` command" +)] struct Opt { /// Number of iterations to execute #[structopt(short = "n", long = "num")] @@ -240,8 +251,12 @@ struct Opt { offset: f64, /// How often to iterate. ex., 5s, 1h1m1s1ms1us - #[structopt(short = "e", long = "every", default_value = "1us", - parse(try_from_str = "parse_duration"))] + #[structopt( + short = "e", + long = "every", + default_value = "1us", + parse(try_from_str = "parse_duration") + )] every: Duration, /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue @@ -249,7 +264,11 @@ struct Opt { ffor: Option>, /// Keep going until the duration has elapsed (example 1m30s) - #[structopt(short = "d", long = "for-duration", parse(try_from_str = "parse_duration"))] + #[structopt( + short = "d", + long = "for-duration", + parse(try_from_str = "parse_duration") + )] for_duration: Option, /// Keep going until the output contains this string @@ -269,7 +288,11 @@ struct Opt { until_match: Option, /// Keep going until a future time, ex. "2018-04-20 04:20:00" (Times in UTC.) - #[structopt(short = "t", long = "until-time", parse(try_from_str = "parse_rfc3339_weak"))] + #[structopt( + short = "t", + long = "until-time", + parse(try_from_str = "parse_rfc3339_weak") + )] until_time: Option, /// Keep going until the command exit status is non-zero, or the value given @@ -301,9 +324,8 @@ struct Opt { summary: bool, /// The command to be looped - #[structopt(raw(multiple="true"))] - input: Vec - + #[structopt(raw(multiple = "true"))] + input: Vec, } fn precision_of(s: &str) -> usize { @@ -334,9 +356,9 @@ fn get_error_code(input: &&str) -> ErrorCode { } fn get_values(input: &&str) -> Vec { - if input.contains('\n'){ + if input.contains('\n') { input.split('\n').map(String::from).collect() - } else if input.contains(','){ + } else if input.contains(',') { input.split(',').map(String::from).collect() } else { input.split(' ').map(String::from).collect() @@ -353,7 +375,7 @@ struct Counter { #[derive(Debug)] struct Summary { successes: u32, - failures: Vec + failures: Vec, } impl Summary { @@ -363,10 +385,15 @@ impl Summary { let errors = if self.failures.is_empty() { String::from("0") } else { - format!("{} ({})", self.failures.len(), self.failures.into_iter() + format!( + "{} ({})", + self.failures.len(), + self.failures + .into_iter() .map(|f| (-(f as i32)).to_string()) .collect::>() - .join(", ")) + .join(", ") + ) }; println!("Total runs:\t{}", total); From cb096bf16939e64961c41670921d787f889fd13b Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 20:57:10 +0100 Subject: [PATCH 02/56] fix clippy warnings --- src/main.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0611e95..8c16dd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -#[macro_use] -extern crate structopt; extern crate atty; extern crate humantime; extern crate regex; +extern crate structopt; extern crate subprocess; extern crate tempfile; @@ -193,17 +192,13 @@ fn main() { if let Some(ref previous_stdout) = previous_stdout { // --until-changes - if opt.until_changes { - if *previous_stdout != stdout { - break; - } + if opt.until_changes && *previous_stdout != stdout { + break; } // --until-same - if opt.until_same { - if *previous_stdout == stdout { - break; - } + if opt.until_same && *previous_stdout == stdout { + break; } } else { previous_stdout = Some(stdout); From 18fcfe64e7cdfd9a3e58ff964f11ef93ce42098f Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 21:18:42 +0100 Subject: [PATCH 03/56] declare Rust 2018 edition in cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33652df..83b03de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "loop-rs" version = "0.6.1" authors = ["Rich Jones "] description = "UNIX's missing loop command" -edition = "2015" +edition = "2018" documentation = "https://github.com/Miserlou/Loop" homepage = "https://github.com/Miserlou/Loop" From 1b445eb27769c66aa9c8cbfcfd3e89163ff5f2b9 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 09:07:19 +0100 Subject: [PATCH 04/56] update dependencies --- Cargo.lock | 319 ++++++++++++++++++++++++++++++++--------------------- Cargo.toml | 12 +- 2 files changed, 201 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ce4712..4d00996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,9 +1,9 @@ [[package]] name = "aho-corasick" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -19,11 +19,16 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.0.4" @@ -31,7 +36,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -57,52 +62,39 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "crossbeam-utils" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "fuchsia-cprng" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "humantime" -version = "1.1.1" +name = "heck" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "humantime" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lazy_static" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "libc" -version = "0.2.43" +version = "0.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -110,26 +102,21 @@ name = "loop-rs" version = "0.6.1" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "subprocess 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "memchr" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "proc-macro2" -version = "0.4.19" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -142,32 +129,119 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand" -version = "0.5.5" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_core" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -175,27 +249,27 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -213,53 +287,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "subprocess" -version = "0.1.14" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "0.14.9" +version = "0.15.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tempfile" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -269,8 +343,8 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -287,12 +361,17 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ucd-util" -version = "0.1.1" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-segmentation" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -307,7 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "utf8-ranges" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -315,16 +394,6 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi" version = "0.3.6" @@ -334,11 +403,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -350,48 +414,55 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" +"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b" -"checksum proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe022fb8c8bd254524b0b3305906c1921fa37a84a644e29079a9e62200c3901" +"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2069749032ea3ec200ca51e4a31df41759190a88edca0d2d86ee8bedf7073341" -"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" +"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" +"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8e9ad6a11096cbecdcca0cc6aa403fdfdbaeda2fb3323a39c98e6a166a1e45a" -"checksum structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4cbce8ccdc62166bd594c14396a3242bf94c337a51dbfa9be1076dd74b3db2af" -"checksum subprocess 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "55d1bb51b38eac391ce6728f02c415d1d268213643ae83a11d0b1f298f63f5b7" -"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" -"checksum tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "55c1195ef8513f3273d55ff59fe5da6940287a0d7a98331254397f464833675b" +"checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3" +"checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04" +"checksum subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "28fc0f40f0c0da73339d347aa7d6d2b90341a95683a47722bc4eebed71ff3c00" +"checksum syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)" = "218aa5a01ab9805df6e9e48074c8d88f317cc9660b1ad6c3dabac2d627d185d6" +"checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 83b03de..3837390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,12 @@ readme = "README.md" license = "MIT" [dependencies] -structopt = "0.2" -humantime = "1.1.1" -atty = "0.2" -regex = "1.0.0" -subprocess = "0.1.12" -tempfile = "3.0.3" +structopt = "0.2.14" +humantime = "1.2.0" +atty = "0.2.11" +regex = "1.1.2" +subprocess = "0.1.18" +tempfile = "3.0.7" [[bin]] name = "loop" From 43d78ec0ad3c08c965bd9bef67e3f6621a756bd1 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 21:30:33 +0100 Subject: [PATCH 05/56] shorten some if statements --- src/main.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8c16dd5..5109972 100644 --- a/src/main.rs +++ b/src/main.rs @@ -138,16 +138,12 @@ fn main() { // --until-contains // We defer loop breaking until the entire result is printed. if let Some(string) = &opt.until_contains { - if line.contains(string) { - has_matched = true; - } + has_matched = line.contains(string); } // --until-match if let Some(regex) = &opt.until_match { - if regex.captures(&line).is_some() { - has_matched = true; - } + has_matched = regex.captures(&line).is_some(); } } @@ -155,9 +151,7 @@ fn main() { if let Some(error_code) = &opt.until_error { match error_code { ErrorCode::Any => { - if !result.exit_status.success() { - has_matched = true; - } + has_matched = !result.exit_status.success(); } ErrorCode::Code(code) => { if result.exit_status == ExitStatus::Exited(*code) { From 3af31fde84dcb81ab1f2c82a1708be3d825fb474 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:11:15 +0100 Subject: [PATCH 06/56] prevent reference to opt by cloning ffor early --- src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5109972..34cfa6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,11 +38,7 @@ fn main() { let program_start = Instant::now(); // Number of iterations - let mut items = if let Some(items) = opt.ffor { - items.clone() - } else { - vec![] - }; + let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); // Get any lines from stdin if opt.stdin || atty::isnt(atty::Stream::Stdin) { From 5e10d28b40b7605f2ba949fc3068159e2054a05e Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 21:57:18 +0100 Subject: [PATCH 07/56] use for_each --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 34cfa6c..d1d0988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -205,9 +205,7 @@ fn main() { 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); - } + stdout.lines().for_each(|line| println!("{}", line)); } if opt.summary { From 9cc0561fc278a3549558cbf840c8323524f08066 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:13:50 +0100 Subject: [PATCH 08/56] use for_each II --- src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index d1d0988..8904ef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,10 +42,10 @@ fn main() { // Get any lines from stdin if opt.stdin || atty::isnt(atty::Stream::Stdin) { - let stdin = io::stdin(); - for line in stdin.lock().lines() { - items.push(line.unwrap().to_owned()) - } + io::stdin() + .lock() + .lines() + .for_each(|line| items.push(line.unwrap().to_owned())); } let joined_input = &opt.input.join(" "); From 1c264de0b4e24d9d18dfd160d6743c5b47478600 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:15:17 +0100 Subject: [PATCH 09/56] use is_empty() on String --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8904ef7..e84e051 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ fn main() { } let joined_input = &opt.input.join(" "); - if joined_input == "" { + if joined_input.is_empty() { println!("No command supplied, exiting."); return; } From 8a28324734a2ac9544ca5fe8ada89b6676895de9 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:19:49 +0100 Subject: [PATCH 10/56] Counter::new_from_opt --- src/main.rs | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index e84e051..e25d91c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,12 +70,7 @@ fn main() { }; let mut previous_stdout = None; - let counter = Counter { - start: opt.offset - opt.count_by, - iters: 0.0, - end: num, - step_by: opt.count_by, - }; + let counter = Counter::new_from_opt(&opt, num); for (count, actual_count) in counter.enumerate() { // Time Start let loop_start = Instant::now(); @@ -355,6 +350,30 @@ struct Counter { step_by: f64, } +impl Counter { + fn new_from_opt(opt: &Opt, num: f64) -> Counter { + Counter { + start: opt.offset - opt.count_by, + iters: 0.0, + end: num, + step_by: opt.count_by, + } + } +} + +impl Iterator for Counter { + type Item = f64; + fn next(&mut self) -> Option { + self.start += self.step_by; + self.iters += 1.0; + if self.iters <= self.end { + Some(self.start) + } else { + None + } + } +} + #[derive(Debug)] struct Summary { successes: u32, @@ -385,15 +404,11 @@ impl Summary { } } -impl Iterator for Counter { - type Item = f64; - fn next(&mut self) -> Option { - self.start += self.step_by; - self.iters += 1.0; - if self.iters <= self.end { - Some(self.start) - } else { - None +impl Default for Summary { + fn default() -> Summary { + Summary { + successes: 0, + failures: Vec::new(), } } } From 7c48a4dd22eb54833a22dc03260320d480f23d57 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:23:23 +0100 Subject: [PATCH 11/56] impl Default for Summary --- src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index e25d91c..e3ded8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,10 +64,7 @@ fn main() { }; let mut has_matched = false; let mut tmpfile = tempfile::tempfile().unwrap(); - let mut summary = Summary { - successes: 0, - failures: Vec::new(), - }; + let mut summary = Summary::default(); let mut previous_stdout = None; let counter = Counter::new_from_opt(&opt, num); From acac946f97293b474dffd703b640c282642a4040 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 21:27:48 +0100 Subject: [PATCH 12/56] fn print_results --- src/main.rs | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index e3ded8a..1ab84d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ extern crate tempfile; use std::env; use std::f64; +use std::fs::File; use std::io::prelude::*; use std::io::{self, BufRead, SeekFrom}; use std::process; @@ -113,27 +114,7 @@ fn main() { // Print the results let mut stdout = String::new(); - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(&mut stdout).ok(); - for line in stdout.lines() { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !opt.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(string) = &opt.until_contains { - has_matched = line.contains(string); - } - - // --until-match - if let Some(regex) = &opt.until_match { - has_matched = regex.captures(&line).is_some(); - } - } + print_results(&opt, &mut stdout, &mut has_matched, &mut tmpfile); // --until-error if let Some(error_code) = &opt.until_error { @@ -206,6 +187,31 @@ fn main() { process::exit(exit_status); } +fn print_results(opt: &Opt, stdout: &mut String, has_matched: &mut bool, tmpfile: &mut File) { + tmpfile.seek(SeekFrom::Start(0)).ok(); + tmpfile.read_to_string(stdout).ok(); + + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !opt.only_last { + println!("{}", line); + } + + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = opt.until_contains { + *has_matched = line.contains(string); + } + + // --until-match + if let Some(ref regex) = opt.until_match { + *has_matched = regex.captures(&line).is_some(); + } + }) +} + #[derive(StructOpt, Debug)] #[structopt( name = "loop", From 20916aab17df3c6f218cb76cd85199cedf421459 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:42:17 +0100 Subject: [PATCH 13/56] fn check_error_code --- src/main.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1ab84d5..6a859e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,18 +117,7 @@ fn main() { print_results(&opt, &mut stdout, &mut has_matched, &mut tmpfile); // --until-error - if let Some(error_code) = &opt.until_error { - match error_code { - ErrorCode::Any => { - has_matched = !result.exit_status.success(); - } - ErrorCode::Code(code) => { - if result.exit_status == ExitStatus::Exited(*code) { - has_matched = true; - } - } - } - } + check_error_code(&opt.until_error, &mut has_matched, result.exit_status); // --until-success if opt.until_success && result.exit_status.success() { @@ -212,6 +201,24 @@ fn print_results(opt: &Opt, stdout: &mut String, has_matched: &mut bool, tmpfile }) } +fn check_error_code( + maybe_error: &Option, + has_matched: &mut bool, + exit_status: subprocess::ExitStatus, +) { + match maybe_error { + Some(ErrorCode::Any) => { + *has_matched = !exit_status.success(); + } + Some(ErrorCode::Code(code)) => { + if exit_status == ExitStatus::Exited(*code) { + *has_matched = true; + } + } + _ => (), + } +} + #[derive(StructOpt, Debug)] #[structopt( name = "loop", From ee9703242681b73498a86eb7b88be1a1600d61bf Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 22:54:36 +0100 Subject: [PATCH 14/56] trait StringFromTempfileStart --- src/main.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6a859e3..c56a079 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,8 +113,8 @@ fn main() { .unwrap(); // Print the results - let mut stdout = String::new(); - print_results(&opt, &mut stdout, &mut has_matched, &mut tmpfile); + let stdout = String::from_temp_start(&mut tmpfile); + print_results(&opt, &mut has_matched, &stdout); // --until-error check_error_code(&opt.until_error, &mut has_matched, result.exit_status); @@ -164,10 +164,9 @@ fn main() { } if opt.only_last { - let mut stdout = String::new(); - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(&mut stdout).ok(); - stdout.lines().for_each(|line| println!("{}", line)); + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); } if opt.summary { @@ -176,10 +175,7 @@ fn main() { process::exit(exit_status); } -fn print_results(opt: &Opt, stdout: &mut String, has_matched: &mut bool, tmpfile: &mut File) { - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(stdout).ok(); - +fn print_results(opt: &Opt, has_matched: &mut bool, stdout: &str) { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, @@ -219,6 +215,19 @@ fn check_error_code( } } +trait StringFromTempfileStart { + fn from_temp_start(tmpfile: &mut File) -> String; +} + +impl StringFromTempfileStart for String { + fn from_temp_start(tmpfile: &mut File) -> String { + let mut stdout = String::new(); + tmpfile.seek(SeekFrom::Start(0)).ok(); + tmpfile.read_to_string(&mut stdout).ok(); + stdout + } +} + #[derive(StructOpt, Debug)] #[structopt( name = "loop", From 8889dafa1fa3be028d722e23bc181b36e95149ff Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Thu, 7 Mar 2019 23:10:52 +0100 Subject: [PATCH 15/56] compute counter values early --- src/main.rs | 66 +++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/src/main.rs b/src/main.rs index c56a079..b71f645 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,20 +56,13 @@ fn main() { } // Counters and State - 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::default(); let mut previous_stdout = None; - let counter = Counter::new_from_opt(&opt, num); - for (count, actual_count) in counter.enumerate() { + let counters = counters_from_opt(&opt, &items); + for (count, actual_count) in counters.iter().enumerate() { // Time Start let loop_start = Instant::now(); @@ -175,6 +168,30 @@ fn main() { process::exit(exit_status); } +fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { + let mut counters = vec![]; + let mut start = opt.offset - opt.count_by; + let mut index = 0_f64; + let step_by = opt.count_by; + let end = if let Some(num) = opt.num { + num + } else if !items.is_empty() { + items.len() as f64 + } else { + f64::INFINITY + }; + loop { + start += step_by; + index += 1_f64; + if index <= end { + counters.push(start) + } else { + break; + } + } + counters +} + fn print_results(opt: &Opt, has_matched: &mut bool, stdout: &str) { stdout.lines().for_each(|line| { // --only-last @@ -362,37 +379,6 @@ fn get_values(input: &&str) -> Vec { } } -struct Counter { - start: f64, - iters: f64, - end: f64, - step_by: f64, -} - -impl Counter { - fn new_from_opt(opt: &Opt, num: f64) -> Counter { - Counter { - start: opt.offset - opt.count_by, - iters: 0.0, - end: num, - step_by: opt.count_by, - } - } -} - -impl Iterator for Counter { - type Item = f64; - fn next(&mut self) -> Option { - self.start += self.step_by; - self.iters += 1.0; - if self.iters <= self.end { - Some(self.start) - } else { - None - } - } -} - #[derive(Debug)] struct Summary { successes: u32, From 9c2c3b3cbf5ed822833f2ea051cb6864c4f75dae Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 09:22:11 +0100 Subject: [PATCH 16/56] exit_app --- src/main.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index b71f645..6d0ff9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,16 +156,7 @@ fn main() { } } - if opt.only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); - } - - if opt.summary { - summary.print() - } - process::exit(exit_status); + exit_app(opt.only_last, opt.summary, exit_status, summary, tmpfile) } fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { @@ -232,6 +223,26 @@ fn check_error_code( } } +fn exit_app( + only_last: bool, + print_summary: bool, + exit_status: i32, + summary: Summary, + mut tmpfile: File, +) { + if only_last { + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); + } + + if print_summary { + summary.print() + } + + process::exit(exit_status); +} + trait StringFromTempfileStart { fn from_temp_start(tmpfile: &mut File) -> String; } From 0ca98e9b9b5a66380e83a2ef9b8a678afe7f1ebb Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 11:04:17 +0100 Subject: [PATCH 17/56] fn setup & struct State --- src/main.rs | 113 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6d0ff9f..7fa6e15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,29 +25,8 @@ static UNKONWN_EXIT_CODE: u32 = 99; static TIMEOUT_EXIT_CODE: i32 = 124; fn main() { - // Load the CLI arguments - let opt = Opt::from_args(); - let count_precision = Opt::clap() - .get_matches() - .value_of("count_by") - .map(precision_of) - .unwrap_or(0); - - let mut exit_status = 0; - // Time - let program_start = Instant::now(); - - // Number of iterations - let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); - - // Get any lines from stdin - if opt.stdin || atty::isnt(atty::Stream::Stdin) { - io::stdin() - .lock() - .lines() - .for_each(|line| items.push(line.unwrap().to_owned())); - } + let (opt, items, count_precision, program_start) = setup(); let joined_input = &opt.input.join(" "); if joined_input.is_empty() { @@ -56,11 +35,7 @@ fn main() { } // Counters and State - let mut has_matched = false; - let mut tmpfile = tempfile::tempfile().unwrap(); - let mut summary = Summary::default(); - let mut previous_stdout = None; - + let mut state = State::default(); let counters = counters_from_opt(&opt, &items); for (count, actual_count) in counters.iter().enumerate() { // Time Start @@ -81,7 +56,7 @@ fn main() { let since = Instant::now().duration_since(program_start); if since >= duration { if opt.error_duration { - exit_status = TIMEOUT_EXIT_CODE + state.exit_status = TIMEOUT_EXIT_CODE } break; } @@ -97,45 +72,45 @@ fn main() { } // Main executor - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.set_len(0).ok(); + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); let result = Exec::shell(joined_input) - .stdout(Redirection::File(tmpfile.try_clone().unwrap())) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) .stderr(Redirection::Merge) .capture() .unwrap(); // Print the results - let stdout = String::from_temp_start(&mut tmpfile); - print_results(&opt, &mut has_matched, &stdout); + let stdout = String::from_temp_start(&mut state.tmpfile); + print_results(&opt, &mut state.has_matched, &stdout); // --until-error - check_error_code(&opt.until_error, &mut has_matched, result.exit_status); + check_error_code(&opt.until_error, &mut state.has_matched, result.exit_status); // --until-success if opt.until_success && result.exit_status.success() { - has_matched = true; + state.has_matched = true; } // --until-fail if opt.until_fail && !(result.exit_status.success()) { - has_matched = true; + state.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), + ExitStatus::Exited(0) => state.summary.successes += 1, + ExitStatus::Exited(n) => state.summary.failures.push(n), + _ => state.summary.failures.push(UNKONWN_EXIT_CODE), } } // Finish if we matched - if has_matched { + if state.has_matched { break; } - if let Some(ref previous_stdout) = previous_stdout { + if let Some(ref previous_stdout) = state.previous_stdout { // --until-changes if opt.until_changes && *previous_stdout != stdout { break; @@ -146,7 +121,7 @@ fn main() { break; } } else { - previous_stdout = Some(stdout); + state.previous_stdout = Some(stdout); } // Delay until next iteration time @@ -156,7 +131,39 @@ fn main() { } } - exit_app(opt.only_last, opt.summary, exit_status, summary, tmpfile) + exit_app( + opt.only_last, + opt.summary, + state.exit_status, + state.summary, + state.tmpfile, + ) +} + +fn setup() -> (Opt, Vec, usize, Instant) { + // Time + let program_start = Instant::now(); + + // Load the CLI arguments + let opt = Opt::from_args(); + let count_precision = Opt::clap() + .get_matches() + .value_of("count_by") + .map(precision_of) + .unwrap_or(0); + + // Number of iterations + let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); + + // Get any lines from stdin + if opt.stdin || atty::isnt(atty::Stream::Stdin) { + io::stdin() + .lock() + .lines() + .for_each(|line| items.push(line.unwrap().to_owned())); + } + + (opt, items, count_precision, program_start) } fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { @@ -390,6 +397,26 @@ fn get_values(input: &&str) -> Vec { } } +struct State { + has_matched: bool, + tmpfile: File, + summary: Summary, + previous_stdout: Option, + exit_status: i32, +} + +impl Default for State { + fn default() -> State { + State { + has_matched: false, + tmpfile: tempfile::tempfile().unwrap(), + summary: Summary::default(), + previous_stdout: None, + exit_status: 0, + } + } +} + #[derive(Debug)] struct Summary { successes: u32, From 53e3c33a3f0e64a1194755702de79490712bb673 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 11:19:02 +0100 Subject: [PATCH 18/56] fn summary_exit_status --- src/main.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7fa6e15..45517ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,11 +98,7 @@ fn main() { } if opt.summary { - match result.exit_status { - ExitStatus::Exited(0) => state.summary.successes += 1, - ExitStatus::Exited(n) => state.summary.failures.push(n), - _ => state.summary.failures.push(UNKONWN_EXIT_CODE), - } + state.summary_exit_status(result.exit_status); } // Finish if we matched @@ -120,9 +116,8 @@ fn main() { if opt.until_same && *previous_stdout == stdout { break; } - } else { - state.previous_stdout = Some(stdout); } + state.previous_stdout = Some(stdout); // Delay until next iteration time let since = Instant::now().duration_since(loop_start); @@ -405,6 +400,16 @@ struct State { exit_status: i32, } +impl State { + fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { + match exit_status { + ExitStatus::Exited(0) => self.summary.successes += 1, + ExitStatus::Exited(n) => self.summary.failures.push(n), + _ => self.summary.failures.push(UNKONWN_EXIT_CODE), + } + } +} + impl Default for State { fn default() -> State { State { From 90da6e90c27725564e3b3c1f2b5fd7c51425b47f Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 11:28:12 +0100 Subject: [PATCH 19/56] move print_results into State impl --- src/main.rs | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 45517ee..7a28c5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,7 +82,7 @@ fn main() { // Print the results let stdout = String::from_temp_start(&mut state.tmpfile); - print_results(&opt, &mut state.has_matched, &stdout); + state.print_results(&opt, &stdout); // --until-error check_error_code(&opt.until_error, &mut state.has_matched, result.exit_status); @@ -185,28 +185,6 @@ fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { counters } -fn print_results(opt: &Opt, has_matched: &mut bool, stdout: &str) { - stdout.lines().for_each(|line| { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !opt.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(ref string) = opt.until_contains { - *has_matched = line.contains(string); - } - - // --until-match - if let Some(ref regex) = opt.until_match { - *has_matched = regex.captures(&line).is_some(); - } - }) -} - fn check_error_code( maybe_error: &Option, has_matched: &mut bool, @@ -408,6 +386,28 @@ impl State { _ => self.summary.failures.push(UNKONWN_EXIT_CODE), } } + + fn print_results(&mut self, opt: &Opt, stdout: &str) { + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !opt.only_last { + println!("{}", line); + } + + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = opt.until_contains { + self.has_matched = line.contains(string); + } + + // --until-match + if let Some(ref regex) = opt.until_match { + self.has_matched = regex.captures(&line).is_some(); + } + }) + } } impl Default for State { From dce56e4c68fa9c17fd66ed24d01f890ed2db9ba8 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 11:35:49 +0100 Subject: [PATCH 20/56] fn run_shell_command --- src/main.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7a28c5e..404445f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,33 +72,27 @@ fn main() { } // Main executor - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); - let result = Exec::shell(joined_input) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap(); + let exit_status = state.run_shell_command(joined_input); // Print the results let stdout = String::from_temp_start(&mut state.tmpfile); state.print_results(&opt, &stdout); // --until-error - check_error_code(&opt.until_error, &mut state.has_matched, result.exit_status); + check_error_code(&opt.until_error, &mut state.has_matched, exit_status); // --until-success - if opt.until_success && result.exit_status.success() { + if opt.until_success && exit_status.success() { state.has_matched = true; } // --until-fail - if opt.until_fail && !(result.exit_status.success()) { + if opt.until_fail && !(exit_status.success()) { state.has_matched = true; } if opt.summary { - state.summary_exit_status(result.exit_status); + state.summary_exit_status(exit_status); } // Finish if we matched @@ -387,6 +381,17 @@ impl State { } } + fn run_shell_command(&mut self, joined_input: &str) -> ExitStatus { + self.tmpfile.seek(SeekFrom::Start(0)).ok(); + self.tmpfile.set_len(0).ok(); + Exec::shell(joined_input) + .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status + } + fn print_results(&mut self, opt: &Opt, stdout: &str) { stdout.lines().for_each(|line| { // --only-last From a2d2a81025a59050a22d947c220cf792e53d1dc1 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 12:10:10 +0100 Subject: [PATCH 21/56] split loop body into own function --- src/main.rs | 249 +++++++++++++++++++++++++++++----------------------- 1 file changed, 139 insertions(+), 110 deletions(-) diff --git a/src/main.rs b/src/main.rs index 404445f..19dc8ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,17 +37,88 @@ fn main() { // Counters and State let mut state = State::default(); let counters = counters_from_opt(&opt, &items); - for (count, actual_count) in counters.iter().enumerate() { + for (index, actual_count) in counters.iter().enumerate() { + let counters = Counters { + index, + count_precision, + actual_count: *actual_count, + }; + if state.loop_body(&opt, &items, joined_input, counters, program_start) { + break; + } + } + + exit_app( + opt.only_last, + opt.summary, + state.exit_status, + state.summary, + state.tmpfile, + ) +} + +fn setup() -> (Opt, Vec, usize, Instant) { + // Time + let program_start = Instant::now(); + + // Load the CLI arguments + let opt = Opt::from_args(); + let count_precision = Opt::clap() + .get_matches() + .value_of("count_by") + .map(precision_of) + .unwrap_or(0); + + // Number of iterations + let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); + + // Get any lines from stdin + if opt.stdin || atty::isnt(atty::Stream::Stdin) { + io::stdin() + .lock() + .lines() + .for_each(|line| items.push(line.unwrap().to_owned())); + } + + (opt, items, count_precision, program_start) +} + +struct State { + has_matched: bool, + tmpfile: File, + summary: Summary, + previous_stdout: Option, + exit_status: i32, +} + +struct Counters { + index: usize, + count_precision: usize, + actual_count: f64, +} + +impl State { + fn loop_body( + &mut self, + opt: &Opt, + items: &[String], + joined_input: &str, + counters: Counters, + program_start: Instant, + ) -> bool { // Time Start let loop_start = Instant::now(); // Set counters before execution // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env::set_var("ACTUALCOUNT", count.to_string()); - env::set_var("COUNT", format!("{:.*}", count_precision, actual_count)); + env::set_var("ACTUALCOUNT", counters.index.to_string()); + env::set_var( + "COUNT", + format!("{:.*}", counters.count_precision, counters.actual_count), + ); // Set iterated item as environment variable - if let Some(item) = items.get(count) { + if let Some(item) = items.get(counters.index) { env::set_var("ITEM", item); } @@ -56,9 +127,9 @@ fn main() { let since = Instant::now().duration_since(program_start); if since >= duration { if opt.error_duration { - state.exit_status = TIMEOUT_EXIT_CODE + self.exit_status = TIMEOUT_EXIT_CODE } - break; + return true; } } @@ -67,92 +138,113 @@ fn main() { // even if the start time is beyond the until time. if let Some(until_time) = opt.until_time { if SystemTime::now().duration_since(until_time).is_ok() { - break; + return true; } } // Main executor - let exit_status = state.run_shell_command(joined_input); + let exit_status = self.run_shell_command(joined_input); // Print the results - let stdout = String::from_temp_start(&mut state.tmpfile); - state.print_results(&opt, &stdout); + let stdout = String::from_temp_start(&mut self.tmpfile); + self.print_results(&opt, &stdout); // --until-error - check_error_code(&opt.until_error, &mut state.has_matched, exit_status); + check_error_code(&opt.until_error, &mut self.has_matched, exit_status); // --until-success if opt.until_success && exit_status.success() { - state.has_matched = true; + self.has_matched = true; } // --until-fail if opt.until_fail && !(exit_status.success()) { - state.has_matched = true; + self.has_matched = true; } if opt.summary { - state.summary_exit_status(exit_status); + self.summary_exit_status(exit_status); } // Finish if we matched - if state.has_matched { - break; + if self.has_matched { + return true; } - if let Some(ref previous_stdout) = state.previous_stdout { + if let Some(ref previous_stdout) = self.previous_stdout { // --until-changes if opt.until_changes && *previous_stdout != stdout { - break; + return true; } // --until-same if opt.until_same && *previous_stdout == stdout { - break; + return true; } } - state.previous_stdout = Some(stdout); + self.previous_stdout = Some(stdout); // Delay until next iteration time let since = Instant::now().duration_since(loop_start); if let Some(time) = opt.every.checked_sub(since) { thread::sleep(time); } + + false } - exit_app( - opt.only_last, - opt.summary, - state.exit_status, - state.summary, - state.tmpfile, - ) -} + fn run_shell_command(&mut self, joined_input: &str) -> ExitStatus { + self.tmpfile.seek(SeekFrom::Start(0)).ok(); + self.tmpfile.set_len(0).ok(); + Exec::shell(joined_input) + .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status + } -fn setup() -> (Opt, Vec, usize, Instant) { - // Time - let program_start = Instant::now(); + fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { + match exit_status { + ExitStatus::Exited(0) => self.summary.successes += 1, + ExitStatus::Exited(n) => self.summary.failures.push(n), + _ => self.summary.failures.push(UNKONWN_EXIT_CODE), + } + } - // Load the CLI arguments - let opt = Opt::from_args(); - let count_precision = Opt::clap() - .get_matches() - .value_of("count_by") - .map(precision_of) - .unwrap_or(0); + fn print_results(&mut self, opt: &Opt, stdout: &str) { + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !opt.only_last { + println!("{}", line); + } - // Number of iterations - let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = opt.until_contains { + self.has_matched = line.contains(string); + } - // Get any lines from stdin - if opt.stdin || atty::isnt(atty::Stream::Stdin) { - io::stdin() - .lock() - .lines() - .for_each(|line| items.push(line.unwrap().to_owned())); + // --until-match + if let Some(ref regex) = opt.until_match { + self.has_matched = regex.captures(&line).is_some(); + } + }) } +} - (opt, items, count_precision, program_start) +impl Default for State { + fn default() -> State { + State { + has_matched: false, + tmpfile: tempfile::tempfile().unwrap(), + summary: Summary::default(), + previous_stdout: None, + exit_status: 0, + } + } } fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { @@ -364,69 +456,6 @@ fn get_values(input: &&str) -> Vec { } } -struct State { - has_matched: bool, - tmpfile: File, - summary: Summary, - previous_stdout: Option, - exit_status: i32, -} - -impl State { - fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { - match exit_status { - ExitStatus::Exited(0) => self.summary.successes += 1, - ExitStatus::Exited(n) => self.summary.failures.push(n), - _ => self.summary.failures.push(UNKONWN_EXIT_CODE), - } - } - - fn run_shell_command(&mut self, joined_input: &str) -> ExitStatus { - self.tmpfile.seek(SeekFrom::Start(0)).ok(); - self.tmpfile.set_len(0).ok(); - Exec::shell(joined_input) - .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status - } - - fn print_results(&mut self, opt: &Opt, stdout: &str) { - stdout.lines().for_each(|line| { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !opt.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(ref string) = opt.until_contains { - self.has_matched = line.contains(string); - } - - // --until-match - if let Some(ref regex) = opt.until_match { - self.has_matched = regex.captures(&line).is_some(); - } - }) - } -} - -impl Default for State { - fn default() -> State { - State { - has_matched: false, - tmpfile: tempfile::tempfile().unwrap(), - summary: Summary::default(), - previous_stdout: None, - exit_status: 0, - } - } -} - #[derive(Debug)] struct Summary { successes: u32, From 4d413f4c2f92a97a687a041f15c47e04d85fe228 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 14:27:18 +0100 Subject: [PATCH 22/56] move setup() and structopt to setup.rs --- src/main.rs | 178 ++------------------------------------------------- src/setup.rs | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 173 deletions(-) create mode 100644 src/setup.rs diff --git a/src/main.rs b/src/main.rs index 19dc8ca..ebc2c65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,14 @@ -extern crate atty; -extern crate humantime; -extern crate regex; -extern crate structopt; -extern crate subprocess; -extern crate tempfile; +mod setup; use std::env; -use std::f64; use std::fs::File; use std::io::prelude::*; -use std::io::{self, BufRead, SeekFrom}; +use std::io::SeekFrom; use std::process; use std::thread; -use std::time::{Duration, Instant, SystemTime}; +use std::time::{Instant, SystemTime}; -use humantime::{parse_duration, parse_rfc3339_weak}; -use regex::Regex; -use structopt::StructOpt; +use setup::{setup, ErrorCode, Opt}; use subprocess::{Exec, ExitStatus, Redirection}; static UNKONWN_EXIT_CODE: u32 = 99; @@ -57,32 +49,6 @@ fn main() { ) } -fn setup() -> (Opt, Vec, usize, Instant) { - // Time - let program_start = Instant::now(); - - // Load the CLI arguments - let opt = Opt::from_args(); - let count_precision = Opt::clap() - .get_matches() - .value_of("count_by") - .map(precision_of) - .unwrap_or(0); - - // Number of iterations - let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); - - // Get any lines from stdin - if opt.stdin || atty::isnt(atty::Stream::Stdin) { - io::stdin() - .lock() - .lines() - .for_each(|line| items.push(line.unwrap().to_owned())); - } - - (opt, items, count_precision, program_start) -} - struct State { has_matched: bool, tmpfile: File, @@ -257,7 +223,7 @@ fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { } else if !items.is_empty() { items.len() as f64 } else { - f64::INFINITY + std::f64::INFINITY }; loop { start += step_by; @@ -322,140 +288,6 @@ impl StringFromTempfileStart for String { } } -#[derive(StructOpt, Debug)] -#[structopt( - name = "loop", - author = "Rich Jones ", - about = "UNIX's missing `loop` command" -)] -struct Opt { - /// Number of iterations to execute - #[structopt(short = "n", long = "num")] - num: Option, - - /// Amount to increment the counter by - #[structopt(short = "b", long = "count-by", default_value = "1")] - count_by: f64, - - /// Amount to offset the initial counter by - #[structopt(short = "o", long = "offset", default_value = "0")] - offset: f64, - - /// How often to iterate. ex., 5s, 1h1m1s1ms1us - #[structopt( - short = "e", - long = "every", - default_value = "1us", - parse(try_from_str = "parse_duration") - )] - every: Duration, - - /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue - #[structopt(long = "for", parse(from_str = "get_values"))] - ffor: Option>, - - /// Keep going until the duration has elapsed (example 1m30s) - #[structopt( - short = "d", - long = "for-duration", - parse(try_from_str = "parse_duration") - )] - for_duration: Option, - - /// Keep going until the output contains this string - #[structopt(short = "c", long = "until-contains")] - until_contains: Option, - - /// Keep going until the output changes - #[structopt(short = "C", long = "until-changes")] - until_changes: bool, - - /// Keep going until the output changes - #[structopt(short = "S", long = "until-same")] - until_same: bool, - - /// Keep going until the output matches this regular expression - #[structopt(short = "m", long = "until-match", parse(try_from_str = "Regex::new"))] - until_match: Option, - - /// Keep going until a future time, ex. "2018-04-20 04:20:00" (Times in UTC.) - #[structopt( - short = "t", - long = "until-time", - parse(try_from_str = "parse_rfc3339_weak") - )] - until_time: Option, - - /// Keep going until the command exit status is non-zero, or the value given - #[structopt(short = "r", long = "until-error", parse(from_str = "get_error_code"))] - until_error: Option, - - /// Keep going until the command exit status is zero - #[structopt(short = "s", long = "until-success")] - until_success: bool, - - /// Keep going until the command exit status is non-zero - #[structopt(short = "f", long = "until-fail")] - until_fail: bool, - - /// Only print the output of the last execution of the command - #[structopt(short = "l", long = "only-last")] - only_last: bool, - - /// Read from standard input - #[structopt(short = "i", long = "stdin")] - stdin: bool, - - /// Exit with timeout error code on duration - #[structopt(short = "D", long = "error-duration")] - error_duration: bool, - - /// Provide a summary - #[structopt(long = "summary")] - summary: bool, - - /// The command to be looped - #[structopt(raw(multiple = "true"))] - input: Vec, -} - -fn precision_of(s: &str) -> usize { - let after_point = match s.find('.') { - // '.' is ASCII so has len 1 - 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::() { - ErrorCode::Code(code) - } else { - ErrorCode::Any - } -} - -fn get_values(input: &&str) -> Vec { - 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() - } -} - #[derive(Debug)] struct Summary { successes: u32, diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..260d459 --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,163 @@ +use humantime::{parse_duration, parse_rfc3339_weak}; +use regex::Regex; +use std::time::{Duration, Instant, SystemTime}; +use structopt::StructOpt; + +pub fn setup() -> (Opt, Vec, usize, Instant) { + use std::io::{self, BufRead}; + + // Time + let program_start = Instant::now(); + + // Load the CLI arguments + let opt = Opt::from_args(); + let count_precision = Opt::clap() + .get_matches() + .value_of("count_by") + .map(precision_of) + .unwrap_or(0); + + // Number of iterations + let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); + + // Get any lines from stdin + if opt.stdin || atty::isnt(atty::Stream::Stdin) { + io::stdin() + .lock() + .lines() + .map(|line| line.unwrap().to_owned()) + .for_each(|line| items.push(line)); + } + + (opt, items, count_precision, program_start) +} + +fn precision_of(s: &str) -> usize { + let after_point = match s.find('.') { + // '.' is ASCII so has len 1 + Some(point) => point + 1, + None => return 0, + }; + let exp = s.find(&['e', 'E'][..]).unwrap_or_else(|| s.len()); + exp - after_point +} + +#[derive(Debug)] +pub enum ErrorCode { + Any, + Code(u32), +} + +fn get_error_code(input: &&str) -> ErrorCode { + input + .parse() + .map(ErrorCode::Code) + .unwrap_or_else(|_| ErrorCode::Any) +} + +fn get_values(input: &&str) -> Vec { + 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() + } +} + +#[derive(StructOpt, Debug)] +#[structopt( + name = "loop", + author = "Rich Jones ", + about = "UNIX's missing `loop` command" +)] +pub struct Opt { + /// Number of iterations to execute + #[structopt(short = "n", long = "num")] + pub num: Option, + + /// Amount to increment the counter by + #[structopt(short = "b", long = "count-by", default_value = "1")] + pub count_by: f64, + + /// Amount to offset the initial counter by + #[structopt(short = "o", long = "offset", default_value = "0")] + pub offset: f64, + + /// How often to iterate. ex., 5s, 1h1m1s1ms1us + #[structopt( + short = "e", + long = "every", + default_value = "1us", + parse(try_from_str = "parse_duration") + )] + pub every: Duration, + + /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue + #[structopt(long = "for", parse(from_str = "get_values"))] + pub ffor: Option>, + + /// Keep going until the duration has elapsed (example 1m30s) + #[structopt( + short = "d", + long = "for-duration", + parse(try_from_str = "parse_duration") + )] + pub for_duration: Option, + + /// Keep going until the output contains this string + #[structopt(short = "c", long = "until-contains")] + pub until_contains: Option, + + /// Keep going until the output changes + #[structopt(short = "C", long = "until-changes")] + pub until_changes: bool, + + /// Keep going until the output changes + #[structopt(short = "S", long = "until-same")] + pub until_same: bool, + + /// Keep going until the output matches this regular expression + #[structopt(short = "m", long = "until-match", parse(try_from_str = "Regex::new"))] + pub until_match: Option, + + /// Keep going until a future time, ex. "2018-04-20 04:20:00" (Times in UTC.) + #[structopt( + short = "t", + long = "until-time", + parse(try_from_str = "parse_rfc3339_weak") + )] + pub until_time: Option, + + /// Keep going until the command exit status is non-zero, or the value given + #[structopt(short = "r", long = "until-error", parse(from_str = "get_error_code"))] + pub until_error: Option, + + /// Keep going until the command exit status is zero + #[structopt(short = "s", long = "until-success")] + pub until_success: bool, + + /// Keep going until the command exit status is non-zero + #[structopt(short = "f", long = "until-fail")] + pub until_fail: bool, + + /// Only print the output of the last execution of the command + #[structopt(short = "l", long = "only-last")] + pub only_last: bool, + + /// Read from standard input + #[structopt(short = "i", long = "stdin")] + pub stdin: bool, + + /// Exit with timeout error code on duration + #[structopt(short = "D", long = "error-duration")] + pub error_duration: bool, + + /// Provide a summary + #[structopt(long = "summary")] + pub summary: bool, + + /// The command to be looped + #[structopt(raw(multiple = "true"))] + pub input: Vec, +} From 15e231e0ff6ce2bec18e998cebe37bc4dd419135 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 8 Mar 2019 14:47:21 +0100 Subject: [PATCH 23/56] move State to state.rs --- src/main.rs | 265 +++------------------------------------------------ src/setup.rs | 3 +- src/state.rs | 238 +++++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 16 ++++ 4 files changed, 268 insertions(+), 254 deletions(-) create mode 100644 src/state.rs create mode 100644 src/util.rs diff --git a/src/main.rs b/src/main.rs index ebc2c65..a92c1d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,32 @@ mod setup; +mod state; +mod util; + +use setup::{setup, Opt}; +use state::{Counters, State, Summary}; -use std::env; use std::fs::File; -use std::io::prelude::*; -use std::io::SeekFrom; use std::process; -use std::thread; -use std::time::{Instant, SystemTime}; - -use setup::{setup, ErrorCode, Opt}; -use subprocess::{Exec, ExitStatus, Redirection}; - -static UNKONWN_EXIT_CODE: u32 = 99; - -// same exit code as use of `timeout` shell command -static TIMEOUT_EXIT_CODE: i32 = 124; fn main() { // Time let (opt, items, count_precision, program_start) = setup(); - let joined_input = &opt.input.join(" "); - if joined_input.is_empty() { + let cmd_with_args = &opt.input.join(" "); + if cmd_with_args.is_empty() { println!("No command supplied, exiting."); return; } // Counters and State let mut state = State::default(); - let counters = counters_from_opt(&opt, &items); - for (index, actual_count) in counters.iter().enumerate() { + for (i, actual_count) in counters_from_opt(&opt, &items).iter().enumerate() { let counters = Counters { - index, count_precision, + index: i, actual_count: *actual_count, }; - if state.loop_body(&opt, &items, joined_input, counters, program_start) { + if state.loop_body(&opt, &items, cmd_with_args, counters, program_start) { break; } } @@ -49,170 +40,6 @@ fn main() { ) } -struct State { - has_matched: bool, - tmpfile: File, - summary: Summary, - previous_stdout: Option, - exit_status: i32, -} - -struct Counters { - index: usize, - count_precision: usize, - actual_count: f64, -} - -impl State { - fn loop_body( - &mut self, - opt: &Opt, - items: &[String], - joined_input: &str, - counters: Counters, - program_start: Instant, - ) -> bool { - // Time Start - let loop_start = Instant::now(); - - // Set counters before execution - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env::set_var("ACTUALCOUNT", counters.index.to_string()); - env::set_var( - "COUNT", - format!("{:.*}", counters.count_precision, counters.actual_count), - ); - - // Set iterated item as environment variable - if let Some(item) = items.get(counters.index) { - env::set_var("ITEM", item); - } - - // Finish if we're over our duration - if let Some(duration) = opt.for_duration { - let since = Instant::now().duration_since(program_start); - if since >= duration { - if opt.error_duration { - self.exit_status = TIMEOUT_EXIT_CODE - } - return true; - } - } - - // Finish if our time until has passed - // In this location, the loop will execute at least once, - // even if the start time is beyond the until time. - if let Some(until_time) = opt.until_time { - if SystemTime::now().duration_since(until_time).is_ok() { - return true; - } - } - - // Main executor - let exit_status = self.run_shell_command(joined_input); - - // Print the results - let stdout = String::from_temp_start(&mut self.tmpfile); - self.print_results(&opt, &stdout); - - // --until-error - check_error_code(&opt.until_error, &mut self.has_matched, exit_status); - - // --until-success - if opt.until_success && exit_status.success() { - self.has_matched = true; - } - - // --until-fail - if opt.until_fail && !(exit_status.success()) { - self.has_matched = true; - } - - if opt.summary { - self.summary_exit_status(exit_status); - } - - // Finish if we matched - if self.has_matched { - return true; - } - - if let Some(ref previous_stdout) = self.previous_stdout { - // --until-changes - if opt.until_changes && *previous_stdout != stdout { - return true; - } - - // --until-same - if opt.until_same && *previous_stdout == stdout { - return true; - } - } - self.previous_stdout = Some(stdout); - - // Delay until next iteration time - let since = Instant::now().duration_since(loop_start); - if let Some(time) = opt.every.checked_sub(since) { - thread::sleep(time); - } - - false - } - - fn run_shell_command(&mut self, joined_input: &str) -> ExitStatus { - self.tmpfile.seek(SeekFrom::Start(0)).ok(); - self.tmpfile.set_len(0).ok(); - Exec::shell(joined_input) - .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status - } - - fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { - match exit_status { - ExitStatus::Exited(0) => self.summary.successes += 1, - ExitStatus::Exited(n) => self.summary.failures.push(n), - _ => self.summary.failures.push(UNKONWN_EXIT_CODE), - } - } - - fn print_results(&mut self, opt: &Opt, stdout: &str) { - stdout.lines().for_each(|line| { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !opt.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(ref string) = opt.until_contains { - self.has_matched = line.contains(string); - } - - // --until-match - if let Some(ref regex) = opt.until_match { - self.has_matched = regex.captures(&line).is_some(); - } - }) - } -} - -impl Default for State { - fn default() -> State { - State { - has_matched: false, - tmpfile: tempfile::tempfile().unwrap(), - summary: Summary::default(), - previous_stdout: None, - exit_status: 0, - } - } -} - fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { let mut counters = vec![]; let mut start = opt.offset - opt.count_by; @@ -237,24 +64,6 @@ fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { counters } -fn check_error_code( - maybe_error: &Option, - has_matched: &mut bool, - exit_status: subprocess::ExitStatus, -) { - match maybe_error { - Some(ErrorCode::Any) => { - *has_matched = !exit_status.success(); - } - Some(ErrorCode::Code(code)) => { - if exit_status == ExitStatus::Exited(*code) { - *has_matched = true; - } - } - _ => (), - } -} - fn exit_app( only_last: bool, print_summary: bool, @@ -262,6 +71,8 @@ fn exit_app( summary: Summary, mut tmpfile: File, ) { + use util::StringFromTempfileStart; + if only_last { String::from_temp_start(&mut tmpfile) .lines() @@ -274,55 +85,3 @@ fn exit_app( process::exit(exit_status); } - -trait StringFromTempfileStart { - fn from_temp_start(tmpfile: &mut File) -> String; -} - -impl StringFromTempfileStart for String { - fn from_temp_start(tmpfile: &mut File) -> String { - let mut stdout = String::new(); - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(&mut stdout).ok(); - stdout - } -} - -#[derive(Debug)] -struct Summary { - successes: u32, - failures: Vec, -} - -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::>() - .join(", ") - ) - }; - - println!("Total runs:\t{}", total); - println!("Successes:\t{}", self.successes); - println!("Failures:\t{}", errors); - } -} - -impl Default for Summary { - fn default() -> Summary { - Summary { - successes: 0, - failures: Vec::new(), - } - } -} diff --git a/src/setup.rs b/src/setup.rs index 260d459..50fa10b 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,6 +1,7 @@ -use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use std::time::{Duration, Instant, SystemTime}; + +use humantime::{parse_duration, parse_rfc3339_weak}; use structopt::StructOpt; pub fn setup() -> (Opt, Vec, usize, Instant) { diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..5e58384 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,238 @@ +use crate::setup::{ErrorCode, Opt}; +use crate::util::StringFromTempfileStart; + +use std::fs::File; +use std::time::{Instant, SystemTime}; + +use subprocess::{Exec, ExitStatus, Redirection}; + +// same exit-code as used by the `timeout` shell command +static TIMEOUT_EXIT_CODE: i32 = 124; +static UNKONWN_EXIT_CODE: u32 = 99; + +pub struct State { + pub tmpfile: File, + pub summary: Summary, + pub exit_status: i32, + previous_stdout: Option, + has_matched: bool, +} + +pub struct Counters { + pub index: usize, + pub count_precision: usize, + pub actual_count: f64, +} + +impl State { + pub fn loop_body( + &mut self, + opt: &Opt, + items: &[String], + cmd_with_args: &str, + counters: Counters, + program_start: Instant, + ) -> bool { + use std::env; + use std::thread; + + // Time Start + let loop_start = Instant::now(); + + // Set counters before execution + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + env::set_var("ACTUALCOUNT", counters.index.to_string()); + env::set_var( + "COUNT", + format!("{:.*}", counters.count_precision, counters.actual_count), + ); + + // Set iterated item as environment variable + if let Some(item) = items.get(counters.index) { + env::set_var("ITEM", item); + } + + // Finish if we're over our duration + if let Some(duration) = opt.for_duration { + let since = Instant::now().duration_since(program_start); + if since >= duration { + if opt.error_duration { + self.exit_status = TIMEOUT_EXIT_CODE + } + return true; + } + } + + // Finish if our time until has passed + // In this location, the loop will execute at least once, + // even if the start time is beyond the until time. + if let Some(until_time) = opt.until_time { + if SystemTime::now().duration_since(until_time).is_ok() { + return true; + } + } + + // Main executor + let exit_status = self.run_shell_command(cmd_with_args); + + // Print the results + let stdout = String::from_temp_start(&mut self.tmpfile); + self.print_results(&opt, &stdout); + + // --until-error + check_for_error(&opt.until_error, &mut self.has_matched, exit_status); + + // --until-success + if opt.until_success && exit_status.success() { + self.has_matched = true; + } + + // --until-fail + if opt.until_fail && !(exit_status.success()) { + self.has_matched = true; + } + + if opt.summary { + self.summary_exit_status(exit_status); + } + + // Finish if we matched + if self.has_matched { + return true; + } + + if let Some(ref previous_stdout) = self.previous_stdout { + // --until-changes + if opt.until_changes && *previous_stdout != stdout { + return true; + } + + // --until-same + if opt.until_same && *previous_stdout == stdout { + return true; + } + } + self.previous_stdout = Some(stdout); + + // Delay until next iteration time + let since = Instant::now().duration_since(loop_start); + if let Some(time) = opt.every.checked_sub(since) { + thread::sleep(time); + } + + false + } + + fn run_shell_command(&mut self, cmd_with_args: &str) -> ExitStatus { + use std::io::{prelude::*, SeekFrom}; + + self.tmpfile.seek(SeekFrom::Start(0)).ok(); + self.tmpfile.set_len(0).ok(); + + Exec::shell(cmd_with_args) + .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status + } + + fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { + match exit_status { + ExitStatus::Exited(0) => self.summary.successes += 1, + ExitStatus::Exited(n) => self.summary.failures.push(n), + _ => self.summary.failures.push(UNKONWN_EXIT_CODE), + } + } + + fn print_results(&mut self, opt: &Opt, stdout: &str) { + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !opt.only_last { + println!("{}", line); + } + + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = opt.until_contains { + self.has_matched = line.contains(string); + } + + // --until-match + if let Some(ref regex) = opt.until_match { + self.has_matched = regex.captures(&line).is_some(); + } + }) + } +} + +impl Default for State { + fn default() -> State { + State { + has_matched: false, + tmpfile: tempfile::tempfile().unwrap(), + summary: Summary::default(), + previous_stdout: None, + exit_status: 0, + } + } +} + +fn check_for_error( + maybe_error: &Option, + has_matched: &mut bool, + exit_status: ExitStatus, +) { + match maybe_error { + Some(ErrorCode::Any) => { + *has_matched = !exit_status.success(); + } + Some(ErrorCode::Code(code)) => { + if exit_status == ExitStatus::Exited(*code) { + *has_matched = true; + } + } + _ => (), + } +} + +#[derive(Debug)] +pub struct Summary { + successes: u32, + failures: Vec, +} + +impl Summary { + pub 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::>() + .join(", ") + ) + }; + + println!("Total runs:\t{}", total); + println!("Successes:\t{}", self.successes); + println!("Failures:\t{}", errors); + } +} + +impl Default for Summary { + fn default() -> Summary { + Summary { + successes: 0, + failures: Vec::new(), + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..89d2099 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,16 @@ +use std::fs::File; + +pub trait StringFromTempfileStart { + fn from_temp_start(tmpfile: &mut File) -> String; +} + +impl StringFromTempfileStart for String { + fn from_temp_start(tmpfile: &mut File) -> String { + use std::io::{prelude::*, SeekFrom}; + + let mut stdout = String::new(); + tmpfile.seek(SeekFrom::Start(0)).ok(); + tmpfile.read_to_string(&mut stdout).ok(); + stdout + } +} From 36b6ad0174cd16736de99d4ae63bf8d1192df08d Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 9 Mar 2019 11:59:01 +0100 Subject: [PATCH 24/56] split loop code from state.rs into loop_step.rs --- src/loop_step.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 11 +++- src/state.rs | 146 ++--------------------------------------------- 3 files changed, 156 insertions(+), 141 deletions(-) create mode 100644 src/loop_step.rs diff --git a/src/loop_step.rs b/src/loop_step.rs new file mode 100644 index 0000000..424d171 --- /dev/null +++ b/src/loop_step.rs @@ -0,0 +1,140 @@ +use crate::setup::{ErrorCode, Opt}; +use crate::state::{Counters, State}; +use crate::util::StringFromTempfileStart; + +use std::time::{Instant, SystemTime}; + +use subprocess::{Exec, ExitStatus, Redirection}; + +// same exit-code as used by the `timeout` shell command +static TIMEOUT_EXIT_CODE: i32 = 124; + +pub fn loop_step( + state: &mut State, + opt: &Opt, + items: &[String], + cmd_with_args: &str, + counters: Counters, + program_start: Instant, +) -> bool { + use std::env; + use std::thread; + + // Time Start + let loop_start = Instant::now(); + + // Set counters before execution + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + env::set_var("ACTUALCOUNT", counters.index.to_string()); + env::set_var( + "COUNT", + format!("{:.*}", counters.count_precision, counters.actual_count), + ); + + // Set iterated item as environment variable + if let Some(item) = items.get(counters.index) { + env::set_var("ITEM", item); + } + + // Finish if we're over our duration + if let Some(duration) = opt.for_duration { + let since = Instant::now().duration_since(program_start); + if since >= duration { + if opt.error_duration { + state.exit_status = TIMEOUT_EXIT_CODE + } + return true; + } + } + + // Finish if our time until has passed + // In this location, the loop will execute at least once, + // even if the start time is beyond the until time. + if let Some(until_time) = opt.until_time { + if SystemTime::now().duration_since(until_time).is_ok() { + return true; + } + } + + // Main executor + let exit_status = run_shell_command(state, cmd_with_args); + + // Print the results + let stdout = String::from_temp_start(&mut state.tmpfile); + state.print_results(&opt, &stdout); + + // --until-error + check_for_error(&opt.until_error, &mut state.has_matched, exit_status); + + // --until-success + if opt.until_success && exit_status.success() { + state.has_matched = true; + } + + // --until-fail + if opt.until_fail && !(exit_status.success()) { + state.has_matched = true; + } + + if opt.summary { + state.summary_exit_status(exit_status); + } + + // Finish if we matched + if state.has_matched { + return true; + } + + if let Some(ref previous_stdout) = state.previous_stdout { + // --until-changes + if opt.until_changes && *previous_stdout != stdout { + return true; + } + + // --until-same + if opt.until_same && *previous_stdout == stdout { + return true; + } + } + state.previous_stdout = Some(stdout); + + // Delay until next iteration time + let since = Instant::now().duration_since(loop_start); + if let Some(time) = opt.every.checked_sub(since) { + thread::sleep(time); + } + + false +} + +fn run_shell_command(state: &mut State, cmd_with_args: &str) -> ExitStatus { + use std::io::{prelude::*, SeekFrom}; + + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); + + Exec::shell(cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status +} + +fn check_for_error( + maybe_error: &Option, + has_matched: &mut bool, + exit_status: ExitStatus, +) { + match maybe_error { + Some(ErrorCode::Any) => { + *has_matched = !exit_status.success(); + } + Some(ErrorCode::Code(code)) => { + if exit_status == ExitStatus::Exited(*code) { + *has_matched = true; + } + } + _ => (), + } +} diff --git a/src/main.rs b/src/main.rs index a92c1d8..0bcb788 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ +mod loop_step; mod setup; mod state; mod util; +use loop_step::loop_step; use setup::{setup, Opt}; use state::{Counters, State, Summary}; @@ -26,7 +28,14 @@ fn main() { index: i, actual_count: *actual_count, }; - if state.loop_body(&opt, &items, cmd_with_args, counters, program_start) { + if loop_step( + &mut state, + &opt, + &items, + cmd_with_args, + counters, + program_start, + ) { break; } } diff --git a/src/state.rs b/src/state.rs index 5e58384..3786d33 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,21 +1,17 @@ -use crate::setup::{ErrorCode, Opt}; -use crate::util::StringFromTempfileStart; +use crate::setup::Opt; use std::fs::File; -use std::time::{Instant, SystemTime}; -use subprocess::{Exec, ExitStatus, Redirection}; +use subprocess::ExitStatus; -// same exit-code as used by the `timeout` shell command -static TIMEOUT_EXIT_CODE: i32 = 124; static UNKONWN_EXIT_CODE: u32 = 99; pub struct State { pub tmpfile: File, pub summary: Summary, pub exit_status: i32, - previous_stdout: Option, - has_matched: bool, + pub previous_stdout: Option, + pub has_matched: bool, } pub struct Counters { @@ -25,119 +21,7 @@ pub struct Counters { } impl State { - pub fn loop_body( - &mut self, - opt: &Opt, - items: &[String], - cmd_with_args: &str, - counters: Counters, - program_start: Instant, - ) -> bool { - use std::env; - use std::thread; - - // Time Start - let loop_start = Instant::now(); - - // Set counters before execution - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env::set_var("ACTUALCOUNT", counters.index.to_string()); - env::set_var( - "COUNT", - format!("{:.*}", counters.count_precision, counters.actual_count), - ); - - // Set iterated item as environment variable - if let Some(item) = items.get(counters.index) { - env::set_var("ITEM", item); - } - - // Finish if we're over our duration - if let Some(duration) = opt.for_duration { - let since = Instant::now().duration_since(program_start); - if since >= duration { - if opt.error_duration { - self.exit_status = TIMEOUT_EXIT_CODE - } - return true; - } - } - - // Finish if our time until has passed - // In this location, the loop will execute at least once, - // even if the start time is beyond the until time. - if let Some(until_time) = opt.until_time { - if SystemTime::now().duration_since(until_time).is_ok() { - return true; - } - } - - // Main executor - let exit_status = self.run_shell_command(cmd_with_args); - - // Print the results - let stdout = String::from_temp_start(&mut self.tmpfile); - self.print_results(&opt, &stdout); - - // --until-error - check_for_error(&opt.until_error, &mut self.has_matched, exit_status); - - // --until-success - if opt.until_success && exit_status.success() { - self.has_matched = true; - } - - // --until-fail - if opt.until_fail && !(exit_status.success()) { - self.has_matched = true; - } - - if opt.summary { - self.summary_exit_status(exit_status); - } - - // Finish if we matched - if self.has_matched { - return true; - } - - if let Some(ref previous_stdout) = self.previous_stdout { - // --until-changes - if opt.until_changes && *previous_stdout != stdout { - return true; - } - - // --until-same - if opt.until_same && *previous_stdout == stdout { - return true; - } - } - self.previous_stdout = Some(stdout); - - // Delay until next iteration time - let since = Instant::now().duration_since(loop_start); - if let Some(time) = opt.every.checked_sub(since) { - thread::sleep(time); - } - - false - } - - fn run_shell_command(&mut self, cmd_with_args: &str) -> ExitStatus { - use std::io::{prelude::*, SeekFrom}; - - self.tmpfile.seek(SeekFrom::Start(0)).ok(); - self.tmpfile.set_len(0).ok(); - - Exec::shell(cmd_with_args) - .stdout(Redirection::File(self.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status - } - - fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { + pub fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { match exit_status { ExitStatus::Exited(0) => self.summary.successes += 1, ExitStatus::Exited(n) => self.summary.failures.push(n), @@ -145,7 +29,7 @@ impl State { } } - fn print_results(&mut self, opt: &Opt, stdout: &str) { + pub fn print_results(&mut self, opt: &Opt, stdout: &str) { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, @@ -180,24 +64,6 @@ impl Default for State { } } -fn check_for_error( - maybe_error: &Option, - has_matched: &mut bool, - exit_status: ExitStatus, -) { - match maybe_error { - Some(ErrorCode::Any) => { - *has_matched = !exit_status.success(); - } - Some(ErrorCode::Code(code)) => { - if exit_status == ExitStatus::Exited(*code) { - *has_matched = true; - } - } - _ => (), - } -} - #[derive(Debug)] pub struct Summary { successes: u32, From 03d0bc5e042256a4967fe948b61c29d4f0686657 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 9 Mar 2019 12:03:35 +0100 Subject: [PATCH 25/56] trait Env --- src/loop_step.rs | 14 +++++++++----- src/main.rs | 13 ++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index 424d171..f716266 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -16,8 +16,8 @@ pub fn loop_step( cmd_with_args: &str, counters: Counters, program_start: Instant, + env: &Env, ) -> bool { - use std::env; use std::thread; // Time Start @@ -25,15 +25,15 @@ pub fn loop_step( // Set counters before execution // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env::set_var("ACTUALCOUNT", counters.index.to_string()); - env::set_var( + env.set_var("ACTUALCOUNT", &counters.index.to_string()); + env.set_var( "COUNT", - format!("{:.*}", counters.count_precision, counters.actual_count), + &format!("{:.*}", counters.count_precision, counters.actual_count), ); // Set iterated item as environment variable if let Some(item) = items.get(counters.index) { - env::set_var("ITEM", item); + env.set_var("ITEM", item); } // Finish if we're over our duration @@ -138,3 +138,7 @@ fn check_for_error( _ => (), } } + +pub trait Env { + fn set_var(&self, k: &str, v: &str); +} diff --git a/src/main.rs b/src/main.rs index 0bcb788..fe11ac8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod setup; mod state; mod util; -use loop_step::loop_step; +use loop_step::{loop_step, Env}; use setup::{setup, Opt}; use state::{Counters, State, Summary}; @@ -22,6 +22,8 @@ fn main() { // Counters and State let mut state = State::default(); + let env: &Env = &RealEnv {}; + for (i, actual_count) in counters_from_opt(&opt, &items).iter().enumerate() { let counters = Counters { count_precision, @@ -35,6 +37,7 @@ fn main() { cmd_with_args, counters, program_start, + env, ) { break; } @@ -94,3 +97,11 @@ fn exit_app( process::exit(exit_status); } + +struct RealEnv {} + +impl Env for RealEnv { + fn set_var(&self, k: &str, v: &str) { + std::env::set_var(k, v); + } +} From ae98b947e5532ee00eca906b36acb6536826c601 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 9 Mar 2019 12:11:36 +0100 Subject: [PATCH 26/56] trait ShellCommand --- src/loop_step.rs | 23 +++++++---------------- src/main.rs | 24 +++++++++++++++++++++++- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index f716266..c983ff1 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -4,7 +4,7 @@ use crate::util::StringFromTempfileStart; use std::time::{Instant, SystemTime}; -use subprocess::{Exec, ExitStatus, Redirection}; +use subprocess::ExitStatus; // same exit-code as used by the `timeout` shell command static TIMEOUT_EXIT_CODE: i32 = 124; @@ -17,6 +17,7 @@ pub fn loop_step( counters: Counters, program_start: Instant, env: &Env, + shell_command: &ShellCommand, ) -> bool { use std::thread; @@ -57,7 +58,7 @@ pub fn loop_step( } // Main executor - let exit_status = run_shell_command(state, cmd_with_args); + let exit_status = shell_command.run(state, cmd_with_args); // Print the results let stdout = String::from_temp_start(&mut state.tmpfile); @@ -107,20 +108,6 @@ pub fn loop_step( false } -fn run_shell_command(state: &mut State, cmd_with_args: &str) -> ExitStatus { - use std::io::{prelude::*, SeekFrom}; - - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); - - Exec::shell(cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status -} - fn check_for_error( maybe_error: &Option, has_matched: &mut bool, @@ -142,3 +129,7 @@ fn check_for_error( pub trait Env { fn set_var(&self, k: &str, v: &str); } + +pub trait ShellCommand { + fn run(&self, state: &mut State, cmd_with_args: &str) -> ExitStatus; +} diff --git a/src/main.rs b/src/main.rs index fe11ac8..77d84a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,15 @@ mod setup; mod state; mod util; -use loop_step::{loop_step, Env}; +use loop_step::{loop_step, Env, ShellCommand}; use setup::{setup, Opt}; use state::{Counters, State, Summary}; use std::fs::File; use std::process; +use subprocess::{Exec, ExitStatus, Redirection}; + fn main() { // Time let (opt, items, count_precision, program_start) = setup(); @@ -23,6 +25,7 @@ fn main() { // Counters and State let mut state = State::default(); let env: &Env = &RealEnv {}; + let shell_command: &ShellCommand = &RealShellCommand {}; for (i, actual_count) in counters_from_opt(&opt, &items).iter().enumerate() { let counters = Counters { @@ -38,6 +41,7 @@ fn main() { counters, program_start, env, + shell_command, ) { break; } @@ -105,3 +109,21 @@ impl Env for RealEnv { std::env::set_var(k, v); } } + +struct RealShellCommand {} + +impl ShellCommand for RealShellCommand { + fn run(&self, state: &mut State, cmd_with_args: &str) -> ExitStatus { + use std::io::{prelude::*, SeekFrom}; + + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); + + Exec::shell(cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status + } +} From 50a15646c8dba649ce999ade73ad0c3d301211c8 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 9 Mar 2019 18:45:07 +0100 Subject: [PATCH 27/56] keep process::exit in main --- src/main.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 77d84a0..1e640fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,13 +47,9 @@ fn main() { } } - exit_app( - opt.only_last, - opt.summary, - state.exit_status, - state.summary, - state.tmpfile, - ) + pre_exit_tasks(opt.only_last, opt.summary, state.summary, state.tmpfile); + + process::exit(state.exit_status); } fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { @@ -80,13 +76,7 @@ fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { counters } -fn exit_app( - only_last: bool, - print_summary: bool, - exit_status: i32, - summary: Summary, - mut tmpfile: File, -) { +fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { use util::StringFromTempfileStart; if only_last { @@ -98,8 +88,6 @@ fn exit_app( if print_summary { summary.print() } - - process::exit(exit_status); } struct RealEnv {} From 62eb1cfedae2e6ef155294f6d849ff914e763cef Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 12:01:13 +0100 Subject: [PATCH 28/56] LoopModel with step() method --- src/loop_step.rs | 190 ++++++++++++++++++++++++++--------------------- src/main.rs | 124 +++++++++++++++++++++++-------- src/setup.rs | 2 +- src/state.rs | 24 ------ 4 files changed, 201 insertions(+), 139 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index c983ff1..ac2b156 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,115 +1,133 @@ -use crate::setup::{ErrorCode, Opt}; +use crate::setup::ErrorCode; use crate::state::{Counters, State}; use crate::util::StringFromTempfileStart; -use std::time::{Instant, SystemTime}; +use std::time::{Duration, Instant, SystemTime}; use subprocess::ExitStatus; -// same exit-code as used by the `timeout` shell command +/// same exit-code as used by the `timeout` shell command static TIMEOUT_EXIT_CODE: i32 = 124; -pub fn loop_step( - state: &mut State, - opt: &Opt, - items: &[String], - cmd_with_args: &str, - counters: Counters, - program_start: Instant, - env: &Env, - shell_command: &ShellCommand, -) -> bool { - use std::thread; - - // Time Start - let loop_start = Instant::now(); - - // Set counters before execution - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env.set_var("ACTUALCOUNT", &counters.index.to_string()); - env.set_var( - "COUNT", - &format!("{:.*}", counters.count_precision, counters.actual_count), - ); - - // Set iterated item as environment variable - if let Some(item) = items.get(counters.index) { - env.set_var("ITEM", item); - } +pub struct LoopModel<'a> { + pub for_duration: Option, + pub error_duration: bool, + pub until_time: Option, + pub until_error: Option, + pub until_success: bool, + pub until_fail: bool, + pub summary: bool, + pub until_changes: bool, + pub until_same: bool, + pub every: Duration, + + pub cmd_with_args: String, + pub program_start: Instant, + pub items: Vec, + + pub env: &'a dyn Env, + pub shell_command: &'a dyn ShellCommand, + pub result_printer: &'a dyn ResultPrinter, +} - // Finish if we're over our duration - if let Some(duration) = opt.for_duration { - let since = Instant::now().duration_since(program_start); - if since >= duration { - if opt.error_duration { - state.exit_status = TIMEOUT_EXIT_CODE - } - return true; +impl<'a> LoopModel<'a> { + pub fn step(&self, state: &mut State, counters: Counters) -> bool { + use std::thread; + + // Time Start + let loop_start = Instant::now(); + + let env = self.env; + let shell_command = self.shell_command; + let result_printer = self.result_printer; + + // Set counters before execution + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + env.set_var("ACTUALCOUNT", &counters.index.to_string()); + env.set_var( + "COUNT", + &format!("{:.*}", counters.count_precision, counters.actual_count), + ); + + // Set iterated item as environment variable + if let Some(item) = self.items.get(counters.index) { + env.set_var("ITEM", item); } - } - // Finish if our time until has passed - // In this location, the loop will execute at least once, - // even if the start time is beyond the until time. - if let Some(until_time) = opt.until_time { - if SystemTime::now().duration_since(until_time).is_ok() { - return true; + // Finish if we're over our duration + if let Some(duration) = self.for_duration { + let since = Instant::now().duration_since(self.program_start); + if since >= duration { + if self.error_duration { + state.exit_status = TIMEOUT_EXIT_CODE + } + return true; + } } - } - // Main executor - let exit_status = shell_command.run(state, cmd_with_args); + // Finish if our time until has passed + // In this location, the loop will execute at least once, + // even if the start time is beyond the until time. + if let Some(until_time) = self.until_time { + if SystemTime::now().duration_since(until_time).is_ok() { + return true; + } + } - // Print the results - let stdout = String::from_temp_start(&mut state.tmpfile); - state.print_results(&opt, &stdout); + // Main executor + let exit_status = shell_command.run(state, &self.cmd_with_args); - // --until-error - check_for_error(&opt.until_error, &mut state.has_matched, exit_status); + // Print the results + let stdout = String::from_temp_start(&mut state.tmpfile); + result_printer.print_and_mutate(state, &stdout); - // --until-success - if opt.until_success && exit_status.success() { - state.has_matched = true; - } + // --until-error + check_for_error(self.until_error, &mut state.has_matched, exit_status); - // --until-fail - if opt.until_fail && !(exit_status.success()) { - state.has_matched = true; - } + // --until-success + if self.until_success && exit_status.success() { + state.has_matched = true; + } - if opt.summary { - state.summary_exit_status(exit_status); - } + // --until-fail + if self.until_fail && !(exit_status.success()) { + state.has_matched = true; + } - // Finish if we matched - if state.has_matched { - return true; - } + if self.summary { + state.summary_exit_status(exit_status); + } - if let Some(ref previous_stdout) = state.previous_stdout { - // --until-changes - if opt.until_changes && *previous_stdout != stdout { + // Finish if we matched + if state.has_matched { return true; } - // --until-same - if opt.until_same && *previous_stdout == stdout { - return true; + if let Some(ref previous_stdout) = state.previous_stdout { + // --until-changes + if self.until_changes && *previous_stdout != stdout { + return true; + } + + // --until-same + if self.until_same && *previous_stdout == stdout { + return true; + } } - } - state.previous_stdout = Some(stdout); + state.previous_stdout = Some(stdout); - // Delay until next iteration time - let since = Instant::now().duration_since(loop_start); - if let Some(time) = opt.every.checked_sub(since) { - thread::sleep(time); - } + // Delay until next iteration time + let since = Instant::now().duration_since(loop_start); + if let Some(time) = self.every.checked_sub(since) { + thread::sleep(time); + } - false + false + } } fn check_for_error( - maybe_error: &Option, + maybe_error: Option, has_matched: &mut bool, exit_status: ExitStatus, ) { @@ -118,7 +136,7 @@ fn check_for_error( *has_matched = !exit_status.success(); } Some(ErrorCode::Code(code)) => { - if exit_status == ExitStatus::Exited(*code) { + if exit_status == ExitStatus::Exited(code) { *has_matched = true; } } @@ -133,3 +151,7 @@ pub trait Env { pub trait ShellCommand { fn run(&self, state: &mut State, cmd_with_args: &str) -> ExitStatus; } + +pub trait ResultPrinter { + fn print_and_mutate(&self, state: &mut State, stdout: &str); +} diff --git a/src/main.rs b/src/main.rs index 1e640fa..4f8b1e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,20 +3,21 @@ mod setup; mod state; mod util; -use loop_step::{loop_step, Env, ShellCommand}; +use loop_step::{Env, LoopModel, ResultPrinter, ShellCommand}; use setup::{setup, Opt}; use state::{Counters, State, Summary}; use std::fs::File; use std::process; +use std::time::Instant; use subprocess::{Exec, ExitStatus, Redirection}; fn main() { // Time let (opt, items, count_precision, program_start) = setup(); - let cmd_with_args = &opt.input.join(" "); + let cmd_with_args = opt.input.join(" "); if cmd_with_args.is_empty() { println!("No command supplied, exiting."); return; @@ -24,25 +25,31 @@ fn main() { // Counters and State let mut state = State::default(); - let env: &Env = &RealEnv {}; - let shell_command: &ShellCommand = &RealShellCommand {}; - - for (i, actual_count) in counters_from_opt(&opt, &items).iter().enumerate() { + let env: &dyn Env = &RealEnv {}; + let shell_command: &dyn ShellCommand = &RealShellCommand {}; + let result_printer: &dyn ResultPrinter = &RealResultPrinter { opt: &opt }; + + let data_vec = counters_from_opt(&opt, &items); + let loop_model = create_loop_model( + &opt, + cmd_with_args, + program_start, + items, + env, + shell_command, + result_printer, + ); + + for (i, actual_count) in data_vec.iter().enumerate() { let counters = Counters { count_precision, index: i, actual_count: *actual_count, }; - if loop_step( - &mut state, - &opt, - &items, - cmd_with_args, - counters, - program_start, - env, - shell_command, - ) { + + let break_loop = loop_model.step(&mut state, counters); + + if break_loop { break; } } @@ -76,20 +83,6 @@ fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { counters } -fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { - use util::StringFromTempfileStart; - - if only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); - } - - if print_summary { - summary.print() - } -} - struct RealEnv {} impl Env for RealEnv { @@ -115,3 +108,74 @@ impl ShellCommand for RealShellCommand { .exit_status } } + +struct RealResultPrinter<'a> { + opt: &'a Opt, +} + +impl<'a> ResultPrinter for RealResultPrinter<'a> { + fn print_and_mutate(&self, state: &mut State, stdout: &str) { + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !self.opt.only_last { + println!("{}", line); + } + + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = self.opt.until_contains { + state.has_matched = line.contains(string); + } + + // --until-match + if let Some(ref regex) = self.opt.until_match { + state.has_matched = regex.captures(&line).is_some(); + } + }) + } +} + +fn create_loop_model<'a>( + opt: &Opt, + cmd_with_args: String, + program_start: Instant, + items: Vec, + env: &'a Env, + shell_command: &'a ShellCommand, + result_printer: &'a ResultPrinter, +) -> LoopModel<'a> { + LoopModel { + cmd_with_args, + program_start, + items, + env, + shell_command, + result_printer, + for_duration: opt.for_duration, + error_duration: opt.error_duration, + until_time: opt.until_time, + until_error: opt.until_error, + until_success: opt.until_success, + until_fail: opt.until_fail, + summary: opt.summary, + until_changes: opt.until_changes, + until_same: opt.until_same, + every: opt.every, + } +} + +fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { + use util::StringFromTempfileStart; + + if only_last { + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); + } + + if print_summary { + summary.print() + } +} diff --git a/src/setup.rs b/src/setup.rs index 50fa10b..9f973a3 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -43,7 +43,7 @@ fn precision_of(s: &str) -> usize { exp - after_point } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum ErrorCode { Any, Code(u32), diff --git a/src/state.rs b/src/state.rs index 3786d33..8c2776d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,3 @@ -use crate::setup::Opt; - use std::fs::File; use subprocess::ExitStatus; @@ -28,28 +26,6 @@ impl State { _ => self.summary.failures.push(UNKONWN_EXIT_CODE), } } - - pub fn print_results(&mut self, opt: &Opt, stdout: &str) { - stdout.lines().for_each(|line| { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !opt.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(ref string) = opt.until_contains { - self.has_matched = line.contains(string); - } - - // --until-match - if let Some(ref regex) = opt.until_match { - self.has_matched = regex.captures(&line).is_some(); - } - }) - } } impl Default for State { From 8e1dddaf02ade8b3da6fd27dea7ab6c2c80434ea Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 13:52:21 +0100 Subject: [PATCH 29/56] move State object instead of using &mut State in loop_step --- src/loop_step.rs | 26 +++++++++++++++----------- src/main.rs | 17 +++++++++++------ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index ac2b156..9eb5c93 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -31,7 +31,8 @@ pub struct LoopModel<'a> { } impl<'a> LoopModel<'a> { - pub fn step(&self, state: &mut State, counters: Counters) -> bool { + #[must_use] + pub fn step(&self, mut state: State, counters: Counters) -> (bool, State) { use std::thread; // Time Start @@ -61,7 +62,7 @@ impl<'a> LoopModel<'a> { if self.error_duration { state.exit_status = TIMEOUT_EXIT_CODE } - return true; + return (true, state); } } @@ -70,16 +71,17 @@ impl<'a> LoopModel<'a> { // even if the start time is beyond the until time. if let Some(until_time) = self.until_time { if SystemTime::now().duration_since(until_time).is_ok() { - return true; + return (true, state); } } // Main executor - let exit_status = shell_command.run(state, &self.cmd_with_args); + let (exit_status, new_state) = shell_command.run(state, &self.cmd_with_args); + state = new_state; // Print the results let stdout = String::from_temp_start(&mut state.tmpfile); - result_printer.print_and_mutate(state, &stdout); + state = result_printer.print_and_mutate(state, &stdout); // --until-error check_for_error(self.until_error, &mut state.has_matched, exit_status); @@ -100,18 +102,18 @@ impl<'a> LoopModel<'a> { // Finish if we matched if state.has_matched { - return true; + return (true, state); } if let Some(ref previous_stdout) = state.previous_stdout { // --until-changes if self.until_changes && *previous_stdout != stdout { - return true; + return (true, state); } // --until-same if self.until_same && *previous_stdout == stdout { - return true; + return (true, state); } } state.previous_stdout = Some(stdout); @@ -122,7 +124,7 @@ impl<'a> LoopModel<'a> { thread::sleep(time); } - false + (false, state) } } @@ -149,9 +151,11 @@ pub trait Env { } pub trait ShellCommand { - fn run(&self, state: &mut State, cmd_with_args: &str) -> ExitStatus; + #[must_use] + fn run(&self, state: State, cmd_with_args: &str) -> (ExitStatus, State); } pub trait ResultPrinter { - fn print_and_mutate(&self, state: &mut State, stdout: &str); + #[must_use] + fn print_and_mutate(&self, state: State, stdout: &str) -> State; } diff --git a/src/main.rs b/src/main.rs index 4f8b1e9..722bdf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,8 @@ fn main() { actual_count: *actual_count, }; - let break_loop = loop_model.step(&mut state, counters); + let (break_loop, new_state) = loop_model.step(state, counters); + state = new_state; if break_loop { break; @@ -94,18 +95,20 @@ impl Env for RealEnv { struct RealShellCommand {} impl ShellCommand for RealShellCommand { - fn run(&self, state: &mut State, cmd_with_args: &str) -> ExitStatus { + fn run(&self, mut state: State, cmd_with_args: &str) -> (ExitStatus, State) { use std::io::{prelude::*, SeekFrom}; state.tmpfile.seek(SeekFrom::Start(0)).ok(); state.tmpfile.set_len(0).ok(); - Exec::shell(cmd_with_args) + let status = Exec::shell(cmd_with_args) .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) .stderr(Redirection::Merge) .capture() .unwrap() - .exit_status + .exit_status; + + (status, state) } } @@ -114,7 +117,7 @@ struct RealResultPrinter<'a> { } impl<'a> ResultPrinter for RealResultPrinter<'a> { - fn print_and_mutate(&self, state: &mut State, stdout: &str) { + fn print_and_mutate(&self, mut state: State, stdout: &str) -> State { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, @@ -133,7 +136,9 @@ impl<'a> ResultPrinter for RealResultPrinter<'a> { if let Some(ref regex) = self.opt.until_match { state.has_matched = regex.captures(&line).is_some(); } - }) + }); + + state } } From ebfb61da83d53e0ca3d8d899921c9a0cf88072f0 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 14:05:36 +0100 Subject: [PATCH 30/56] use again custom iterator in main loop --- src/main.rs | 80 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/src/main.rs b/src/main.rs index 722bdf0..0f185ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,9 @@ fn main() { // Time let (opt, items, count_precision, program_start) = setup(); + let opt_only_last = opt.only_last; + let opt_summary = opt.summary; + let cmd_with_args = opt.input.join(" "); if cmd_with_args.is_empty() { println!("No command supplied, exiting."); @@ -24,12 +27,12 @@ fn main() { } // Counters and State - let mut state = State::default(); let env: &dyn Env = &RealEnv {}; let shell_command: &dyn ShellCommand = &RealShellCommand {}; let result_printer: &dyn ResultPrinter = &RealResultPrinter { opt: &opt }; - let data_vec = counters_from_opt(&opt, &items); + let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); + let loop_model = create_loop_model( &opt, cmd_with_args, @@ -40,11 +43,13 @@ fn main() { result_printer, ); - for (i, actual_count) in data_vec.iter().enumerate() { + let mut state = State::default(); + + for (index, actual_count) in iterator.enumerate() { let counters = Counters { count_precision, - index: i, - actual_count: *actual_count, + index, + actual_count, }; let (break_loop, new_state) = loop_model.step(state, counters); @@ -55,35 +60,11 @@ fn main() { } } - pre_exit_tasks(opt.only_last, opt.summary, state.summary, state.tmpfile); + pre_exit_tasks(opt_only_last, opt_summary, state.summary, state.tmpfile); process::exit(state.exit_status); } -fn counters_from_opt(opt: &Opt, items: &[String]) -> Vec { - let mut counters = vec![]; - let mut start = opt.offset - opt.count_by; - let mut index = 0_f64; - let step_by = opt.count_by; - let end = if let Some(num) = opt.num { - num - } else if !items.is_empty() { - items.len() as f64 - } else { - std::f64::INFINITY - }; - loop { - start += step_by; - index += 1_f64; - if index <= end { - counters.push(start) - } else { - break; - } - } - counters -} - struct RealEnv {} impl Env for RealEnv { @@ -184,3 +165,42 @@ fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tm summary.print() } } + +struct LoopIterator { + start: f64, + iters: f64, + end: f64, + step_by: f64, +} + +impl LoopIterator { + fn new(offset: f64, count_by: f64, num: Option, items: &[String]) -> LoopIterator { + let end = if let Some(num) = num { + num + } else if !items.is_empty() { + items.len() as f64 + } else { + std::f64::INFINITY + }; + LoopIterator { + start: offset - count_by, + iters: 0.0, + end, + step_by: count_by, + } + } +} + +impl Iterator for LoopIterator { + type Item = f64; + + fn next(&mut self) -> Option { + self.start += self.step_by; + self.iters += 1.0; + if self.iters <= self.end { + Some(self.start) + } else { + None + } + } +} From 5b904665560e6295e58f7d72bb65161ae095ffd0 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 14:38:01 +0100 Subject: [PATCH 31/56] clean-up --- Cargo.lock | 18 +++++++------- Cargo.toml | 2 +- src/loop_step.rs | 20 ++++++++-------- src/main.rs | 62 +++++++++++++++--------------------------------- src/setup.rs | 37 +++++++++++++++++++++++++++-- src/state.rs | 14 +++++------ 6 files changed, 81 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d00996..07ef4e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -287,22 +287,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -317,7 +317,7 @@ dependencies = [ [[package]] name = "syn" -version = "0.15.28" +version = "0.15.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -449,10 +449,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3" -"checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04" +"checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" +"checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" "checksum subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "28fc0f40f0c0da73339d347aa7d6d2b90341a95683a47722bc4eebed71ff3c00" -"checksum syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)" = "218aa5a01ab9805df6e9e48074c8d88f317cc9660b1ad6c3dabac2d627d185d6" +"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" "checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" diff --git a/Cargo.toml b/Cargo.toml index 3837390..06076bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" license = "MIT" [dependencies] -structopt = "0.2.14" +structopt = "0.2.15" humantime = "1.2.0" atty = "0.2.11" regex = "1.1.2" diff --git a/src/loop_step.rs b/src/loop_step.rs index 9eb5c93..b856a63 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -44,15 +44,15 @@ impl<'a> LoopModel<'a> { // Set counters before execution // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env.set_var("ACTUALCOUNT", &counters.index.to_string()); - env.set_var( + env.set("ACTUALCOUNT", &counters.index.to_string()); + env.set( "COUNT", &format!("{:.*}", counters.count_precision, counters.actual_count), ); // Set iterated item as environment variable if let Some(item) = self.items.get(counters.index) { - env.set_var("ITEM", item); + env.set("ITEM", item); } // Finish if we're over our duration @@ -81,10 +81,10 @@ impl<'a> LoopModel<'a> { // Print the results let stdout = String::from_temp_start(&mut state.tmpfile); - state = result_printer.print_and_mutate(state, &stdout); + state = result_printer.print(state, &stdout); // --until-error - check_for_error(self.until_error, &mut state.has_matched, exit_status); + check_for_error(exit_status, self.until_error, &mut state.has_matched); // --until-success if self.until_success && exit_status.success() { @@ -92,12 +92,12 @@ impl<'a> LoopModel<'a> { } // --until-fail - if self.until_fail && !(exit_status.success()) { + if self.until_fail && !exit_status.success() { state.has_matched = true; } if self.summary { - state.summary_exit_status(exit_status); + state.update_summary(exit_status); } // Finish if we matched @@ -129,9 +129,9 @@ impl<'a> LoopModel<'a> { } fn check_for_error( + exit_status: ExitStatus, maybe_error: Option, has_matched: &mut bool, - exit_status: ExitStatus, ) { match maybe_error { Some(ErrorCode::Any) => { @@ -147,7 +147,7 @@ fn check_for_error( } pub trait Env { - fn set_var(&self, k: &str, v: &str); + fn set(&self, k: &str, v: &str); } pub trait ShellCommand { @@ -157,5 +157,5 @@ pub trait ShellCommand { pub trait ResultPrinter { #[must_use] - fn print_and_mutate(&self, state: State, stdout: &str) -> State; + fn print(&self, state: State, stdout: &str) -> State; } diff --git a/src/main.rs b/src/main.rs index 0f185ed..2f1b064 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,14 @@ mod setup; mod state; mod util; -use loop_step::{Env, LoopModel, ResultPrinter, ShellCommand}; -use setup::{setup, Opt}; +use loop_step::{Env, ResultPrinter, ShellCommand}; +use setup::setup; use state::{Counters, State, Summary}; use std::fs::File; use std::process; -use std::time::Instant; +use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; fn main() { @@ -29,12 +29,15 @@ fn main() { // Counters and State let env: &dyn Env = &RealEnv {}; let shell_command: &dyn ShellCommand = &RealShellCommand {}; - let result_printer: &dyn ResultPrinter = &RealResultPrinter { opt: &opt }; + let result_printer: &dyn ResultPrinter = &RealResultPrinter { + only_last: opt.only_last, + until_contains: opt.until_contains.clone(), + until_match: opt.until_match.clone(), + }; let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); - let loop_model = create_loop_model( - &opt, + let loop_model = opt.into_loop_model( cmd_with_args, program_start, items, @@ -68,7 +71,7 @@ fn main() { struct RealEnv {} impl Env for RealEnv { - fn set_var(&self, k: &str, v: &str) { + fn set(&self, k: &str, v: &str) { std::env::set_var(k, v); } } @@ -93,28 +96,30 @@ impl ShellCommand for RealShellCommand { } } -struct RealResultPrinter<'a> { - opt: &'a Opt, +struct RealResultPrinter { + only_last: bool, + until_contains: Option, + until_match: Option, } -impl<'a> ResultPrinter for RealResultPrinter<'a> { - fn print_and_mutate(&self, mut state: State, stdout: &str) -> State { +impl ResultPrinter for RealResultPrinter { + fn print(&self, mut state: State, stdout: &str) -> State { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, // defer printing until later - if !self.opt.only_last { + if !self.only_last { println!("{}", line); } // --until-contains // We defer loop breaking until the entire result is printed. - if let Some(ref string) = self.opt.until_contains { + if let Some(ref string) = self.until_contains { state.has_matched = line.contains(string); } // --until-match - if let Some(ref regex) = self.opt.until_match { + if let Some(ref regex) = self.until_match { state.has_matched = regex.captures(&line).is_some(); } }); @@ -123,35 +128,6 @@ impl<'a> ResultPrinter for RealResultPrinter<'a> { } } -fn create_loop_model<'a>( - opt: &Opt, - cmd_with_args: String, - program_start: Instant, - items: Vec, - env: &'a Env, - shell_command: &'a ShellCommand, - result_printer: &'a ResultPrinter, -) -> LoopModel<'a> { - LoopModel { - cmd_with_args, - program_start, - items, - env, - shell_command, - result_printer, - for_duration: opt.for_duration, - error_duration: opt.error_duration, - until_time: opt.until_time, - until_error: opt.until_error, - until_success: opt.until_success, - until_fail: opt.until_fail, - summary: opt.summary, - until_changes: opt.until_changes, - until_same: opt.until_same, - every: opt.every, - } -} - fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { use util::StringFromTempfileStart; diff --git a/src/setup.rs b/src/setup.rs index 9f973a3..4dea74d 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,3 +1,5 @@ +use crate::loop_step::{Env, LoopModel, ResultPrinter, ShellCommand}; + use regex::Regex; use std::time::{Duration, Instant, SystemTime}; @@ -49,14 +51,14 @@ pub enum ErrorCode { Code(u32), } -fn get_error_code(input: &&str) -> ErrorCode { +fn get_error_code(input: &str) -> ErrorCode { input .parse() .map(ErrorCode::Code) .unwrap_or_else(|_| ErrorCode::Any) } -fn get_values(input: &&str) -> Vec { +fn get_values(input: &str) -> Vec { if input.contains('\n') { input.split('\n').map(String::from).collect() } else if input.contains(',') { @@ -162,3 +164,34 @@ pub struct Opt { #[structopt(raw(multiple = "true"))] pub input: Vec, } + +impl<'a> Opt { + pub fn into_loop_model( + self, + cmd_with_args: String, + program_start: Instant, + items: Vec, + env: &'a Env, + shell_command: &'a ShellCommand, + result_printer: &'a ResultPrinter, + ) -> LoopModel<'a> { + LoopModel { + cmd_with_args, + program_start, + items, + env, + shell_command, + result_printer, + for_duration: self.for_duration, + error_duration: self.error_duration, + until_time: self.until_time, + until_error: self.until_error, + until_success: self.until_success, + until_fail: self.until_fail, + summary: self.summary, + until_changes: self.until_changes, + until_same: self.until_same, + every: self.every, + } + } +} diff --git a/src/state.rs b/src/state.rs index 8c2776d..c6f67e8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,14 +12,8 @@ pub struct State { pub has_matched: bool, } -pub struct Counters { - pub index: usize, - pub count_precision: usize, - pub actual_count: f64, -} - impl State { - pub fn summary_exit_status(&mut self, exit_status: subprocess::ExitStatus) { + pub fn update_summary(&mut self, exit_status: subprocess::ExitStatus) { match exit_status { ExitStatus::Exited(0) => self.summary.successes += 1, ExitStatus::Exited(n) => self.summary.failures.push(n), @@ -40,6 +34,12 @@ impl Default for State { } } +pub struct Counters { + pub index: usize, + pub count_precision: usize, + pub actual_count: f64, +} + #[derive(Debug)] pub struct Summary { successes: u32, From 056f9becfe50940ef92648ee518bbc590c1d707d Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 16:44:57 +0100 Subject: [PATCH 32/56] move LoopIterator into own file --- src/loop_iterator.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 41 ++--------------------------------------- 2 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 src/loop_iterator.rs diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs new file mode 100644 index 0000000..e2b513f --- /dev/null +++ b/src/loop_iterator.rs @@ -0,0 +1,38 @@ +pub struct LoopIterator { + start: f64, + iters: f64, + end: f64, + step_by: f64, +} + +impl LoopIterator { + pub fn new(offset: f64, count_by: f64, num: Option, items: &[String]) -> LoopIterator { + let end = if let Some(num) = num { + num + } else if !items.is_empty() { + items.len() as f64 + } else { + std::f64::INFINITY + }; + LoopIterator { + start: offset - count_by, + iters: 0.0, + end, + step_by: count_by, + } + } +} + +impl Iterator for LoopIterator { + type Item = f64; + + fn next(&mut self) -> Option { + self.start += self.step_by; + self.iters += 1.0; + if self.iters <= self.end { + Some(self.start) + } else { + None + } + } +} diff --git a/src/main.rs b/src/main.rs index 2f1b064..f2fa802 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +mod loop_iterator; mod loop_step; mod setup; mod state; mod util; +use loop_iterator::LoopIterator; use loop_step::{Env, ResultPrinter, ShellCommand}; use setup::setup; use state::{Counters, State, Summary}; @@ -141,42 +143,3 @@ fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tm summary.print() } } - -struct LoopIterator { - start: f64, - iters: f64, - end: f64, - step_by: f64, -} - -impl LoopIterator { - fn new(offset: f64, count_by: f64, num: Option, items: &[String]) -> LoopIterator { - let end = if let Some(num) = num { - num - } else if !items.is_empty() { - items.len() as f64 - } else { - std::f64::INFINITY - }; - LoopIterator { - start: offset - count_by, - iters: 0.0, - end, - step_by: count_by, - } - } -} - -impl Iterator for LoopIterator { - type Item = f64; - - fn next(&mut self) -> Option { - self.start += self.step_by; - self.iters += 1.0; - if self.iters <= self.end { - Some(self.start) - } else { - None - } - } -} From 46eb285b65483591b69429ba3b5c4a62d920b925 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 17:04:38 +0100 Subject: [PATCH 33/56] use impl Trait --- src/loop_step.rs | 21 ++++++++++----------- src/main.rs | 20 ++++++-------------- src/setup.rs | 12 +++--------- 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index b856a63..1838865 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -9,7 +9,7 @@ use subprocess::ExitStatus; /// same exit-code as used by the `timeout` shell command static TIMEOUT_EXIT_CODE: i32 = 124; -pub struct LoopModel<'a> { +pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, pub until_time: Option, @@ -24,24 +24,23 @@ pub struct LoopModel<'a> { pub cmd_with_args: String, pub program_start: Instant, pub items: Vec, - - pub env: &'a dyn Env, - pub shell_command: &'a dyn ShellCommand, - pub result_printer: &'a dyn ResultPrinter, } -impl<'a> LoopModel<'a> { +impl LoopModel { #[must_use] - pub fn step(&self, mut state: State, counters: Counters) -> (bool, State) { + pub fn step( + &self, + mut state: State, + counters: Counters, + env: &impl Env, + shell_command: &impl ShellCommand, + result_printer: &impl ResultPrinter, + ) -> (bool, State) { use std::thread; // Time Start let loop_start = Instant::now(); - let env = self.env; - let shell_command = self.shell_command; - let result_printer = self.result_printer; - // Set counters before execution // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. env.set("ACTUALCOUNT", &counters.index.to_string()); diff --git a/src/main.rs b/src/main.rs index f2fa802..e46b64d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,24 +29,15 @@ fn main() { } // Counters and State - let env: &dyn Env = &RealEnv {}; - let shell_command: &dyn ShellCommand = &RealShellCommand {}; - let result_printer: &dyn ResultPrinter = &RealResultPrinter { + let env = RealEnv {}; + let shell_command = RealShellCommand {}; + let result_printer = RealResultPrinter { only_last: opt.only_last, until_contains: opt.until_contains.clone(), until_match: opt.until_match.clone(), }; - let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); - - let loop_model = opt.into_loop_model( - cmd_with_args, - program_start, - items, - env, - shell_command, - result_printer, - ); + let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); let mut state = State::default(); @@ -57,7 +48,8 @@ fn main() { actual_count, }; - let (break_loop, new_state) = loop_model.step(state, counters); + let (break_loop, new_state) = + loop_model.step(state, counters, &env, &shell_command, &result_printer); state = new_state; if break_loop { diff --git a/src/setup.rs b/src/setup.rs index 4dea74d..5f9e036 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,4 +1,4 @@ -use crate::loop_step::{Env, LoopModel, ResultPrinter, ShellCommand}; +use crate::loop_step::LoopModel; use regex::Regex; use std::time::{Duration, Instant, SystemTime}; @@ -165,23 +165,17 @@ pub struct Opt { pub input: Vec, } -impl<'a> Opt { +impl Opt { pub fn into_loop_model( self, cmd_with_args: String, program_start: Instant, items: Vec, - env: &'a Env, - shell_command: &'a ShellCommand, - result_printer: &'a ResultPrinter, - ) -> LoopModel<'a> { + ) -> LoopModel { LoopModel { cmd_with_args, program_start, items, - env, - shell_command, - result_printer, for_duration: self.for_duration, error_duration: self.error_duration, until_time: self.until_time, From 8cc290e7434fb24b6395eadddfd2b7a7d1a2e058 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 17:16:38 +0100 Subject: [PATCH 34/56] move io-funtions into io.rs --- src/io.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 94 +++++----------------------------------------------- 2 files changed, 104 insertions(+), 85 deletions(-) create mode 100644 src/io.rs diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..083088d --- /dev/null +++ b/src/io.rs @@ -0,0 +1,95 @@ +use crate::loop_step::{Env, ResultPrinter, ShellCommand}; +use crate::state::{State, Summary}; + +use std::fs::File; + +use regex::Regex; +use subprocess::{Exec, ExitStatus, Redirection}; + +pub struct RealEnv {} + +impl Env for RealEnv { + fn set(&self, k: &str, v: &str) { + std::env::set_var(k, v); + } +} + +pub struct RealShellCommand {} + +impl ShellCommand for RealShellCommand { + fn run(&self, mut state: State, cmd_with_args: &str) -> (ExitStatus, State) { + use std::io::{prelude::*, SeekFrom}; + + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); + + let status = Exec::shell(cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status; + + (status, state) + } +} + +pub struct RealResultPrinter { + only_last: bool, + until_contains: Option, + until_match: Option, +} + +impl RealResultPrinter { + pub fn new( + only_last: bool, + until_contains: Option, + until_match: Option, + ) -> RealResultPrinter { + RealResultPrinter { + only_last, + until_contains, + until_match, + } + } +} + +impl ResultPrinter for RealResultPrinter { + fn print(&self, mut state: State, stdout: &str) -> State { + stdout.lines().for_each(|line| { + // --only-last + // If we only want output from the last execution, + // defer printing until later + if !self.only_last { + println!("{}", line); + } + + // --until-contains + // We defer loop breaking until the entire result is printed. + if let Some(ref string) = self.until_contains { + state.has_matched = line.contains(string); + } + + // --until-match + if let Some(ref regex) = self.until_match { + state.has_matched = regex.captures(&line).is_some(); + } + }); + + state + } +} + +pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { + use crate::util::StringFromTempfileStart; + + if only_last { + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); + } + + if print_summary { + summary.print() + } +} diff --git a/src/main.rs b/src/main.rs index e46b64d..5030862 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,17 @@ +mod io; mod loop_iterator; mod loop_step; mod setup; mod state; mod util; +use io::{pre_exit_tasks, RealEnv, RealResultPrinter, RealShellCommand}; use loop_iterator::LoopIterator; -use loop_step::{Env, ResultPrinter, ShellCommand}; use setup::setup; -use state::{Counters, State, Summary}; +use state::{Counters, State}; -use std::fs::File; use std::process; -use regex::Regex; -use subprocess::{Exec, ExitStatus, Redirection}; - fn main() { // Time let (opt, items, count_precision, program_start) = setup(); @@ -31,11 +28,12 @@ fn main() { // Counters and State let env = RealEnv {}; let shell_command = RealShellCommand {}; - let result_printer = RealResultPrinter { - only_last: opt.only_last, - until_contains: opt.until_contains.clone(), - until_match: opt.until_match.clone(), - }; + let result_printer = RealResultPrinter::new( + opt.only_last, + opt.until_contains.clone(), + opt.until_match.clone(), + ); + let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); @@ -61,77 +59,3 @@ fn main() { process::exit(state.exit_status); } - -struct RealEnv {} - -impl Env for RealEnv { - fn set(&self, k: &str, v: &str) { - std::env::set_var(k, v); - } -} - -struct RealShellCommand {} - -impl ShellCommand for RealShellCommand { - fn run(&self, mut state: State, cmd_with_args: &str) -> (ExitStatus, State) { - use std::io::{prelude::*, SeekFrom}; - - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); - - let status = Exec::shell(cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status; - - (status, state) - } -} - -struct RealResultPrinter { - only_last: bool, - until_contains: Option, - until_match: Option, -} - -impl ResultPrinter for RealResultPrinter { - fn print(&self, mut state: State, stdout: &str) -> State { - stdout.lines().for_each(|line| { - // --only-last - // If we only want output from the last execution, - // defer printing until later - if !self.only_last { - println!("{}", line); - } - - // --until-contains - // We defer loop breaking until the entire result is printed. - if let Some(ref string) = self.until_contains { - state.has_matched = line.contains(string); - } - - // --until-match - if let Some(ref regex) = self.until_match { - state.has_matched = regex.captures(&line).is_some(); - } - }); - - state - } -} - -fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { - use util::StringFromTempfileStart; - - if only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); - } - - if print_summary { - summary.print() - } -} From 4580ac871fa2ab044978b387d22d1fc2eeee7e6f Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 17:45:39 +0100 Subject: [PATCH 35/56] keep StructOpt local to setup.rs --- src/main.rs | 41 +++++++---------------- src/setup.rs | 93 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 53 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5030862..3237c0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,49 +5,32 @@ mod setup; mod state; mod util; -use io::{pre_exit_tasks, RealEnv, RealResultPrinter, RealShellCommand}; -use loop_iterator::LoopIterator; -use setup::setup; -use state::{Counters, State}; - -use std::process; - fn main() { - // Time - let (opt, items, count_precision, program_start) = setup(); + use io::pre_exit_tasks; + use setup::setup; + use state::{Counters, State}; + use std::process; - let opt_only_last = opt.only_last; - let opt_summary = opt.summary; + let m = setup(); - let cmd_with_args = opt.input.join(" "); - if cmd_with_args.is_empty() { + if m.is_no_command_supplied { println!("No command supplied, exiting."); return; } - // Counters and State - let env = RealEnv {}; - let shell_command = RealShellCommand {}; - let result_printer = RealResultPrinter::new( - opt.only_last, - opt.until_contains.clone(), - opt.until_match.clone(), - ); - - let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); - let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); - let mut state = State::default(); + let loop_model = m.loop_model; - for (index, actual_count) in iterator.enumerate() { + for (index, actual_count) in m.iterator.enumerate() { let counters = Counters { - count_precision, + count_precision: m.count_precision, index, actual_count, }; let (break_loop, new_state) = - loop_model.step(state, counters, &env, &shell_command, &result_printer); + loop_model.step(state, counters, &m.env, &m.shell_command, &m.result_printer); + state = new_state; if break_loop { @@ -55,7 +38,7 @@ fn main() { } } - pre_exit_tasks(opt_only_last, opt_summary, state.summary, state.tmpfile); + pre_exit_tasks(m.opt_only_last, m.opt_summary, state.summary, state.tmpfile); process::exit(state.exit_status); } diff --git a/src/setup.rs b/src/setup.rs index 5f9e036..4a874ce 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,12 +1,28 @@ +use crate::io::{RealEnv, RealResultPrinter, RealShellCommand}; +use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; -use regex::Regex; use std::time::{Duration, Instant, SystemTime}; use humantime::{parse_duration, parse_rfc3339_weak}; +use regex::Regex; use structopt::StructOpt; -pub fn setup() -> (Opt, Vec, usize, Instant) { +pub struct Setup { + pub count_precision: usize, + pub is_no_command_supplied: bool, + pub opt_only_last: bool, + pub opt_summary: bool, + + pub env: RealEnv, + pub shell_command: RealShellCommand, + pub result_printer: RealResultPrinter, + + pub iterator: LoopIterator, + pub loop_model: LoopModel, +} + +pub fn setup() -> Setup { use std::io::{self, BufRead}; // Time @@ -14,12 +30,26 @@ pub fn setup() -> (Opt, Vec, usize, Instant) { // Load the CLI arguments let opt = Opt::from_args(); + let count_precision = Opt::clap() .get_matches() .value_of("count_by") .map(precision_of) .unwrap_or(0); + let cmd_with_args = opt.input.join(" "); + let is_no_command_supplied = cmd_with_args.is_empty(); + let opt_only_last = opt.only_last; + let opt_summary = opt.summary; + + let env = RealEnv {}; + let shell_command = RealShellCommand {}; + let result_printer = RealResultPrinter::new( + opt.only_last, + opt.until_contains.clone(), + opt.until_match.clone(), + ); + // Number of iterations let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); @@ -32,7 +62,22 @@ pub fn setup() -> (Opt, Vec, usize, Instant) { .for_each(|line| items.push(line)); } - (opt, items, count_precision, program_start) + let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); + let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); + + Setup { + count_precision, + is_no_command_supplied, + opt_only_last, + opt_summary, + + env, + shell_command, + result_printer, + + iterator, + loop_model, + } } fn precision_of(s: &str) -> usize { @@ -74,18 +119,18 @@ fn get_values(input: &str) -> Vec { author = "Rich Jones ", about = "UNIX's missing `loop` command" )] -pub struct Opt { +struct Opt { /// Number of iterations to execute #[structopt(short = "n", long = "num")] - pub num: Option, + num: Option, /// Amount to increment the counter by #[structopt(short = "b", long = "count-by", default_value = "1")] - pub count_by: f64, + count_by: f64, /// Amount to offset the initial counter by #[structopt(short = "o", long = "offset", default_value = "0")] - pub offset: f64, + offset: f64, /// How often to iterate. ex., 5s, 1h1m1s1ms1us #[structopt( @@ -94,11 +139,11 @@ pub struct Opt { default_value = "1us", parse(try_from_str = "parse_duration") )] - pub every: Duration, + every: Duration, /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue #[structopt(long = "for", parse(from_str = "get_values"))] - pub ffor: Option>, + ffor: Option>, /// Keep going until the duration has elapsed (example 1m30s) #[structopt( @@ -106,23 +151,23 @@ pub struct Opt { long = "for-duration", parse(try_from_str = "parse_duration") )] - pub for_duration: Option, + for_duration: Option, /// Keep going until the output contains this string #[structopt(short = "c", long = "until-contains")] - pub until_contains: Option, + until_contains: Option, /// Keep going until the output changes #[structopt(short = "C", long = "until-changes")] - pub until_changes: bool, + until_changes: bool, /// Keep going until the output changes #[structopt(short = "S", long = "until-same")] - pub until_same: bool, + until_same: bool, /// Keep going until the output matches this regular expression #[structopt(short = "m", long = "until-match", parse(try_from_str = "Regex::new"))] - pub until_match: Option, + until_match: Option, /// Keep going until a future time, ex. "2018-04-20 04:20:00" (Times in UTC.) #[structopt( @@ -130,43 +175,43 @@ pub struct Opt { long = "until-time", parse(try_from_str = "parse_rfc3339_weak") )] - pub until_time: Option, + until_time: Option, /// Keep going until the command exit status is non-zero, or the value given #[structopt(short = "r", long = "until-error", parse(from_str = "get_error_code"))] - pub until_error: Option, + until_error: Option, /// Keep going until the command exit status is zero #[structopt(short = "s", long = "until-success")] - pub until_success: bool, + until_success: bool, /// Keep going until the command exit status is non-zero #[structopt(short = "f", long = "until-fail")] - pub until_fail: bool, + until_fail: bool, /// Only print the output of the last execution of the command #[structopt(short = "l", long = "only-last")] - pub only_last: bool, + only_last: bool, /// Read from standard input #[structopt(short = "i", long = "stdin")] - pub stdin: bool, + stdin: bool, /// Exit with timeout error code on duration #[structopt(short = "D", long = "error-duration")] - pub error_duration: bool, + error_duration: bool, /// Provide a summary #[structopt(long = "summary")] - pub summary: bool, + summary: bool, /// The command to be looped #[structopt(raw(multiple = "true"))] - pub input: Vec, + input: Vec, } impl Opt { - pub fn into_loop_model( + fn into_loop_model( self, cmd_with_args: String, program_start: Instant, From 736e19d7134d3997a7d7afa4589afde363f9724b Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 18:23:36 +0100 Subject: [PATCH 36/56] use process::exit too when no command is supplied --- src/loop_step.rs | 4 ++-- src/main.rs | 6 ++++-- src/state.rs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index 1838865..7c5eeb1 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -7,7 +7,7 @@ use std::time::{Duration, Instant, SystemTime}; use subprocess::ExitStatus; /// same exit-code as used by the `timeout` shell command -static TIMEOUT_EXIT_CODE: i32 = 124; +static EXIT_CODE_TIMEOUT: i32 = 124; pub struct LoopModel { pub for_duration: Option, @@ -59,7 +59,7 @@ impl LoopModel { let since = Instant::now().duration_since(self.program_start); if since >= duration { if self.error_duration { - state.exit_status = TIMEOUT_EXIT_CODE + state.exit_status = EXIT_CODE_TIMEOUT } return (true, state); } diff --git a/src/main.rs b/src/main.rs index 3237c0b..5d0d3ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ mod setup; mod state; mod util; +static EXIT_CODE_MINOR_ERROR: i32 = 2; + fn main() { use io::pre_exit_tasks; use setup::setup; @@ -14,8 +16,8 @@ fn main() { let m = setup(); if m.is_no_command_supplied { - println!("No command supplied, exiting."); - return; + eprintln!("No command supplied, exiting."); + process::exit(EXIT_CODE_MINOR_ERROR); } let mut state = State::default(); diff --git a/src/state.rs b/src/state.rs index c6f67e8..9cab35e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use std::fs::File; use subprocess::ExitStatus; -static UNKONWN_EXIT_CODE: u32 = 99; +static EXIT_CODE_UNKONWN: u32 = 99; pub struct State { pub tmpfile: File, @@ -17,7 +17,7 @@ impl State { match exit_status { ExitStatus::Exited(0) => self.summary.successes += 1, ExitStatus::Exited(n) => self.summary.failures.push(n), - _ => self.summary.failures.push(UNKONWN_EXIT_CODE), + _ => self.summary.failures.push(EXIT_CODE_UNKONWN), } } } From 4e2284727ffaa358095c98ae41d4b66c7d74ec22 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Fri, 15 Mar 2019 22:32:46 +0100 Subject: [PATCH 37/56] enum ExitCode --- src/loop_step.rs | 7 ++----- src/main.rs | 8 +++----- src/state.rs | 36 ++++++++++++++++++++++++------------ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/loop_step.rs b/src/loop_step.rs index 7c5eeb1..e3d624e 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,14 +1,11 @@ use crate::setup::ErrorCode; -use crate::state::{Counters, State}; +use crate::state::{Counters, ExitCode, State}; use crate::util::StringFromTempfileStart; use std::time::{Duration, Instant, SystemTime}; use subprocess::ExitStatus; -/// same exit-code as used by the `timeout` shell command -static EXIT_CODE_TIMEOUT: i32 = 124; - pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, @@ -59,7 +56,7 @@ impl LoopModel { let since = Instant::now().duration_since(self.program_start); if since >= duration { if self.error_duration { - state.exit_status = EXIT_CODE_TIMEOUT + state.exit_code = ExitCode::Timeout; } return (true, state); } diff --git a/src/main.rs b/src/main.rs index 5d0d3ec..cfbaaa4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,19 +5,17 @@ mod setup; mod state; mod util; -static EXIT_CODE_MINOR_ERROR: i32 = 2; - fn main() { use io::pre_exit_tasks; use setup::setup; - use state::{Counters, State}; + use state::{Counters, ExitCode, State}; use std::process; let m = setup(); if m.is_no_command_supplied { eprintln!("No command supplied, exiting."); - process::exit(EXIT_CODE_MINOR_ERROR); + process::exit(ExitCode::MinorError.into()); } let mut state = State::default(); @@ -42,5 +40,5 @@ fn main() { pre_exit_tasks(m.opt_only_last, m.opt_summary, state.summary, state.tmpfile); - process::exit(state.exit_status); + process::exit(state.exit_code.into()); } diff --git a/src/state.rs b/src/state.rs index 9cab35e..fbbb5fe 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,22 +2,20 @@ use std::fs::File; use subprocess::ExitStatus; -static EXIT_CODE_UNKONWN: u32 = 99; - pub struct State { pub tmpfile: File, pub summary: Summary, - pub exit_status: i32, + pub exit_code: ExitCode, pub previous_stdout: Option, pub has_matched: bool, } impl State { - pub fn update_summary(&mut self, exit_status: subprocess::ExitStatus) { + pub fn update_summary(&mut self, exit_status: ExitStatus) { match exit_status { ExitStatus::Exited(0) => self.summary.successes += 1, - ExitStatus::Exited(n) => self.summary.failures.push(n), - _ => self.summary.failures.push(EXIT_CODE_UNKONWN), + ExitStatus::Exited(n) => self.summary.failures.push(n as i32), + _ => self.summary.failures.push(ExitCode::Unkonwn.into()), } } } @@ -29,7 +27,7 @@ impl Default for State { tmpfile: tempfile::tempfile().unwrap(), summary: Summary::default(), previous_stdout: None, - exit_status: 0, + exit_code: ExitCode::Okay, } } } @@ -42,13 +40,13 @@ pub struct Counters { #[derive(Debug)] pub struct Summary { - successes: u32, - failures: Vec, + successes: i32, + failures: Vec, } impl Summary { pub fn print(self) { - let total = self.successes + self.failures.len() as u32; + let total = self.successes + self.failures.len() as i32; let errors = if self.failures.is_empty() { String::from("0") @@ -58,8 +56,8 @@ impl Summary { self.failures.len(), self.failures .into_iter() - .map(|f| (-(f as i32)).to_string()) - .collect::>() + .map(|f| f.to_string()) + .collect::>() .join(", ") ) }; @@ -78,3 +76,17 @@ impl Default for Summary { } } } + +pub enum ExitCode { + Okay = 0, + MinorError = 2, + /// same exit-code as used by the `timeout` shell command + Timeout = 124, + Unkonwn = 99, +} + +impl From for i32 { + fn from(ec: ExitCode) -> i32 { + ec as i32 + } +} From 0722efe67bfd9be243081411b19cbc9e66d61016 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 16 Mar 2019 08:18:39 +0100 Subject: [PATCH 38/56] move main loop to run.rs --- src/main.rs | 29 +++++------------------------ src/run.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 src/run.rs diff --git a/src/main.rs b/src/main.rs index cfbaaa4..01963fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ mod io; mod loop_iterator; mod loop_step; +mod run; mod setup; mod state; mod util; fn main() { - use io::pre_exit_tasks; + use run::run; use setup::setup; - use state::{Counters, ExitCode, State}; + use state::ExitCode; use std::process; let m = setup(); @@ -18,27 +19,7 @@ fn main() { process::exit(ExitCode::MinorError.into()); } - let mut state = State::default(); - let loop_model = m.loop_model; + let exit_code = run(m); - for (index, actual_count) in m.iterator.enumerate() { - let counters = Counters { - count_precision: m.count_precision, - index, - actual_count, - }; - - let (break_loop, new_state) = - loop_model.step(state, counters, &m.env, &m.shell_command, &m.result_printer); - - state = new_state; - - if break_loop { - break; - } - } - - pre_exit_tasks(m.opt_only_last, m.opt_summary, state.summary, state.tmpfile); - - process::exit(state.exit_code.into()); + process::exit(exit_code.into()); } diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..d780288 --- /dev/null +++ b/src/run.rs @@ -0,0 +1,31 @@ +use crate::setup::Setup; +use crate::state::ExitCode; + +pub fn run(m: Setup) -> ExitCode { + use crate::io::pre_exit_tasks; + use crate::state::{Counters, State}; + + let mut state = State::default(); + let loop_model = m.loop_model; + + for (index, actual_count) in m.iterator.enumerate() { + let counters = Counters { + count_precision: m.count_precision, + index, + actual_count, + }; + + let (break_loop, new_state) = + loop_model.step(state, counters, &m.env, &m.shell_command, &m.result_printer); + + state = new_state; + + if break_loop { + break; + } + } + + pre_exit_tasks(m.opt_only_last, m.opt_summary, state.summary, state.tmpfile); + + state.exit_code +} From 7e67f38fe23b94599322be3c6b2dfee29abd8a7c Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 16 Mar 2019 08:24:03 +0100 Subject: [PATCH 39/56] rename struct Setup to App --- src/main.rs | 6 +++--- src/run.rs | 14 +++++++------- src/setup.rs | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 01963fc..f799cda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,14 +12,14 @@ fn main() { use state::ExitCode; use std::process; - let m = setup(); + let app = setup(); - if m.is_no_command_supplied { + if app.is_no_command_supplied { eprintln!("No command supplied, exiting."); process::exit(ExitCode::MinorError.into()); } - let exit_code = run(m); + let exit_code = run(app); process::exit(exit_code.into()); } diff --git a/src/run.rs b/src/run.rs index d780288..bc26366 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,22 +1,22 @@ -use crate::setup::Setup; +use crate::setup::App; use crate::state::ExitCode; -pub fn run(m: Setup) -> ExitCode { +pub fn run(a: App) -> ExitCode { use crate::io::pre_exit_tasks; use crate::state::{Counters, State}; let mut state = State::default(); - let loop_model = m.loop_model; + let loop_model = a.loop_model; - for (index, actual_count) in m.iterator.enumerate() { + for (index, actual_count) in a.iterator.enumerate() { let counters = Counters { - count_precision: m.count_precision, + count_precision: a.count_precision, index, actual_count, }; let (break_loop, new_state) = - loop_model.step(state, counters, &m.env, &m.shell_command, &m.result_printer); + loop_model.step(state, counters, &a.env, &a.shell_command, &a.result_printer); state = new_state; @@ -25,7 +25,7 @@ pub fn run(m: Setup) -> ExitCode { } } - pre_exit_tasks(m.opt_only_last, m.opt_summary, state.summary, state.tmpfile); + pre_exit_tasks(a.opt_only_last, a.opt_summary, state.summary, state.tmpfile); state.exit_code } diff --git a/src/setup.rs b/src/setup.rs index 4a874ce..79cf3ac 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -8,7 +8,7 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub struct Setup { +pub struct App { pub count_precision: usize, pub is_no_command_supplied: bool, pub opt_only_last: bool, @@ -22,7 +22,7 @@ pub struct Setup { pub loop_model: LoopModel, } -pub fn setup() -> Setup { +pub fn setup() -> App { use std::io::{self, BufRead}; // Time @@ -65,7 +65,7 @@ pub fn setup() -> Setup { let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); - Setup { + App { count_precision, is_no_command_supplied, opt_only_last, From 7c26fc029742cb4991f106bc465c7beed7424155 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 16 Mar 2019 09:19:01 +0100 Subject: [PATCH 40/56] merge ErrorCode into ExitCode --- src/io.rs | 30 ++++++++++++++++++++++++++++++ src/loop_step.rs | 20 ++++++++------------ src/main.rs | 2 +- src/run.rs | 2 +- src/setup.rs | 18 ++++++------------ src/state.rs | 40 +++++++++++++--------------------------- 6 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/io.rs b/src/io.rs index 083088d..6f5aa1b 100644 --- a/src/io.rs +++ b/src/io.rs @@ -93,3 +93,33 @@ pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mu summary.print() } } + +#[derive(Debug, Clone, Copy)] +pub enum ExitCode { + Okay, + Error, + MinorError, + /// same exit-code as used by the `timeout` shell command (99) + Timeout, + Unkonwn, + Other(u32), +} + +impl Into for ExitCode { + fn into(self) -> u32 { + match self { + ExitCode::Okay => 0, + ExitCode::Error => 1, + ExitCode::MinorError => 2, + ExitCode::Unkonwn => 99, + ExitCode::Timeout => 124, + ExitCode::Other(code) => code, + } + } +} + +impl Into for ExitCode { + fn into(self) -> i32 { + Into::::into(self) as i32 + } +} diff --git a/src/loop_step.rs b/src/loop_step.rs index e3d624e..df1fd46 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,5 +1,5 @@ -use crate::setup::ErrorCode; -use crate::state::{Counters, ExitCode, State}; +use crate::io::ExitCode; +use crate::state::{Counters, State}; use crate::util::StringFromTempfileStart; use std::time::{Duration, Instant, SystemTime}; @@ -10,7 +10,7 @@ pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, pub until_time: Option, - pub until_error: Option, + pub until_error: Option, pub until_success: bool, pub until_fail: bool, pub summary: bool, @@ -93,7 +93,7 @@ impl LoopModel { } if self.summary { - state.update_summary(exit_status); + state.summary.update(exit_status); } // Finish if we matched @@ -124,17 +124,13 @@ impl LoopModel { } } -fn check_for_error( - exit_status: ExitStatus, - maybe_error: Option, - has_matched: &mut bool, -) { +fn check_for_error(exit_status: ExitStatus, maybe_error: Option, has_matched: &mut bool) { match maybe_error { - Some(ErrorCode::Any) => { + Some(ExitCode::Error) => { *has_matched = !exit_status.success(); } - Some(ErrorCode::Code(code)) => { - if exit_status == ExitStatus::Exited(code) { + Some(ExitCode::Other(code)) => { + if ExitStatus::Exited(code) == exit_status { *has_matched = true; } } diff --git a/src/main.rs b/src/main.rs index f799cda..fa4d085 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,9 @@ mod state; mod util; fn main() { + use io::ExitCode; use run::run; use setup::setup; - use state::ExitCode; use std::process; let app = setup(); diff --git a/src/run.rs b/src/run.rs index bc26366..6607869 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,5 +1,5 @@ +use crate::io::ExitCode; use crate::setup::App; -use crate::state::ExitCode; pub fn run(a: App) -> ExitCode { use crate::io::pre_exit_tasks; diff --git a/src/setup.rs b/src/setup.rs index 79cf3ac..dd0a331 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,4 +1,4 @@ -use crate::io::{RealEnv, RealResultPrinter, RealShellCommand}; +use crate::io::{ExitCode, RealEnv, RealResultPrinter, RealShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -90,17 +90,11 @@ fn precision_of(s: &str) -> usize { exp - after_point } -#[derive(Debug, Clone, Copy)] -pub enum ErrorCode { - Any, - Code(u32), -} - -fn get_error_code(input: &str) -> ErrorCode { +fn get_exit_code(input: &str) -> ExitCode { input .parse() - .map(ErrorCode::Code) - .unwrap_or_else(|_| ErrorCode::Any) + .map(ExitCode::Other) + .unwrap_or_else(|_| ExitCode::Error) } fn get_values(input: &str) -> Vec { @@ -178,8 +172,8 @@ struct Opt { until_time: Option, /// Keep going until the command exit status is non-zero, or the value given - #[structopt(short = "r", long = "until-error", parse(from_str = "get_error_code"))] - until_error: Option, + #[structopt(short = "r", long = "until-error", parse(from_str = "get_exit_code"))] + until_error: Option, /// Keep going until the command exit status is zero #[structopt(short = "s", long = "until-success")] diff --git a/src/state.rs b/src/state.rs index fbbb5fe..77b4d79 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,5 @@ +use crate::io::ExitCode; + use std::fs::File; use subprocess::ExitStatus; @@ -10,16 +12,6 @@ pub struct State { pub has_matched: bool, } -impl State { - pub fn update_summary(&mut self, exit_status: ExitStatus) { - match exit_status { - ExitStatus::Exited(0) => self.summary.successes += 1, - ExitStatus::Exited(n) => self.summary.failures.push(n as i32), - _ => self.summary.failures.push(ExitCode::Unkonwn.into()), - } - } -} - impl Default for State { fn default() -> State { State { @@ -40,13 +32,21 @@ pub struct Counters { #[derive(Debug)] pub struct Summary { - successes: i32, - failures: Vec, + successes: u32, + failures: Vec, } impl Summary { + pub fn update(&mut self, exit_status: ExitStatus) { + match exit_status { + ExitStatus::Exited(0) => self.successes += 1, + ExitStatus::Exited(n) => self.failures.push(n), + _ => self.failures.push(ExitCode::Unkonwn.into()), + } + } + pub fn print(self) { - let total = self.successes + self.failures.len() as i32; + let total = self.successes + self.failures.len() as u32; let errors = if self.failures.is_empty() { String::from("0") @@ -76,17 +76,3 @@ impl Default for Summary { } } } - -pub enum ExitCode { - Okay = 0, - MinorError = 2, - /// same exit-code as used by the `timeout` shell command - Timeout = 124, - Unkonwn = 99, -} - -impl From for i32 { - fn from(ec: ExitCode) -> i32 { - ec as i32 - } -} From 6d0eadfbd5687c385dab12121fbebb7a6ea2d1b1 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 16 Mar 2019 10:33:24 +0100 Subject: [PATCH 41/56] use mem::swap instead of clone() to get items out of Opt in setup.rs --- src/setup.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/setup.rs b/src/setup.rs index dd0a331..8aeacbb 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -24,12 +24,13 @@ pub struct App { pub fn setup() -> App { use std::io::{self, BufRead}; + use std::mem; // Time let program_start = Instant::now(); // Load the CLI arguments - let opt = Opt::from_args(); + let mut opt = Opt::from_args(); let count_precision = Opt::clap() .get_matches() @@ -51,7 +52,10 @@ pub fn setup() -> App { ); // Number of iterations - let mut items: Vec = opt.ffor.clone().unwrap_or_else(|| vec![]); + let mut items: Vec = vec![]; + if let Some(ref mut v) = opt.ffor { + mem::swap(&mut items, v); + } // Get any lines from stdin if opt.stdin || atty::isnt(atty::Stream::Stdin) { From 02da008214ea81d734c7700652a6f4b6b21c8f15 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sat, 16 Mar 2019 13:06:49 +0100 Subject: [PATCH 42/56] struct AppError and first test --- src/io.rs | 5 ++- src/loop_iterator.rs | 1 + src/loop_step.rs | 1 + src/main.rs | 21 ++++++------ src/run.rs | 1 + src/setup.rs | 77 ++++++++++++++++++++++++++++++++++++++------ 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/src/io.rs b/src/io.rs index 6f5aa1b..6a44595 100644 --- a/src/io.rs +++ b/src/io.rs @@ -6,6 +6,7 @@ use std::fs::File; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; +#[derive(Debug)] pub struct RealEnv {} impl Env for RealEnv { @@ -14,6 +15,7 @@ impl Env for RealEnv { } } +#[derive(Debug)] pub struct RealShellCommand {} impl ShellCommand for RealShellCommand { @@ -34,6 +36,7 @@ impl ShellCommand for RealShellCommand { } } +#[derive(Debug)] pub struct RealResultPrinter { only_last: bool, until_contains: Option, @@ -94,7 +97,7 @@ pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mu } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum ExitCode { Okay, Error, diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index e2b513f..3733f59 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct LoopIterator { start: f64, iters: f64, diff --git a/src/loop_step.rs b/src/loop_step.rs index df1fd46..651b229 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -6,6 +6,7 @@ use std::time::{Duration, Instant, SystemTime}; use subprocess::ExitStatus; +#[derive(Debug)] pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, diff --git a/src/main.rs b/src/main.rs index fa4d085..60c679a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,19 +7,22 @@ mod state; mod util; fn main() { - use io::ExitCode; use run::run; - use setup::setup; + use setup::{setup, Opt}; use std::process; + use structopt::StructOpt; - let app = setup(); + let app = setup(Opt::from_args()); - if app.is_no_command_supplied { - eprintln!("No command supplied, exiting."); - process::exit(ExitCode::MinorError.into()); - } - - let exit_code = run(app); + let exit_code = match app { + Ok(app) => run(app), + Err(err) => { + if !err.message.is_empty() { + eprintln!("{}", err.message); + } + err.exit_code + } + }; process::exit(exit_code.into()); } diff --git a/src/run.rs b/src/run.rs index 6607869..04f6433 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,6 +1,7 @@ use crate::io::ExitCode; use crate::setup::App; +#[must_use] pub fn run(a: App) -> ExitCode { use crate::io::pre_exit_tasks; use crate::state::{Counters, State}; diff --git a/src/setup.rs b/src/setup.rs index 8aeacbb..03442ad 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -8,9 +8,9 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; +#[derive(Debug)] pub struct App { pub count_precision: usize, - pub is_no_command_supplied: bool, pub opt_only_last: bool, pub opt_summary: bool, @@ -22,16 +22,13 @@ pub struct App { pub loop_model: LoopModel, } -pub fn setup() -> App { +pub fn setup(mut opt: Opt) -> Result { use std::io::{self, BufRead}; use std::mem; // Time let program_start = Instant::now(); - // Load the CLI arguments - let mut opt = Opt::from_args(); - let count_precision = Opt::clap() .get_matches() .value_of("count_by") @@ -39,7 +36,13 @@ pub fn setup() -> App { .unwrap_or(0); let cmd_with_args = opt.input.join(" "); - let is_no_command_supplied = cmd_with_args.is_empty(); + if cmd_with_args.is_empty() { + return Err(AppError::new( + ExitCode::MinorError, + "No command supplied, exiting.", + )); + } + let opt_only_last = opt.only_last; let opt_summary = opt.summary; @@ -69,9 +72,8 @@ pub fn setup() -> App { let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); - App { + Ok(App { count_precision, - is_no_command_supplied, opt_only_last, opt_summary, @@ -81,6 +83,24 @@ pub fn setup() -> App { iterator, loop_model, + }) +} + +#[derive(Debug, PartialEq, Clone)] +pub struct AppError { + pub exit_code: ExitCode, + pub message: String, +} + +impl AppError { + pub fn new(exit_code: ExitCode, msg: M) -> AppError + where + M: Into, + { + AppError { + exit_code, + message: msg.into(), + } } } @@ -117,7 +137,7 @@ fn get_values(input: &str) -> Vec { author = "Rich Jones ", about = "UNIX's missing `loop` command" )] -struct Opt { +pub struct Opt { /// Number of iterations to execute #[structopt(short = "n", long = "num")] num: Option, @@ -232,3 +252,42 @@ impl Opt { } } } + +impl Default for Opt { + fn default() -> Opt { + Opt { + num: None, + count_by: 1_f64, + offset: 0_f64, + every: parse_duration("1us").unwrap(), + ffor: None, + for_duration: None, + until_contains: None, + until_changes: false, + until_same: false, + until_match: None, + until_time: None, + until_error: None, + until_success: false, + until_fail: false, + only_last: false, + stdin: false, + error_duration: false, + summary: false, + input: vec![], + } + } +} + +#[test] +fn test_setup() { + // okay + let mut opt = Opt::default(); + opt.input = vec!["foobar".to_owned()]; + assert!(setup(opt).is_ok()); + + // no command + let opt = Opt::default(); + let app_error = AppError::new(ExitCode::MinorError, "No command supplied, exiting."); + assert_eq!(setup(opt).unwrap_err(), app_error); +} From 78fb1f4e220b6d6900ec4e2cde9df69c3c109579 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 09:03:11 +0100 Subject: [PATCH 43/56] rename run.rs to app.rs and move the App struct into it --- src/app.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/io.rs | 18 +------------ src/main.rs | 5 ++-- src/run.rs | 32 ---------------------- src/setup.rs | 15 +---------- 5 files changed, 80 insertions(+), 66 deletions(-) create mode 100644 src/app.rs delete mode 100644 src/run.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..a169196 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,76 @@ +use crate::io::{ExitCode, RealEnv, RealResultPrinter, RealShellCommand}; +use crate::loop_iterator::LoopIterator; +use crate::loop_step::LoopModel; +use crate::state::Summary; + +use std::fs::File; + +#[derive(Debug)] +pub struct App { + pub count_precision: usize, + pub opt_only_last: bool, + pub opt_summary: bool, + + pub env: RealEnv, + pub shell_command: RealShellCommand, + pub result_printer: RealResultPrinter, + + pub iterator: LoopIterator, + pub loop_model: LoopModel, +} + +impl App { + #[must_use] + pub fn run(self) -> ExitCode { + // use crate::io::pre_exit_tasks; + use crate::state::{Counters, State}; + + let mut state = State::default(); + let loop_model = self.loop_model; + + for (index, actual_count) in self.iterator.enumerate() { + let counters = Counters { + count_precision: self.count_precision, + index, + actual_count, + }; + + let (break_loop, new_state) = loop_model.step( + state, + counters, + &self.env, + &self.shell_command, + &self.result_printer, + ); + + state = new_state; + + if break_loop { + break; + } + } + + pre_exit_tasks( + self.opt_only_last, + self.opt_summary, + state.summary, + state.tmpfile, + ); + + state.exit_code + } +} + +pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { + use crate::util::StringFromTempfileStart; + + if only_last { + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); + } + + if print_summary { + summary.print() + } +} diff --git a/src/io.rs b/src/io.rs index 6a44595..a9f3a67 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,7 +1,5 @@ use crate::loop_step::{Env, ResultPrinter, ShellCommand}; -use crate::state::{State, Summary}; - -use std::fs::File; +use crate::state::State; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; @@ -83,20 +81,6 @@ impl ResultPrinter for RealResultPrinter { } } -pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { - use crate::util::StringFromTempfileStart; - - if only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); - } - - if print_summary { - summary.print() - } -} - #[derive(Debug, PartialEq, Clone, Copy)] pub enum ExitCode { Okay, diff --git a/src/main.rs b/src/main.rs index 60c679a..86d8e2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ +mod app; mod io; mod loop_iterator; mod loop_step; -mod run; mod setup; mod state; mod util; fn main() { - use run::run; use setup::{setup, Opt}; use std::process; use structopt::StructOpt; @@ -15,7 +14,7 @@ fn main() { let app = setup(Opt::from_args()); let exit_code = match app { - Ok(app) => run(app), + Ok(app) => app.run(), Err(err) => { if !err.message.is_empty() { eprintln!("{}", err.message); diff --git a/src/run.rs b/src/run.rs deleted file mode 100644 index 04f6433..0000000 --- a/src/run.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::io::ExitCode; -use crate::setup::App; - -#[must_use] -pub fn run(a: App) -> ExitCode { - use crate::io::pre_exit_tasks; - use crate::state::{Counters, State}; - - let mut state = State::default(); - let loop_model = a.loop_model; - - for (index, actual_count) in a.iterator.enumerate() { - let counters = Counters { - count_precision: a.count_precision, - index, - actual_count, - }; - - let (break_loop, new_state) = - loop_model.step(state, counters, &a.env, &a.shell_command, &a.result_printer); - - state = new_state; - - if break_loop { - break; - } - } - - pre_exit_tasks(a.opt_only_last, a.opt_summary, state.summary, state.tmpfile); - - state.exit_code -} diff --git a/src/setup.rs b/src/setup.rs index 03442ad..1ba3680 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,3 +1,4 @@ +use crate::app::App; use crate::io::{ExitCode, RealEnv, RealResultPrinter, RealShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -8,20 +9,6 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -#[derive(Debug)] -pub struct App { - pub count_precision: usize, - pub opt_only_last: bool, - pub opt_summary: bool, - - pub env: RealEnv, - pub shell_command: RealShellCommand, - pub result_printer: RealResultPrinter, - - pub iterator: LoopIterator, - pub loop_model: LoopModel, -} - pub fn setup(mut opt: Opt) -> Result { use std::io::{self, BufRead}; use std::mem; From 58158680fc1580af45301f1c8d307353c73e1620 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 09:23:46 +0100 Subject: [PATCH 44/56] simplified loop_step --- src/app.rs | 62 +++++++++++++--------- src/io.rs | 117 ++++++++++++++++++++++++----------------- src/loop_iterator.rs | 1 - src/loop_step.rs | 120 ++++++++++++++++--------------------------- src/main.rs | 2 +- src/setup.rs | 60 ++++++++++------------ src/state.rs | 18 ++----- 7 files changed, 187 insertions(+), 193 deletions(-) diff --git a/src/app.rs b/src/app.rs index a169196..601435e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,53 +1,58 @@ -use crate::io::{ExitCode, RealEnv, RealResultPrinter, RealShellCommand}; +use crate::io::ExitCode; +use crate::io::Printer; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; use crate::state::Summary; use std::fs::File; +use std::time::{Duration, Instant}; -#[derive(Debug)] pub struct App { pub count_precision: usize, pub opt_only_last: bool, pub opt_summary: bool, - - pub env: RealEnv, - pub shell_command: RealShellCommand, - pub result_printer: RealResultPrinter, - + pub cmd_with_args: String, + pub every: Duration, pub iterator: LoopIterator, pub loop_model: LoopModel, + pub items: Vec, } impl App { #[must_use] - pub fn run(self) -> ExitCode { - // use crate::io::pre_exit_tasks; - use crate::state::{Counters, State}; + pub fn run(self, printer: Printer) -> ExitCode { + use crate::io::{setup_environment, shell_command}; + use crate::state::State; let mut state = State::default(); let loop_model = self.loop_model; + let cmd_with_args = self.cmd_with_args; + + let command = |state: State| -> (ExitCode, State) { + // cmd_with_args doesn't change, keep it in the closue + shell_command(&cmd_with_args, state) + }; + let printer = |stdout: &str, state: State| -> State { + // object of struct Printer doesn't change, keep it in the closue + printer.print(stdout, state) + }; - for (index, actual_count) in self.iterator.enumerate() { - let counters = Counters { - count_precision: self.count_precision, - index, - actual_count, - }; - - let (break_loop, new_state) = loop_model.step( - state, - counters, - &self.env, - &self.shell_command, - &self.result_printer, - ); + for (i, actual_count) in self.iterator.enumerate() { + let step_start_time = Instant::now(); + let count_precision = self.count_precision; + let item = self.items.get(i); + let setup_envs = || setup_environment(item, i, count_precision, actual_count); + + let (break_loop, new_state) = loop_model.step(state, setup_envs, command, printer); state = new_state; if break_loop { break; } + + // Delay until next iteration time + maybe_sleep(step_start_time, self.every); } pre_exit_tasks( @@ -61,6 +66,15 @@ impl App { } } +fn maybe_sleep(step_start: Instant, every: Duration) { + use std::thread; + + let since = Instant::now().duration_since(step_start); + if let Some(time) = every.checked_sub(since) { + thread::sleep(time); + } +} + pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { use crate::util::StringFromTempfileStart; diff --git a/src/io.rs b/src/io.rs index a9f3a67..d25b953 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,68 +1,58 @@ -use crate::loop_step::{Env, ResultPrinter, ShellCommand}; use crate::state::State; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; -#[derive(Debug)] -pub struct RealEnv {} - -impl Env for RealEnv { - fn set(&self, k: &str, v: &str) { - std::env::set_var(k, v); +pub fn setup_environment( + item: Option<&String>, + index: usize, + count_precision: usize, + actual_count: f64, +) { + use std::env::set_var; + + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + set_var("ACTUALCOUNT", index.to_string()); + set_var("COUNT", format!("{:.*}", count_precision, actual_count)); + + // Set current item as environment variable + if let Some(item) = item { + set_var("ITEM", item); } } -#[derive(Debug)] -pub struct RealShellCommand {} - -impl ShellCommand for RealShellCommand { - fn run(&self, mut state: State, cmd_with_args: &str) -> (ExitStatus, State) { - use std::io::{prelude::*, SeekFrom}; +#[must_use] +pub fn shell_command(cmd_with_args: &str, mut state: State) -> (ExitCode, State) { + use std::io::{prelude::*, SeekFrom}; - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); - let status = Exec::shell(cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status; + let exit_status = Exec::shell(cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status; - (status, state) - } + (exit_status.into(), state) } -#[derive(Debug)] -pub struct RealResultPrinter { - only_last: bool, - until_contains: Option, - until_match: Option, +pub struct Printer { + pub only_last: bool, + pub until_contains: Option, + pub until_match: Option, } -impl RealResultPrinter { - pub fn new( - only_last: bool, - until_contains: Option, - until_match: Option, - ) -> RealResultPrinter { - RealResultPrinter { - only_last, - until_contains, - until_match, - } - } -} - -impl ResultPrinter for RealResultPrinter { - fn print(&self, mut state: State, stdout: &str) -> State { +impl Printer { + #[must_use] + pub fn print(&self, stdout: &str, mut state: State) -> State { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, // defer printing until later if !self.only_last { - println!("{}", line); + println!("{}", line); // THIS IS THE MAIN PRINT FUNCTION } // --until-contains @@ -86,12 +76,47 @@ pub enum ExitCode { Okay, Error, MinorError, - /// same exit-code as used by the `timeout` shell command (99) + /// same exit-code as used by the `timeout` shell command (124) Timeout, + /// the process has completed, but the exit-code is unknown (99) Unkonwn, Other(u32), } +impl ExitCode { + pub fn success(self) -> bool { + ExitCode::Okay == self + } +} + +impl From for ExitCode { + fn from(n: u32) -> ExitCode { + match n { + 0 => ExitCode::Okay, + 1 => ExitCode::Error, + 2 => ExitCode::MinorError, + 99 => ExitCode::Unkonwn, + 124 => ExitCode::Timeout, + code => ExitCode::Other(code), + } + } +} + +impl From for ExitCode { + fn from(n: i32) -> ExitCode { + ExitCode::from(n as u32) + } +} + +impl From for ExitCode { + fn from(exit_status: ExitStatus) -> ExitCode { + match exit_status { + ExitStatus::Exited(code) => ExitCode::from(code), + _ => ExitCode::Unkonwn, + } + } +} + impl Into for ExitCode { fn into(self) -> u32 { match self { diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index 3733f59..e2b513f 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -1,4 +1,3 @@ -#[derive(Debug)] pub struct LoopIterator { start: f64, iters: f64, diff --git a/src/loop_step.rs b/src/loop_step.rs index 651b229..ae83c3c 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,12 +1,9 @@ use crate::io::ExitCode; -use crate::state::{Counters, State}; +use crate::state::State; use crate::util::StringFromTempfileStart; use std::time::{Duration, Instant, SystemTime}; -use subprocess::ExitStatus; - -#[derive(Debug)] pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, @@ -17,11 +14,7 @@ pub struct LoopModel { pub summary: bool, pub until_changes: bool, pub until_same: bool, - pub every: Duration, - - pub cmd_with_args: String, pub program_start: Instant, - pub items: Vec, } impl LoopModel { @@ -29,28 +22,12 @@ impl LoopModel { pub fn step( &self, mut state: State, - counters: Counters, - env: &impl Env, - shell_command: &impl ShellCommand, - result_printer: &impl ResultPrinter, + setup_environment: impl Fn(), + shell_command: impl Fn(State) -> (ExitCode, State), + result_printer: impl Fn(&str, State) -> State, ) -> (bool, State) { - use std::thread; - - // Time Start - let loop_start = Instant::now(); - // Set counters before execution - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - env.set("ACTUALCOUNT", &counters.index.to_string()); - env.set( - "COUNT", - &format!("{:.*}", counters.count_precision, counters.actual_count), - ); - - // Set iterated item as environment variable - if let Some(item) = self.items.get(counters.index) { - env.set("ITEM", item); - } + setup_environment(); // Finish if we're over our duration if let Some(duration) = self.for_duration { @@ -73,28 +50,18 @@ impl LoopModel { } // Main executor - let (exit_status, new_state) = shell_command.run(state, &self.cmd_with_args); + let (new_state, exit_code, stdout) = run_command( + state, + self.until_error, + self.until_success, + self.until_fail, + shell_command, + result_printer, + ); state = new_state; - // Print the results - let stdout = String::from_temp_start(&mut state.tmpfile); - state = result_printer.print(state, &stdout); - - // --until-error - check_for_error(exit_status, self.until_error, &mut state.has_matched); - - // --until-success - if self.until_success && exit_status.success() { - state.has_matched = true; - } - - // --until-fail - if self.until_fail && !exit_status.success() { - state.has_matched = true; - } - if self.summary { - state.summary.update(exit_status); + state.summary.update(exit_code); } // Finish if we matched @@ -115,40 +82,43 @@ impl LoopModel { } state.previous_stdout = Some(stdout); - // Delay until next iteration time - let since = Instant::now().duration_since(loop_start); - if let Some(time) = self.every.checked_sub(since) { - thread::sleep(time); - } - (false, state) } } -fn check_for_error(exit_status: ExitStatus, maybe_error: Option, has_matched: &mut bool) { - match maybe_error { - Some(ExitCode::Error) => { - *has_matched = !exit_status.success(); - } - Some(ExitCode::Other(code)) => { - if ExitStatus::Exited(code) == exit_status { - *has_matched = true; - } - } - _ => (), - } -} +fn run_command( + state: State, + until_error: Option, + until_success: bool, + until_fail: bool, + shell_command: impl Fn(State) -> (ExitCode, State), + result_printer: impl Fn(&str, State) -> State, +) -> (State, ExitCode, String) { + let (exit_code, mut state) = shell_command(state); -pub trait Env { - fn set(&self, k: &str, v: &str); -} + // Print the results + let stdout = String::from_temp_start(&mut state.tmpfile); + state = result_printer(&stdout, state); -pub trait ShellCommand { - #[must_use] - fn run(&self, state: State, cmd_with_args: &str) -> (ExitStatus, State); + // --until-error + until_error_check(&mut state.has_matched, until_error, exit_code); + + // --until-success + state.has_matched = until_success && exit_code.success(); + + // --until-fail + state.has_matched = until_fail && !exit_code.success(); + + (state, exit_code, stdout) } -pub trait ResultPrinter { - #[must_use] - fn print(&self, state: State, stdout: &str) -> State; +/// Check if the exit-code is non-zero, or the given exit-code. +fn until_error_check(has_matched: &mut bool, to_check: Option, exit_code: ExitCode) { + match to_check { + Some(ExitCode::Error) => *has_matched = !exit_code.success(), + Some(expected_exit_code) => { + *has_matched = expected_exit_code == exit_code; + } + _ => (), + } } diff --git a/src/main.rs b/src/main.rs index 86d8e2d..963af01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ fn main() { let app = setup(Opt::from_args()); let exit_code = match app { - Ok(app) => app.run(), + Ok((app, printer)) => app.run(printer), Err(err) => { if !err.message.is_empty() { eprintln!("{}", err.message); diff --git a/src/setup.rs b/src/setup.rs index 1ba3680..482c1c4 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::io::{ExitCode, RealEnv, RealResultPrinter, RealShellCommand}; +use crate::io::{ExitCode, Printer}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -9,7 +9,7 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup(mut opt: Opt) -> Result { +pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { use std::io::{self, BufRead}; use std::mem; @@ -32,19 +32,19 @@ pub fn setup(mut opt: Opt) -> Result { let opt_only_last = opt.only_last; let opt_summary = opt.summary; + let every = opt.every; - let env = RealEnv {}; - let shell_command = RealShellCommand {}; - let result_printer = RealResultPrinter::new( - opt.only_last, - opt.until_contains.clone(), - opt.until_match.clone(), - ); + let printer_model = Printer { + only_last: opt.only_last, + until_contains: opt.until_contains.clone(), + until_match: opt.until_match.clone(), + }; // Number of iterations let mut items: Vec = vec![]; if let Some(ref mut v) = opt.ffor { mem::swap(&mut items, v); + opt.ffor = None; } // Get any lines from stdin @@ -57,20 +57,21 @@ pub fn setup(mut opt: Opt) -> Result { } let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); - let loop_model = opt.into_loop_model(cmd_with_args, program_start, items); - - Ok(App { - count_precision, - opt_only_last, - opt_summary, + let loop_model = opt.into_loop_model(program_start); - env, - shell_command, - result_printer, - - iterator, - loop_model, - }) + Ok(( + App { + count_precision, + opt_only_last, + opt_summary, + cmd_with_args, + every, + iterator, + loop_model, + items, + }, + printer_model, + )) } #[derive(Debug, PartialEq, Clone)] @@ -216,16 +217,9 @@ pub struct Opt { } impl Opt { - fn into_loop_model( - self, - cmd_with_args: String, - program_start: Instant, - items: Vec, - ) -> LoopModel { + fn into_loop_model(self, program_start: Instant) -> LoopModel { LoopModel { - cmd_with_args, program_start, - items, for_duration: self.for_duration, error_duration: self.error_duration, until_time: self.until_time, @@ -235,7 +229,6 @@ impl Opt { summary: self.summary, until_changes: self.until_changes, until_same: self.until_same, - every: self.every, } } } @@ -276,5 +269,8 @@ fn test_setup() { // no command let opt = Opt::default(); let app_error = AppError::new(ExitCode::MinorError, "No command supplied, exiting."); - assert_eq!(setup(opt).unwrap_err(), app_error); + match setup(opt) { + Err(err) => assert_eq!(err, app_error), + _ => panic!(), + } } diff --git a/src/state.rs b/src/state.rs index 77b4d79..8e65a22 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,8 +2,6 @@ use crate::io::ExitCode; use std::fs::File; -use subprocess::ExitStatus; - pub struct State { pub tmpfile: File, pub summary: Summary, @@ -24,24 +22,16 @@ impl Default for State { } } -pub struct Counters { - pub index: usize, - pub count_precision: usize, - pub actual_count: f64, -} - -#[derive(Debug)] pub struct Summary { successes: u32, failures: Vec, } impl Summary { - pub fn update(&mut self, exit_status: ExitStatus) { - match exit_status { - ExitStatus::Exited(0) => self.successes += 1, - ExitStatus::Exited(n) => self.failures.push(n), - _ => self.failures.push(ExitCode::Unkonwn.into()), + pub fn update(&mut self, exit_code: ExitCode) { + match exit_code { + ExitCode::Okay => self.successes += 1, + err => self.failures.push(err.into()), } } From 5d7bc6743662bfde2d6961f2b7c694d1b5d3998d Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 17:01:03 +0100 Subject: [PATCH 45/56] struct PreExitTasks --- src/app.rs | 30 +++--------------------------- src/io.rs | 25 ++++++++++++++++++++++++- src/main.rs | 2 +- src/setup.rs | 14 ++++++++------ 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/app.rs b/src/app.rs index 601435e..f6e1119 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,16 +1,11 @@ -use crate::io::ExitCode; -use crate::io::Printer; +use crate::io::{ExitCode, PreExitTasks, Printer}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; -use crate::state::Summary; -use std::fs::File; use std::time::{Duration, Instant}; pub struct App { pub count_precision: usize, - pub opt_only_last: bool, - pub opt_summary: bool, pub cmd_with_args: String, pub every: Duration, pub iterator: LoopIterator, @@ -20,7 +15,7 @@ pub struct App { impl App { #[must_use] - pub fn run(self, printer: Printer) -> ExitCode { + pub fn run(self, printer: Printer, exit_tasks: PreExitTasks) -> ExitCode { use crate::io::{setup_environment, shell_command}; use crate::state::State; @@ -55,12 +50,7 @@ impl App { maybe_sleep(step_start_time, self.every); } - pre_exit_tasks( - self.opt_only_last, - self.opt_summary, - state.summary, - state.tmpfile, - ); + exit_tasks.run(state.summary, state.tmpfile); state.exit_code } @@ -74,17 +64,3 @@ fn maybe_sleep(step_start: Instant, every: Duration) { thread::sleep(time); } } - -pub fn pre_exit_tasks(only_last: bool, print_summary: bool, summary: Summary, mut tmpfile: File) { - use crate::util::StringFromTempfileStart; - - if only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); - } - - if print_summary { - summary.print() - } -} diff --git a/src/io.rs b/src/io.rs index d25b953..99614c4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,4 +1,6 @@ -use crate::state::State; +use crate::state::{State, Summary}; + +use std::fs::File; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; @@ -71,6 +73,27 @@ impl Printer { } } +pub struct PreExitTasks { + pub opt_only_last: bool, + pub opt_summary: bool, +} + +impl PreExitTasks { + pub fn run(&self, summary: Summary, mut tmpfile: File) { + use crate::util::StringFromTempfileStart; + + if self.opt_only_last { + String::from_temp_start(&mut tmpfile) + .lines() + .for_each(|line| println!("{}", line)); + } + + if self.opt_summary { + summary.print() + } + } +} + #[derive(Debug, PartialEq, Clone, Copy)] pub enum ExitCode { Okay, diff --git a/src/main.rs b/src/main.rs index 963af01..ad54fcf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ fn main() { let app = setup(Opt::from_args()); let exit_code = match app { - Ok((app, printer)) => app.run(printer), + Ok((app, printer, exit_tasks)) => app.run(printer, exit_tasks), Err(err) => { if !err.message.is_empty() { eprintln!("{}", err.message); diff --git a/src/setup.rs b/src/setup.rs index 482c1c4..3c8cda2 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::io::{ExitCode, Printer}; +use crate::io::{ExitCode, PreExitTasks, Printer}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -9,7 +9,7 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { +pub fn setup(mut opt: Opt) -> Result<(App, Printer, PreExitTasks), AppError> { use std::io::{self, BufRead}; use std::mem; @@ -30,8 +30,6 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { )); } - let opt_only_last = opt.only_last; - let opt_summary = opt.summary; let every = opt.every; let printer_model = Printer { @@ -40,6 +38,11 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { until_match: opt.until_match.clone(), }; + let exit_tasks = PreExitTasks { + opt_only_last: opt.only_last, + opt_summary: opt.summary, + }; + // Number of iterations let mut items: Vec = vec![]; if let Some(ref mut v) = opt.ffor { @@ -62,8 +65,6 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { Ok(( App { count_precision, - opt_only_last, - opt_summary, cmd_with_args, every, iterator, @@ -71,6 +72,7 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer), AppError> { items, }, printer_model, + exit_tasks, )) } From ce91641d9b42510238e9da1f20ba0f50ca2ba702 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 17:49:18 +0100 Subject: [PATCH 46/56] move IO out of App::run() --- src/app.rs | 32 ++++++++++---------------- src/io.rs | 64 ++++++++++++++++++++++++++++++---------------------- src/main.rs | 32 +++++++++++++++++++++++++- src/setup.rs | 30 ++++++++++++------------ 4 files changed, 96 insertions(+), 62 deletions(-) diff --git a/src/app.rs b/src/app.rs index f6e1119..8fea5fb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,12 +1,12 @@ -use crate::io::{ExitCode, PreExitTasks, Printer}; +use crate::io::ExitCode; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; +use crate::state::{State, Summary}; +use std::fs::File; use std::time::{Duration, Instant}; pub struct App { - pub count_precision: usize, - pub cmd_with_args: String, pub every: Duration, pub iterator: LoopIterator, pub loop_model: LoopModel, @@ -15,29 +15,21 @@ pub struct App { impl App { #[must_use] - pub fn run(self, printer: Printer, exit_tasks: PreExitTasks) -> ExitCode { - use crate::io::{setup_environment, shell_command}; - use crate::state::State; - + pub fn run( + self, + printer: &impl Fn(&str, State) -> State, + command: &impl Fn(State) -> (ExitCode, State), + exit_tasks: &impl Fn(Summary, File), + setup_environment: &impl Fn(Option<&String>, usize, f64), + ) -> ExitCode { let mut state = State::default(); let loop_model = self.loop_model; - let cmd_with_args = self.cmd_with_args; - - let command = |state: State| -> (ExitCode, State) { - // cmd_with_args doesn't change, keep it in the closue - shell_command(&cmd_with_args, state) - }; - let printer = |stdout: &str, state: State| -> State { - // object of struct Printer doesn't change, keep it in the closue - printer.print(stdout, state) - }; for (i, actual_count) in self.iterator.enumerate() { let step_start_time = Instant::now(); - let count_precision = self.count_precision; let item = self.items.get(i); - let setup_envs = || setup_environment(item, i, count_precision, actual_count); + let setup_envs = || setup_environment(item, i, actual_count); let (break_loop, new_state) = loop_model.step(state, setup_envs, command, printer); state = new_state; @@ -50,7 +42,7 @@ impl App { maybe_sleep(step_start_time, self.every); } - exit_tasks.run(state.summary, state.tmpfile); + exit_tasks(state.summary, state.tmpfile); state.exit_code } diff --git a/src/io.rs b/src/io.rs index 99614c4..8859e87 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,39 +5,49 @@ use std::fs::File; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; -pub fn setup_environment( - item: Option<&String>, - index: usize, - count_precision: usize, - actual_count: f64, -) { - use std::env::set_var; - - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - set_var("ACTUALCOUNT", index.to_string()); - set_var("COUNT", format!("{:.*}", count_precision, actual_count)); - - // Set current item as environment variable - if let Some(item) = item { - set_var("ITEM", item); +pub struct SetupEnv { + pub count_precision: usize, +} + +impl SetupEnv { + pub fn run(&self, item: Option<&String>, index: usize, actual_count: f64) { + use std::env::set_var; + + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + set_var("ACTUALCOUNT", index.to_string()); + set_var( + "COUNT", + format!("{:.*}", self.count_precision, actual_count), + ); + + // Set current item as environment variable + if let Some(item) = item { + set_var("ITEM", item); + } } } -#[must_use] -pub fn shell_command(cmd_with_args: &str, mut state: State) -> (ExitCode, State) { - use std::io::{prelude::*, SeekFrom}; +pub struct ShellCommand { + pub cmd_with_args: String, +} + +impl ShellCommand { + #[must_use] + pub fn run(&self, mut state: State) -> (ExitCode, State) { + use std::io::{prelude::*, SeekFrom}; - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); - let exit_status = Exec::shell(cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status; + let exit_status = Exec::shell(&self.cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status; - (exit_status.into(), state) + (exit_status.into(), state) + } } pub struct Printer { diff --git a/src/main.rs b/src/main.rs index ad54fcf..df4f102 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,9 @@ mod setup; mod state; mod util; +use app::App; +use io::{ExitCode, PreExitTasks, Printer, SetupEnv, ShellCommand}; + fn main() { use setup::{setup, Opt}; use std::process; @@ -14,7 +17,9 @@ fn main() { let app = setup(Opt::from_args()); let exit_code = match app { - Ok((app, printer, exit_tasks)) => app.run(printer, exit_tasks), + Ok((app, printer, exit_tasks, setup_env, shell_command)) => { + run_app(app, printer, exit_tasks, setup_env, shell_command) + } Err(err) => { if !err.message.is_empty() { eprintln!("{}", err.message); @@ -25,3 +30,28 @@ fn main() { process::exit(exit_code.into()); } + +fn run_app( + app: App, + printer: Printer, + exit_tasks: PreExitTasks, + setup_env: SetupEnv, + shell_command: ShellCommand, +) -> ExitCode { + use crate::io::ExitCode; + use crate::state::{State, Summary}; + use std::fs::File; + + let command = |state: State| -> (ExitCode, State) { shell_command.run(state) }; + + let printer = |stdout: &str, state: State| -> State { printer.print(stdout, state) }; + + let exit_tasks = |summary: Summary, tmpfile: File| { + exit_tasks.run(summary, tmpfile); + }; + let setup_environment = |item: Option<&String>, index: usize, actual_count: f64| { + setup_env.run(item, index, actual_count) + }; + + app.run(&printer, &command, &exit_tasks, &setup_environment) +} diff --git a/src/setup.rs b/src/setup.rs index 3c8cda2..3e2c01f 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::io::{ExitCode, PreExitTasks, Printer}; +use crate::io::{ExitCode, PreExitTasks, Printer, SetupEnv, ShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -9,7 +9,9 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup(mut opt: Opt) -> Result<(App, Printer, PreExitTasks), AppError> { +pub fn setup( + mut opt: Opt, +) -> Result<(App, Printer, PreExitTasks, SetupEnv, ShellCommand), AppError> { use std::io::{self, BufRead}; use std::mem; @@ -43,6 +45,10 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, PreExitTasks), AppError> { opt_summary: opt.summary, }; + let setup_env = SetupEnv { count_precision }; + + let shell_command = ShellCommand { cmd_with_args }; + // Number of iterations let mut items: Vec = vec![]; if let Some(ref mut v) = opt.ffor { @@ -62,18 +68,14 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, PreExitTasks), AppError> { let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); let loop_model = opt.into_loop_model(program_start); - Ok(( - App { - count_precision, - cmd_with_args, - every, - iterator, - loop_model, - items, - }, - printer_model, - exit_tasks, - )) + let app = App { + every, + iterator, + loop_model, + items, + }; + + Ok((app, printer_model, exit_tasks, setup_env, shell_command)) } #[derive(Debug, PartialEq, Clone)] From 16802809d01325b8c1f3c8f95c23e8c0e6c4ce4f Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 18:34:41 +0100 Subject: [PATCH 47/56] move items into LoopLiterator --- src/app.rs | 10 ++++------ src/io.rs | 2 +- src/loop_iterator.rs | 34 ++++++++++++++++++++++++++-------- src/main.rs | 2 +- src/setup.rs | 3 +-- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8fea5fb..a330776 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,6 @@ pub struct App { pub every: Duration, pub iterator: LoopIterator, pub loop_model: LoopModel, - pub items: Vec, } impl App { @@ -20,16 +19,15 @@ impl App { printer: &impl Fn(&str, State) -> State, command: &impl Fn(State) -> (ExitCode, State), exit_tasks: &impl Fn(Summary, File), - setup_environment: &impl Fn(Option<&String>, usize, f64), + setup_environment: &impl Fn(Option, f64, f64), ) -> ExitCode { - let mut state = State::default(); let loop_model = self.loop_model; + let mut state = State::default(); - for (i, actual_count) in self.iterator.enumerate() { + for it in self.iterator { let step_start_time = Instant::now(); - let item = self.items.get(i); - let setup_envs = || setup_environment(item, i, actual_count); + let setup_envs = || setup_environment(it.item.clone(), it.index, it.actual_count); let (break_loop, new_state) = loop_model.step(state, setup_envs, command, printer); state = new_state; diff --git a/src/io.rs b/src/io.rs index 8859e87..6795005 100644 --- a/src/io.rs +++ b/src/io.rs @@ -10,7 +10,7 @@ pub struct SetupEnv { } impl SetupEnv { - pub fn run(&self, item: Option<&String>, index: usize, actual_count: f64) { + pub fn run(&self, item: Option, index: f64, actual_count: f64) { use std::env::set_var; // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index e2b513f..6526e0d 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -1,12 +1,13 @@ pub struct LoopIterator { start: f64, - iters: f64, + index: f64, end: f64, step_by: f64, + items: Vec, } impl LoopIterator { - pub fn new(offset: f64, count_by: f64, num: Option, items: &[String]) -> LoopIterator { + pub fn new(offset: f64, count_by: f64, num: Option, items: Vec) -> LoopIterator { let end = if let Some(num) = num { num } else if !items.is_empty() { @@ -16,21 +17,38 @@ impl LoopIterator { }; LoopIterator { start: offset - count_by, - iters: 0.0, + index: 0.0, end, step_by: count_by, + items, } } } +pub struct LoopResult { + pub item: Option, + pub actual_count: f64, + pub index: f64, +} + impl Iterator for LoopIterator { - type Item = f64; + type Item = LoopResult; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { + let current_index = self.index; self.start += self.step_by; - self.iters += 1.0; - if self.iters <= self.end { - Some(self.start) + self.index += 1.0; + + if self.index <= self.end { + let item = self.items.get(self.index as usize).map(ToString::to_string); + + let res = LoopResult { + item, + actual_count: self.start, + index: current_index, + }; + + Some(res) } else { None } diff --git a/src/main.rs b/src/main.rs index df4f102..906d274 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ fn run_app( let exit_tasks = |summary: Summary, tmpfile: File| { exit_tasks.run(summary, tmpfile); }; - let setup_environment = |item: Option<&String>, index: usize, actual_count: f64| { + let setup_environment = |item: Option, index: f64, actual_count: f64| { setup_env.run(item, index, actual_count) }; diff --git a/src/setup.rs b/src/setup.rs index 3e2c01f..4c68bee 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -65,14 +65,13 @@ pub fn setup( .for_each(|line| items.push(line)); } - let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, &items); + let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, items); let loop_model = opt.into_loop_model(program_start); let app = App { every, iterator, loop_model, - items, }; Ok((app, printer_model, exit_tasks, setup_env, shell_command)) From b49dba96d322f72aac9e8e35c4e0f97e57864037 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 19:35:30 +0100 Subject: [PATCH 48/56] impl Default for LoopModel and some clean-up --- src/app.rs | 2 +- src/io.rs | 94 ++++++++++++++++++++++++------------------------ src/loop_step.rs | 19 ++++++++++ src/main.rs | 4 +-- src/setup.rs | 10 +++--- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/app.rs b/src/app.rs index a330776..a562b67 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,8 +8,8 @@ use std::time::{Duration, Instant}; pub struct App { pub every: Duration, - pub iterator: LoopIterator, pub loop_model: LoopModel, + pub iterator: LoopIterator, } impl App { diff --git a/src/io.rs b/src/io.rs index 6795005..db327b1 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,51 +5,6 @@ use std::fs::File; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; -pub struct SetupEnv { - pub count_precision: usize, -} - -impl SetupEnv { - pub fn run(&self, item: Option, index: f64, actual_count: f64) { - use std::env::set_var; - - // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. - set_var("ACTUALCOUNT", index.to_string()); - set_var( - "COUNT", - format!("{:.*}", self.count_precision, actual_count), - ); - - // Set current item as environment variable - if let Some(item) = item { - set_var("ITEM", item); - } - } -} - -pub struct ShellCommand { - pub cmd_with_args: String, -} - -impl ShellCommand { - #[must_use] - pub fn run(&self, mut state: State) -> (ExitCode, State) { - use std::io::{prelude::*, SeekFrom}; - - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); - - let exit_status = Exec::shell(&self.cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture() - .unwrap() - .exit_status; - - (exit_status.into(), state) - } -} - pub struct Printer { pub only_last: bool, pub until_contains: Option, @@ -83,12 +38,35 @@ impl Printer { } } -pub struct PreExitTasks { +pub struct ShellCommand { + pub cmd_with_args: String, +} + +impl ShellCommand { + #[must_use] + pub fn run(&self, mut state: State) -> (ExitCode, State) { + use std::io::{prelude::*, SeekFrom}; + + state.tmpfile.seek(SeekFrom::Start(0)).ok(); + state.tmpfile.set_len(0).ok(); + + let exit_status = Exec::shell(&self.cmd_with_args) + .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + .stderr(Redirection::Merge) + .capture() + .unwrap() + .exit_status; + + (exit_status.into(), state) + } +} + +pub struct ExitTasks { pub opt_only_last: bool, pub opt_summary: bool, } -impl PreExitTasks { +impl ExitTasks { pub fn run(&self, summary: Summary, mut tmpfile: File) { use crate::util::StringFromTempfileStart; @@ -104,6 +82,28 @@ impl PreExitTasks { } } +pub struct SetupEnv { + pub count_precision: usize, +} + +impl SetupEnv { + pub fn run(&self, item: Option, index: f64, actual_count: f64) { + use std::env::set_var; + + // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. + set_var("ACTUALCOUNT", index.to_string()); + set_var( + "COUNT", + format!("{:.*}", self.count_precision, actual_count), + ); + + // Set current item as environment variable + if let Some(item) = item { + set_var("ITEM", item); + } + } +} + #[derive(Debug, PartialEq, Clone, Copy)] pub enum ExitCode { Okay, diff --git a/src/loop_step.rs b/src/loop_step.rs index ae83c3c..efca2ef 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -122,3 +122,22 @@ fn until_error_check(has_matched: &mut bool, to_check: Option, exit_co _ => (), } } + +impl Default for LoopModel { + fn default() -> LoopModel { + use std::time::Instant; + + LoopModel { + for_duration: None, + error_duration: false, + until_time: None, + until_error: None, + until_success: false, + until_fail: false, + summary: false, + until_changes: false, + until_same: false, + program_start: Instant::now(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 906d274..358f1bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod state; mod util; use app::App; -use io::{ExitCode, PreExitTasks, Printer, SetupEnv, ShellCommand}; +use io::{ExitCode, ExitTasks, Printer, SetupEnv, ShellCommand}; fn main() { use setup::{setup, Opt}; @@ -34,7 +34,7 @@ fn main() { fn run_app( app: App, printer: Printer, - exit_tasks: PreExitTasks, + exit_tasks: ExitTasks, setup_env: SetupEnv, shell_command: ShellCommand, ) -> ExitCode { diff --git a/src/setup.rs b/src/setup.rs index 4c68bee..dec45c3 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::io::{ExitCode, PreExitTasks, Printer, SetupEnv, ShellCommand}; +use crate::io::{ExitCode, ExitTasks, Printer, SetupEnv, ShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -9,9 +9,7 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup( - mut opt: Opt, -) -> Result<(App, Printer, PreExitTasks, SetupEnv, ShellCommand), AppError> { +pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCommand), AppError> { use std::io::{self, BufRead}; use std::mem; @@ -40,7 +38,7 @@ pub fn setup( until_match: opt.until_match.clone(), }; - let exit_tasks = PreExitTasks { + let exit_tasks = ExitTasks { opt_only_last: opt.only_last, opt_summary: opt.summary, }; @@ -70,8 +68,8 @@ pub fn setup( let app = App { every, - iterator, loop_model, + iterator, }; Ok((app, printer_model, exit_tasks, setup_env, shell_command)) From 31530eab97703a084dbe083f70a7474ed372d1ba Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 19:36:14 +0100 Subject: [PATCH 49/56] App::run() example test --- src/app.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index a562b67..c4e577c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,7 +16,7 @@ impl App { #[must_use] pub fn run( self, - printer: &impl Fn(&str, State) -> State, + print: &impl Fn(&str, State) -> State, command: &impl Fn(State) -> (ExitCode, State), exit_tasks: &impl Fn(Summary, File), setup_environment: &impl Fn(Option, f64, f64), @@ -29,7 +29,7 @@ impl App { let setup_envs = || setup_environment(it.item.clone(), it.index, it.actual_count); - let (break_loop, new_state) = loop_model.step(state, setup_envs, command, printer); + let (break_loop, new_state) = loop_model.step(state, setup_envs, command, print); state = new_state; if break_loop { @@ -54,3 +54,38 @@ fn maybe_sleep(step_start: Instant, every: Duration) { thread::sleep(time); } } + +#[test] +fn test_run() { + use humantime::parse_duration; + use std::cell::RefCell; + + // test that the print closure is called twice + let expected_loop_count = 2; + let counter = RefCell::new(0); + + let app = App { + every: parse_duration("1us").unwrap(), + iterator: { + let num = Some(expected_loop_count as f64); + let items = vec!["a", "b", "c"].into_iter().map(str::to_owned).collect(); + let offset = 0_f64; + let count_by = 1_f64; + LoopIterator::new(offset, count_by, num, items) + }, + loop_model: LoopModel::default(), + }; + + let exit_code = app.run( + &|_stdout: &str, state: State| -> State { + let mut my_ref = counter.borrow_mut(); + *my_ref += 1; + state + }, + &|state: State| (ExitCode::Okay, state), + &|_summary: Summary, _tmpfile: File| {}, + &|_item: Option, _index: f64, _actual_count: f64| {}, + ); + assert_eq!(ExitCode::Okay, exit_code); + assert_eq!(expected_loop_count, *counter.borrow()); +} From 1d3af00472c0ca891405cffb0d43a22e5581b390 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Wed, 20 Mar 2019 11:28:47 +0100 Subject: [PATCH 50/56] clean-up --- src/app.rs | 30 +++++----- src/io.rs | 22 +++---- src/loop_iterator.rs | 93 ++++++++++++++++++++++++++---- src/loop_step.rs | 37 ++++++++---- src/main.rs | 30 +++++----- src/setup.rs | 133 ++++++++++++++++++++++--------------------- 6 files changed, 211 insertions(+), 134 deletions(-) diff --git a/src/app.rs b/src/app.rs index c4e577c..077737c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use std::fs::File; use std::time::{Duration, Instant}; pub struct App { - pub every: Duration, + pub every: Option, pub loop_model: LoopModel, pub iterator: LoopIterator, } @@ -21,15 +21,15 @@ impl App { exit_tasks: &impl Fn(Summary, File), setup_environment: &impl Fn(Option, f64, f64), ) -> ExitCode { - let loop_model = self.loop_model; + let m = self.loop_model; let mut state = State::default(); for it in self.iterator { let step_start_time = Instant::now(); - let setup_envs = || setup_environment(it.item.clone(), it.index, it.actual_count); + let setup_envs = || setup_environment(it.item, it.actual_count, it.count); - let (break_loop, new_state) = loop_model.step(state, setup_envs, command, print); + let (break_loop, new_state) = m.step(state, setup_envs, command, print); state = new_state; if break_loop { @@ -37,7 +37,13 @@ impl App { } // Delay until next iteration time - maybe_sleep(step_start_time, self.every); + if let Some(every) = self.every { + let since = Instant::now().duration_since(step_start_time); + + if let Some(time) = every.checked_sub(since) { + std::thread::sleep(time); + } + } } exit_tasks(state.summary, state.tmpfile); @@ -46,18 +52,8 @@ impl App { } } -fn maybe_sleep(step_start: Instant, every: Duration) { - use std::thread; - - let since = Instant::now().duration_since(step_start); - if let Some(time) = every.checked_sub(since) { - thread::sleep(time); - } -} - #[test] fn test_run() { - use humantime::parse_duration; use std::cell::RefCell; // test that the print closure is called twice @@ -65,7 +61,7 @@ fn test_run() { let counter = RefCell::new(0); let app = App { - every: parse_duration("1us").unwrap(), + every: None, iterator: { let num = Some(expected_loop_count as f64); let items = vec!["a", "b", "c"].into_iter().map(str::to_owned).collect(); @@ -84,7 +80,7 @@ fn test_run() { }, &|state: State| (ExitCode::Okay, state), &|_summary: Summary, _tmpfile: File| {}, - &|_item: Option, _index: f64, _actual_count: f64| {}, + &|_item: Option, _index: f64, _count: f64| {}, ); assert_eq!(ExitCode::Okay, exit_code); assert_eq!(expected_loop_count, *counter.borrow()); diff --git a/src/io.rs b/src/io.rs index db327b1..c7bcf83 100644 --- a/src/io.rs +++ b/src/io.rs @@ -62,21 +62,21 @@ impl ShellCommand { } pub struct ExitTasks { - pub opt_only_last: bool, - pub opt_summary: bool, + pub only_last: bool, + pub summary: bool, } impl ExitTasks { pub fn run(&self, summary: Summary, mut tmpfile: File) { use crate::util::StringFromTempfileStart; - if self.opt_only_last { + if self.only_last { String::from_temp_start(&mut tmpfile) .lines() .for_each(|line| println!("{}", line)); } - if self.opt_summary { + if self.summary { summary.print() } } @@ -87,15 +87,12 @@ pub struct SetupEnv { } impl SetupEnv { - pub fn run(&self, item: Option, index: f64, actual_count: f64) { + pub fn run(&self, item: Option, index: f64, count: f64) { use std::env::set_var; // THESE ARE FLIPPED AND I CAN'T UNFLIP THEM. set_var("ACTUALCOUNT", index.to_string()); - set_var( - "COUNT", - format!("{:.*}", self.count_precision, actual_count), - ); + set_var("COUNT", format!("{:.*}", self.count_precision, count)); // Set current item as environment variable if let Some(item) = item { @@ -108,6 +105,7 @@ impl SetupEnv { pub enum ExitCode { Okay, Error, + /// e.g. when no argument is passed to loop-rs (2) MinorError, /// same exit-code as used by the `timeout` shell command (124) Timeout, @@ -135,12 +133,6 @@ impl From for ExitCode { } } -impl From for ExitCode { - fn from(n: i32) -> ExitCode { - ExitCode::from(n as u32) - } -} - impl From for ExitCode { fn from(exit_status: ExitStatus) -> ExitCode { match exit_status { diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index 6526e0d..ca8faa6 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -8,13 +8,13 @@ pub struct LoopIterator { impl LoopIterator { pub fn new(offset: f64, count_by: f64, num: Option, items: Vec) -> LoopIterator { - let end = if let Some(num) = num { - num - } else if !items.is_empty() { - items.len() as f64 - } else { - std::f64::INFINITY - }; + let end = num.unwrap_or_else(|| { + if items.is_empty() { + std::f64::INFINITY + } else { + items.len() as f64 + } + }); LoopIterator { start: offset - count_by, index: 0.0, @@ -26,9 +26,12 @@ impl LoopIterator { } pub struct LoopResult { + /// value of $ITEM pub item: Option, + /// index $COUNT of the loop which can be affected by loop-rs flags + pub count: f64, + /// the position of the index named $ACTUALCOUNT in loop-rs pub actual_count: f64, - pub index: f64, } impl Iterator for LoopIterator { @@ -40,12 +43,15 @@ impl Iterator for LoopIterator { self.index += 1.0; if self.index <= self.end { - let item = self.items.get(self.index as usize).map(ToString::to_string); + let item = self + .items + .get(current_index as usize) + .map(ToString::to_string); let res = LoopResult { item, - actual_count: self.start, - index: current_index, + count: self.start, + actual_count: current_index, }; Some(res) @@ -54,3 +60,68 @@ impl Iterator for LoopIterator { } } } + +#[test] +fn test_loop_iterator() { + // okay + let mut iter = { + let count_by = 1_f64; + let offset = 0_f64; + LoopIterator::new( + offset, + count_by, + None, + vec!["a", "b"].into_iter().map(str::to_owned).collect(), + ) + .into_iter() + }; + let it = iter.next().unwrap(); + assert_eq!(it.item, Some("a".to_owned())); + assert_eq!(it.count, 0_f64); + assert_eq!(it.actual_count, 0_f64); + + let it = iter.next().unwrap(); + assert_eq!(it.item, Some("b".to_owned())); + assert_eq!(it.count, 1_f64); + assert_eq!(it.actual_count, 1_f64); + + // --count-by 2 + let mut iter = { + let count_by = 2_f64; + let offset = 0_f64; + LoopIterator::new( + offset, + count_by, + None, + vec!["a", "b"].into_iter().map(str::to_owned).collect(), + ) + .into_iter() + }; + let it = iter.next().unwrap(); + assert_eq!(it.item, Some("a".to_owned())); + assert_eq!(it.count, 0_f64); + assert_eq!(it.actual_count, 0_f64); + + let it = iter.next().unwrap(); + assert_eq!(it.item, Some("b".to_owned())); + assert_eq!(it.count, 2_f64); + assert_eq!(it.actual_count, 1_f64); + + // --num 1 + let mut iter = { + let count_by = 1_f64; + let offset = 0_f64; + LoopIterator::new( + offset, + count_by, + Some(1_f64), + vec!["a", "b"].into_iter().map(str::to_owned).collect(), + ) + .into_iter() + }; + let it = iter.next().unwrap(); + assert_eq!(it.item, Some("a".to_owned())); + assert_eq!(it.count, 0_f64); + assert_eq!(it.actual_count, 0_f64); + assert!(iter.next().is_none()); +} diff --git a/src/loop_step.rs b/src/loop_step.rs index efca2ef..d33b4da 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -22,7 +22,7 @@ impl LoopModel { pub fn step( &self, mut state: State, - setup_environment: impl Fn(), + setup_environment: impl FnOnce(), shell_command: impl Fn(State) -> (ExitCode, State), result_printer: impl Fn(&str, State) -> State, ) -> (bool, State) { @@ -101,7 +101,7 @@ fn run_command( state = result_printer(&stdout, state); // --until-error - until_error_check(&mut state.has_matched, until_error, exit_code); + state.has_matched = check_until_error(until_error, exit_code); // --until-success state.has_matched = until_success && exit_code.success(); @@ -112,14 +112,12 @@ fn run_command( (state, exit_code, stdout) } -/// Check if the exit-code is non-zero, or the given exit-code. -fn until_error_check(has_matched: &mut bool, to_check: Option, exit_code: ExitCode) { - match to_check { - Some(ExitCode::Error) => *has_matched = !exit_code.success(), - Some(expected_exit_code) => { - *has_matched = expected_exit_code == exit_code; - } - _ => (), +/// Check `exit_code` if --until-error flag is set. +fn check_until_error(should_check: Option, exit_code: ExitCode) -> bool { + match should_check { + Some(ExitCode::Other(expected)) => expected == exit_code.into(), + Some(_) => !exit_code.success(), + _ => false, } } @@ -141,3 +139,22 @@ impl Default for LoopModel { } } } + +#[test] +fn test_check_until_error() { + // check generic error + let has_matched = check_until_error(Some(ExitCode::Error), ExitCode::MinorError); + assert!(has_matched); + + // check specific error-code + let has_matched = check_until_error(Some(ExitCode::Other(99)), ExitCode::Other(99)); + assert!(has_matched); + + // check specific error-code, no match + let has_matched = check_until_error(Some(ExitCode::Other(20)), ExitCode::Other(99)); + assert!(!has_matched); + + // --until-error flag not set + let has_matched = check_until_error(None, ExitCode::Okay); + assert!(!has_matched); +} diff --git a/src/main.rs b/src/main.rs index 358f1bd..398f9a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,21 +14,18 @@ fn main() { use std::process; use structopt::StructOpt; - let app = setup(Opt::from_args()); - - let exit_code = match app { - Ok((app, printer, exit_tasks, setup_env, shell_command)) => { + let exit_code = setup(Opt::from_args()) + .map(|(app, printer, exit_tasks, setup_env, shell_command)| { run_app(app, printer, exit_tasks, setup_env, shell_command) - } - Err(err) => { + }) + .unwrap_or_else(|err| { if !err.message.is_empty() { eprintln!("{}", err.message); } err.exit_code - } - }; - - process::exit(exit_code.into()); + }) + .into(); + process::exit(exit_code); } fn run_app( @@ -38,20 +35,19 @@ fn run_app( setup_env: SetupEnv, shell_command: ShellCommand, ) -> ExitCode { - use crate::io::ExitCode; use crate::state::{State, Summary}; use std::fs::File; - let command = |state: State| -> (ExitCode, State) { shell_command.run(state) }; + let command = &|state: State| -> (ExitCode, State) { shell_command.run(state) }; - let printer = |stdout: &str, state: State| -> State { printer.print(stdout, state) }; + let printer = &|stdout: &str, state: State| -> State { printer.print(stdout, state) }; - let exit_tasks = |summary: Summary, tmpfile: File| { + let exit_tasks = &|summary: Summary, tmpfile: File| { exit_tasks.run(summary, tmpfile); }; - let setup_environment = |item: Option, index: f64, actual_count: f64| { - setup_env.run(item, index, actual_count) + let setup_environment = &|item: Option, actual_count: f64, count: f64| { + setup_env.run(item, actual_count, count) }; - app.run(&printer, &command, &exit_tasks, &setup_environment) + app.run(printer, command, exit_tasks, setup_environment) } diff --git a/src/setup.rs b/src/setup.rs index dec45c3..10c2a0e 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -10,19 +10,10 @@ use regex::Regex; use structopt::StructOpt; pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCommand), AppError> { - use std::io::{self, BufRead}; - use std::mem; - // Time let program_start = Instant::now(); - - let count_precision = Opt::clap() - .get_matches() - .value_of("count_by") - .map(precision_of) - .unwrap_or(0); - let cmd_with_args = opt.input.join(" "); + if cmd_with_args.is_empty() { return Err(AppError::new( ExitCode::MinorError, @@ -30,8 +21,6 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCo )); } - let every = opt.every; - let printer_model = Printer { only_last: opt.only_last, until_contains: opt.until_contains.clone(), @@ -39,37 +28,24 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCo }; let exit_tasks = ExitTasks { - opt_only_last: opt.only_last, - opt_summary: opt.summary, + only_last: opt.only_last, + summary: opt.summary, }; - let setup_env = SetupEnv { count_precision }; + let setup_env = SetupEnv { + count_precision: Opt::clap() + .get_matches() + .value_of("count_by") + .map(precision_of) + .unwrap_or(0), + }; let shell_command = ShellCommand { cmd_with_args }; - // Number of iterations - let mut items: Vec = vec![]; - if let Some(ref mut v) = opt.ffor { - mem::swap(&mut items, v); - opt.ffor = None; - } - - // Get any lines from stdin - if opt.stdin || atty::isnt(atty::Stream::Stdin) { - io::stdin() - .lock() - .lines() - .map(|line| line.unwrap().to_owned()) - .for_each(|line| items.push(line)); - } - - let iterator = LoopIterator::new(opt.offset, opt.count_by, opt.num, items); - let loop_model = opt.into_loop_model(program_start); - let app = App { - every, - loop_model, - iterator, + every: opt.every, + iterator: LoopIterator::from(&mut opt), + loop_model: LoopModel::from_opt(opt, program_start), }; Ok((app, printer_model, exit_tasks, setup_env, shell_command)) @@ -105,8 +81,8 @@ fn precision_of(s: &str) -> usize { fn get_exit_code(input: &str) -> ExitCode { input - .parse() - .map(ExitCode::Other) + .parse::() + .map(ExitCode::from) .unwrap_or_else(|_| ExitCode::Error) } @@ -140,13 +116,8 @@ pub struct Opt { offset: f64, /// How often to iterate. ex., 5s, 1h1m1s1ms1us - #[structopt( - short = "e", - long = "every", - default_value = "1us", - parse(try_from_str = "parse_duration") - )] - every: Duration, + #[structopt(short = "e", long = "every", parse(try_from_str = "parse_duration"))] + every: Option, /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue #[structopt(long = "for", parse(from_str = "get_values"))] @@ -217,30 +188,13 @@ pub struct Opt { input: Vec, } -impl Opt { - fn into_loop_model(self, program_start: Instant) -> LoopModel { - LoopModel { - program_start, - for_duration: self.for_duration, - error_duration: self.error_duration, - until_time: self.until_time, - until_error: self.until_error, - until_success: self.until_success, - until_fail: self.until_fail, - summary: self.summary, - until_changes: self.until_changes, - until_same: self.until_same, - } - } -} - impl Default for Opt { fn default() -> Opt { Opt { num: None, count_by: 1_f64, offset: 0_f64, - every: parse_duration("1us").unwrap(), + every: None, ffor: None, for_duration: None, until_contains: None, @@ -260,6 +214,49 @@ impl Default for Opt { } } +impl From<&mut Opt> for LoopIterator { + fn from(opt: &mut Opt) -> LoopIterator { + use std::io::{self, BufRead}; + use std::mem; + + // Number of iterations + let mut items: Vec = vec![]; + + if let Some(ref mut v) = opt.ffor { + mem::swap(&mut items, v); + opt.ffor = None; + } + + // Get any lines from stdin + if opt.stdin || atty::isnt(atty::Stream::Stdin) { + io::stdin() + .lock() + .lines() + .map(|line| line.unwrap().to_owned()) + .for_each(|line| items.push(line)); + } + + LoopIterator::new(opt.offset, opt.count_by, opt.num, items) + } +} + +impl LoopModel { + fn from_opt(opt: Opt, program_start: Instant) -> LoopModel { + LoopModel { + program_start, + for_duration: opt.for_duration, + error_duration: opt.error_duration, + until_time: opt.until_time, + until_error: opt.until_error, + until_success: opt.until_success, + until_fail: opt.until_fail, + summary: opt.summary, + until_changes: opt.until_changes, + until_same: opt.until_same, + } + } +} + #[test] fn test_setup() { // okay @@ -275,3 +272,11 @@ fn test_setup() { _ => panic!(), } } + +#[test] +fn test_get_exit_code() { + assert_eq!(ExitCode::Okay, get_exit_code("0")); + assert_eq!(ExitCode::Error, get_exit_code("")); + assert_eq!(ExitCode::Timeout, get_exit_code("124")); + assert_eq!(ExitCode::Other(50), get_exit_code("50")); +} From d7f15e450d61bd85532b6a4bba5ad2071d6199cb Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 18 Mar 2019 22:14:29 +0100 Subject: [PATCH 51/56] remove tempfile dependence --- Cargo.lock | 166 ----------------------------------------------- Cargo.toml | 1 - src/app.rs | 32 +++++---- src/io.rs | 42 ++++++------ src/loop_step.rs | 29 ++++----- src/main.rs | 12 ++-- src/state.rs | 23 +++++-- src/util.rs | 16 ----- 8 files changed, 79 insertions(+), 242 deletions(-) delete mode 100644 src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 07ef4e6..5642b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,21 +24,11 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "autocfg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cfg-if" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "clap" version = "2.32.0" @@ -53,24 +43,11 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "crossbeam-utils" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "heck" version = "0.3.1" @@ -106,7 +83,6 @@ dependencies = [ "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -135,110 +111,6 @@ dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_jitter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "redox_syscall" version = "0.1.51" @@ -272,14 +144,6 @@ dependencies = [ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "remove_dir_all" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "strsim" version = "0.7.0" @@ -325,19 +189,6 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "tempfile" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "termion" version = "1.5.1" @@ -417,13 +268,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" @@ -432,28 +279,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" "checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" -"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" "checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" "checksum subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "28fc0f40f0c0da73339d347aa7d6d2b90341a95683a47722bc4eebed71ff3c00" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" -"checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" diff --git a/Cargo.toml b/Cargo.toml index 06076bd..e47e992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ humantime = "1.2.0" atty = "0.2.11" regex = "1.1.2" subprocess = "0.1.18" -tempfile = "3.0.7" [[bin]] name = "loop" diff --git a/src/app.rs b/src/app.rs index 077737c..3a0c883 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,7 @@ use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; use crate::state::{State, Summary}; -use std::fs::File; +use std::io::Cursor; use std::time::{Duration, Instant}; pub struct App { @@ -16,11 +16,11 @@ impl App { #[must_use] pub fn run( self, - print: &impl Fn(&str, State) -> State, - command: &impl Fn(State) -> (ExitCode, State), - exit_tasks: &impl Fn(Summary, File), + print: &impl Fn(&str, &mut State), + command: &impl Fn(&mut State) -> ExitCode, + exit_tasks: &impl Fn(&Summary, &mut Cursor>), setup_environment: &impl Fn(Option, f64, f64), - ) -> ExitCode { + ) -> State { let m = self.loop_model; let mut state = State::default(); @@ -46,17 +46,18 @@ impl App { } } - exit_tasks(state.summary, state.tmpfile); + exit_tasks(&state.summary, &mut state.buf); - state.exit_code + state } } #[test] fn test_run() { use std::cell::RefCell; + use std::io::{Cursor, Write}; - // test that the print closure is called twice + // test that loop step method is called twice let expected_loop_count = 2; let counter = RefCell::new(0); @@ -72,16 +73,19 @@ fn test_run() { loop_model: LoopModel::default(), }; - let exit_code = app.run( - &|_stdout: &str, state: State| -> State { + let mut state = app.run( + &|_stdout: &str, _state: &mut State| { let mut my_ref = counter.borrow_mut(); *my_ref += 1; - state }, - &|state: State| (ExitCode::Okay, state), - &|_summary: Summary, _tmpfile: File| {}, + &|state: &mut State| { + writeln!(state.buf, "123").unwrap(); + ExitCode::Okay + }, + &|_summary: &Summary, _buf: &mut Cursor>| {}, &|_item: Option, _index: f64, _count: f64| {}, ); - assert_eq!(ExitCode::Okay, exit_code); + assert_eq!(ExitCode::Okay, state.exit_code); assert_eq!(expected_loop_count, *counter.borrow()); + assert_eq!("123\n123\n", state.buffer_to_string()); } diff --git a/src/io.rs b/src/io.rs index c7bcf83..2dce0b7 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,6 +1,6 @@ use crate::state::{State, Summary}; -use std::fs::File; +use std::io::{Read, Seek}; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; @@ -13,7 +13,7 @@ pub struct Printer { impl Printer { #[must_use] - pub fn print(&self, stdout: &str, mut state: State) -> State { + pub fn print(&self, stdout: &str, state: &mut State) { stdout.lines().for_each(|line| { // --only-last // If we only want output from the last execution, @@ -32,9 +32,7 @@ impl Printer { if let Some(ref regex) = self.until_match { state.has_matched = regex.captures(&line).is_some(); } - }); - - state + }) } } @@ -44,20 +42,21 @@ pub struct ShellCommand { impl ShellCommand { #[must_use] - pub fn run(&self, mut state: State) -> (ExitCode, State) { - use std::io::{prelude::*, SeekFrom}; + pub fn run(&self, state: &mut State) -> ExitCode { + use std::io::{SeekFrom, Write}; - state.tmpfile.seek(SeekFrom::Start(0)).ok(); - state.tmpfile.set_len(0).ok(); + state.buf.seek(SeekFrom::Start(0)).ok(); + state.buf.set_position(0_u64); - let exit_status = Exec::shell(&self.cmd_with_args) - .stdout(Redirection::File(state.tmpfile.try_clone().unwrap())) + let captured_data = Exec::shell(&self.cmd_with_args) + .stdout(Redirection::Pipe) .stderr(Redirection::Merge) .capture() - .unwrap() - .exit_status; + .unwrap(); + + state.buf.write_all(captured_data.stdout.as_ref()).unwrap(); - (exit_status.into(), state) + captured_data.exit_status.into() } } @@ -67,13 +66,18 @@ pub struct ExitTasks { } impl ExitTasks { - pub fn run(&self, summary: Summary, mut tmpfile: File) { - use crate::util::StringFromTempfileStart; + pub fn run(&self, summary: &Summary, buf: &mut F) + where + F: Seek + Read, + { + use std::io::SeekFrom; if self.only_last { - String::from_temp_start(&mut tmpfile) - .lines() - .for_each(|line| println!("{}", line)); + let mut output = String::new(); + buf.seek(SeekFrom::Start(0)).ok(); + buf.read_to_string(&mut output).ok(); + + output.lines().for_each(|line| println!("{}", line)); } if self.summary { diff --git a/src/loop_step.rs b/src/loop_step.rs index d33b4da..e1d1ee5 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,6 +1,5 @@ use crate::io::ExitCode; use crate::state::State; -use crate::util::StringFromTempfileStart; use std::time::{Duration, Instant, SystemTime}; @@ -23,8 +22,8 @@ impl LoopModel { &self, mut state: State, setup_environment: impl FnOnce(), - shell_command: impl Fn(State) -> (ExitCode, State), - result_printer: impl Fn(&str, State) -> State, + shell_command: impl Fn(&mut State) -> ExitCode, + printer: impl Fn(&str, &mut State), ) -> (bool, State) { // Set counters before execution setup_environment(); @@ -50,13 +49,13 @@ impl LoopModel { } // Main executor - let (new_state, exit_code, stdout) = run_command( + let (new_state, exit_code, cmd_output) = run_command( state, self.until_error, self.until_success, self.until_fail, shell_command, - result_printer, + printer, ); state = new_state; @@ -71,34 +70,34 @@ impl LoopModel { if let Some(ref previous_stdout) = state.previous_stdout { // --until-changes - if self.until_changes && *previous_stdout != stdout { + if self.until_changes && *previous_stdout != cmd_output { return (true, state); } // --until-same - if self.until_same && *previous_stdout == stdout { + if self.until_same && *previous_stdout == cmd_output { return (true, state); } } - state.previous_stdout = Some(stdout); + state.previous_stdout = Some(cmd_output); (false, state) } } fn run_command( - state: State, + mut state: State, until_error: Option, until_success: bool, until_fail: bool, - shell_command: impl Fn(State) -> (ExitCode, State), - result_printer: impl Fn(&str, State) -> State, + shell_command: impl Fn(&mut State) -> ExitCode, + printer: impl Fn(&str, &mut State), ) -> (State, ExitCode, String) { - let (exit_code, mut state) = shell_command(state); + let exit_code = shell_command(&mut state); // Print the results - let stdout = String::from_temp_start(&mut state.tmpfile); - state = result_printer(&stdout, state); + let cmd_output = state.buffer_to_string(); + printer(&cmd_output, &mut state); // --until-error state.has_matched = check_until_error(until_error, exit_code); @@ -109,7 +108,7 @@ fn run_command( // --until-fail state.has_matched = until_fail && !exit_code.success(); - (state, exit_code, stdout) + (state, exit_code, cmd_output) } /// Check `exit_code` if --until-error flag is set. diff --git a/src/main.rs b/src/main.rs index 398f9a5..55cd081 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod loop_iterator; mod loop_step; mod setup; mod state; -mod util; use app::App; use io::{ExitCode, ExitTasks, Printer, SetupEnv, ShellCommand}; @@ -36,18 +35,19 @@ fn run_app( shell_command: ShellCommand, ) -> ExitCode { use crate::state::{State, Summary}; - use std::fs::File; + use std::io::Cursor; - let command = &|state: State| -> (ExitCode, State) { shell_command.run(state) }; + let command = &|state: &mut State| -> ExitCode { shell_command.run(state) }; - let printer = &|stdout: &str, state: State| -> State { printer.print(stdout, state) }; + let printer = &|stdout: &str, state: &mut State| printer.print(stdout, state); - let exit_tasks = &|summary: Summary, tmpfile: File| { - exit_tasks.run(summary, tmpfile); + let exit_tasks = &|summary: &Summary, buf: &mut Cursor>| { + exit_tasks.run(summary, buf); }; let setup_environment = &|item: Option, actual_count: f64, count: f64| { setup_env.run(item, actual_count, count) }; app.run(printer, command, exit_tasks, setup_environment) + .exit_code } diff --git a/src/state.rs b/src/state.rs index 8e65a22..6670a3b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,20 +1,33 @@ use crate::io::ExitCode; -use std::fs::File; +use std::io::Cursor; pub struct State { - pub tmpfile: File, + /// shell command in-memory output buffer + pub buf: Cursor>, pub summary: Summary, pub exit_code: ExitCode, pub previous_stdout: Option, pub has_matched: bool, } +impl State { + pub fn buffer_to_string(&mut self) -> String { + use std::io::SeekFrom; + use std::io::{Read, Seek}; + + let mut output = String::new(); + self.buf.seek(SeekFrom::Start(0)).ok(); + self.buf.read_to_string(&mut output).ok(); + output + } +} + impl Default for State { fn default() -> State { State { has_matched: false, - tmpfile: tempfile::tempfile().unwrap(), + buf: Cursor::new(vec![]), summary: Summary::default(), previous_stdout: None, exit_code: ExitCode::Okay, @@ -35,7 +48,7 @@ impl Summary { } } - pub fn print(self) { + pub fn print(&self) { let total = self.successes + self.failures.len() as u32; let errors = if self.failures.is_empty() { @@ -45,7 +58,7 @@ impl Summary { "{} ({})", self.failures.len(), self.failures - .into_iter() + .iter() .map(|f| f.to_string()) .collect::>() .join(", ") diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 89d2099..0000000 --- a/src/util.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::fs::File; - -pub trait StringFromTempfileStart { - fn from_temp_start(tmpfile: &mut File) -> String; -} - -impl StringFromTempfileStart for String { - fn from_temp_start(tmpfile: &mut File) -> String { - use std::io::{prelude::*, SeekFrom}; - - let mut stdout = String::new(); - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(&mut stdout).ok(); - stdout - } -} From 753a0888adac9eaf08f69835c841e507d622dd91 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sun, 24 Mar 2019 12:05:42 +0100 Subject: [PATCH 52/56] remove global buffer --- src/app.rs | 64 ++++++++++++++++-------------- src/io.rs | 93 +++++++++++++++++++++++--------------------- src/loop_iterator.rs | 30 ++++++++++---- src/loop_step.rs | 17 ++++---- src/main.rs | 36 +++-------------- src/setup.rs | 23 +++++------ src/state.rs | 66 ++++++++++--------------------- 7 files changed, 152 insertions(+), 177 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3a0c883..4747e7f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,9 @@ use crate::io::ExitCode; +use crate::io::Printer; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; -use crate::state::{State, Summary}; +use crate::state::State; -use std::io::Cursor; use std::time::{Duration, Instant}; pub struct App { @@ -16,10 +16,9 @@ impl App { #[must_use] pub fn run( self, - print: &impl Fn(&str, &mut State), - command: &impl Fn(&mut State) -> ExitCode, - exit_tasks: &impl Fn(&Summary, &mut Cursor>), setup_environment: &impl Fn(Option, f64, f64), + command: &impl Fn() -> (String, ExitCode), + mut printer: Printer, ) -> State { let m = self.loop_model; let mut state = State::default(); @@ -27,39 +26,42 @@ impl App { for it in self.iterator { let step_start_time = Instant::now(); + let is_last = it.is_last; + let setup_envs = || setup_environment(it.item, it.actual_count, it.count); - let (break_loop, new_state) = m.step(state, setup_envs, command, print); + let (break_loop, new_state) = m.step(state, setup_envs, command, &mut printer); state = new_state; if break_loop { break; } - // Delay until next iteration time - if let Some(every) = self.every { - let since = Instant::now().duration_since(step_start_time); + if is_last { + printer.terminatory_print(|| state.to_string()); + } else { + // Delay until next iteration time + if let Some(every) = self.every { + let since = Instant::now().duration_since(step_start_time); - if let Some(time) = every.checked_sub(since) { - std::thread::sleep(time); + if let Some(time) = every.checked_sub(since) { + std::thread::sleep(time); + } } } } - exit_tasks(&state.summary, &mut state.buf); - state } } #[test] -fn test_run() { +#[allow(non_snake_case)] +fn run__num() { use std::cell::RefCell; - use std::io::{Cursor, Write}; - // test that loop step method is called twice + // test that loop step method is called twice, flag: --num 2 let expected_loop_count = 2; - let counter = RefCell::new(0); let app = App { every: None, @@ -73,19 +75,23 @@ fn test_run() { loop_model: LoopModel::default(), }; - let mut state = app.run( - &|_stdout: &str, _state: &mut State| { - let mut my_ref = counter.borrow_mut(); - *my_ref += 1; - }, - &|state: &mut State| { - writeln!(state.buf, "123").unwrap(); - ExitCode::Okay + let cmd_output = RefCell::new(vec![]); + + let state = app.run( + &|_item, _index, _count| {}, + &|| { + let mut buf = cmd_output.borrow_mut(); + let output = match buf.len() { + 0 => "123".to_owned(), + _ => "abc".to_owned(), + }; + buf.push(output.clone()); + + (output, ExitCode::Okay) }, - &|_summary: &Summary, _buf: &mut Cursor>| {}, - &|_item: Option, _index: f64, _count: f64| {}, + Printer::default(), ); assert_eq!(ExitCode::Okay, state.exit_code); - assert_eq!(expected_loop_count, *counter.borrow()); - assert_eq!("123\n123\n", state.buffer_to_string()); + assert_eq!(expected_loop_count, cmd_output.borrow().len()); + assert_eq!(vec!["123", "abc"], cmd_output.into_inner()); } diff --git a/src/io.rs b/src/io.rs index 2dce0b7..18b78d6 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,6 +1,6 @@ -use crate::state::{State, Summary}; +use crate::state::State; -use std::io::{Read, Seek}; +use std::io::Write; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; @@ -9,12 +9,30 @@ pub struct Printer { pub only_last: bool, pub until_contains: Option, pub until_match: Option, + pub summary: bool, + pub last_output: String, } impl Printer { - #[must_use] - pub fn print(&self, stdout: &str, state: &mut State) { - stdout.lines().for_each(|line| { + pub fn terminatory_print(&self, create_summary: impl Fn() -> String) { + use std::io::stdout; + + let handle = stdout(); + let mut stdout = handle.lock(); + + if self.only_last { + writeln!(stdout, "{}", self.last_output).unwrap(); + } + + if self.summary { + stdout.write_all(create_summary().as_bytes()).unwrap(); + } + } + + pub fn print(&mut self, text: &str, state: &mut State) { + let mut last_line = String::default(); + + text.lines().for_each(|line| { // --only-last // If we only want output from the last execution, // defer printing until later @@ -32,7 +50,28 @@ impl Printer { if let Some(ref regex) = self.until_match { state.has_matched = regex.captures(&line).is_some(); } - }) + + if self.only_last { + last_line = line.to_owned(); + } + }); + + // maybe keep the last line for later + if self.only_last { + self.last_output = last_line; + } + } +} + +impl Default for Printer { + fn default() -> Printer { + Printer { + only_last: false, + until_contains: None, + until_match: None, + summary: false, + last_output: String::new(), + } } } @@ -42,47 +81,13 @@ pub struct ShellCommand { impl ShellCommand { #[must_use] - pub fn run(&self, state: &mut State) -> ExitCode { - use std::io::{SeekFrom, Write}; - - state.buf.seek(SeekFrom::Start(0)).ok(); - state.buf.set_position(0_u64); - - let captured_data = Exec::shell(&self.cmd_with_args) + pub fn run(&self) -> (String, ExitCode) { + Exec::shell(&self.cmd_with_args) .stdout(Redirection::Pipe) .stderr(Redirection::Merge) .capture() - .unwrap(); - - state.buf.write_all(captured_data.stdout.as_ref()).unwrap(); - - captured_data.exit_status.into() - } -} - -pub struct ExitTasks { - pub only_last: bool, - pub summary: bool, -} - -impl ExitTasks { - pub fn run(&self, summary: &Summary, buf: &mut F) - where - F: Seek + Read, - { - use std::io::SeekFrom; - - if self.only_last { - let mut output = String::new(); - buf.seek(SeekFrom::Start(0)).ok(); - buf.read_to_string(&mut output).ok(); - - output.lines().for_each(|line| println!("{}", line)); - } - - if self.summary { - summary.print() - } + .map(|it| (it.stdout_str(), it.exit_status.into())) + .unwrap() } } diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index ca8faa6..78f9a44 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -23,21 +23,28 @@ impl LoopIterator { items, } } + + fn is_last_iterm(&self, current_index: f64) -> bool { + // end is loop length or --num flag value + ((self.end - 1_f64) - current_index).abs() < 0.01 + } } -pub struct LoopResult { +pub struct Item { /// value of $ITEM pub item: Option, /// index $COUNT of the loop which can be affected by loop-rs flags pub count: f64, /// the position of the index named $ACTUALCOUNT in loop-rs pub actual_count: f64, + /// true if it is the last item of the iterator + pub is_last: bool, } impl Iterator for LoopIterator { - type Item = LoopResult; + type Item = Item; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { let current_index = self.index; self.start += self.step_by; self.index += 1.0; @@ -48,10 +55,11 @@ impl Iterator for LoopIterator { .get(current_index as usize) .map(ToString::to_string); - let res = LoopResult { + let res = Item { item, count: self.start, actual_count: current_index, + is_last: self.is_last_iterm(current_index), }; Some(res) @@ -62,8 +70,8 @@ impl Iterator for LoopIterator { } #[test] -fn test_loop_iterator() { - // okay +#[allow(non_snake_case)] +fn loop_iterator__okay() { let mut iter = { let count_by = 1_f64; let offset = 0_f64; @@ -84,8 +92,11 @@ fn test_loop_iterator() { assert_eq!(it.item, Some("b".to_owned())); assert_eq!(it.count, 1_f64); assert_eq!(it.actual_count, 1_f64); +} - // --count-by 2 +#[test] +#[allow(non_snake_case)] +fn loop_iterator__count_by_2() { let mut iter = { let count_by = 2_f64; let offset = 0_f64; @@ -106,8 +117,11 @@ fn test_loop_iterator() { assert_eq!(it.item, Some("b".to_owned())); assert_eq!(it.count, 2_f64); assert_eq!(it.actual_count, 1_f64); +} - // --num 1 +#[test] +#[allow(non_snake_case)] +fn loop_iterator__num_1() { let mut iter = { let count_by = 1_f64; let offset = 0_f64; diff --git a/src/loop_step.rs b/src/loop_step.rs index e1d1ee5..fd7f27c 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,4 +1,4 @@ -use crate::io::ExitCode; +use crate::io::{ExitCode, Printer}; use crate::state::State; use std::time::{Duration, Instant, SystemTime}; @@ -22,8 +22,8 @@ impl LoopModel { &self, mut state: State, setup_environment: impl FnOnce(), - shell_command: impl Fn(&mut State) -> ExitCode, - printer: impl Fn(&str, &mut State), + shell_command: impl Fn() -> (String, ExitCode), + printer: &mut Printer, ) -> (bool, State) { // Set counters before execution setup_environment(); @@ -60,7 +60,7 @@ impl LoopModel { state = new_state; if self.summary { - state.summary.update(exit_code); + state.update_summary(exit_code); } // Finish if we matched @@ -90,14 +90,13 @@ fn run_command( until_error: Option, until_success: bool, until_fail: bool, - shell_command: impl Fn(&mut State) -> ExitCode, - printer: impl Fn(&str, &mut State), + shell_command: impl Fn() -> (String, ExitCode), + printer: &mut Printer, ) -> (State, ExitCode, String) { - let exit_code = shell_command(&mut state); + let (cmd_output, exit_code) = shell_command(); // Print the results - let cmd_output = state.buffer_to_string(); - printer(&cmd_output, &mut state); + printer.print(&cmd_output, &mut state); // --until-error state.has_matched = check_until_error(until_error, exit_code); diff --git a/src/main.rs b/src/main.rs index 55cd081..2147e84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,17 +5,17 @@ mod loop_step; mod setup; mod state; -use app::App; -use io::{ExitCode, ExitTasks, Printer, SetupEnv, ShellCommand}; - fn main() { use setup::{setup, Opt}; use std::process; use structopt::StructOpt; let exit_code = setup(Opt::from_args()) - .map(|(app, printer, exit_tasks, setup_env, shell_command)| { - run_app(app, printer, exit_tasks, setup_env, shell_command) + .map(|(app, setup_env, command, printer)| { + let setup_env = &|item: Option, actual_count: f64, count: f64| { + setup_env.run(item, actual_count, count) + }; + app.run(setup_env, &|| command.run(), printer).exit_code }) .unwrap_or_else(|err| { if !err.message.is_empty() { @@ -24,30 +24,6 @@ fn main() { err.exit_code }) .into(); - process::exit(exit_code); -} - -fn run_app( - app: App, - printer: Printer, - exit_tasks: ExitTasks, - setup_env: SetupEnv, - shell_command: ShellCommand, -) -> ExitCode { - use crate::state::{State, Summary}; - use std::io::Cursor; - let command = &|state: &mut State| -> ExitCode { shell_command.run(state) }; - - let printer = &|stdout: &str, state: &mut State| printer.print(stdout, state); - - let exit_tasks = &|summary: &Summary, buf: &mut Cursor>| { - exit_tasks.run(summary, buf); - }; - let setup_environment = &|item: Option, actual_count: f64, count: f64| { - setup_env.run(item, actual_count, count) - }; - - app.run(printer, command, exit_tasks, setup_environment) - .exit_code + process::exit(exit_code); } diff --git a/src/setup.rs b/src/setup.rs index 10c2a0e..d535c56 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::io::{ExitCode, ExitTasks, Printer, SetupEnv, ShellCommand}; +use crate::io::{ExitCode, Printer, SetupEnv, ShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; @@ -9,7 +9,7 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCommand), AppError> { +pub fn setup(mut opt: Opt) -> Result<(App, SetupEnv, ShellCommand, Printer), AppError> { // Time let program_start = Instant::now(); let cmd_with_args = opt.input.join(" "); @@ -21,15 +21,12 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCo )); } - let printer_model = Printer { + let printer = Printer { only_last: opt.only_last, until_contains: opt.until_contains.clone(), until_match: opt.until_match.clone(), - }; - - let exit_tasks = ExitTasks { - only_last: opt.only_last, summary: opt.summary, + last_output: String::new(), }; let setup_env = SetupEnv { @@ -48,7 +45,7 @@ pub fn setup(mut opt: Opt) -> Result<(App, Printer, ExitTasks, SetupEnv, ShellCo loop_model: LoopModel::from_opt(opt, program_start), }; - Ok((app, printer_model, exit_tasks, setup_env, shell_command)) + Ok((app, setup_env, shell_command, printer)) } #[derive(Debug, PartialEq, Clone)] @@ -258,13 +255,17 @@ impl LoopModel { } #[test] -fn test_setup() { - // okay +#[allow(non_snake_case)] +fn setup__okay() { let mut opt = Opt::default(); opt.input = vec!["foobar".to_owned()]; assert!(setup(opt).is_ok()); +} - // no command +#[test] +#[allow(non_snake_case)] +fn setup__no_command() { + // no command supplied to loop-rs let opt = Opt::default(); let app_error = AppError::new(ExitCode::MinorError, "No command supplied, exiting."); match setup(opt) { diff --git a/src/state.rs b/src/state.rs index 6670a3b..6b67922 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,54 +1,24 @@ use crate::io::ExitCode; -use std::io::Cursor; - pub struct State { - /// shell command in-memory output buffer - pub buf: Cursor>, - pub summary: Summary, - pub exit_code: ExitCode, - pub previous_stdout: Option, pub has_matched: bool, + pub previous_stdout: Option, + pub successes: u32, + pub failures: Vec, + pub exit_code: ExitCode, } impl State { - pub fn buffer_to_string(&mut self) -> String { - use std::io::SeekFrom; - use std::io::{Read, Seek}; - - let mut output = String::new(); - self.buf.seek(SeekFrom::Start(0)).ok(); - self.buf.read_to_string(&mut output).ok(); - output - } -} - -impl Default for State { - fn default() -> State { - State { - has_matched: false, - buf: Cursor::new(vec![]), - summary: Summary::default(), - previous_stdout: None, - exit_code: ExitCode::Okay, - } - } -} - -pub struct Summary { - successes: u32, - failures: Vec, -} - -impl Summary { - pub fn update(&mut self, exit_code: ExitCode) { + pub fn update_summary(&mut self, exit_code: ExitCode) { match exit_code { ExitCode::Okay => self.successes += 1, err => self.failures.push(err.into()), } } +} - pub fn print(&self) { +impl ToString for State { + fn to_string(&self) -> String { let total = self.successes + self.failures.len() as u32; let errors = if self.failures.is_empty() { @@ -64,18 +34,22 @@ impl Summary { .join(", ") ) }; - - println!("Total runs:\t{}", total); - println!("Successes:\t{}", self.successes); - println!("Failures:\t{}", errors); + let mut s = String::new(); + s.push_str(&format!("Total runs:\t{}\n", total)); + s.push_str(&format!("Successes:\t{}\n", self.successes)); + s.push_str(&format!("Failures:\t{}\n", errors)); + s } } -impl Default for Summary { - fn default() -> Summary { - Summary { +impl Default for State { + fn default() -> State { + State { + has_matched: false, + previous_stdout: None, successes: 0, - failures: Vec::new(), + failures: vec![], + exit_code: ExitCode::Okay, } } } From e0e9fee9cee3d8a046fe445c0989591f5d84df45 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Tue, 26 Mar 2019 20:06:23 +0100 Subject: [PATCH 53/56] derive Default --- src/io.rs | 19 +++++++------------ src/state.rs | 13 +------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/io.rs b/src/io.rs index 18b78d6..015be0e 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,6 +5,7 @@ use std::io::Write; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; +#[derive(Default)] pub struct Printer { pub only_last: bool, pub until_contains: Option, @@ -63,18 +64,6 @@ impl Printer { } } -impl Default for Printer { - fn default() -> Printer { - Printer { - only_last: false, - until_contains: None, - until_match: None, - summary: false, - last_output: String::new(), - } - } -} - pub struct ShellCommand { pub cmd_with_args: String, } @@ -129,6 +118,12 @@ impl ExitCode { } } +impl Default for ExitCode { + fn default() -> ExitCode { + ExitCode::Okay + } +} + impl From for ExitCode { fn from(n: u32) -> ExitCode { match n { diff --git a/src/state.rs b/src/state.rs index 6b67922..c23be71 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,6 @@ use crate::io::ExitCode; +#[derive(Default)] pub struct State { pub has_matched: bool, pub previous_stdout: Option, @@ -41,15 +42,3 @@ impl ToString for State { s } } - -impl Default for State { - fn default() -> State { - State { - has_matched: false, - previous_stdout: None, - successes: 0, - failures: vec![], - exit_code: ExitCode::Okay, - } - } -} From 64f1b1a848483f5f3c2837b8bbdc30706df72361 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sun, 31 Mar 2019 11:10:41 +0200 Subject: [PATCH 54/56] rustfmt.toml & updated dependencies --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 2 +- rustfmt.toml | 18 ++++++++++++++++++ src/app.rs | 9 ++++++--- src/loop_iterator.rs | 13 ++++++++----- src/loop_step.rs | 14 ++++++++++---- src/main.rs | 7 ++++--- src/setup.rs | 35 ++++++++++++++++++++++------------- 8 files changed, 87 insertions(+), 47 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 5642b79..6415535 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [[package]] name = "aho-corasick" -version = "0.6.10" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -19,7 +19,7 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -71,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.50" +version = "0.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -80,7 +80,7 @@ version = "0.6.1" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -121,24 +121,24 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -175,7 +175,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -194,8 +194,8 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -265,7 +265,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" @@ -274,15 +274,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" +"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" -"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" +"checksum redox_syscall 0.1.52 (registry+https://github.com/rust-lang/crates.io-index)" = "d32b3053e5ced86e4bc0411fec997389532bf56b000e66cb4884eeeb41413d69" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" -"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" +"checksum regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "559008764a17de49a3146b234641644ed37d118d1ef641a0bb573d146edc6ce0" +"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" "checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" diff --git a/Cargo.toml b/Cargo.toml index e47e992..45ff78f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ license = "MIT" structopt = "0.2.15" humantime = "1.2.0" atty = "0.2.11" -regex = "1.1.2" +regex = "1.1.5" subprocess = "0.1.18" [[bin]] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..55de32f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,18 @@ +max_width = 80 +newline_style = "Unix" +use_field_init_shorthand = true +use_try_shorthand = true + +# wrap_comments = true +# comment_width = 100 +# fn_single_line = true +# format_strings = true +# merge_imports = true +# match_block_trailing_comma = true +# force_multiline_blocks = true +# normalize_comments = true +# struct_field_align_threshold = 20 +# spaces_around_ranges = true +# format_doc_comments = true +# condense_wildcard_suffixes = true +# overflow_delimited_expr = true diff --git a/src/app.rs b/src/app.rs index 4747e7f..1f8173c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,9 +28,11 @@ impl App { let is_last = it.is_last; - let setup_envs = || setup_environment(it.item, it.actual_count, it.count); + let setup_envs = + || setup_environment(it.item, it.actual_count, it.count); - let (break_loop, new_state) = m.step(state, setup_envs, command, &mut printer); + let (break_loop, new_state) = + m.step(state, setup_envs, command, &mut printer); state = new_state; if break_loop { @@ -67,7 +69,8 @@ fn run__num() { every: None, iterator: { let num = Some(expected_loop_count as f64); - let items = vec!["a", "b", "c"].into_iter().map(str::to_owned).collect(); + let items = + vec!["a", "b", "c"].into_iter().map(str::to_owned).collect(); let offset = 0_f64; let count_by = 1_f64; LoopIterator::new(offset, count_by, num, items) diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index 78f9a44..2f2f6c6 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -7,7 +7,12 @@ pub struct LoopIterator { } impl LoopIterator { - pub fn new(offset: f64, count_by: f64, num: Option, items: Vec) -> LoopIterator { + pub fn new( + offset: f64, + count_by: f64, + num: Option, + items: Vec, + ) -> LoopIterator { let end = num.unwrap_or_else(|| { if items.is_empty() { std::f64::INFINITY @@ -50,10 +55,8 @@ impl Iterator for LoopIterator { self.index += 1.0; if self.index <= self.end { - let item = self - .items - .get(current_index as usize) - .map(ToString::to_string); + let item = + self.items.get(current_index as usize).map(ToString::to_string); let res = Item { item, diff --git a/src/loop_step.rs b/src/loop_step.rs index fd7f27c..69d59ba 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -111,7 +111,10 @@ fn run_command( } /// Check `exit_code` if --until-error flag is set. -fn check_until_error(should_check: Option, exit_code: ExitCode) -> bool { +fn check_until_error( + should_check: Option, + exit_code: ExitCode, +) -> bool { match should_check { Some(ExitCode::Other(expected)) => expected == exit_code.into(), Some(_) => !exit_code.success(), @@ -141,15 +144,18 @@ impl Default for LoopModel { #[test] fn test_check_until_error() { // check generic error - let has_matched = check_until_error(Some(ExitCode::Error), ExitCode::MinorError); + let has_matched = + check_until_error(Some(ExitCode::Error), ExitCode::MinorError); assert!(has_matched); // check specific error-code - let has_matched = check_until_error(Some(ExitCode::Other(99)), ExitCode::Other(99)); + let has_matched = + check_until_error(Some(ExitCode::Other(99)), ExitCode::Other(99)); assert!(has_matched); // check specific error-code, no match - let has_matched = check_until_error(Some(ExitCode::Other(20)), ExitCode::Other(99)); + let has_matched = + check_until_error(Some(ExitCode::Other(20)), ExitCode::Other(99)); assert!(!has_matched); // --until-error flag not set diff --git a/src/main.rs b/src/main.rs index 2147e84..ff4bca0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,9 +12,10 @@ fn main() { let exit_code = setup(Opt::from_args()) .map(|(app, setup_env, command, printer)| { - let setup_env = &|item: Option, actual_count: f64, count: f64| { - setup_env.run(item, actual_count, count) - }; + let setup_env = + &|item: Option, actual_count: f64, count: f64| { + setup_env.run(item, actual_count, count) + }; app.run(setup_env, &|| command.run(), printer).exit_code }) .unwrap_or_else(|err| { diff --git a/src/setup.rs b/src/setup.rs index d535c56..83e9535 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -9,7 +9,9 @@ use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; use structopt::StructOpt; -pub fn setup(mut opt: Opt) -> Result<(App, SetupEnv, ShellCommand, Printer), AppError> { +pub fn setup( + mut opt: Opt, +) -> Result<(App, SetupEnv, ShellCommand, Printer), AppError> { // Time let program_start = Instant::now(); let cmd_with_args = opt.input.join(" "); @@ -59,10 +61,7 @@ impl AppError { where M: Into, { - AppError { - exit_code, - message: msg.into(), - } + AppError { exit_code, message: msg.into() } } } @@ -77,10 +76,7 @@ fn precision_of(s: &str) -> usize { } fn get_exit_code(input: &str) -> ExitCode { - input - .parse::() - .map(ExitCode::from) - .unwrap_or_else(|_| ExitCode::Error) + input.parse::().map(ExitCode::from).unwrap_or_else(|_| ExitCode::Error) } fn get_values(input: &str) -> Vec { @@ -113,7 +109,11 @@ pub struct Opt { offset: f64, /// How often to iterate. ex., 5s, 1h1m1s1ms1us - #[structopt(short = "e", long = "every", parse(try_from_str = "parse_duration"))] + #[structopt( + short = "e", + long = "every", + parse(try_from_str = "parse_duration") + )] every: Option, /// A comma-separated list of values, placed into 4ITEM. ex., red,green,blue @@ -141,7 +141,11 @@ pub struct Opt { until_same: bool, /// Keep going until the output matches this regular expression - #[structopt(short = "m", long = "until-match", parse(try_from_str = "Regex::new"))] + #[structopt( + short = "m", + long = "until-match", + parse(try_from_str = "Regex::new") + )] until_match: Option, /// Keep going until a future time, ex. "2018-04-20 04:20:00" (Times in UTC.) @@ -153,7 +157,11 @@ pub struct Opt { until_time: Option, /// Keep going until the command exit status is non-zero, or the value given - #[structopt(short = "r", long = "until-error", parse(from_str = "get_exit_code"))] + #[structopt( + short = "r", + long = "until-error", + parse(from_str = "get_exit_code") + )] until_error: Option, /// Keep going until the command exit status is zero @@ -267,7 +275,8 @@ fn setup__okay() { fn setup__no_command() { // no command supplied to loop-rs let opt = Opt::default(); - let app_error = AppError::new(ExitCode::MinorError, "No command supplied, exiting."); + let app_error = + AppError::new(ExitCode::MinorError, "No command supplied, exiting."); match setup(opt) { Err(err) => assert_eq!(err, app_error), _ => panic!(), From 1e91cc40e4d248eb20054e6cbf07c8671c50ecae Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Sun, 31 Mar 2019 14:45:19 +0200 Subject: [PATCH 55/56] Printer with generic Write trait --- src/app.rs | 140 ++++++++++++++++++++++++++++--------------- src/io.rs | 22 ++++--- src/loop_iterator.rs | 6 +- src/loop_step.rs | 9 +-- src/main.rs | 4 +- src/setup.rs | 14 ++++- 6 files changed, 124 insertions(+), 71 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1f8173c..607add8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,9 @@ -use crate::io::ExitCode; -use crate::io::Printer; +use crate::io::{ExitCode, Printer}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; use crate::state::State; +use std::io::Write; use std::time::{Duration, Instant}; pub struct App { @@ -14,13 +14,12 @@ pub struct App { impl App { #[must_use] - pub fn run( + pub fn run( self, setup_environment: &impl Fn(Option, f64, f64), command: &impl Fn() -> (String, ExitCode), - mut printer: Printer, - ) -> State { - let m = self.loop_model; + printer: &mut Printer, + ) -> ExitCode { let mut state = State::default(); for it in self.iterator { @@ -32,7 +31,7 @@ impl App { || setup_environment(it.item, it.actual_count, it.count); let (break_loop, new_state) = - m.step(state, setup_envs, command, &mut printer); + self.loop_model.step(state, setup_envs, command, printer); state = new_state; if break_loop { @@ -53,48 +52,93 @@ impl App { } } - state + state.exit_code } } -#[test] -#[allow(non_snake_case)] -fn run__num() { - use std::cell::RefCell; - - // test that loop step method is called twice, flag: --num 2 - let expected_loop_count = 2; - - let app = App { - every: None, - iterator: { - let num = Some(expected_loop_count as f64); - let items = - vec!["a", "b", "c"].into_iter().map(str::to_owned).collect(); - let offset = 0_f64; - let count_by = 1_f64; - LoopIterator::new(offset, count_by, num, items) - }, - loop_model: LoopModel::default(), - }; - - let cmd_output = RefCell::new(vec![]); - - let state = app.run( - &|_item, _index, _count| {}, - &|| { - let mut buf = cmd_output.borrow_mut(); - let output = match buf.len() { - 0 => "123".to_owned(), - _ => "abc".to_owned(), - }; - buf.push(output.clone()); - - (output, ExitCode::Okay) - }, - Printer::default(), - ); - assert_eq!(ExitCode::Okay, state.exit_code); - assert_eq!(expected_loop_count, cmd_output.borrow().len()); - assert_eq!(vec!["123", "abc"], cmd_output.into_inner()); +#[cfg(test)] +mod tests { + use super::*; + use std::fmt::{self, Debug, Formatter}; + + #[test] + #[allow(non_snake_case)] + fn run__num() { + use std::cell::RefCell; + + // test that loop step method is called twice, flag: --num 2 + let expected_loop_count = 2; + + let app = App { + every: None, + iterator: { + let num = Some(expected_loop_count as f64); + let items = vec!["a", "b", "c"] + .into_iter() + .map(str::to_owned) + .collect(); + let offset = 0_f64; + let count_by = 1_f64; + LoopIterator::new(offset, count_by, num, items) + }, + loop_model: LoopModel::default(), + }; + + let mut printer = Printer::default(); + let counter = RefCell::new(0); + + let exit_code = app.run( + &|_item, _index, _count| {}, + &|| { + let mut count = counter.borrow_mut(); + let output = match *count { + 0 => "123".to_owned(), + _ => "abc".to_owned(), + }; + *count += 1; + + (output, ExitCode::Okay) + }, + &mut printer, + ); + + let inner_data = printer.into_inner(); + + assert_eq!(ExitCode::Okay, exit_code); + assert_eq!(expected_loop_count, *counter.borrow()); + assert_eq!(vec!["123", "abc"], inner_data); + } + + impl Printer> { + #[allow(dead_code)] + pub fn into_inner(self) -> Vec { + String::from_utf8(self.w) + .unwrap_or_default() + .lines() + .map(str::to_owned) + .collect() + } + } + + impl Debug for Printer + where + T: Write + Debug, + { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } + } + + impl Default for Printer> { + fn default() -> Printer> { + Printer { + only_last: false, + until_contains: None, + until_match: None, + summary: false, + last_output: String::default(), + w: vec![], + } + } + } } diff --git a/src/io.rs b/src/io.rs index 015be0e..910330f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -5,28 +5,26 @@ use std::io::Write; use regex::Regex; use subprocess::{Exec, ExitStatus, Redirection}; -#[derive(Default)] -pub struct Printer { +pub struct Printer { pub only_last: bool, pub until_contains: Option, pub until_match: Option, pub summary: bool, pub last_output: String, + pub w: T, } -impl Printer { - pub fn terminatory_print(&self, create_summary: impl Fn() -> String) { - use std::io::stdout; - - let handle = stdout(); - let mut stdout = handle.lock(); - +impl Printer +where + W: Write, +{ + pub fn terminatory_print(&mut self, create_summary: impl Fn() -> String) { if self.only_last { - writeln!(stdout, "{}", self.last_output).unwrap(); + writeln!(self.w, "{}", self.last_output).unwrap(); } if self.summary { - stdout.write_all(create_summary().as_bytes()).unwrap(); + self.w.write_all(create_summary().as_bytes()).unwrap(); } } @@ -38,7 +36,7 @@ impl Printer { // If we only want output from the last execution, // defer printing until later if !self.only_last { - println!("{}", line); // THIS IS THE MAIN PRINT FUNCTION + writeln!(self.w, "{}", line).unwrap(); // THIS IS THE MAIN PRINT FUNCTION } // --until-contains diff --git a/src/loop_iterator.rs b/src/loop_iterator.rs index 2f2f6c6..6576376 100644 --- a/src/loop_iterator.rs +++ b/src/loop_iterator.rs @@ -55,8 +55,10 @@ impl Iterator for LoopIterator { self.index += 1.0; if self.index <= self.end { - let item = - self.items.get(current_index as usize).map(ToString::to_string); + let item = self + .items + .get(current_index as usize) + .map(ToString::to_string); let res = Item { item, diff --git a/src/loop_step.rs b/src/loop_step.rs index 69d59ba..600203a 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -1,6 +1,7 @@ use crate::io::{ExitCode, Printer}; use crate::state::State; +use std::io::Write; use std::time::{Duration, Instant, SystemTime}; pub struct LoopModel { @@ -18,12 +19,12 @@ pub struct LoopModel { impl LoopModel { #[must_use] - pub fn step( + pub fn step( &self, mut state: State, setup_environment: impl FnOnce(), shell_command: impl Fn() -> (String, ExitCode), - printer: &mut Printer, + printer: &mut Printer, ) -> (bool, State) { // Set counters before execution setup_environment(); @@ -85,13 +86,13 @@ impl LoopModel { } } -fn run_command( +fn run_command( mut state: State, until_error: Option, until_success: bool, until_fail: bool, shell_command: impl Fn() -> (String, ExitCode), - printer: &mut Printer, + printer: &mut Printer, ) -> (State, ExitCode, String) { let (cmd_output, exit_code) = shell_command(); diff --git a/src/main.rs b/src/main.rs index ff4bca0..e20b6b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,12 +11,12 @@ fn main() { use structopt::StructOpt; let exit_code = setup(Opt::from_args()) - .map(|(app, setup_env, command, printer)| { + .map(|(app, setup_env, command, mut printer)| { let setup_env = &|item: Option, actual_count: f64, count: f64| { setup_env.run(item, actual_count, count) }; - app.run(setup_env, &|| command.run(), printer).exit_code + app.run(setup_env, &|| command.run(), &mut printer) }) .unwrap_or_else(|err| { if !err.message.is_empty() { diff --git a/src/setup.rs b/src/setup.rs index 83e9535..4f64297 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -3,6 +3,7 @@ use crate::io::{ExitCode, Printer, SetupEnv, ShellCommand}; use crate::loop_iterator::LoopIterator; use crate::loop_step::LoopModel; +use std::io; use std::time::{Duration, Instant, SystemTime}; use humantime::{parse_duration, parse_rfc3339_weak}; @@ -11,7 +12,7 @@ use structopt::StructOpt; pub fn setup( mut opt: Opt, -) -> Result<(App, SetupEnv, ShellCommand, Printer), AppError> { +) -> Result<(App, SetupEnv, ShellCommand, Printer), AppError> { // Time let program_start = Instant::now(); let cmd_with_args = opt.input.join(" "); @@ -29,6 +30,7 @@ pub fn setup( until_match: opt.until_match.clone(), summary: opt.summary, last_output: String::new(), + w: io::stdout(), }; let setup_env = SetupEnv { @@ -61,7 +63,10 @@ impl AppError { where M: Into, { - AppError { exit_code, message: msg.into() } + AppError { + exit_code, + message: msg.into(), + } } } @@ -76,7 +81,10 @@ fn precision_of(s: &str) -> usize { } fn get_exit_code(input: &str) -> ExitCode { - input.parse::().map(ExitCode::from).unwrap_or_else(|_| ExitCode::Error) + input + .parse::() + .map(ExitCode::from) + .unwrap_or_else(|_| ExitCode::Error) } fn get_values(input: &str) -> Vec { From b2b56c36f25b3a2191dcc70ff28924aa0e87db03 Mon Sep 17 00:00:00 2001 From: Thibaut Brandscheid Date: Mon, 1 Apr 2019 19:27:59 +0200 Subject: [PATCH 56/56] use crate smart-default --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 33 +++++++++++++++++---------------- src/io.rs | 11 ++++------- src/loop_step.rs | 24 +++++------------------- src/setup.rs | 30 +++--------------------------- 5 files changed, 41 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6415535..e28d4ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,7 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smart-default 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -144,6 +145,16 @@ dependencies = [ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "smart-default" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.7.0" @@ -283,6 +294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "559008764a17de49a3146b234641644ed37d118d1ef641a0bb573d146edc6ce0" "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" +"checksum smart-default 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fae090012788f95156fc33f43631242480f311cca6bf7f600943eba14ae032ed" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3d0760c312538987d363c36c42339b55f5ee176ea8808bbe4543d484a291c8d1" "checksum structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "528aeb7351d042e6ffbc2a6fb76a86f9b622fdf7c25932798e7a82cb03bc94c6" diff --git a/Cargo.toml b/Cargo.toml index 45ff78f..f21f200 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,25 @@ [package] -name = "loop-rs" -version = "0.6.1" -authors = ["Rich Jones "] -description = "UNIX's missing loop command" -edition = "2018" +name = "loop-rs" +version = "0.6.1" +authors = ["Rich Jones "] +description = "UNIX's missing loop command" +edition = "2018" documentation = "https://github.com/Miserlou/Loop" -homepage = "https://github.com/Miserlou/Loop" -repository = "https://github.com/Miserlou/Loop" +homepage = "https://github.com/Miserlou/Loop" +repository = "https://github.com/Miserlou/Loop" -readme = "README.md" -license = "MIT" +readme = "README.md" +license = "MIT" [dependencies] -structopt = "0.2.15" -humantime = "1.2.0" -atty = "0.2.11" -regex = "1.1.5" -subprocess = "0.1.18" +structopt = "0.2.15" +humantime = "1.2.0" +atty = "0.2.11" +regex = "1.1.5" +subprocess = "0.1.18" +smart-default = "0.5.1" [[bin]] -name = "loop" -path = "src/main.rs" +name = "loop" +path = "src/main.rs" diff --git a/src/io.rs b/src/io.rs index 910330f..c4f2cef 100644 --- a/src/io.rs +++ b/src/io.rs @@ -3,6 +3,7 @@ use crate::state::State; use std::io::Write; use regex::Regex; +use smart_default::SmartDefault; use subprocess::{Exec, ExitStatus, Redirection}; pub struct Printer { @@ -97,9 +98,11 @@ impl SetupEnv { } } -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy, SmartDefault)] pub enum ExitCode { + #[default] Okay, + Error, /// e.g. when no argument is passed to loop-rs (2) MinorError, @@ -116,12 +119,6 @@ impl ExitCode { } } -impl Default for ExitCode { - fn default() -> ExitCode { - ExitCode::Okay - } -} - impl From for ExitCode { fn from(n: u32) -> ExitCode { match n { diff --git a/src/loop_step.rs b/src/loop_step.rs index 600203a..f2e70cb 100644 --- a/src/loop_step.rs +++ b/src/loop_step.rs @@ -4,6 +4,9 @@ use crate::state::State; use std::io::Write; use std::time::{Duration, Instant, SystemTime}; +use smart_default::SmartDefault; + +#[derive(SmartDefault)] pub struct LoopModel { pub for_duration: Option, pub error_duration: bool, @@ -14,6 +17,8 @@ pub struct LoopModel { pub summary: bool, pub until_changes: bool, pub until_same: bool, + + #[default(Instant::now())] pub program_start: Instant, } @@ -123,25 +128,6 @@ fn check_until_error( } } -impl Default for LoopModel { - fn default() -> LoopModel { - use std::time::Instant; - - LoopModel { - for_duration: None, - error_duration: false, - until_time: None, - until_error: None, - until_success: false, - until_fail: false, - summary: false, - until_changes: false, - until_same: false, - program_start: Instant::now(), - } - } -} - #[test] fn test_check_until_error() { // check generic error diff --git a/src/setup.rs b/src/setup.rs index 4f64297..a794695 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -8,6 +8,7 @@ use std::time::{Duration, Instant, SystemTime}; use humantime::{parse_duration, parse_rfc3339_weak}; use regex::Regex; +use smart_default::SmartDefault; use structopt::StructOpt; pub fn setup( @@ -97,7 +98,7 @@ fn get_values(input: &str) -> Vec { } } -#[derive(StructOpt, Debug)] +#[derive(StructOpt, Debug, SmartDefault)] #[structopt( name = "loop", author = "Rich Jones ", @@ -110,6 +111,7 @@ pub struct Opt { /// Amount to increment the counter by #[structopt(short = "b", long = "count-by", default_value = "1")] + #[default = 1.0] count_by: f64, /// Amount to offset the initial counter by @@ -201,32 +203,6 @@ pub struct Opt { input: Vec, } -impl Default for Opt { - fn default() -> Opt { - Opt { - num: None, - count_by: 1_f64, - offset: 0_f64, - every: None, - ffor: None, - for_duration: None, - until_contains: None, - until_changes: false, - until_same: false, - until_match: None, - until_time: None, - until_error: None, - until_success: false, - until_fail: false, - only_last: false, - stdin: false, - error_duration: false, - summary: false, - input: vec![], - } - } -} - impl From<&mut Opt> for LoopIterator { fn from(opt: &mut Opt) -> LoopIterator { use std::io::{self, BufRead};