mirror of
https://github.com/zaphar/runwhen.git
synced 2025-07-24 20:59:49 -04:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
27cf79ffe4 | |||
fb954c6f0d | |||
e980306761 | |||
2f0ea778b6 | |||
a7caf8a1c8 | |||
38b0b6aa59 | |||
4d33126c69 | |||
c5f339cd27 | |||
18c964138a | |||
5cfed8963f | |||
a57d6f9551 | |||
b58c94e9e6 | |||
9829d0f38f | |||
78d6d5bd4b | |||
cee205ec0f |
350
Cargo.lock
generated
350
Cargo.lock
generated
@ -2,15 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -23,16 +14,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.4.0"
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -40,12 +25,6 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
@ -60,50 +39,88 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
version = "3.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.1.15"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f"
|
||||
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.2.17"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05"
|
||||
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0",
|
||||
"bitflags",
|
||||
"fsevent-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "0.1.6"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874"
|
||||
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fuchsia-zircon-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -115,18 +132,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e9298fffb2a54569e1fcb818e9c2ff77caa2fad68d64b6e409b9f777bdb1960"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.2.3"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8458c07bdbdaf309c80e2c3304d14c3db64e7465d4f07cf589ccb83fd0ff31a"
|
||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -141,21 +185,18 @@ dependencies = [
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
dependencies = [
|
||||
"log 0.4.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.16"
|
||||
@ -167,26 +208,40 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.5.1"
|
||||
version = "0.6.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e"
|
||||
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg-if 0.1.10",
|
||||
"fuchsia-zircon",
|
||||
"fuchsia-zircon-sys",
|
||||
"iovec",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"log 0.3.9",
|
||||
"log",
|
||||
"miow",
|
||||
"net2",
|
||||
"nix",
|
||||
"slab",
|
||||
"time",
|
||||
"winapi 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.1.5"
|
||||
name = "mio-extras"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1"
|
||||
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
||||
dependencies = [
|
||||
"lazycell",
|
||||
"log",
|
||||
"mio",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"net2",
|
||||
@ -205,116 +260,105 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "3.0.1"
|
||||
version = "4.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13fdd4a6894329b193f38f03a88823ce721275fdfdb29820c44a30515033524e"
|
||||
checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0",
|
||||
"bitflags",
|
||||
"filetime",
|
||||
"fsevent",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"mio",
|
||||
"time",
|
||||
"mio-extras",
|
||||
"walkdir",
|
||||
"winapi 0.2.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
name = "once_cell"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "runwhen"
|
||||
version = "0.0.5"
|
||||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"glob",
|
||||
"humantime",
|
||||
"notify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.1.3"
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "0.1.8"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"winapi 0.2.8",
|
||||
"same-file",
|
||||
"winapi 0.3.9",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
@ -343,12 +387,64 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "runwhen"
|
||||
version = "0.0.5"
|
||||
version = "0.0.8"
|
||||
authors = ["Jeremy Wall <jeremy@marzhillstudios.com>"]
|
||||
description = "Runs a command on user specified triggers."
|
||||
repository = "https://github.com/zaphar/runwhen"
|
||||
@ -9,6 +9,10 @@ keywords = ["file", "watcher", "command-line", "trigger"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
clap = "~2.34"
|
||||
humantime = "~1.0.0"
|
||||
notify = "~3.0.0"
|
||||
humantime = "2.1.0"
|
||||
notify = "4.0.17"
|
||||
glob = "0.3.1"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "3.2.17"
|
||||
features = [ "cargo" ]
|
||||
|
55
flake.lock
generated
55
flake.lock
generated
@ -31,21 +31,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1637014545,
|
||||
"narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
@ -78,49 +63,11 @@
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1650222748,
|
||||
"narHash": "sha256-AHh/goEfG5hlhIMVgGQwACbuv5Wit2ND9vrcB4QthJs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ba88a5afa6fff7710c17b5423ff9d721386c4164",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1650162887,
|
||||
"narHash": "sha256-e23LlN7NQGxrsSWNNAjyvrWlZ3kwFSav9kXbayibKWc=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "26b570500cdd7a359526524e9abad341891122a6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
"naersk": "naersk"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
19
flake.nix
19
flake.nix
@ -2,33 +2,26 @@
|
||||
description = "runwhen";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
flake-compat = {
|
||||
url = github:edolstra/flake-compat;
|
||||
url = "github:edolstra/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {self, nixpkgs, flake-utils, rust-overlay, naersk, flake-compat}:
|
||||
outputs = {self, flake-utils, naersk, flake-compat}:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
overlays = [ rust-overlay.overlay ];
|
||||
pkgs = import nixpkgs { inherit system overlays; };
|
||||
naersk-lib = naersk.lib."${system}";
|
||||
in
|
||||
{
|
||||
defaultPackage = with pkgs;
|
||||
naersk-lib.buildPackage rec {
|
||||
inherit flake-compat;
|
||||
defaultPackage = naersk-lib.buildPackage rec {
|
||||
pname = "runwhen";
|
||||
version = "0.0.4";
|
||||
version = "0.0.8";
|
||||
src = ./.;
|
||||
cargoBuildOptions = opts: opts ++ ["-p" "${pname}" ];
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
1
result
Symbolic link
1
result
Symbolic link
@ -0,0 +1 @@
|
||||
/nix/store/wllb7d3wx2wh2p4h8vj7y10p31f37jd8-runwhen-0.0.4
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
use notify;
|
||||
|
||||
@ -37,3 +38,9 @@ impl From<notify::Error> for CommandError {
|
||||
CommandError::new(format!("{}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for CommandError {
|
||||
fn from(e: io::Error) -> CommandError {
|
||||
CommandError::new(format!("IO: {}", e))
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,19 @@ pub enum WatchEventType {
|
||||
Ignore,
|
||||
}
|
||||
|
||||
pub fn get_file(evt: &DebouncedEvent) -> Option<&std::path::PathBuf> {
|
||||
match evt {
|
||||
DebouncedEvent::NoticeWrite(b)
|
||||
| DebouncedEvent::NoticeRemove(b)
|
||||
| DebouncedEvent::Create(b)
|
||||
| DebouncedEvent::Write(b)
|
||||
| DebouncedEvent::Chmod(b)
|
||||
| DebouncedEvent::Remove(b)
|
||||
| DebouncedEvent::Rename(b, _) => Some(b),
|
||||
DebouncedEvent::Error(_, _) | DebouncedEvent::Rescan => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DebouncedEvent> for WatchEventType {
|
||||
fn from(e: DebouncedEvent) -> WatchEventType {
|
||||
match e {
|
||||
|
181
src/exec.rs
181
src/exec.rs
@ -11,11 +11,10 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use error::CommandError;
|
||||
use traits::Process;
|
||||
|
||||
@ -30,79 +29,151 @@ fn env_var_to_tuple(var: &str) -> (String, String) {
|
||||
("".to_string(), "".to_string())
|
||||
}
|
||||
|
||||
pub fn run_cmd(cmd: &str, env: &Option<Vec<&str>>) -> Result<i32, CommandError> {
|
||||
let args = cmd
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
if args.len() < 1 {
|
||||
return Err(CommandError::new("Empty command string passed in"));
|
||||
}
|
||||
let mut exec = Command::new(args[0]);
|
||||
if args.len() > 1 {
|
||||
exec.args(&args[1..]);
|
||||
}
|
||||
exec.stdout(Stdio::inherit());
|
||||
exec.stderr(Stdio::inherit());
|
||||
if let &Some(ref env_vars) = env {
|
||||
for var in env_vars {
|
||||
let tpl = env_var_to_tuple(var);
|
||||
exec.env(tpl.0, tpl.1);
|
||||
pub struct CancelableProcess {
|
||||
cmd: String,
|
||||
env: Option<Vec<String>>,
|
||||
exec: Option<Command>,
|
||||
handle: Option<Child>,
|
||||
}
|
||||
|
||||
impl CancelableProcess {
|
||||
pub fn new(cmd: &str, env: Option<Vec<String>>) -> Self {
|
||||
Self {
|
||||
cmd: cmd.to_string(),
|
||||
env,
|
||||
exec: None,
|
||||
handle: None,
|
||||
}
|
||||
}
|
||||
return match exec.output() {
|
||||
Ok(out) => match out.status.code() {
|
||||
Some(val) => Ok(val),
|
||||
None => Ok(0),
|
||||
},
|
||||
// TODO(jeremy): We should not swallow this error.
|
||||
Err(_) => Err(CommandError::new("Error running command")),
|
||||
};
|
||||
}
|
||||
|
||||
fn is_cmd_success(cmd: &str, env: Option<Vec<&str>>) -> bool {
|
||||
match run_cmd(cmd, &env) {
|
||||
Ok(code) => code == 0,
|
||||
_ => false,
|
||||
fn create_command(cmd: &str, env: &Option<Vec<String>>) -> Result<Command, CommandError> {
|
||||
let args = cmd
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
if args.len() < 1 {
|
||||
return Err(CommandError::new("Empty command string passed in"));
|
||||
}
|
||||
let mut exec = Command::new(args[0]);
|
||||
if args.len() > 1 {
|
||||
exec.args(&args[1..]);
|
||||
}
|
||||
exec.stdout(Stdio::inherit());
|
||||
exec.stderr(Stdio::inherit());
|
||||
if let &Some(ref env_vars) = env {
|
||||
for var in env_vars {
|
||||
let tpl = env_var_to_tuple(var);
|
||||
exec.env(tpl.0, tpl.1);
|
||||
}
|
||||
}
|
||||
return Ok(exec);
|
||||
}
|
||||
|
||||
pub fn block(&mut self) -> Result<i32, CommandError> {
|
||||
if let Some(ref mut handle) = self.handle {
|
||||
let code = handle.wait()?.code().unwrap_or(0);
|
||||
self.exec = None;
|
||||
self.handle = None;
|
||||
Ok(code)
|
||||
} else {
|
||||
let mut exec = Self::create_command(&self.cmd, &self.env)?;
|
||||
return match exec.output() {
|
||||
Ok(out) => match out.status.code() {
|
||||
Some(val) => Ok(val),
|
||||
None => Ok(0),
|
||||
},
|
||||
// TODO(jeremy): We should not swallow this error.
|
||||
Err(_) => Err(CommandError::new("Error running command")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_success(&mut self) -> bool {
|
||||
match self.block() {
|
||||
Ok(code) => code == 0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(jwall): We want to actually use this some time when we figure out if it can be made to not block or not.
|
||||
#[allow(dead_code)]
|
||||
pub fn check(&mut self) -> Result<Option<i32>, CommandError> {
|
||||
Ok(match self.handle {
|
||||
// TODO(jwall): This appears to block the thread despite the documenation. Figure out if this is fixable or not.
|
||||
Some(ref mut h) => match h.try_wait()? {
|
||||
Some(status) => Some(status.code().unwrap_or(0)),
|
||||
None => Some(h.wait()?.code().unwrap_or(0)),
|
||||
},
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> Result<(), CommandError> {
|
||||
let mut exec = Self::create_command(&self.cmd, &self.env)?;
|
||||
let handle = exec.spawn()?;
|
||||
self.exec = Some(exec);
|
||||
self.handle = Some(handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) -> Result<(), CommandError> {
|
||||
if let Some(ref mut h) = self.handle {
|
||||
let _ = h.kill();
|
||||
}
|
||||
|
||||
self.exec = None;
|
||||
self.handle = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), CommandError> {
|
||||
self.cancel()?;
|
||||
self.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExecProcess<'a> {
|
||||
test_cmd: &'a str,
|
||||
// TODO(jwall): Make these CancelableProcess instead.
|
||||
pub struct ExecProcess {
|
||||
test_cmd: CancelableProcess,
|
||||
negate: bool,
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
cmd: CancelableProcess,
|
||||
poll: Duration,
|
||||
}
|
||||
|
||||
impl<'a> ExecProcess<'a> {
|
||||
impl ExecProcess {
|
||||
pub fn new(
|
||||
test_cmd: &'a str,
|
||||
cmd: &'a str,
|
||||
test_cmd: &str,
|
||||
cmd: &str,
|
||||
negate: bool,
|
||||
env: Option<Vec<&'a str>>,
|
||||
env: Option<Vec<String>>,
|
||||
poll: Duration,
|
||||
) -> ExecProcess<'a> {
|
||||
) -> ExecProcess {
|
||||
let test_cmd = CancelableProcess::new(test_cmd, None);
|
||||
let cmd = CancelableProcess::new(cmd, env);
|
||||
ExecProcess {
|
||||
test_cmd: test_cmd,
|
||||
negate: negate,
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
poll: poll,
|
||||
test_cmd,
|
||||
negate,
|
||||
cmd,
|
||||
poll,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_loop_step(&mut self) {
|
||||
let test_result = self.test_cmd.is_success();
|
||||
if (test_result && !self.negate) || (!test_result && self.negate) {
|
||||
if let Err(err) = self.cmd.block() {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Process for ExecProcess<'a> {
|
||||
fn run(&self) -> Result<(), CommandError> {
|
||||
impl Process for ExecProcess {
|
||||
fn run(&mut self) -> Result<(), CommandError> {
|
||||
loop {
|
||||
// TODO(jwall): Should we set the environment the same as the other command?
|
||||
let test_result = is_cmd_success(self.test_cmd, None);
|
||||
if (test_result && !self.negate) || (!test_result && self.negate) {
|
||||
if let Err(err) = run_cmd(self.cmd, &self.env) {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
}
|
||||
self.run_loop_step();
|
||||
thread::sleep(self.poll);
|
||||
}
|
||||
}
|
||||
|
170
src/file.rs
170
src/file.rs
@ -12,49 +12,52 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use glob;
|
||||
use notify::{watcher, RecursiveMode, Watcher};
|
||||
|
||||
use error::CommandError;
|
||||
use events::WatchEventType;
|
||||
use exec::run_cmd;
|
||||
use exec::CancelableProcess;
|
||||
use traits::Process;
|
||||
|
||||
pub struct FileProcess<'a> {
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
env: Option<Vec<String>>,
|
||||
files: Vec<&'a str>,
|
||||
exclude: Option<Vec<&'a str>>,
|
||||
method: WatchEventType,
|
||||
poll: Duration,
|
||||
poll: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<'a> FileProcess<'a> {
|
||||
pub fn new(
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
env: Option<Vec<String>>,
|
||||
file: Vec<&'a str>,
|
||||
exclude: Option<Vec<&'a str>>,
|
||||
method: WatchEventType,
|
||||
poll: Duration,
|
||||
poll: Option<Duration>,
|
||||
) -> FileProcess<'a> {
|
||||
FileProcess {
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
cmd,
|
||||
env,
|
||||
method,
|
||||
poll,
|
||||
exclude,
|
||||
files: file,
|
||||
method: method,
|
||||
poll: poll,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_runner_thread(
|
||||
lock: Arc<Mutex<bool>>,
|
||||
fn watch_for_change_events(
|
||||
ch: Receiver<()>,
|
||||
cmd: String,
|
||||
env: Option<Vec<&str>>,
|
||||
poll: Duration,
|
||||
env: Option<Vec<String>>,
|
||||
poll: Option<Duration>,
|
||||
) {
|
||||
let copied_env = env.and_then(|v| {
|
||||
Some(
|
||||
@ -64,52 +67,52 @@ fn spawn_runner_thread(
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
});
|
||||
thread::spawn(move || {
|
||||
let copied_env_refs: Option<Vec<&str>> = match copied_env {
|
||||
Some(ref vec) => {
|
||||
let mut refs: Vec<&str> = Vec::new();
|
||||
for s in vec.iter() {
|
||||
refs.push(s);
|
||||
}
|
||||
Some(refs)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let mut exec = CancelableProcess::new(&cmd, copied_env);
|
||||
println!("Spawning command");
|
||||
exec.spawn().expect("Failed to start command");
|
||||
println!("Starting watch loop");
|
||||
run_loop_step(&mut exec);
|
||||
println!("Waiting for first change event");
|
||||
if let Some(poll) = poll {
|
||||
let mut poll_time = Instant::now();
|
||||
loop {
|
||||
// Wait our requisit number of seconds
|
||||
thread::sleep(poll);
|
||||
// Default to not running the command.
|
||||
match lock.lock() {
|
||||
Ok(mut signal) => {
|
||||
if *signal {
|
||||
// set signal to false so we won't trigger on the
|
||||
// next loop iteration unless we recieved more events.
|
||||
*signal = false;
|
||||
// Run our command!
|
||||
println!("exec: {}", cmd);
|
||||
if let Err(err) = run_cmd(&cmd, &copied_env_refs) {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Unexpected error; {}", err);
|
||||
return;
|
||||
}
|
||||
let _ = ch.recv().expect("Channel was closed!!!");
|
||||
let elapsed = Instant::now().duration_since(poll_time);
|
||||
poll_time = Instant::now();
|
||||
if elapsed >= poll {
|
||||
run_loop_step(&mut exec);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loop {
|
||||
let _ = ch.recv().expect("Channel was closed!!!");
|
||||
run_loop_step(&mut exec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_loop_step(exec: &mut CancelableProcess) {
|
||||
// We always want to check on our process each iteration of the loop.
|
||||
// set signal to false so we won't trigger on the
|
||||
// next loop iteration unless we recieved more events.
|
||||
// On a true signal we want to start or restart our process.
|
||||
println!("Restarting process");
|
||||
if let Err(err) = exec.reset() {
|
||||
println!("Failed to start command");
|
||||
println!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_fs_events(
|
||||
lock: Arc<Mutex<bool>>,
|
||||
ch: Sender<()>,
|
||||
method: WatchEventType,
|
||||
files: &Vec<&str>,
|
||||
excluded: &Option<Vec<&str>>,
|
||||
) -> Result<(), CommandError> {
|
||||
// Notify requires a channel for communication.
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = watcher(tx, Duration::from_secs(1))?;
|
||||
// TODO(jwall): Better error handling.
|
||||
for file in files {
|
||||
// NOTE(jwall): this is necessary because notify::fsEventWatcher panics
|
||||
// if the path doesn't exist. :-(
|
||||
@ -121,48 +124,61 @@ fn wait_for_fs_events(
|
||||
watcher.watch(*file, RecursiveMode::Recursive)?;
|
||||
println!("Watching {:?}", *file);
|
||||
}
|
||||
let mut patterns = Vec::new();
|
||||
if let Some(exclude) = excluded {
|
||||
for ef in exclude.iter() {
|
||||
patterns.push(glob::Pattern::new(*ef).expect("Invalid path pattern"));
|
||||
}
|
||||
}
|
||||
loop {
|
||||
let evt: WatchEventType = match rx.recv() {
|
||||
Ok(event) => WatchEventType::from(event),
|
||||
Err(_) => WatchEventType::Error,
|
||||
Ok(event) => {
|
||||
// TODO(jwall): Filter this based on the exclude pattern
|
||||
if let Some(f) = crate::events::get_file(&event) {
|
||||
for pat in patterns.iter() {
|
||||
if pat.matches_path(&f) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
WatchEventType::from(event)
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Watch Error: {}", e);
|
||||
WatchEventType::Error
|
||||
}
|
||||
};
|
||||
match evt {
|
||||
WatchEventType::Ignore => {
|
||||
// We ignore this one.
|
||||
}
|
||||
WatchEventType::Error => {
|
||||
// We log this one.
|
||||
WatchEventType::Ignore | WatchEventType::Error => {
|
||||
// We ignore these.
|
||||
//println!("Event: Ignore");
|
||||
}
|
||||
WatchEventType::Touched => {
|
||||
if method == WatchEventType::Touched {
|
||||
let mut signal = lock.lock().unwrap();
|
||||
*signal = true;
|
||||
} else {
|
||||
println!("Ignoring touched event");
|
||||
ch.send(()).unwrap();
|
||||
}
|
||||
}
|
||||
WatchEventType::Changed => match lock.lock() {
|
||||
Ok(mut signal) => *signal = true,
|
||||
Err(err) => {
|
||||
println!("Unexpected error; {}", err);
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
WatchEventType::Changed => {
|
||||
ch.send(()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Process for FileProcess<'a> {
|
||||
fn run(&self) -> Result<(), CommandError> {
|
||||
fn run(&mut self) -> Result<(), CommandError> {
|
||||
// TODO(jeremy): Is this sufficent or do we want to ignore
|
||||
// any events that come in while the command is running?
|
||||
let lock = Arc::new(Mutex::new(false));
|
||||
spawn_runner_thread(
|
||||
lock.clone(),
|
||||
self.cmd.to_string(),
|
||||
self.env.clone(),
|
||||
self.poll,
|
||||
);
|
||||
wait_for_fs_events(lock, self.method.clone(), &self.files)
|
||||
let (tx, rx) = channel();
|
||||
thread::spawn({
|
||||
let cmd = self.cmd.to_string();
|
||||
let env = self.env.clone();
|
||||
let poll = self.poll.clone();
|
||||
move || {
|
||||
watch_for_change_events(rx, cmd, env, poll);
|
||||
}
|
||||
});
|
||||
wait_for_fs_events(tx, self.method.clone(), &self.files, &self.exclude)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
165
src/main.rs
165
src/main.rs
@ -14,11 +14,11 @@
|
||||
// runwhen - A utility that runs commands on user defined triggers.
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate glob;
|
||||
extern crate humantime;
|
||||
extern crate notify;
|
||||
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
use std::{process, str::FromStr};
|
||||
|
||||
mod error;
|
||||
mod events;
|
||||
@ -33,42 +33,39 @@ use file::FileProcess;
|
||||
use timer::TimerProcess;
|
||||
use traits::Process;
|
||||
|
||||
fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
||||
clap_app!(
|
||||
runwhen =>
|
||||
(version: crate_version!())
|
||||
(author: crate_authors!())
|
||||
(about: "Runs a command on user defined triggers.")
|
||||
(@arg cmd: -c --cmd +required +takes_value "Command to run on supplied triggers")
|
||||
(@arg env: -e --env +takes_value ... "Env variables to set for the command")
|
||||
(@subcommand watch =>
|
||||
(about: "Trigger that fires when a file or directory changes.")
|
||||
// TODO(jeremy): We need to support filters
|
||||
(@arg file: -f --file +takes_value ...
|
||||
"File/Directory to watch. (default current working directory)")
|
||||
(@arg filetouch: --touch
|
||||
"Watches for attribute modifications as well as content changes.")
|
||||
(@arg wait: --poll +takes_value
|
||||
"How frequently to poll for events (default 5s)")
|
||||
)
|
||||
(@subcommand timer =>
|
||||
(about: "Trigger that fires on a timer.")
|
||||
(@arg duration: -t --duration +required +takes_value
|
||||
"Defines timer frequency.")
|
||||
(@arg repeat: -n --repeat +takes_value
|
||||
"Defines an optional max number times to run on repeat.")
|
||||
)
|
||||
(@subcommand success =>
|
||||
(about: "Trigger that fires if a command runs successful.")
|
||||
(@arg ifcmd: --if +required +takes_value
|
||||
"The command to test for successful exit from")
|
||||
(@arg not: --not
|
||||
"Negate the test command so we run on failure instead of success.")
|
||||
(@arg wait: --poll +takes_value
|
||||
"How frequently to test command (default 5s)")
|
||||
)
|
||||
)
|
||||
.get_matches()
|
||||
#[rustfmt::skip]
|
||||
fn do_flags() -> clap::ArgMatches {
|
||||
clap::command!()
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Runs a command on user defined triggers.")
|
||||
.arg(arg!(-c --cmd).takes_value(true).help("The command to run on the trigger"))
|
||||
.arg(arg!(-e --env ...).takes_value(true).help("Set of environment variables to set for the command"))
|
||||
.subcommand(
|
||||
clap::Command::new("watch")
|
||||
.about("Trigger that fires when a file or directory changes.")
|
||||
.arg(
|
||||
arg!(-f --file ...).name("file")
|
||||
.takes_value(true).help("File or directory to watch for changes"),
|
||||
)
|
||||
.arg(
|
||||
arg!(-e --exclude ...).name("exclude")
|
||||
.takes_value(true).help("path names to skip when watching. Specified in unix glob format."),
|
||||
)
|
||||
.arg(arg!(--touch).name("filetouch").help("Use file or directory timestamps to monitor for changes."))
|
||||
.arg(arg!(--poll).name("poll").takes_value(true).value_parser(value_parser!(humantime::Duration)).help("Duration of time between polls")))
|
||||
.subcommand(
|
||||
clap::Command::new("timer")
|
||||
.about("Run command on a timer")
|
||||
.arg(arg!(-t --duration).takes_value(true).value_parser(value_parser!(humantime::Duration)).help("Duration between runs"))
|
||||
.arg(arg!(-n --repeat).value_parser(value_parser!(u32)).help("Number of times to run before finishing")))
|
||||
.subcommand(
|
||||
clap::Command::new("success")
|
||||
.about("Run a command when a test command succeeds")
|
||||
.arg(arg!(--if).value_parser(value_parser!(String)).help("The command to run and check for success on"))
|
||||
.arg(arg!(--not).help("Negate the success of the command"))
|
||||
.arg(arg!(--poll).value_parser(value_parser!(humantime::Duration)).help("Duration of time between poll")))
|
||||
.get_matches()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -79,12 +76,12 @@ fn main() {
|
||||
if let Some(env_values) = app.values_of("env") {
|
||||
let mut env_vec = Vec::new();
|
||||
for v in env_values {
|
||||
env_vec.push(v);
|
||||
env_vec.push(v.to_string());
|
||||
}
|
||||
maybe_env = Some(env_vec);
|
||||
}
|
||||
let mut process: Option<Box<dyn Process>> = None;
|
||||
if let Some(matches) = app.subcommand_matches("watch") {
|
||||
|
||||
let mut proc: Box<dyn Process> = if let Some(matches) = app.subcommand_matches("watch") {
|
||||
let file = match matches.values_of("file") {
|
||||
Some(v) => v.collect(),
|
||||
// The default is our current directory
|
||||
@ -94,66 +91,44 @@ fn main() {
|
||||
if matches.is_present("filetouch") {
|
||||
method = WatchEventType::Touched;
|
||||
}
|
||||
let poll = matches.value_of("poll").unwrap_or("5s");
|
||||
let dur = humantime::parse_duration(poll).expect("Invalid poll value.");
|
||||
process = Some(Box::new(FileProcess::new(
|
||||
cmd, maybe_env, file, method, dur,
|
||||
)));
|
||||
let duration = match matches.get_one::<humantime::Duration>("poll") {
|
||||
Some(d) => Some((*d).into()),
|
||||
None => None,
|
||||
};
|
||||
let exclude = match matches.values_of("exclude") {
|
||||
Some(vr) => Some(vr.collect()),
|
||||
None => None,
|
||||
};
|
||||
println!("Enforcing a poll time of {:?}", duration);
|
||||
Box::new(FileProcess::new(
|
||||
cmd, maybe_env, file, exclude, method, duration,
|
||||
))
|
||||
} else if let Some(matches) = app.subcommand_matches("timer") {
|
||||
// TODO(jwall): This should use cancelable commands.
|
||||
// Unwrap because this flag is required.
|
||||
let dur = humantime::parse_duration(
|
||||
matches
|
||||
.value_of("duration")
|
||||
.expect("duration flag is required"),
|
||||
);
|
||||
match dur {
|
||||
Ok(duration) => {
|
||||
let max_repeat = if let Some(val) = matches.value_of("repeat") {
|
||||
match u32::from_str(val) {
|
||||
Ok(n) => Some(n),
|
||||
Err(e) => {
|
||||
println!("Invalid --repeat value {}", e);
|
||||
println!("{}", matches.usage());
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
process = Some(Box::new(TimerProcess::new(
|
||||
cmd, maybe_env, duration, max_repeat,
|
||||
)));
|
||||
}
|
||||
Err(msg) => {
|
||||
println!("Malformed duration {:?}", msg);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
let duration = matches
|
||||
.get_one::<humantime::Duration>("duration")
|
||||
.expect("duration flag is required")
|
||||
.clone();
|
||||
let max_repeat = matches.get_one::<u32>("repeat").cloned();
|
||||
Box::new(TimerProcess::new(cmd, maybe_env, *duration, max_repeat))
|
||||
} else if let Some(matches) = app.subcommand_matches("success") {
|
||||
// unwrap because this is required.
|
||||
let ifcmd = matches.value_of("ifcmd").expect("ifcmd flag is required");
|
||||
let negate = matches.is_present("not");
|
||||
let dur = humantime::parse_duration(matches.value_of("poll").unwrap_or("5s"));
|
||||
process = match dur {
|
||||
Ok(duration) => Some(Box::new(ExecProcess::new(
|
||||
ifcmd, cmd, negate, maybe_env, duration,
|
||||
))),
|
||||
Err(msg) => {
|
||||
println!("Malformed poll {:?}", msg);
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
match process {
|
||||
Some(process) => match process.run() {
|
||||
Ok(_) => return,
|
||||
Err(err) => {
|
||||
println!("{0}", err);
|
||||
process::exit(1)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
println!("You must specify a subcommand.");
|
||||
let duration = *matches
|
||||
.get_one::<humantime::Duration>("poll")
|
||||
.cloned()
|
||||
.unwrap_or(humantime::Duration::from_str("5s").unwrap());
|
||||
Box::new(ExecProcess::new(ifcmd, cmd, negate, maybe_env, duration))
|
||||
} else {
|
||||
println!("You must specify a subcommand.");
|
||||
process::exit(1)
|
||||
};
|
||||
match proc.run() {
|
||||
Ok(_) => return,
|
||||
Err(err) => {
|
||||
println!("{0}", err);
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
|
29
src/timer.rs
29
src/timer.rs
@ -14,41 +14,40 @@
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use exec::CancelableProcess;
|
||||
use error::CommandError;
|
||||
use exec::run_cmd;
|
||||
use traits::Process;
|
||||
|
||||
pub struct TimerProcess<'a> {
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
pub struct TimerProcess {
|
||||
cmd: CancelableProcess,
|
||||
poll_duration: Duration,
|
||||
max_repeat: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> TimerProcess<'a> {
|
||||
impl TimerProcess {
|
||||
pub fn new(
|
||||
cmd: &'a str,
|
||||
env: Option<Vec<&'a str>>,
|
||||
cmd: &str,
|
||||
env: Option<Vec<String>>,
|
||||
poll_duration: Duration,
|
||||
max_repeat: Option<u32>,
|
||||
) -> TimerProcess<'a> {
|
||||
) -> TimerProcess {
|
||||
let cmd = CancelableProcess::new(cmd, env);
|
||||
TimerProcess {
|
||||
cmd: cmd,
|
||||
env: env,
|
||||
poll_duration: poll_duration,
|
||||
max_repeat: max_repeat,
|
||||
cmd,
|
||||
poll_duration,
|
||||
max_repeat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Process for TimerProcess<'a> {
|
||||
fn run(&self) -> Result<(), CommandError> {
|
||||
impl Process for TimerProcess {
|
||||
fn run(&mut self) -> Result<(), CommandError> {
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
if self.max_repeat.is_some() && counter >= self.max_repeat.unwrap() {
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(err) = run_cmd(self.cmd, &self.env) {
|
||||
if let Err(err) = self.cmd.block() {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
thread::sleep(self.poll_duration);
|
||||
|
@ -14,5 +14,5 @@
|
||||
use error::CommandError;
|
||||
|
||||
pub trait Process {
|
||||
fn run(&self) -> Result<(), CommandError>;
|
||||
fn run(&mut self) -> Result<(), CommandError>;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user