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 /target
bacon.toml

282
Cargo.lock generated

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

@ -1,19 +1,13 @@
[package] [package]
name = "pswatch" name = "pswatch"
license = "AGPL-3.0-or-later" version = "0.1.0"
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"
edition = "2021" edition = "2021"
default-run = "pswatch" default-run = "pswatch"
[[example]] [[bin]]
name = "condition" 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 # 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] [dev-dependencies]
rstest = "0.21.0" rstest = "0.21.0"
mock_instant = "0.5.1" 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 pswatch is a minimalist process monitoring and task scheduler that allows you to
watch system processes and run custom commands when specific conditions or 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** **Features**
- Process Matching: match running processes by substring or regex patterns in name, exe path or the entire command line. - match running processes by pattern in: name, exe path or the entire command line.
- Define conditions and actions. - Define multiple conditions and actions.
- Execute actions when conditions are met on the matched processes. - 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 ## Installation
### From Crates.io To install pswatch, clone the repository from GitHub and build it using
Cargo:
`cargo install pswatch`
### From source
```sh ```sh
git clone https://github.com/your-username/pswatch.git git clone https://github.com/your-username/pswatch.git
cd pswatch cd pswatch
cargo install --path . cargo build --release
``` ```
The binary will be located in `target/release/pswatch`.
## Usage ## 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 ```sh
./pswatch -c /path/to/config.toml ./pswatch -c /path/to/config.toml
@ -38,10 +37,10 @@ patterns defined in the configuration file.
## Configuration File ## Configuration File
pswatch's behavior is configured using a TOML-formatted configuration file. The pswatch's behavior is configured using a TOML-formatted configuration file.
file should contain a list of `profiles`, each containing a `matching` directive The file should contain a list of `watches`, each containing a `pattern` (the
(the process to match), an (optional) `regex` flag (set to `true` if the process name to match), a `regex` flag (set to `true` if the pattern is a
pattern is a regular expression), and a list of `commands`. regular expression), and a list of `commands`.
Each command contains a condition (either `seen` or `not_seen` with a duration) 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 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: Here's an example configuration file:
```toml ```toml
[[profiles]] [[watches]]
matching = { name = "foo" } pattern = "foo"
regex = false
# command 1 [[watches.commands]]
[[profiles.commands]]
condition = {seen = "5s"} condition = {seen = "5s"}
exec = ["sh", "-c", "notify-send psw 'foo action'"] exec = ["sh", "-c", "notify-end action!"]
# run_once = false # uncomment to run the command only once per process
# command 2 detection
[[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"]
``` ```
## 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. You can use multiple watches within a single configuration file to monitor
Here's an example configuration that uses two profiles: different processes and execute commands based on their patterns. Here's an
example configuration that uses two watches:
```toml ```toml
[[profiles]] [[watches]]
pattern = "bar" pattern = "bar"
regex = false
# matches the process name [[watches.commands]]
matching = { name = "bar" }
[[profiles.commands]]
condition = {not_seen = "5s"} condition = {not_seen = "5s"}
exec = ["sh", "-c", "notify-send psw 'bar not seen!'"] exec = ["sh", "-c", "echo not seen!"]
[[profiles]] [[watches]]
# matches the full executable path pattern = "baz"
matching = { exe_path = '.*baz$', regex = true} regex = true
[[profiles.commands]] [[watches.commands]]
condition = {seen = "10s"} condition = {seen = "10s"}
exec = ["sh", "-c", "notify-send psw '/baz action !'"] exec = ["sh", "-c", "say 'baz detected!'"]
run_once = true # run the command only once when a match is triggered run_once = true # run the command only once per process detection
[[profiles]]
# matches the command line
matching = { cmdline = '\-buz.*', regex = true}
[[profiles.commands]]
condition = {seen = "10s"}
exec = ["sh", "-c", "notify-send psw 'someproc -buz action !'"]
``` ```
In this example, pswatch will watch for three processes: "bar", "baz" and "buz". 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
- It matches `bar` by process name (simple string). regular expression) is detected, it will execute `say 'baz detected!'` after a
- Matches `.*baz$` and `\-buz.*` by a regex pattern of the executable path and delay of 10 seconds. The command for "baz" will be run only once per process
command line respectively. detection.
- 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.
## Example Scenarios ## 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"). - 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** 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. - 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.
## 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
```
## Troubleshooting ## 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 ## 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. 1. Fork the repository on GitHub.
2. Clone your fork to your local machine: `git clone 2. Clone your fork to your local machine: `git clone
https://github.com/your-username/pswatch.git`. https://github.com/your-username/pswatch.git`.
3. Create a new branch for your changes: `git checkout -b my-feature`. 3. Create a new branch for your changes: `git checkout -b my-feature`.
4. Make your changes and commit them with descriptive messages: 4. Make your changes and commit them with descriptive messages: `git commit
`git commit -am 'Add some feature'`. -am 'Add some feature'`.
5. Push your branch: `git push origin my-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. 6. Submit a pull request from your GitHub fork to the main repository.
## License ## License
pswatch is licensed under the AGPLv3 License. pswatch is licensed under the AGPLv3 License. See [LICENSE](LICENSE) for more
details.
See [LICENSE](LICENSE) for more details.

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

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

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

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

@ -1,11 +1,10 @@
use std::{fmt::Display, os::unix::ffi::OsStrExt}; use std::{fmt::Display, os::unix::ffi::OsStrExt};
use memchr::memmem; use memchr::memmem;
use regex::Regex; use serde::Deserialize;
use serde::{de, Deserialize};
// TODO!: // TODO!:
/// Match a process by a given `Criteria' /// Match a process by a given criteria
pub trait MatchBy<Criteria> pub trait MatchBy<Criteria>
where where
Criteria: Display, Criteria: Display,
@ -17,98 +16,21 @@ where
// pub trait PatternMatcher<Pat> where Pat: String, Regex ... // pub trait PatternMatcher<Pat> where Pat: String, Regex ...
/// A PatternMatcher for processes. Matches a running process given a generic pattern P /// 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_exe(&self, pattern: P) -> bool;
fn matches_cmdline(&self, pattern: P) -> bool; fn matches_cmdline(&self, pattern: P) -> bool;
fn matches_name(&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)] #[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PatternIn<P> { pub enum PatternIn<P> {
ExePath(P), ExePath(P),
Cmdline(P), Cmdline(P),
Name(P), Name(P),
} }
impl PatternMatcher<String> for sysinfo::Process {
impl MatchProcByPattern<String> for sysinfo::Process {
fn matches_exe(&self, pattern: String) -> bool { fn matches_exe(&self, pattern: String) -> bool {
let finder = memmem::Finder::new(&pattern); let finder = memmem::Finder::new(&pattern);
self.exe() 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 { impl<P> Display for PatternIn<P> where P: Display {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@ -160,7 +66,7 @@ impl<P> Display for PatternIn<P> where P: Display {
impl<P> MatchBy<PatternIn<P>> for sysinfo::Process impl<P> MatchBy<PatternIn<P>> for sysinfo::Process
where where
sysinfo::Process: MatchProcByPattern<P>, sysinfo::Process: PatternMatcher<P>,
P: Display P: Display
{ {
fn match_by(&self, matcher: PatternIn<P>) -> bool { 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 std::{fmt::Display, time::Duration};
use crate::matching::{MatchBy, ProcessMatcher}; use crate::matching::{MatchBy, PatternIn};
use crate::state::{ConditionMatcher, StateTracker}; use crate::state::{ConditionMatcher, StateTracker};
use log::{debug, log_enabled, trace}; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use sysinfo::{self, Pid, ProcessStatus}; use sysinfo;
#[cfg(test)] #[cfg(test)]
use mock_instant::thread_local::Instant; use mock_instant::thread_local::Instant;
@ -83,23 +84,23 @@ impl ProcCondition {
#[derive(Debug)] #[derive(Debug)]
pub struct Process { pub struct Process {
matcher: ProcessMatcher, pattern: PatternIn<String>,
lifetime: ProcLifetime, lifetime: ProcLifetime,
pids: Vec<usize>, pids: Vec<usize>,
} }
impl Process { impl Process {
pub fn build(matcher: ProcessMatcher, state_matcher: ProcLifetime) -> Self { pub fn build(pat: PatternIn<String>, state_matcher: ProcLifetime) -> Self {
Self { Self {
matcher, pattern: pat,
lifetime: state_matcher, lifetime: state_matcher,
pids: vec![], pids: vec![],
} }
} }
pub fn from_pattern(pat: impl Into<ProcessMatcher>) -> Self { pub fn from_pattern(pat: PatternIn<String>) -> Self {
Self { Self {
matcher: pat.into(), pattern: pat,
lifetime: ProcLifetime::new(), lifetime: ProcLifetime::new(),
pids: vec![], pids: vec![],
} }
@ -111,12 +112,16 @@ impl Process {
if !matches!(self.state(), ProcState::NeverSeen) { if !matches!(self.state(), ProcState::NeverSeen) {
self.lifetime.prev_state = Some(self.lifetime.state.clone()); self.lifetime.prev_state = Some(self.lifetime.state.clone());
self.lifetime.state = ProcState::NotSeen; self.lifetime.state = ProcState::NotSeen;
self.lifetime.state_exit = self.lifetime.prev_state != Some(ProcState::NotSeen); if self.lifetime.prev_state != Some(ProcState::NotSeen) {
debug!("<{}>: process disappread", self.matcher); self.lifetime.state_exit = true;
} else {
self.lifetime.state_exit = false;
}
debug!("<{}>: process disappread", self.pattern);
} else { } else {
self.lifetime.state_exit = false; self.lifetime.state_exit = false;
self.lifetime.prev_state = Some(ProcState::NeverSeen); self.lifetime.prev_state = Some(ProcState::NeverSeen);
debug!("<{}>: never seen so far", self.matcher); debug!("<{}>: never seen so far", self.pattern);
} }
// process found // process found
} else { } else {
@ -124,10 +129,10 @@ impl Process {
ProcState::NeverSeen => { ProcState::NeverSeen => {
self.lifetime.state_exit = false; self.lifetime.state_exit = false;
self.lifetime.first_seen = self.lifetime.last_refresh; self.lifetime.first_seen = self.lifetime.last_refresh;
debug!("<{}>: process seen first time", self.matcher); debug!("<{}>: process seen first time", self.pattern);
} }
ProcState::NotSeen => { ProcState::NotSeen => {
debug!("<{}>: process reappeared", self.matcher); debug!("<{}>: process reappeared", self.pattern);
self.lifetime.state_exit = true; self.lifetime.state_exit = true;
// reset first_seen // reset first_seen
@ -135,7 +140,7 @@ impl Process {
} }
ProcState::Seen => { ProcState::Seen => {
self.lifetime.state_exit = false; 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()); 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; type State = ProcState;
/// updates the state and return a copy of the new state /// updates the state and return a copy of the new state
@ -153,26 +159,12 @@ impl StateTracker for Process {
self.pids = info self.pids = info
.processes() .processes()
.iter() .iter()
// .filter(|(_, proc)| MatchBy::match_by(*proc, self.matching.pattern.clone())) // .filter(|(_, proc)| MatchBy::match_by(*proc, self.pattern.clone()))
.filter(|(_, proc)| proc.match_by(self.matcher.clone())) .filter(|(_, proc)| proc.match_by(self.pattern.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)
})
.map(|(_, proc)| proc.pid().into()) .map(|(_, proc)| proc.pid().into())
.collect(); .collect();
debug!("<{}> detected pids: {}", self.matcher, self.pids.len()); debug!("<{}> detected pids: {}", self.pattern, 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));
}
self.lifetime.prev_refresh = self.lifetime.last_refresh; self.lifetime.prev_refresh = self.lifetime.last_refresh;
self.lifetime.last_refresh = Some(t_refresh); self.lifetime.last_refresh = Some(t_refresh);
@ -192,6 +184,8 @@ impl StateTracker for Process {
fn exiting(&self) -> bool { fn exiting(&self) -> bool {
self.lifetime.state_exit self.lifetime.state_exit
} }
} }
impl ConditionMatcher for Process { impl ConditionMatcher for Process {
@ -204,6 +198,8 @@ impl ConditionMatcher for Process {
fn partial_match(&self, c: Self::Condition) -> Option<bool> { fn partial_match(&self, c: Self::Condition) -> Option<bool> {
self.lifetime.partial_match(c) self.lifetime.partial_match(c)
} }
} }
impl ConditionMatcher for ProcLifetime { impl ConditionMatcher for ProcLifetime {
@ -239,28 +235,27 @@ impl ConditionMatcher for ProcLifetime {
fn partial_match(&self, cond: Self::Condition) -> Option<bool> { fn partial_match(&self, cond: Self::Condition) -> Option<bool> {
match cond { match cond {
ProcCondition::Seen(_) => Some(matches!(self.state, ProcState::Seen)), ProcCondition::Seen(_) => {
ProcCondition::NotSeen(_) => Some(matches!( Some(matches!(self.state, ProcState::Seen))
self.state, }
ProcState::NotSeen | ProcState::NeverSeen ProcCondition::NotSeen(_) => {
)), Some(matches!(self.state, ProcState::NotSeen | ProcState::NeverSeen))
}
} }
} }
} }
//DEBUG:
#[cfg(test)] #[cfg(test)]
#[allow(unused_imports)] #[allow(unused_imports)]
mod test { mod test {
use super::*; use super::*;
use crate::{matching::PatternIn, sched::Scheduler, state::*}; use crate::{sched::Scheduler, state::*};
use mock_instant::thread_local::MockClock; use mock_instant::thread_local::MockClock;
use regex::Regex;
use sysinfo::System; use sysinfo::System;
#[test] #[test]
fn default_process() { 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)) assert!(matches!(p.state(), ProcState::NeverSeen))
} }
@ -275,9 +270,8 @@ mod test {
.unwrap(); .unwrap();
std::thread::sleep(Duration::from_secs(1)); std::thread::sleep(Duration::from_secs(1));
let mut p_match = Process::from_pattern(PatternIn::Name(pattern.to_string())); let mut p_match = Process::from_pattern(PatternIn::Name(pattern.into()));
let mut p_does_not_match = let mut p_does_not_match = Process::from_pattern(PatternIn::Name("foobar_234324".into()));
Process::from_pattern(PatternIn::Name("foobar_234324".to_string()));
let mut sys = System::new(); let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs()); sys.refresh_specifics(Scheduler::process_refresh_specs());
@ -293,15 +287,15 @@ mod test {
#[test] #[test]
fn match_pattern_exe() -> anyhow::Result<(), std::io::Error> { fn match_pattern_exe() -> anyhow::Result<(), std::io::Error> {
let pattern = "fake_bins/sleep-w61Z"; let pattern = "/bin";
let mut target = std::process::Command::new("tests/fake_bins/sleep-w61Z") let mut target = std::process::Command::new("tests/fake_bins/proc-89MLx.sh")
.arg("300") .arg("300")
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.spawn() .spawn()
.unwrap(); .unwrap();
std::thread::sleep(Duration::from_secs(1)); 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(); let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs()); sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now()); p_match.update_state(&sys, Instant::now());
@ -309,63 +303,6 @@ mod test {
target.kill() 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] #[test]
fn match_pattern_cmdline() -> anyhow::Result<(), std::io::Error> { fn match_pattern_cmdline() -> anyhow::Result<(), std::io::Error> {
let pattern = "300"; let pattern = "300";
@ -376,7 +313,7 @@ mod test {
.unwrap(); .unwrap();
std::thread::sleep(Duration::from_secs(1)); 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(); let mut sys = System::new();
sys.refresh_specifics(Scheduler::process_refresh_specs()); sys.refresh_specifics(Scheduler::process_refresh_specs());
p_match.update_state(&sys, Instant::now()); p_match.update_state(&sys, Instant::now());
@ -388,7 +325,7 @@ mod test {
fn cond_seen_since() { fn cond_seen_since() {
MockClock::set_time(Duration::ZERO); MockClock::set_time(Duration::ZERO);
let cond_seen = ProcCondition::Seen(Duration::from_secs(5)); 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()); p.lifetime.last_refresh = Some(Instant::now());
// no process detected initially // no process detected initially
@ -451,7 +388,7 @@ mod test {
fn test_not_seen_since() { fn test_not_seen_since() {
MockClock::set_time(Duration::ZERO); MockClock::set_time(Duration::ZERO);
let cond_not_seen = ProcCondition::NotSeen(Duration::from_secs(5)); 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()); p.lifetime.last_refresh = Some(Instant::now());
let t1 = p.lifetime.last_refresh; let t1 = p.lifetime.last_refresh;
p.update_inner_state(); p.update_inner_state();

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

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

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

Loading…
Cancel
Save