Compare commits

..

No commits in common. 'master' and 'one-off-cmd' have entirely different histories.

@ -1 +0,0 @@
github: [blob42]

1
.gitignore vendored

@ -1 +1,2 @@
/target
bacon.toml

282
Cargo.lock generated

@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.15"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
@ -28,33 +28,33 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.8"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
@ -62,15 +62,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.89"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bitflags"
@ -86,9 +86,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.19"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
dependencies = [
"clap_builder",
"clap_derive",
@ -96,9 +96,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.19"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
dependencies = [
"anstream",
"anstyle",
@ -108,9 +108,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.18"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck",
"proc-macro2",
@ -120,21 +120,21 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.2"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
[[package]]
name = "colorchoice"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crossbeam-deque"
@ -190,9 +190,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_filter"
version = "0.1.2"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [
"log",
"regex",
@ -200,9 +200,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.5"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
"anstream",
"anstyle",
@ -331,9 +331,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.15.0"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
@ -359,9 +359,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
@ -375,15 +375,15 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "libc"
version = "0.2.159"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libredox"
@ -395,16 +395,6 @@ dependencies = [
"libc",
]
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
@ -434,12 +424,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.20.1"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
dependencies = [
"portable-atomic",
]
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "option-ext"
@ -447,29 +434,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@ -482,19 +446,13 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]]
name = "proc-macro-crate"
version = "3.2.0"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
dependencies = [
"toml_edit",
"toml_edit 0.21.1",
]
[[package]]
@ -523,7 +481,6 @@ dependencies = [
"rstest",
"sd-notify",
"serde",
"serial_test",
"sysinfo",
"toml",
"xdg",
@ -531,9 +488,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@ -558,20 +515,11 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
@ -580,9 +528,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.0"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
@ -592,9 +540,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.8"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
@ -603,9 +551,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "relative-path"
@ -645,40 +593,19 @@ dependencies = [
[[package]]
name = "rustc_version"
version = "0.4.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "scc"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50"
dependencies = [
"sdd",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sd-notify"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4646d6f919800cd25c50edb49438a1381e2cd4833c027e75e8897981c50b8b5e"
[[package]]
name = "sdd"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc"
[[package]]
name = "semver"
version = "1.0.23"
@ -687,18 +614,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
@ -707,38 +634,13 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d"
dependencies = [
"futures",
"log",
"once_cell",
"parking_lot",
"scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -748,12 +650,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "strsim"
version = "0.11.1"
@ -762,9 +658,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.79"
version = "2.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
dependencies = [
"proc-macro2",
"quote",
@ -788,18 +684,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.64"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
dependencies = [
"proc-macro2",
"quote",
@ -808,43 +704,54 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.19"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
"toml_edit 0.22.15",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.22"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
"indexmap",
"toml_datetime",
"winnow 0.5.40",
]
[[package]]
name = "toml_edit"
version = "0.22.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
"winnow 0.6.13",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
@ -1040,9 +947,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.20"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
dependencies = [
"memchr",
]

@ -1,19 +1,13 @@
[package]
name = "pswatch"
license = "AGPL-3.0-or-later"
repository = "https://git.blob42.xyz/blob42/pswatch"
readme = "README.md"
keywords = ["process", "monitoring", "scheduler", "timer", "resource-control"]
categories = ["command-line-utilities", "config", "os"]
description = "minimalist process monitoring and task scheduler"
version = "0.1.1"
version = "0.1.0"
edition = "2021"
default-run = "pswatch"
[[example]]
[[bin]]
name = "condition"
path = "examples/proto_condition.rs"
path = "src/bin/proto_condition.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -36,7 +30,3 @@ xdg = "2.5.2"
[dev-dependencies]
rstest = "0.21.0"
mock_instant = "0.5.1"
serial_test = "3.1.1"
[profile.release]
lto = true

@ -2,32 +2,31 @@
pswatch is a minimalist process monitoring and task scheduler that allows you to
watch system processes and run custom commands when specific conditions or
patterns are matched.
patterns are matched. It also implements the `notify` signal with systemd.
**Features**
- Process Matching: match running processes by substring or regex patterns in name, exe path or the entire command line.
- Define conditions and actions.
- match running processes by pattern in: name, exe path or the entire command line.
- Define multiple conditions and actions.
- Execute actions when conditions are met on the matched processes.
- Create multiple profiles for complex conditions and action sets
- Systemd `notify` process type integration.
## Installation
### From Crates.io
`cargo install pswatch`
### From source
To install pswatch, clone the repository from GitHub and build it using
Cargo:
```sh
git clone https://github.com/your-username/pswatch.git
cd pswatch
cargo install --path .
cargo build --release
```
The binary will be located in `target/release/pswatch`.
## Usage
Pswatch requires a `TOML` based configuration file. By default it uses the config file under `$XDG_CONFIG_DIR/pswatch/config.toml` or the one provided as parameter.
Pswatch requires a `TOML` based configuration file. By default it uses the
config file under $XDG_CONFIG_DIR/pswatch/config.toml or the one provided as
parameter.
```sh
./pswatch -c /path/to/config.toml
@ -38,10 +37,10 @@ patterns defined in the configuration file.
## Configuration File
pswatch's behavior is configured using a TOML-formatted configuration file. The
file should contain a list of `profiles`, each containing a `matching` directive
(the process to match), an (optional) `regex` flag (set to `true` if the
pattern is a regular expression), and a list of `commands`.
pswatch's behavior is configured using a TOML-formatted configuration file.
The file should contain a list of `watches`, each containing a `pattern` (the
process name to match), a `regex` flag (set to `true` if the pattern is a
regular expression), and a list of `commands`.
Each command contains a condition (either `seen` or `not_seen` with a duration)
and an array of shell commands (`exec`) to execute when the condition is met. An
@ -51,87 +50,47 @@ detection.
Here's an example configuration file:
```toml
[[profiles]]
matching = { name = "foo" }
[[watches]]
pattern = "foo"
regex = false
# command 1
[[profiles.commands]]
[[watches.commands]]
condition = {seen = "5s"}
exec = ["sh", "-c", "notify-send psw 'foo action'"]
# command 2
[[profiles.commands]]
condition = { not_seen = "60s" }
exec = ["sh", "-c", "notify-send psw 'where is foo ?'"]
run_once = true
```
## Example: Toggle Power Saving
Here is a more realistic example that toggles the CPU turbo mode or power saving when a compilation job is detected:
```toml
[[profiles]]
# matches common compilers for C,C++ and Rust
matching = { name = 'cc1.*|^cc$|gcc$|c\+\+$|c89$|c99$|cpp$|g\+\+$|rustc$', regex = true }
[[profiles.commands]]
condition = {seen = "3s"}
# command to execute when condition is met
exec = ["sh", "-c", "enable_turbo"]
# when exec_end is defined the schedule behaves like a toggle
# command is executed when exiting the condition
exec_end = ["sh", "-c", "disable_turbo"]
exec = ["sh", "-c", "notify-end action!"]
# run_once = false # uncomment to run the command only once per process
detection
```
## Examples with Multiple Profiles
## Examples with Multiple Watches
You can use multiple profiles within a single configuration file to monitor different processes and execute commands for matched conditions.
Here's an example configuration that uses two profiles:
You can use multiple watches within a single configuration file to monitor
different processes and execute commands based on their patterns. Here's an
example configuration that uses two watches:
```toml
[[profiles]]
[[watches]]
pattern = "bar"
regex = false
# matches the process name
matching = { name = "bar" }
[[profiles.commands]]
[[watches.commands]]
condition = {not_seen = "5s"}
exec = ["sh", "-c", "notify-send psw 'bar not seen!'"]
exec = ["sh", "-c", "echo not seen!"]
[[profiles]]
# matches the full executable path
matching = { exe_path = '.*baz$', regex = true}
[[watches]]
pattern = "baz"
regex = true
[[profiles.commands]]
[[watches.commands]]
condition = {seen = "10s"}
exec = ["sh", "-c", "notify-send psw '/baz action !'"]
run_once = true # run the command only once when a match is triggered
[[profiles]]
# matches the command line
matching = { cmdline = '\-buz.*', regex = true}
[[profiles.commands]]
condition = {seen = "10s"}
exec = ["sh", "-c", "notify-send psw 'someproc -buz action !'"]
exec = ["sh", "-c", "say 'baz detected!'"]
run_once = true # run the command only once per process detection
```
In this example, pswatch will watch for three processes: "bar", "baz" and "buz".
- It matches `bar` by process name (simple string).
- Matches `.*baz$` and `\-buz.*` by a regex pattern of the executable path and
command line respectively.
- When "bar" is not seen for 5 seconds, it will execute the `exec` action.
- When "baz" (a regular expression) is detected, it will execute the
corresponding `exec` after a delay of 10 seconds.
- The command for "baz" will be run only once per process detection.
In this example, pswatch will watch for two processes: "bar" and "baz". When
"bar" is not seen for 5 seconds, it will execute `echo not seen!`. When "baz" (a
regular expression) is detected, it will execute `say 'baz detected!'` after a
delay of 10 seconds. The command for "baz" will be run only once per process
detection.
## Example Scenarios
@ -142,46 +101,31 @@ corresponding `exec` after a delay of 10 seconds.
- Define a watch with the desired process name and use `{not_seen = "duration"}` to specify that the command should be executed when the process has been absent for a specified duration (e.g., "5s").
3. **Execute multiple commands based on different conditions**
- Define multiple watch configurations in the same TOML file and specify separate `condition` and `exec` settings for each. pswatch will monitor all configured profiles and execute their respective commands when appropriate.
## Systemd User Unit
```ini
[Unit]
Description=pswatch process watcher
[Service]
Type=notify
ExecStart=%h/.cargo/bin/pswatch
Restart=on-failure
; Use this to enable debug or trace
;Environment=RUST_LOG=debug
[Install]
WantedBy=default.target
```
- Define multiple watch configurations in the same TOML file and specify separate `condition` and `exec` settings for each. pswatch will monitor all configured watches and execute their respective commands when appropriate.
## Troubleshooting
You can enable more verbose output using the `-d` flag or setting the environment variable to `debug` or `trace`.
If you encounter any issues while using pswatch, please refer to the
[TROUBLESHOOTING.md](TROUBLESHOOTING.md) file in this repository for
troubleshooting tips and solutions.
## Contributing
Contributions are welcome ! If you'd like to contribute, please follow these steps:
Contributions are welcome! If you'd like to contribute to pswatch, please
follow these steps:
1. Fork the repository on GitHub.
2. Clone your fork to your local machine: `git clone
https://github.com/your-username/pswatch.git`.
3. Create a new branch for your changes: `git checkout -b my-feature`.
4. Make your changes and commit them with descriptive messages:
`git commit -am 'Add some feature'`.
5. Push your branch: `git push origin my-feature`.
6. Submit a pull request from your GitHub fork to the main repository.
4. Make your changes and commit them with descriptive messages: `git commit
-am 'Add some feature'`.
5. Push your branch to your GitHub fork: `git push origin my-feature`.
6. Submit a pull request from your GitHub fork to the main repository.
## License
pswatch is licensed under the AGPLv3 License.
See [LICENSE](LICENSE) for more details.
pswatch is licensed under the AGPLv3 License. See [LICENSE](LICENSE) for more
details.

@ -1,22 +1,11 @@
[ ] cli commands: manage, add, delete ...
[ ] process detection helpers
- help user target specific process
- help user generate profile
[ ] profile names
[x] match multiple patterns: handled by regex
[x] cmd exec:
[ ] use state machine
[ ] match multiple patterns
[ ] cmd exec:
[x] repeat on cmd failure
[x] disable profile on cmd failure
[x] exec_end: cmd to execute when matching state ends
[ ] on-off commands
[x] should execute cmd once if `run_once` is true
[ ] should reset the state next time process appears [test]
[ ] use state machine ?
[ ] should reset the state next time process appears
- conditions:
- resource conditions
- cpu time
- cpu %
- cpu load
- ram %
- ram size
- resource conditions: ram, cpu, net usage ...

@ -1,114 +0,0 @@
# This is a configuration file for the bacon tool
#
# Bacon repository: https://github.com/Canop/bacon
# Complete help on configuration: https://dystroy.org/bacon/config/
# You can also check bacon's own bacon.toml file
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
default_job = "check"
[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false
# Run clippy on the default target
[jobs.clippy]
command = [
"cargo", "clippy",
"--color", "always",
]
need_stdout = false
# Run clippy on all targets
# To disable some lints, you may change the job this way:
# [jobs.clippy-all]
# command = [
# "cargo", "clippy",
# "--all-targets",
# "--color", "always",
# "--",
# "-A", "clippy::bool_to_int_with_if",
# "-A", "clippy::collapsible_if",
# "-A", "clippy::derive_partial_eq_without_eq",
# ]
# need_stdout = false
[jobs.clippy-all]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = [
"cargo", "test", "--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.test-process]
command = [
"cargo", "test", "--test", "process", "--", "--test-threads=1"
]
need_stdout = true
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
# If your program never stops (eg a server), you may set `background`
# to false to have the cargo run output immediately displayed instead
# of waiting for program's end.
[jobs.run]
command = [
"cargo", "run",
"--color", "always",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true
# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true
[jobs.condition]
command = ["cargo", "run", "--color", "always", "--bin", "condition"]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
p = "job:test-process"

@ -5,7 +5,7 @@ use log::debug;
use serde::Deserialize;
mod profile;
pub use profile::{Profile, CmdSchedule};
pub use profile::{CmdSchedule, Profile, ProfileMatching};
/// Main config for project. It is loaded from TOML or YAML in that order
#[derive(Debug, Deserialize)]
@ -52,8 +52,6 @@ mod tests {
condition = {seen = "10s"}
exec = ["echo", "still there"]
###
[[profiles]]
matching = { name = "foo_not_seen" }
@ -61,20 +59,11 @@ mod tests {
condition = {not_seen = "5s"}
exec = ["echo", "not seen"]
###
[[profiles]]
matching = { exe_path = "b.n.*sh", regex = true }
[[profiles.commands]]
condition = {not_seen = "5s"}
exec = ["echo", "not seen"]
"###};
let c = parse_config(config)?;
assert_eq!(c.profiles.len(), 3, "non matching number of declared profiles");
assert_eq!(c.profiles[0].commands.len(), 2, "non matching number of commands on profile1");
assert_eq!(c.profiles.len(), 2);
assert_eq!(c.profiles[0].commands.len(), 2);
Ok(())
}
}

@ -1,19 +1,38 @@
use crate::{matching::ProcessMatcher, process::ProcCondition};
use crate::{matching::PatternIn, process::ProcCondition};
use serde::Deserialize;
use std::time::Duration;
#[derive(Debug, Deserialize, Clone)]
pub struct ProfileMatching {
/// process identification
#[serde(flatten)]
pub pattern: PatternIn<String>,
/// Interpret `pattern` as regex
#[serde(default)]
pub regex: bool
}
#[derive(Debug, Deserialize, Clone)]
pub struct Profile {
/// pattern of process name to match against
pub matching: ProcessMatcher,
pub matching: ProfileMatching,
// /// Where to match the process pattern (exe, cmdline, name)
// #[serde(default)]
// pub pattern_in: PatternIn,
/// List of commands to run when condition is met
pub commands: Vec<CmdSchedule>,
/// Interpret `pattern` as regex
// #[serde(default)]
// pub regex: bool,
//TODO:
// pub match_by:
/// process watch sampling rate
#[serde(default = "default_watch_interval", with = "humantime_serde")]
pub interval: Duration,

@ -2,7 +2,6 @@
//! patterns are matched. This application is designed for managing and automating tasks based on
//! the presence or absence of certain processes in your system.
//DEBUG:
#![allow(dead_code, unused_variables, unused_imports)]
mod utils;
@ -16,7 +15,6 @@ use std::{
use anyhow::{bail, Context};
use clap::Parser;
use log::trace;
use pswatch::{config, sched::Scheduler};
use sd_notify::{notify, NotifyState};
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
@ -42,30 +40,26 @@ fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let mut logger = env_logger::builder();
if let Ok(envlog) = std::env::var("RUST_LOG") {
println!("RUST_LOG={}", envlog);
} else {
logger.filter_level(log::LevelFilter::Info);
match cli.debug {
0 => {
logger.filter_level(log::LevelFilter::Warn);
}
1 => {
logger.filter_level(log::LevelFilter::Info);
}
2 => {
logger.filter_level(log::LevelFilter::Debug);
}
3 => {
logger.filter_level(log::LevelFilter::Trace);
}
_ => {}
logger.filter_level(log::LevelFilter::Info);
match cli.debug {
0 => {
logger.filter_level(log::LevelFilter::Warn);
}
1 => {
logger.filter_level(log::LevelFilter::Info);
}
2 => {
logger.filter_level(log::LevelFilter::Debug);
}
3 => {
logger.filter_level(log::LevelFilter::Trace);
}
_ => {}
}
logger.init();
let program_cfg = config::read_config(cli.config).context("missing config file")?;
trace!("CONFIG: \n{:#?}", program_cfg);
// dbg!(program_cfg);
let mut scheduler = Scheduler::from_profiles(program_cfg.profiles);
//TODO: own thread

@ -1,11 +1,10 @@
use std::{fmt::Display, os::unix::ffi::OsStrExt};
use memchr::memmem;
use regex::Regex;
use serde::{de, Deserialize};
use serde::Deserialize;
// TODO!:
/// Match a process by a given `Criteria'
/// Match a process by a given criteria
pub trait MatchBy<Criteria>
where
Criteria: Display,
@ -17,98 +16,21 @@ where
// pub trait PatternMatcher<Pat> where Pat: String, Regex ...
/// A PatternMatcher for processes. Matches a running process given a generic pattern P
trait MatchProcByPattern<P> {
pub trait PatternMatcher<P> {
fn matches_exe(&self, pattern: P) -> bool;
fn matches_cmdline(&self, pattern: P) -> bool;
fn matches_name(&self, pattern: P) -> bool;
}
// Raw structures for deseiralizing matchers
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum PatternInRaw {
ExePath(String),
Cmdline(String),
Name(String)
}
#[derive(Deserialize, Clone, Debug)]
struct ProcessMatcherRaw {
#[serde(flatten)]
pattern: PatternInRaw,
regex: Option<bool>
}
//NOTE: help from https://users.rust-lang.org/t/serde-deserializing-a-generic-enum/117560
impl TryFrom<ProcessMatcherRaw> for ProcessMatcher {
type Error = de::value::Error;
fn try_from(raw: ProcessMatcherRaw) -> Result<Self, Self::Error> {
if raw.regex.is_some_and(|x| x) {
let pattern = convert_pattern(raw.pattern, parse_regex)?;
Ok(ProcessMatcher::RegexPattern(pattern))
} else {
let pattern = convert_pattern(raw.pattern, Ok)?;
Ok(ProcessMatcher::StringPattern(pattern))
}
}
}
fn convert_pattern<F, P, E>(raw: PatternInRaw, convert: F) -> Result<PatternIn<P>, E>
where
F: FnOnce(String) -> Result<P, E>
{
Ok(match raw {
PatternInRaw::ExePath(s) => PatternIn::ExePath(convert(s)?),
PatternInRaw::Cmdline(s) => PatternIn::Cmdline(convert(s)?),
PatternInRaw::Name(s) => PatternIn::Name(convert(s)?),
})
}
fn parse_regex(raw: String) -> Result<Regex, de::value::Error> {
raw.parse::<Regex>().map_err(de::Error::custom)
}
#[derive(Deserialize, Debug, Clone)]
#[serde(try_from = "ProcessMatcherRaw")]
pub enum ProcessMatcher {
StringPattern(PatternIn<String>),
RegexPattern(PatternIn<Regex>)
}
impl From<PatternIn<String>> for ProcessMatcher {
fn from(value: PatternIn<String>) -> Self {
Self::StringPattern(value)
}
}
impl From<PatternIn<Regex>> for ProcessMatcher {
fn from(value: PatternIn<Regex>) -> Self {
Self::RegexPattern(value)
}
}
impl Display for ProcessMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StringPattern(p) => {
p.fmt(f)
},
Self::RegexPattern(p) => {p.fmt(f)},
}
}
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PatternIn<P> {
ExePath(P),
Cmdline(P),
Name(P),
}
impl MatchProcByPattern<String> for sysinfo::Process {
impl PatternMatcher<String> for sysinfo::Process {
fn matches_exe(&self, pattern: String) -> bool {
let finder = memmem::Finder::new(&pattern);
self.exe()
@ -126,22 +48,6 @@ impl MatchProcByPattern<String> for sysinfo::Process {
}
}
impl MatchProcByPattern<Regex> for sysinfo::Process {
fn matches_exe(&self, pattern: Regex) -> bool {
self.exe()
.and_then(|exe_name| exe_name.as_os_str().to_str())
.is_some_and(|hay| pattern.is_match(hay))
}
fn matches_cmdline(&self, pattern: Regex) -> bool {
pattern.is_match(&self.cmd().join(" "))
}
fn matches_name(&self, pattern: Regex) -> bool {
pattern.is_match(self.name())
}
}
impl<P> Display for PatternIn<P> where P: Display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -160,7 +66,7 @@ impl<P> Display for PatternIn<P> where P: Display {
impl<P> MatchBy<PatternIn<P>> for sysinfo::Process
where
sysinfo::Process: MatchProcByPattern<P>,
sysinfo::Process: PatternMatcher<P>,
P: Display
{
fn match_by(&self, matcher: PatternIn<P>) -> bool {
@ -171,13 +77,3 @@ where
}
}
}
impl MatchBy<ProcessMatcher> for sysinfo::Process {
fn match_by(&self, matcher: ProcessMatcher) -> bool {
match matcher {
ProcessMatcher::StringPattern(pat) => self.match_by(pat),
ProcessMatcher::RegexPattern(pat) => self.match_by(pat),
}
}
}

@ -1,10 +1,11 @@
use core::fmt::Debug;
use std::{fmt::Display, time::Duration};
use crate::matching::{MatchBy, ProcessMatcher};
use crate::matching::{MatchBy, PatternIn};
use crate::state::{ConditionMatcher, StateTracker};
use log::{debug, log_enabled, trace};
use log::debug;
use serde::Deserialize;
use sysinfo::{self, Pid, ProcessStatus};
use sysinfo;
#[cfg(test)]
use mock_instant::thread_local::Instant;
@ -83,23 +84,23 @@ impl ProcCondition {
#[derive(Debug)]
pub struct Process {
matcher: ProcessMatcher,
pattern: PatternIn<String>,
lifetime: ProcLifetime,
pids: Vec<usize>,
}
impl Process {
pub fn build(matcher: ProcessMatcher, state_matcher: ProcLifetime) -> Self {
pub fn build(pat: PatternIn<String>, state_matcher: ProcLifetime) -> Self {
Self {
matcher,
pattern: pat,
lifetime: state_matcher,
pids: vec![],
}
}
pub fn from_pattern(pat: impl Into<ProcessMatcher>) -> Self {
pub fn from_pattern(pat: PatternIn<String>) -> Self {
Self {
matcher: pat.into(),
pattern: pat,
lifetime: ProcLifetime::new(),
pids: vec![],
}
@ -111,12 +112,16 @@ impl Process {
if !matches!(self.state(), ProcState::NeverSeen) {
self.lifetime.prev_state = Some(self.lifetime.state.clone());
self.lifetime.state = ProcState::NotSeen;
self.lifetime.state_exit = self.lifetime.prev_state != Some(ProcState::NotSeen);
debug!("<{}>: process disappread", self.matcher);
if self.lifetime.prev_state != Some(ProcState::NotSeen) {
self.lifetime.state_exit = true;
} else {
self.lifetime.state_exit = false;
}
debug!("<{}>: process disappread", self.pattern);
} else {
self.lifetime.state_exit = false;
self.lifetime.prev_state = Some(ProcState::NeverSeen);
debug!("<{}>: never seen so far", self.matcher);
debug!("<{}>: never seen so far", self.pattern);
}
// process found
} else {
@ -124,10 +129,10 @@ impl Process {
ProcState::NeverSeen => {
self.lifetime.state_exit = false;
self.lifetime.first_seen = self.lifetime.last_refresh;
debug!("<{}>: process seen first time", self.matcher);
debug!("<{}>: process seen first time", self.pattern);
}
ProcState::NotSeen => {
debug!("<{}>: process reappeared", self.matcher);
debug!("<{}>: process reappeared", self.pattern);
self.lifetime.state_exit = true;
// reset first_seen
@ -135,7 +140,7 @@ impl Process {
}
ProcState::Seen => {
self.lifetime.state_exit = false;
debug!("<{}>: process still running", self.matcher);
debug!("<{}>: process still running", self.pattern);
}
}
self.lifetime.prev_state = Some(self.lifetime.state.clone());
@ -145,7 +150,8 @@ impl Process {
}
}
impl StateTracker for Process {
impl StateTracker for Process
{
type State = ProcState;
/// updates the state and return a copy of the new state
@ -153,26 +159,12 @@ impl StateTracker for Process {
self.pids = info
.processes()
.iter()
// .filter(|(_, proc)| MatchBy::match_by(*proc, self.matching.pattern.clone()))
.filter(|(_, proc)| proc.match_by(self.matcher.clone()))
// .inspect(|(pid, proc)| debug!("[{}] status: {}", pid, (proc.status())))
// filter out non active and dead processes
.filter(|(_, proc)| {
!matches!(proc.status(), ProcessStatus::Stop | ProcessStatus::Dead | ProcessStatus::Zombie)
})
// .filter(|(_, proc)| MatchBy::match_by(*proc, self.pattern.clone()))
.filter(|(_, proc)| proc.match_by(self.pattern.clone()))
.map(|(_, proc)| proc.pid().into())
.collect();
debug!("<{}> detected pids: {}", self.matcher, self.pids.len());
// trace matched pids
if log_enabled!(log::Level::Trace) {
self.pids
.iter()
.filter_map(|pid| info.processes().get(&Pid::from(*pid)))
.for_each(|p| trace!("- {}: \n {:#?}", p.pid(), p));
}
debug!("<{}> detected pids: {}", self.pattern, self.pids.len());
self.lifetime.prev_refresh = self.lifetime.last_refresh;
self.lifetime.last_refresh = Some(t_refresh);
@ -192,6 +184,8 @@ impl StateTracker for Process {
fn exiting(&self) -> bool {
self.lifetime.state_exit
}
}
impl ConditionMatcher for Process {
@ -204,6 +198,8 @@ impl ConditionMatcher for Process {
fn partial_match(&self, c: Self::Condition) -> Option<bool> {
self.lifetime.partial_match(c)
}
}
impl ConditionMatcher for ProcLifetime {
@ -239,28 +235,27 @@ impl ConditionMatcher for ProcLifetime {
fn partial_match(&self, cond: Self::Condition) -> Option<bool> {
match cond {
ProcCondition::Seen(_) => Some(matches!(self.state, ProcState::Seen)),
ProcCondition::NotSeen(_) => Some(matches!(
self.state,
ProcState::NotSeen | ProcState::NeverSeen
)),
ProcCondition::Seen(_) => {
Some(matches!(self.state, ProcState::Seen))
}
ProcCondition::NotSeen(_) => {
Some(matches!(self.state, ProcState::NotSeen | ProcState::NeverSeen))
}
}
}
}
//DEBUG:
#[cfg(test)]
#[allow(unused_imports)]
mod test {
use super::*;
use crate::{matching::PatternIn, sched::Scheduler, state::*};
use crate::{sched::Scheduler, state::*};
use mock_instant::thread_local::MockClock;
use regex::Regex;
use sysinfo::System;
#[test]
fn default_process() {
let p = Process::from_pattern(PatternIn::Name("foo".to_string()));
let p = Process::from_pattern(PatternIn::Name("foo".into()));
assert!(matches!(p.state(), ProcState::NeverSeen))
}
@ -275,9 +270,8 @@ mod test {
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::Name(pattern.to_string()));
let mut p_does_not_match =
Process::from_pattern(PatternIn::Name("foobar_234324".to_string()));
let mut p_match = Process::from_pattern(PatternIn::Name(pattern.into()));
let mut p_does_not_match = Process::from_pattern(PatternIn::Name("foobar_234324".into()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
@ -293,15 +287,15 @@ mod test {
#[test]
fn match_pattern_exe() -> anyhow::Result<(), std::io::Error> {
let pattern = "fake_bins/sleep-w61Z";
let mut target = std::process::Command::new("tests/fake_bins/sleep-w61Z")
let pattern = "/bin";
let mut target = std::process::Command::new("tests/fake_bins/proc-89MLx.sh")
.arg("300")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::ExePath(pattern.to_string()));
let mut p_match = Process::from_pattern(PatternIn::ExePath(pattern.into()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now());
@ -309,63 +303,6 @@ mod test {
target.kill()
}
// exe path dereferences the os symlink so exe path here is the target of the symlink
#[test]
fn regex_pattern_exe() -> anyhow::Result<(), std::io::Error> {
let pattern = r"sl\w+-w\d{2}Z$"; // Example regex for files ending with .sh
let mut target = std::process::Command::new("tests/fake_bins/sleep-583a")
.arg("5")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::ExePath(Regex::new(pattern).unwrap()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now());
assert!(!p_match.pids.is_empty());
target.kill() // Ensure you handle the Result from kill properly
}
// regex for process name
#[test]
fn regex_pattern_name() -> anyhow::Result<(), std::io::Error> {
let pattern = r"sleep-\d{3}a$"; // Example regex for files ending with .sh
let mut target = std::process::Command::new("tests/fake_bins/sleep-583a")
.arg("5")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::Name(Regex::new(pattern).unwrap()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now());
assert!(!p_match.pids.is_empty());
target.kill() // Ensure you handle the Result from kill properly
}
// regex for process cmdline
#[test]
fn regex_pattern_cmdline() -> anyhow::Result<(), std::io::Error> {
let pattern = r"sleep-\d{3}a\s5"; // Example regex for files ending with .sh
let mut target = std::process::Command::new("tests/fake_bins/sleep-583a")
.arg("5")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::Cmdline(Regex::new(pattern).unwrap()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now());
assert!(!p_match.pids.is_empty());
target.kill() // Ensure you handle the Result from kill properly
}
#[test]
fn match_pattern_cmdline() -> anyhow::Result<(), std::io::Error> {
let pattern = "300";
@ -376,7 +313,7 @@ mod test {
.unwrap();
std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::Cmdline(pattern.to_string()));
let mut p_match = Process::from_pattern(PatternIn::Cmdline(pattern.into()));
let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now());
@ -388,7 +325,7 @@ mod test {
fn cond_seen_since() {
MockClock::set_time(Duration::ZERO);
let cond_seen = ProcCondition::Seen(Duration::from_secs(5));
let mut p = Process::from_pattern(PatternIn::Name("foo".to_string()));
let mut p = Process::from_pattern(PatternIn::Name("foo".into()));
p.lifetime.last_refresh = Some(Instant::now());
// no process detected initially
@ -451,7 +388,7 @@ mod test {
fn test_not_seen_since() {
MockClock::set_time(Duration::ZERO);
let cond_not_seen = ProcCondition::NotSeen(Duration::from_secs(5));
let mut p = Process::from_pattern(PatternIn::Name("foo".to_string()));
let mut p = Process::from_pattern(PatternIn::Name("foo".into()));
p.lifetime.last_refresh = Some(Instant::now());
let t1 = p.lifetime.last_refresh;
p.update_inner_state();

@ -10,8 +10,7 @@ use std::time::Instant;
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
use crate::config::{CmdSchedule, Profile};
use crate::matching::ProcessMatcher;
use crate::config::{CmdSchedule, Profile, ProfileMatching};
use crate::process::ProcLifetime;
use crate::state::{ConditionMatcher, StateTracker};
@ -37,20 +36,20 @@ impl ProfileJob<Process> {
Self {
profile: profile.clone(),
object: Process::build(profile.matching, ProcLifetime::new()),
object: Process::build(profile.matching.pattern, ProcLifetime::new()),
}
}
}
fn run_cmd(cmd: &mut CmdSchedule, matching: ProcessMatcher, exec_end: bool) {
fn run_cmd(cmd: &mut CmdSchedule, matching: ProfileMatching, exec_end: bool) {
// handle end exec
let out = if exec_end && cmd.exec_end.is_some() {
dbg!("run exec_end !");
Command::new(&cmd.exec_end.as_ref().unwrap()[0]).args(&cmd.exec_end.as_ref().unwrap()[1..]).output()
} else if exec_end && cmd.exec_end.is_none() {
return;
// run normal execs
} else {
dbg!("running command !");
Command::new(&cmd.exec[0]).args(&cmd.exec[1..]).output()
};
@ -84,12 +83,12 @@ impl Job for ProfileJob<Process> {
fn update(&mut self, sysinfo: &System, last_refresh: Instant) {
let _ = self.object.update_state(sysinfo, last_refresh);
trace!("{:#?}", &self.object);
dbg!(&self.object);
// run commands when entering match state `exec`
self.profile.commands.iter_mut()
// only process enabled commands
.filter(|cmd| !cmd.disabled)
.filter(|cmd| self.object.matches(cmd.condition.clone()))
.filter(|cmd| dbg!(self.object.matches(cmd.condition.clone())))
.for_each(|cmd| {
debug!("running exec cmd");

@ -1,4 +1,4 @@
#!/bin/sh
sleep "$1"
sleep 300

Binary file not shown.

@ -1,18 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int sleep_secs;
if (argc != 2 || ( (sleep_secs = atoi(argv[1])) ) <= 0 ) {
fprintf(stderr, "Usage: %s SECS\n", argv[0]);
return EXIT_FAILURE;
}
sleep(sleep_secs);
return EXIT_SUCCESS;
}

@ -4,13 +4,11 @@ use std::time::{Duration, Instant};
use pswatch::{process::{self, ProcCondition}, matching::PatternIn, sched::Scheduler, state::*};
use rstest::rstest;
use sysinfo::System;
use serial_test::serial;
#[rstest]
#[case((200, 400), true)]
#[case((200, 100), false)]
#[serial]
#[test]
// cond: seen for 200ms
// start state: seen
@ -24,7 +22,6 @@ fn match_cond_seen(
let test_span = Duration::from_millis(spans.1);
let mut s = System::new();
let mut target = std::process::Command::new("tests/fake_bins/proc-OPL6J.sh")
.arg("300")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
@ -60,7 +57,6 @@ fn match_cond_seen(
#[rstest]
#[case((400, 500), true)]
#[case((400, 300), false)]
#[serial]
#[test]
fn match_cond_not_seen(
#[case] spans: (u64, u64),
@ -102,7 +98,6 @@ fn match_cond_not_seen(
// REVIEW:
#[case((400, 200), false)]
#[case((200, 400), true)]
#[serial]
#[test]
fn match_cond_not_seen_2(
#[case] spans: (u64, u64),
@ -116,7 +111,6 @@ fn match_cond_not_seen_2(
let cond = ProcCondition::NotSeen(cond_span);
let mut target = std::process::Command::new("tests/fake_bins/proc-dZWY4.sh")
.arg("300")
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();

@ -3,8 +3,7 @@
# [[profiles.matching]]
# exe_path = "foo"
# matching = { cmdline = "foo", regex = true }
matching = { cmdline = "foo"}
matching = { cmdline = "foo" }
[[profiles.commands]]
@ -20,24 +19,13 @@ exec_end = ["sh", "-c", "notify-send 'foo end'"]
run_once = true
[[profiles]]
matching = { cmdline = '^bar.*', regex = true }
# regex = false
[[profiles.commands]]
condition = {seen = "5s"}
# one off command
exec = ["sh", "-c", "notify-send 'bar seen'"]
exec_end = ["sh", "-c", "notify-send 'bar end'"]
[[profiles]]
matching = { cmdline = '\-baz.*', regex = true }
# regex = false
[[profiles.commands]]
condition = {seen = "5s"}
# one off command
exec = ["sh", "-c", "notify-send pswatch 'baz seen'"]
#exec_end = ["sh", "-c", "notify-send '-bar end'"]
# [[profiles]]
# matching = { cmdline = "^fo?", regex = true }
# # regex = false
#
# [[profiles.commands]]
# condition = {seen = "5s"}
#
# # one off command
# exec = ["sh", "-c", "notify-send foo"]
# exec_end = ["sh", "-c", "notify-send 'foo end'"]

Loading…
Cancel
Save