diff --git a/Cargo.lock b/Cargo.lock index 8f80144..e28d4ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,9 +1,9 @@ [[package]] name = "aho-corasick" -version = "0.6.8" +version = "0.7.3" 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,7 +19,7 @@ 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.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)", ] @@ -29,11 +29,6 @@ name = "bitflags" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cfg-if" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "clap" version = "2.32.0" @@ -49,87 +44,56 @@ dependencies = [ ] [[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" -version = "0.2.12" +name = "crossbeam-utils" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "heck" +version = "0.3.1" 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)", + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "humantime" -version = "1.1.1" +version = "1.2.0" 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)", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -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)", -] - [[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.51" 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)", - "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.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)", ] [[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 +106,15 @@ 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" -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)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -175,35 +122,37 @@ 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.52 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.0.5" +version = "1.1.5" 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.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.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.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.6" 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]] -name = "remove_dir_all" +name = "smart-default" 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)", + "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]] @@ -213,64 +162,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.10" +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.10 (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.10" +version = "0.2.15" 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.29 (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.51 (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.29" 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" -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)", - "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" 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.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)", ] @@ -287,12 +223,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 +248,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 +256,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 +265,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 +276,39 @@ 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.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" -"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "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 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.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.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 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.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 remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"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.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.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 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 90abedb..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" +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" -humantime = "1.1.1" -atty = "0.2" -regex = "1.0.0" -subprocess = "0.1.12" -tempfile = "3.0.3" +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/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 new file mode 100644 index 0000000..607add8 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,144 @@ +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 { + pub every: Option, + pub loop_model: LoopModel, + pub iterator: LoopIterator, +} + +impl App { + #[must_use] + pub fn run( + self, + setup_environment: &impl Fn(Option, f64, f64), + command: &impl Fn() -> (String, ExitCode), + printer: &mut Printer, + ) -> ExitCode { + let mut state = State::default(); + + 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) = + self.loop_model.step(state, setup_envs, command, printer); + state = new_state; + + if break_loop { + break; + } + + 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); + } + } + } + } + + state.exit_code + } +} + +#[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 new file mode 100644 index 0000000..c4f2cef --- /dev/null +++ b/src/io.rs @@ -0,0 +1,161 @@ +use crate::state::State; + +use std::io::Write; + +use regex::Regex; +use smart_default::SmartDefault; +use subprocess::{Exec, ExitStatus, Redirection}; + +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 +where + W: Write, +{ + pub fn terminatory_print(&mut self, create_summary: impl Fn() -> String) { + if self.only_last { + writeln!(self.w, "{}", self.last_output).unwrap(); + } + + if self.summary { + self.w.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 + if !self.only_last { + writeln!(self.w, "{}", line).unwrap(); // THIS IS THE MAIN PRINT FUNCTION + } + + // --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(); + } + + 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; + } + } +} + +pub struct ShellCommand { + pub cmd_with_args: String, +} + +impl ShellCommand { + #[must_use] + pub fn run(&self) -> (String, ExitCode) { + Exec::shell(&self.cmd_with_args) + .stdout(Redirection::Pipe) + .stderr(Redirection::Merge) + .capture() + .map(|it| (it.stdout_str(), it.exit_status.into())) + .unwrap() + } +} + +pub struct SetupEnv { + pub count_precision: usize, +} + +impl SetupEnv { + 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, count)); + + // Set current item as environment variable + if let Some(item) = item { + set_var("ITEM", item); + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy, SmartDefault)] +pub enum ExitCode { + #[default] + 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, + /// 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(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 { + 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_iterator.rs b/src/loop_iterator.rs new file mode 100644 index 0000000..6576376 --- /dev/null +++ b/src/loop_iterator.rs @@ -0,0 +1,146 @@ +pub struct LoopIterator { + start: f64, + index: f64, + end: f64, + step_by: f64, + items: Vec, +} + +impl 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 + } else { + items.len() as f64 + } + }); + LoopIterator { + start: offset - count_by, + index: 0.0, + end, + step_by: count_by, + 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 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 = Item; + + fn next(&mut self) -> Option { + let current_index = self.index; + self.start += self.step_by; + self.index += 1.0; + + if self.index <= self.end { + let item = self + .items + .get(current_index as usize) + .map(ToString::to_string); + + let res = Item { + item, + count: self.start, + actual_count: current_index, + is_last: self.is_last_iterm(current_index), + }; + + Some(res) + } else { + None + } + } +} + +#[test] +#[allow(non_snake_case)] +fn 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); +} + +#[test] +#[allow(non_snake_case)] +fn loop_iterator__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); +} + +#[test] +#[allow(non_snake_case)] +fn loop_iterator__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 new file mode 100644 index 0000000..f2e70cb --- /dev/null +++ b/src/loop_step.rs @@ -0,0 +1,151 @@ +use crate::io::{ExitCode, Printer}; +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, + 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, + + #[default(Instant::now())] + pub program_start: Instant, +} + +impl LoopModel { + #[must_use] + pub fn step( + &self, + mut state: State, + setup_environment: impl FnOnce(), + shell_command: impl Fn() -> (String, ExitCode), + printer: &mut Printer, + ) -> (bool, State) { + // Set counters before execution + setup_environment(); + + // 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_code = ExitCode::Timeout; + } + return (true, state); + } + } + + // 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, state); + } + } + + // Main executor + let (new_state, exit_code, cmd_output) = run_command( + state, + self.until_error, + self.until_success, + self.until_fail, + shell_command, + printer, + ); + state = new_state; + + if self.summary { + state.update_summary(exit_code); + } + + // Finish if we matched + if state.has_matched { + return (true, state); + } + + if let Some(ref previous_stdout) = state.previous_stdout { + // --until-changes + if self.until_changes && *previous_stdout != cmd_output { + return (true, state); + } + + // --until-same + if self.until_same && *previous_stdout == cmd_output { + return (true, state); + } + } + state.previous_stdout = Some(cmd_output); + + (false, state) + } +} + +fn run_command( + mut state: State, + until_error: Option, + until_success: bool, + until_fail: bool, + shell_command: impl Fn() -> (String, ExitCode), + printer: &mut Printer, +) -> (State, ExitCode, String) { + let (cmd_output, exit_code) = shell_command(); + + // Print the results + printer.print(&cmd_output, &mut state); + + // --until-error + state.has_matched = check_until_error(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, cmd_output) +} + +/// 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, + } +} + +#[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 8f0fc94..e20b6b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,389 +1,30 @@ -#[macro_use] -extern crate structopt; -extern crate humantime; -extern crate atty; -extern crate regex; -extern crate subprocess; -extern crate tempfile; - -use std::env; -use std::f64; -use std::io::prelude::*; -use std::io::{self, BufRead, SeekFrom}; -use std::process; -use std::thread; -use std::time::{Duration, Instant, SystemTime}; - -use humantime::{parse_duration, parse_rfc3339_weak}; -use regex::Regex; -use subprocess::{Exec, ExitStatus, Redirection}; -use structopt::StructOpt; - -static UNKONWN_EXIT_CODE: u32 = 99; - -// same exit code as use of `timeout` shell command -static TIMEOUT_EXIT_CODE: i32 = 124; +mod app; +mod io; +mod loop_iterator; +mod loop_step; +mod setup; +mod state; 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 = if let Some(items) = opt.ffor { items.clone() } else { vec![] }; - - // 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()) - } - } - - let joined_input = &opt.input.join(" "); - if joined_input == "" { - println!("No command supplied, exiting."); - return; - } - - // 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 { successes: 0, failures: Vec::new() }; - let mut previous_stdout = None; - - let counter = Counter { - start: opt.offset - opt.count_by, - iters: 0.0, - end: num, - step_by: opt.count_by - }; - for (count, actual_count) in counter.enumerate() { - - // 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)); - - // Set iterated item as environment variable - if let Some(item) = items.get(count) { - 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 { - exit_status = TIMEOUT_EXIT_CODE - } - break; - } - } - - // 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() { - break; - } - } - - // Main executor - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.set_len(0).ok(); - let result = Exec::shell(joined_input) - .stdout(Redirection::File(tmpfile.try_clone().unwrap())) - .stderr(Redirection::Merge) - .capture().unwrap(); - - // 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 { - if line.contains(string){ - has_matched = true; - } - } - - // --until-match - if let Some(regex) = &opt.until_match { - if regex.captures(&line).is_some() { - has_matched = true; - } - } - } - - // --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) => { - if result.exit_status == ExitStatus::Exited(*code) { - has_matched = true; - } - } - } - } - - // --until-success - if opt.until_success && result.exit_status.success() { - has_matched = true; - } - - // --until-fail - if opt.until_fail && !(result.exit_status.success()) { - has_matched = true; - } - - if opt.summary { - match result.exit_status { - ExitStatus::Exited(0) => summary.successes += 1, - ExitStatus::Exited(n) => summary.failures.push(n), - _ => summary.failures.push(UNKONWN_EXIT_CODE), - } - } - - // Finish if we matched - if has_matched { - break; - } - - if let Some(ref previous_stdout) = previous_stdout { - // --until-changes - if opt.until_changes { - if *previous_stdout != stdout { - break; - } - } - - // --until-same - if opt.until_same { - if *previous_stdout == stdout { - break; - } - } - } else { - 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); - } - } - - if opt.only_last { - let mut stdout = String::new(); - tmpfile.seek(SeekFrom::Start(0)).ok(); - tmpfile.read_to_string(&mut stdout).ok(); - for line in stdout.lines() { - println!("{}", line); - } - } - - if opt.summary { - summary.print() - } - process::exit(exit_status); -} - -#[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() - } -} - -struct Counter { - start: f64, - iters: f64, - end: f64, - step_by: f64, -} - -#[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 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 - } - } + use setup::{setup, Opt}; + use std::process; + use structopt::StructOpt; + + let exit_code = setup(Opt::from_args()) + .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(), &mut printer) + }) + .unwrap_or_else(|err| { + if !err.message.is_empty() { + eprintln!("{}", err.message); + } + err.exit_code + }) + .into(); + + process::exit(exit_code); } diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..a794695 --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,276 @@ +use crate::app::App; +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}; +use regex::Regex; +use smart_default::SmartDefault; +use structopt::StructOpt; + +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(" "); + + if cmd_with_args.is_empty() { + return Err(AppError::new( + ExitCode::MinorError, + "No command supplied, exiting.", + )); + } + + let printer = Printer { + only_last: opt.only_last, + until_contains: opt.until_contains.clone(), + until_match: opt.until_match.clone(), + summary: opt.summary, + last_output: String::new(), + w: io::stdout(), + }; + + 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 }; + + let app = App { + every: opt.every, + iterator: LoopIterator::from(&mut opt), + loop_model: LoopModel::from_opt(opt, program_start), + }; + + Ok((app, setup_env, shell_command, printer)) +} + +#[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(), + } + } +} + +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 +} + +fn get_exit_code(input: &str) -> ExitCode { + input + .parse::() + .map(ExitCode::from) + .unwrap_or_else(|_| ExitCode::Error) +} + +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, SmartDefault)] +#[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")] + num: Option, + + /// 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 + #[structopt(short = "o", long = "offset", default_value = "0")] + offset: f64, + + /// How often to iterate. ex., 5s, 1h1m1s1ms1us + #[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"))] + 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_exit_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, +} + +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] +#[allow(non_snake_case)] +fn setup__okay() { + let mut opt = Opt::default(); + opt.input = vec!["foobar".to_owned()]; + assert!(setup(opt).is_ok()); +} + +#[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) { + Err(err) => assert_eq!(err, app_error), + _ => 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")); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..c23be71 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,44 @@ +use crate::io::ExitCode; + +#[derive(Default)] +pub struct State { + pub has_matched: bool, + pub previous_stdout: Option, + pub successes: u32, + pub failures: Vec, + pub exit_code: ExitCode, +} + +impl State { + pub fn update_summary(&mut self, exit_code: ExitCode) { + match exit_code { + ExitCode::Okay => self.successes += 1, + err => self.failures.push(err.into()), + } + } +} + +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() { + String::from("0") + } else { + format!( + "{} ({})", + self.failures.len(), + self.failures + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", ") + ) + }; + 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 + } +}