├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── cli.yml ├── main.rs ├── rorshach.rs └── rorshach │ ├── consumer.rs │ ├── event.rs │ ├── event_type.rs │ ├── executor.rs │ ├── producer.rs │ ├── rule.rs │ └── rule_parser.rs └── test └── integration ├── .rorshach.conf ├── golden.output ├── start_test.sh └── test_data └── test.cpp /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /test/integration/output 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.9.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 17 | 18 | [[package]] 19 | name = "anyhow" 20 | version = "1.0.32" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" 23 | 24 | [[package]] 25 | name = "arrayref" 26 | version = "0.3.6" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 29 | 30 | [[package]] 31 | name = "arrayvec" 32 | version = "0.5.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 35 | 36 | [[package]] 37 | name = "atty" 38 | version = "0.2.14" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 41 | dependencies = [ 42 | "hermit-abi", 43 | "libc", 44 | "winapi 0.3.9", 45 | ] 46 | 47 | [[package]] 48 | name = "autocfg" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 52 | 53 | [[package]] 54 | name = "base64" 55 | version = "0.11.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 58 | 59 | [[package]] 60 | name = "bitflags" 61 | version = "0.9.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 64 | 65 | [[package]] 66 | name = "bitflags" 67 | version = "1.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 70 | 71 | [[package]] 72 | name = "blake2b_simd" 73 | version = "0.5.10" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 76 | dependencies = [ 77 | "arrayref", 78 | "arrayvec", 79 | "constant_time_eq", 80 | ] 81 | 82 | [[package]] 83 | name = "bstr" 84 | version = "0.2.13" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" 87 | dependencies = [ 88 | "lazy_static", 89 | "memchr", 90 | "regex-automata", 91 | "serde", 92 | ] 93 | 94 | [[package]] 95 | name = "byteorder" 96 | version = "1.3.4" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 99 | 100 | [[package]] 101 | name = "cfg-if" 102 | version = "0.1.10" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 105 | 106 | [[package]] 107 | name = "chrono" 108 | version = "0.4.13" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" 111 | dependencies = [ 112 | "num-integer", 113 | "num-traits", 114 | "time", 115 | ] 116 | 117 | [[package]] 118 | name = "clap" 119 | version = "2.27.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180" 122 | dependencies = [ 123 | "ansi_term", 124 | "atty", 125 | "bitflags 0.9.1", 126 | "strsim", 127 | "textwrap", 128 | "unicode-width", 129 | "vec_map", 130 | "yaml-rust", 131 | ] 132 | 133 | [[package]] 134 | name = "colored" 135 | version = "1.9.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" 138 | dependencies = [ 139 | "atty", 140 | "lazy_static", 141 | "winapi 0.3.9", 142 | ] 143 | 144 | [[package]] 145 | name = "constant_time_eq" 146 | version = "0.1.5" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 149 | 150 | [[package]] 151 | name = "crossbeam-utils" 152 | version = "0.7.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 155 | dependencies = [ 156 | "autocfg", 157 | "cfg-if", 158 | "lazy_static", 159 | ] 160 | 161 | [[package]] 162 | name = "csv" 163 | version = "1.1.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" 166 | dependencies = [ 167 | "bstr", 168 | "csv-core", 169 | "itoa", 170 | "ryu", 171 | "serde", 172 | ] 173 | 174 | [[package]] 175 | name = "csv-core" 176 | version = "0.1.10" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 179 | dependencies = [ 180 | "memchr", 181 | ] 182 | 183 | [[package]] 184 | name = "dirs" 185 | version = "2.0.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 188 | dependencies = [ 189 | "cfg-if", 190 | "dirs-sys", 191 | ] 192 | 193 | [[package]] 194 | name = "dirs-sys" 195 | version = "0.3.5" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 198 | dependencies = [ 199 | "libc", 200 | "redox_users", 201 | "winapi 0.3.9", 202 | ] 203 | 204 | [[package]] 205 | name = "filetime" 206 | version = "0.2.11" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "e500da2fab70bdc43f8f0e0b350a227f31c72311c56aba48f01d5cd62bb0345b" 209 | dependencies = [ 210 | "cfg-if", 211 | "libc", 212 | "redox_syscall", 213 | "winapi 0.3.9", 214 | ] 215 | 216 | [[package]] 217 | name = "fsevent" 218 | version = "0.4.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 221 | dependencies = [ 222 | "bitflags 1.2.1", 223 | "fsevent-sys", 224 | ] 225 | 226 | [[package]] 227 | name = "fsevent-sys" 228 | version = "2.0.1" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 231 | dependencies = [ 232 | "libc", 233 | ] 234 | 235 | [[package]] 236 | name = "fuchsia-zircon" 237 | version = "0.3.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 240 | dependencies = [ 241 | "bitflags 1.2.1", 242 | "fuchsia-zircon-sys", 243 | ] 244 | 245 | [[package]] 246 | name = "fuchsia-zircon-sys" 247 | version = "0.3.3" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 250 | 251 | [[package]] 252 | name = "futures" 253 | version = "0.3.5" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 256 | dependencies = [ 257 | "futures-channel", 258 | "futures-core", 259 | "futures-executor", 260 | "futures-io", 261 | "futures-sink", 262 | "futures-task", 263 | "futures-util", 264 | ] 265 | 266 | [[package]] 267 | name = "futures-channel" 268 | version = "0.3.5" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 271 | dependencies = [ 272 | "futures-core", 273 | "futures-sink", 274 | ] 275 | 276 | [[package]] 277 | name = "futures-core" 278 | version = "0.3.5" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 281 | 282 | [[package]] 283 | name = "futures-executor" 284 | version = "0.3.5" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" 287 | dependencies = [ 288 | "futures-core", 289 | "futures-task", 290 | "futures-util", 291 | ] 292 | 293 | [[package]] 294 | name = "futures-io" 295 | version = "0.3.5" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 298 | 299 | [[package]] 300 | name = "futures-macro" 301 | version = "0.3.5" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" 304 | dependencies = [ 305 | "proc-macro-hack", 306 | "proc-macro2", 307 | "quote", 308 | "syn", 309 | ] 310 | 311 | [[package]] 312 | name = "futures-sink" 313 | version = "0.3.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 316 | 317 | [[package]] 318 | name = "futures-task" 319 | version = "0.3.5" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 322 | dependencies = [ 323 | "once_cell", 324 | ] 325 | 326 | [[package]] 327 | name = "futures-util" 328 | version = "0.3.5" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 331 | dependencies = [ 332 | "futures-channel", 333 | "futures-core", 334 | "futures-io", 335 | "futures-macro", 336 | "futures-sink", 337 | "futures-task", 338 | "memchr", 339 | "pin-project", 340 | "pin-utils", 341 | "proc-macro-hack", 342 | "proc-macro-nested", 343 | "slab", 344 | ] 345 | 346 | [[package]] 347 | name = "getrandom" 348 | version = "0.1.14" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 351 | dependencies = [ 352 | "cfg-if", 353 | "libc", 354 | "wasi", 355 | ] 356 | 357 | [[package]] 358 | name = "heck" 359 | version = "0.3.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 362 | dependencies = [ 363 | "unicode-segmentation", 364 | ] 365 | 366 | [[package]] 367 | name = "hermit-abi" 368 | version = "0.1.15" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 371 | dependencies = [ 372 | "libc", 373 | ] 374 | 375 | [[package]] 376 | name = "hotwatch" 377 | version = "0.4.3" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "ba0add391e9cd7d19c29024617a44df79c867ab003bce7f3224c1636595ec740" 380 | dependencies = [ 381 | "log", 382 | "notify", 383 | ] 384 | 385 | [[package]] 386 | name = "inotify" 387 | version = "0.7.1" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 390 | dependencies = [ 391 | "bitflags 1.2.1", 392 | "inotify-sys", 393 | "libc", 394 | ] 395 | 396 | [[package]] 397 | name = "inotify-sys" 398 | version = "0.1.3" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" 401 | dependencies = [ 402 | "libc", 403 | ] 404 | 405 | [[package]] 406 | name = "iovec" 407 | version = "0.1.4" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 410 | dependencies = [ 411 | "libc", 412 | ] 413 | 414 | [[package]] 415 | name = "itoa" 416 | version = "0.4.6" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 419 | 420 | [[package]] 421 | name = "kernel32-sys" 422 | version = "0.2.2" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 425 | dependencies = [ 426 | "winapi 0.2.8", 427 | "winapi-build", 428 | ] 429 | 430 | [[package]] 431 | name = "lazy_static" 432 | version = "1.4.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 435 | 436 | [[package]] 437 | name = "lazycell" 438 | version = "1.2.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 441 | 442 | [[package]] 443 | name = "libc" 444 | version = "0.2.74" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" 447 | 448 | [[package]] 449 | name = "log" 450 | version = "0.4.11" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 453 | dependencies = [ 454 | "cfg-if", 455 | ] 456 | 457 | [[package]] 458 | name = "memchr" 459 | version = "2.3.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 462 | 463 | [[package]] 464 | name = "mio" 465 | version = "0.6.22" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 468 | dependencies = [ 469 | "cfg-if", 470 | "fuchsia-zircon", 471 | "fuchsia-zircon-sys", 472 | "iovec", 473 | "kernel32-sys", 474 | "libc", 475 | "log", 476 | "miow", 477 | "net2", 478 | "slab", 479 | "winapi 0.2.8", 480 | ] 481 | 482 | [[package]] 483 | name = "mio-extras" 484 | version = "2.0.6" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 487 | dependencies = [ 488 | "lazycell", 489 | "log", 490 | "mio", 491 | "slab", 492 | ] 493 | 494 | [[package]] 495 | name = "miow" 496 | version = "0.2.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 499 | dependencies = [ 500 | "kernel32-sys", 501 | "net2", 502 | "winapi 0.2.8", 503 | "ws2_32-sys", 504 | ] 505 | 506 | [[package]] 507 | name = "net2" 508 | version = "0.2.34" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 511 | dependencies = [ 512 | "cfg-if", 513 | "libc", 514 | "winapi 0.3.9", 515 | ] 516 | 517 | [[package]] 518 | name = "notify" 519 | version = "4.0.15" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" 522 | dependencies = [ 523 | "bitflags 1.2.1", 524 | "filetime", 525 | "fsevent", 526 | "fsevent-sys", 527 | "inotify", 528 | "libc", 529 | "mio", 530 | "mio-extras", 531 | "walkdir", 532 | "winapi 0.3.9", 533 | ] 534 | 535 | [[package]] 536 | name = "num-integer" 537 | version = "0.1.43" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 540 | dependencies = [ 541 | "autocfg", 542 | "num-traits", 543 | ] 544 | 545 | [[package]] 546 | name = "num-traits" 547 | version = "0.2.12" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 550 | dependencies = [ 551 | "autocfg", 552 | ] 553 | 554 | [[package]] 555 | name = "once_cell" 556 | version = "1.4.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 559 | 560 | [[package]] 561 | name = "pin-project" 562 | version = "0.4.23" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" 565 | dependencies = [ 566 | "pin-project-internal", 567 | ] 568 | 569 | [[package]] 570 | name = "pin-project-internal" 571 | version = "0.4.23" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" 574 | dependencies = [ 575 | "proc-macro2", 576 | "quote", 577 | "syn", 578 | ] 579 | 580 | [[package]] 581 | name = "pin-utils" 582 | version = "0.1.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 585 | 586 | [[package]] 587 | name = "ppv-lite86" 588 | version = "0.2.8" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 591 | 592 | [[package]] 593 | name = "proc-macro-hack" 594 | version = "0.5.18" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" 597 | 598 | [[package]] 599 | name = "proc-macro-nested" 600 | version = "0.1.6" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" 603 | 604 | [[package]] 605 | name = "proc-macro2" 606 | version = "1.0.19" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 609 | dependencies = [ 610 | "unicode-xid", 611 | ] 612 | 613 | [[package]] 614 | name = "pub-sub" 615 | version = "2.0.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "0fb2d6fe8cd1b1aacecb5ac1cfecdc38fdb834425626668b2de2bbd775d79dc0" 618 | dependencies = [ 619 | "uuid", 620 | ] 621 | 622 | [[package]] 623 | name = "quote" 624 | version = "1.0.7" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 627 | dependencies = [ 628 | "proc-macro2", 629 | ] 630 | 631 | [[package]] 632 | name = "rand" 633 | version = "0.7.3" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 636 | dependencies = [ 637 | "getrandom", 638 | "libc", 639 | "rand_chacha", 640 | "rand_core", 641 | "rand_hc", 642 | ] 643 | 644 | [[package]] 645 | name = "rand_chacha" 646 | version = "0.2.2" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 649 | dependencies = [ 650 | "ppv-lite86", 651 | "rand_core", 652 | ] 653 | 654 | [[package]] 655 | name = "rand_core" 656 | version = "0.5.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 659 | dependencies = [ 660 | "getrandom", 661 | ] 662 | 663 | [[package]] 664 | name = "rand_hc" 665 | version = "0.2.0" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 668 | dependencies = [ 669 | "rand_core", 670 | ] 671 | 672 | [[package]] 673 | name = "redox_syscall" 674 | version = "0.1.57" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 677 | 678 | [[package]] 679 | name = "redox_users" 680 | version = "0.3.4" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 683 | dependencies = [ 684 | "getrandom", 685 | "redox_syscall", 686 | "rust-argon2", 687 | ] 688 | 689 | [[package]] 690 | name = "regex" 691 | version = "1.3.9" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 694 | dependencies = [ 695 | "aho-corasick", 696 | "memchr", 697 | "regex-syntax", 698 | "thread_local", 699 | ] 700 | 701 | [[package]] 702 | name = "regex-automata" 703 | version = "0.1.9" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 706 | dependencies = [ 707 | "byteorder", 708 | ] 709 | 710 | [[package]] 711 | name = "regex-syntax" 712 | version = "0.6.18" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 715 | 716 | [[package]] 717 | name = "rorshach" 718 | version = "0.2.0" 719 | dependencies = [ 720 | "anyhow", 721 | "clap", 722 | "csv", 723 | "futures", 724 | "hotwatch", 725 | "log", 726 | "pub-sub", 727 | "regex", 728 | "serde", 729 | "shellexpand", 730 | "simple_logger", 731 | "strum", 732 | "strum_macros", 733 | ] 734 | 735 | [[package]] 736 | name = "rust-argon2" 737 | version = "0.7.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 740 | dependencies = [ 741 | "base64", 742 | "blake2b_simd", 743 | "constant_time_eq", 744 | "crossbeam-utils", 745 | ] 746 | 747 | [[package]] 748 | name = "ryu" 749 | version = "1.0.5" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 752 | 753 | [[package]] 754 | name = "same-file" 755 | version = "1.0.6" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 758 | dependencies = [ 759 | "winapi-util", 760 | ] 761 | 762 | [[package]] 763 | name = "serde" 764 | version = "1.0.114" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 767 | dependencies = [ 768 | "serde_derive", 769 | ] 770 | 771 | [[package]] 772 | name = "serde_derive" 773 | version = "1.0.114" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 776 | dependencies = [ 777 | "proc-macro2", 778 | "quote", 779 | "syn", 780 | ] 781 | 782 | [[package]] 783 | name = "shellexpand" 784 | version = "1.1.1" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "2c7e79eddc7b411f9beeaaf2d421de7e7cb3b1ab9eaf1b79704c0e4130cba6b5" 787 | dependencies = [ 788 | "dirs", 789 | ] 790 | 791 | [[package]] 792 | name = "simple_logger" 793 | version = "1.6.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "fea0c4611f32f4c2bac73754f22dca1f57e6c1945e0590dae4e5f2a077b92367" 796 | dependencies = [ 797 | "atty", 798 | "chrono", 799 | "colored", 800 | "log", 801 | "winapi 0.3.9", 802 | ] 803 | 804 | [[package]] 805 | name = "slab" 806 | version = "0.4.2" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 809 | 810 | [[package]] 811 | name = "strsim" 812 | version = "0.6.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 815 | 816 | [[package]] 817 | name = "strum" 818 | version = "0.18.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" 821 | 822 | [[package]] 823 | name = "strum_macros" 824 | version = "0.18.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" 827 | dependencies = [ 828 | "heck", 829 | "proc-macro2", 830 | "quote", 831 | "syn", 832 | ] 833 | 834 | [[package]] 835 | name = "syn" 836 | version = "1.0.36" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" 839 | dependencies = [ 840 | "proc-macro2", 841 | "quote", 842 | "unicode-xid", 843 | ] 844 | 845 | [[package]] 846 | name = "textwrap" 847 | version = "0.9.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 850 | dependencies = [ 851 | "unicode-width", 852 | ] 853 | 854 | [[package]] 855 | name = "thread_local" 856 | version = "1.0.1" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 859 | dependencies = [ 860 | "lazy_static", 861 | ] 862 | 863 | [[package]] 864 | name = "time" 865 | version = "0.1.43" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 868 | dependencies = [ 869 | "libc", 870 | "winapi 0.3.9", 871 | ] 872 | 873 | [[package]] 874 | name = "unicode-segmentation" 875 | version = "1.6.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 878 | 879 | [[package]] 880 | name = "unicode-width" 881 | version = "0.1.8" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 884 | 885 | [[package]] 886 | name = "unicode-xid" 887 | version = "0.2.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 890 | 891 | [[package]] 892 | name = "uuid" 893 | version = "0.8.1" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" 896 | dependencies = [ 897 | "rand", 898 | ] 899 | 900 | [[package]] 901 | name = "vec_map" 902 | version = "0.8.2" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 905 | 906 | [[package]] 907 | name = "walkdir" 908 | version = "2.3.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 911 | dependencies = [ 912 | "same-file", 913 | "winapi 0.3.9", 914 | "winapi-util", 915 | ] 916 | 917 | [[package]] 918 | name = "wasi" 919 | version = "0.9.0+wasi-snapshot-preview1" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 922 | 923 | [[package]] 924 | name = "winapi" 925 | version = "0.2.8" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 928 | 929 | [[package]] 930 | name = "winapi" 931 | version = "0.3.9" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 934 | dependencies = [ 935 | "winapi-i686-pc-windows-gnu", 936 | "winapi-x86_64-pc-windows-gnu", 937 | ] 938 | 939 | [[package]] 940 | name = "winapi-build" 941 | version = "0.1.1" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 944 | 945 | [[package]] 946 | name = "winapi-i686-pc-windows-gnu" 947 | version = "0.4.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 950 | 951 | [[package]] 952 | name = "winapi-util" 953 | version = "0.1.5" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 956 | dependencies = [ 957 | "winapi 0.3.9", 958 | ] 959 | 960 | [[package]] 961 | name = "winapi-x86_64-pc-windows-gnu" 962 | version = "0.4.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 965 | 966 | [[package]] 967 | name = "ws2_32-sys" 968 | version = "0.2.1" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 971 | dependencies = [ 972 | "winapi 0.2.8", 973 | "winapi-build", 974 | ] 975 | 976 | [[package]] 977 | name = "yaml-rust" 978 | version = "0.3.5" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" 981 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rorshach" 3 | version = "0.2.0" 4 | authors = ["Sam Radhakrishnan "] 5 | edition = "2018" 6 | description = "A wacthman for directories" 7 | license = "MIT" 8 | keywords = ["file-io", "file-watcher", "inotify", "utility-tools", "watchdog"] 9 | categories = ["command-line-utilities", "filesystem"] 10 | 11 | [dependencies] 12 | clap = {version = "~2.27.0", features = ["yaml"]} 13 | hotwatch = "0.4.3" 14 | csv = "1.1.3" 15 | serde = { version = "1.0", features = ["derive"] } 16 | strum = "0.18.0" 17 | strum_macros = "0.18.0" 18 | shellexpand = "1.0.0" 19 | regex = "1" 20 | log = "0.4.11" 21 | simple_logger = "1.6.0" 22 | pub-sub = "2.0.0" 23 | anyhow = "1.0" 24 | futures = "0.3" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sam Radhakrishnan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 3 | ![Build](https://github.com/sam09/rorshach/workflows/Build/badge.svg) 4 | 5 | # Rorshach ![rorschach.gif](http://www.gomotes.com/emoticon/rorschach.gif) 6 | 7 | A watchman for your directories. Rorshach allows you to watch your directories for changes and trigger certain commands when these events occur. 8 | 9 | ### Installlation 10 | 11 | At the moment the only way to install is from crates.io (https://crates.io/crates/rorshach). 12 | Run `cargo install rorshach` to install it on a linux machine. 13 | 14 | ### Usage 15 | 16 | * `./rorshach -f [-config ] [-t ]` 17 | 18 | The default config file lies at `~/.rorshach.conf` 19 | 20 | 21 | ### Config 22 | 23 | Configuration file is a tab separated values (TSV) file, and has the following format: 24 | 25 | ``` 26 | EVENT PATTERN ACTION 27 | ... ... ... 28 | ``` 29 | #### Sample Configuration File 30 | Please ensure you have physical TAB characters as delimiters. If you are using vim, you can use `:set list` to show invisible characters. It would look like this: 31 | 32 | ![image](https://user-images.githubusercontent.com/30809170/123977658-2fc52f00-d98d-11eb-9223-0f06b6d46bf1.png) 33 | 34 | Otherwise, you will receive an error during start. 35 | 36 | ### Events 37 | 38 | `EVENTS` can be `CREATE`, `DELETE` `RENAME` or `MODIFY`. Each event is trigger when a file in the directory being watched is CREATED, DELETED, MODIFIED or RENAMED respectively. 39 | `PATTERNS` are patterns to match the files in a directory. Example `*.cpp` matches all the C++ files in a directory. 40 | `ACTIONS` are commands that can be executed when a `EVENT` occures 41 | 42 | There are following environment variable available while executing an action, they are :- 43 | `{FULLPATH}` - Full path to the file, 44 | `{BASEDIR}` - Path to the directory that rorshach is watching 45 | `{NEWFULLPATH}` - New Path to the file, when a file is renamed else empty. 46 | 47 | 48 | ### Examples 49 | 50 | ``` 51 | CREATE * echo " New file named ${FULLPATH} created" 52 | ``` 53 | 54 | The above will print a line of the form `New file named created` every time a new file is created. 55 | 56 | 57 | ``` 58 | MODIFY *.cpp g++ ${FULLPATH} {BASEDIR}/test 59 | ``` 60 | 61 | Whenver a change is detected in a c++ file, `rorshach` will compile that file and create an executable named `test` in the base directory 62 | 63 | 64 | ### TODO 65 | - [x] Add more events to listen like `Rename` 66 | - [ ] Support execution of a chain of commands for a single event 67 | - [ ] Move Command Line passing to a different struct 68 | - [ ] ~Add a threadpool to execute each task once an event is spawned~ 69 | - [x] Add a pub-sub mechanism for events 70 | - [ ] Add Tests? 71 | - [x] Move parse_rules to an enclosing struct 72 | - [x] Use `log` create for logging. 73 | - [x] Provide better messages for errors. 74 | - [x] Add a pub sub mechanism to listening to events and consuming them 75 | -------------------------------------------------------------------------------- /src/cli.yml: -------------------------------------------------------------------------------- 1 | name: rorshach 2 | version: "1.0" 3 | author: Sam Radhakrishnan 4 | about: A watchmen for your directories. 5 | args: 6 | - config: 7 | short: c 8 | long: config 9 | value_name: CONFIG 10 | help: Sets a custom config file 11 | takes_value: true 12 | - time: 13 | help: Sets the time period to reload 14 | long: time 15 | value_name: TIME 16 | short: -t 17 | - file: 18 | help: Filepath of the dir/ file to watch 19 | long: file 20 | short: f 21 | value_name: FILE 22 | required: true 23 | - verbose: 24 | short: v 25 | multiple: true 26 | help: Sets the level of verbosity 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use shellexpand; 2 | use clap::{App, load_yaml}; 3 | use std::time::Duration; 4 | use hotwatch::blocking::{Flow, Hotwatch}; 5 | use anyhow::{Context, Result}; 6 | 7 | use simple_logger::init_with_level; 8 | use log::error; 9 | 10 | mod rorshach; 11 | use crate::rorshach::rule_parser::RuleParser; 12 | use crate::rorshach::executor::Executor; 13 | 14 | fn main() -> Result<()> { 15 | init_with_level(log::Level::Info).context("Failed to initliaze logger")?; 16 | let yaml = load_yaml!("cli.yml"); 17 | let matches = App::from_yaml(yaml).get_matches(); 18 | let default_config = &shellexpand::tilde("~/.rorshach.conf"); 19 | let config = matches.value_of("config").unwrap_or(default_config); 20 | let time = matches.value_of("time").unwrap_or("1").parse::().context("failed to parse Time variable from command line")?; 21 | let dir = matches.value_of("file").context("No file specified")?; 22 | let duration = Duration::new(time, 0); 23 | let mut rules = RuleParser::new(); 24 | rules.parse_rules(config).context("Error occurred parsing rules")?; 25 | 26 | let dir_string = dir.to_string(); 27 | let mut hotwatch = match Hotwatch::new_with_custom_delay(duration) { 28 | Err(e) => { 29 | error!("Error occured created watcher {}", e); 30 | std::process::exit(1); 31 | }, 32 | Ok(v) => { 33 | v 34 | } 35 | }; 36 | 37 | let executor = Executor::new(dir_string, rules); 38 | 39 | hotwatch.watch(&dir, move |event| { 40 | executor.run(&event); 41 | Flow::Continue 42 | }).context("Failed to initalize watcher")?; 43 | 44 | hotwatch.run(); 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /src/rorshach.rs: -------------------------------------------------------------------------------- 1 | pub use self::rule::Rule; 2 | pub use self::event::Event; 3 | pub use self::event_type::EventType; 4 | pub use self::producer::Producer; 5 | pub use self::consumer::Consumer; 6 | pub use self::rule_parser::RuleParser; 7 | pub use self::executor::Executor; 8 | pub mod rule; 9 | pub mod event; 10 | pub mod event_type; 11 | pub mod rule_parser; 12 | pub mod executor; 13 | pub mod producer; 14 | pub mod consumer; -------------------------------------------------------------------------------- /src/rorshach/consumer.rs: -------------------------------------------------------------------------------- 1 | use pub_sub::Subscription; 2 | use crate::rorshach::event::Event; 3 | use crate::rorshach::rule::Rule; 4 | use regex::Regex; 5 | use log::error; 6 | use std::process::Command; 7 | use anyhow::{Result, Context, bail}; 8 | 9 | pub struct Consumer { 10 | receiver: Subscription, 11 | rule: Rule, 12 | dir: String, 13 | } 14 | 15 | impl Consumer { 16 | 17 | pub fn new(receiver: Subscription, rule: &Rule, dir: String) -> Self { 18 | Consumer{ receiver, rule: rule.clone(), dir} 19 | } 20 | 21 | fn exec_rule(&self, event: &Event) -> Result<()> { 22 | let old_path_str = match event.get_old_path() { 23 | Some(path) => path.to_string_lossy().to_string(), 24 | None => bail!("No path found for event: {}", &event), 25 | }; 26 | 27 | let new_path_str = match event.get_new_path() { 28 | Some(path) => path.to_string_lossy().to_string(), 29 | None => "".to_string(), 30 | }; 31 | 32 | let re_str = format!("^{}$", self.rule.get_file_pattern()); 33 | let re = Regex::new(&re_str).with_context(|| format!("Invalid pattern {}", re_str))?; 34 | if re.is_match(&old_path_str) { 35 | Command::new("sh") 36 | .arg("-c") 37 | .arg(self.rule.get_cmd()) 38 | .env("FULLPATH", &old_path_str) 39 | .env("NEWFULLPATH", &new_path_str) 40 | .env("BASEDIR", &self.dir) 41 | .spawn() 42 | .with_context(|| format!("Spawning command {} on {} failed", self.rule.get_cmd(), &old_path_str))?; 43 | } 44 | Ok(()) 45 | } 46 | 47 | pub async fn consume(&self) { 48 | match self.receiver.recv() { 49 | Ok(event) => { 50 | if event.get_event_type() == self.rule.get_event_type() { 51 | if let Err(e) = self.exec_rule(&event) { 52 | error!("Error executing {} for {}: {}", self.rule, &event, e); 53 | } 54 | } 55 | }, 56 | Err(err) => { 57 | error!("Error occurred while consuming event: {}", err) 58 | } 59 | }; 60 | } 61 | } -------------------------------------------------------------------------------- /src/rorshach/event.rs: -------------------------------------------------------------------------------- 1 | use crate::rorshach::event_type::EventType; 2 | use std::path::PathBuf; 3 | use std::fmt; 4 | 5 | #[derive(Clone)] 6 | pub struct Event { 7 | event_type: EventType, 8 | old_path: Option, 9 | new_path: Option, 10 | } 11 | 12 | impl Event { 13 | pub fn new(event_type: EventType, old_path: Option, new_path: Option) -> Self { 14 | Event{event_type, old_path, new_path} 15 | } 16 | 17 | pub fn get_event_type(&self) -> EventType { 18 | self.event_type 19 | } 20 | 21 | pub fn get_old_path(&self) -> &Option { 22 | &self.old_path 23 | } 24 | 25 | pub fn get_new_path(&self) -> &Option { 26 | &self.new_path 27 | } 28 | } 29 | 30 | impl fmt::Display for Event { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | write!(f, "{:?} {:?} {:?}", self.event_type, self.old_path, self.new_path) 33 | } 34 | } -------------------------------------------------------------------------------- /src/rorshach/event_type.rs: -------------------------------------------------------------------------------- 1 | use strum_macros::{EnumString, Display}; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, PartialEq, Deserialize, Copy, Clone, EnumString, Display)] 5 | pub enum EventType { 6 | CREATE, 7 | MODIFY, 8 | DELETE, 9 | RENAME, 10 | UNSUPPORTED, 11 | } 12 | -------------------------------------------------------------------------------- /src/rorshach/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::rorshach::event::Event; 2 | use crate::rorshach::event_type::EventType; 3 | use crate::rorshach::rule_parser::RuleParser; 4 | use crate::rorshach::producer::Producer; 5 | use crate::rorshach::consumer::Consumer; 6 | use hotwatch::Event as FileEvent; 7 | use log::info; 8 | use pub_sub::PubSub; 9 | use futures::{join, future::join_all, executor::block_on}; 10 | 11 | pub struct Executor { 12 | producer: Producer, 13 | consumers: Vec, 14 | } 15 | 16 | impl Executor { 17 | 18 | pub fn new(dir: String, rules: RuleParser) -> Self { 19 | let channel = PubSub::new(); 20 | let producer = Producer::new(channel.clone()); 21 | let mut consumers = Vec::::new(); 22 | for rule in rules.get_rules() { 23 | consumers.push(Consumer::new(channel.subscribe().clone(), rule, dir.clone())) 24 | } 25 | Executor{producer, consumers} 26 | } 27 | 28 | async fn produce(&self, file_event: &FileEvent) { 29 | match file_event { 30 | FileEvent::Create(path) => { 31 | info!("File {} created", path.display()); 32 | self.producer.send(Event::new(EventType::CREATE, Some(path.to_path_buf()), None)); 33 | }, 34 | FileEvent::Write(path) => { 35 | info!("File {} changed", path.display()); 36 | self.producer.send(Event::new(EventType::MODIFY, Some(path.to_path_buf()), None)); 37 | }, 38 | FileEvent::Rename(old_path, new_path) => { 39 | self.producer.send( 40 | Event::new(EventType::RENAME, Some(old_path.to_path_buf()),Some(new_path.to_path_buf())) 41 | ); 42 | info!("{} renamed to {}", old_path.display(), new_path.display()); 43 | }, 44 | FileEvent::Remove(path) => { 45 | self.producer.send(Event::new(EventType::DELETE, Some(path.to_path_buf()), None)); 46 | info!("File {} deleted", path.display()); 47 | }, 48 | _ => self.producer.send(Event::new(EventType::UNSUPPORTED, None, None)), 49 | }; 50 | } 51 | 52 | async fn start_consumers(&self) { 53 | let mut v = Vec::new(); 54 | for consumer in &self.consumers { 55 | v.push(consumer.consume()); 56 | } 57 | join_all(v).await; 58 | } 59 | 60 | async fn async_run(&self, file_event: &FileEvent) { 61 | join!(self.produce(file_event), self.start_consumers()); 62 | } 63 | 64 | pub fn run(&self, file_event: &FileEvent) { 65 | block_on(self.async_run(file_event)) 66 | } 67 | } -------------------------------------------------------------------------------- /src/rorshach/producer.rs: -------------------------------------------------------------------------------- 1 | use pub_sub::PubSub; 2 | use crate::rorshach::event::Event; 3 | use log::{error, debug}; 4 | 5 | pub struct Producer { 6 | sender: PubSub 7 | } 8 | 9 | impl Producer { 10 | 11 | pub fn new(sender: PubSub ) -> Self { 12 | Producer{ sender} 13 | } 14 | 15 | pub fn send(&self, event: Event) { 16 | match self.sender.send(event) { 17 | Err(err) => { 18 | error!("Error occurred sending event: {}", err); 19 | }, 20 | Ok(_) => debug!("Successfully sent event") 21 | }; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/rorshach/rule.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::fmt; 3 | use crate::rorshach::event_type::EventType; 4 | 5 | #[derive(Debug, Deserialize, Clone)] 6 | pub struct Rule { 7 | event_type: EventType, 8 | file_pattern: String, 9 | cmd: String, 10 | } 11 | 12 | impl fmt::Display for Rule { 13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 14 | write!(f, "{:?} {:?} {:?}", self.event_type, self.file_pattern, self.cmd) 15 | } 16 | } 17 | 18 | impl Rule { 19 | pub fn get_cmd(&self) -> &str { 20 | &self.cmd 21 | } 22 | pub fn get_file_pattern(&self) -> &str { 23 | &self.file_pattern 24 | } 25 | pub fn get_event_type(&self) -> EventType { 26 | self.event_type 27 | } 28 | } -------------------------------------------------------------------------------- /src/rorshach/rule_parser.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::rorshach::rule::Rule; 3 | use std::fs::File; 4 | use anyhow::{Result, Context}; 5 | 6 | pub struct RuleParser { 7 | rules: Vec, 8 | } 9 | 10 | impl RuleParser { 11 | 12 | pub fn new() -> Self { 13 | RuleParser { 14 | rules: Vec::new(), 15 | } 16 | } 17 | 18 | pub fn parse_rules(&mut self, config_path: &str) -> Result<()> { 19 | let file = File::open(config_path).with_context(|| format!("No such file or directory {}", &config_path))?; 20 | let mut csv_file = csv::ReaderBuilder::new().has_headers(false).delimiter(b'\t').from_reader(file); 21 | 22 | for record in csv_file.deserialize() { 23 | let rule: Rule = record.with_context(|| format!("Invalid file syntax in config file"))?; 24 | self.rules.push(rule); 25 | } 26 | 27 | Ok(()) 28 | } 29 | 30 | pub fn get_rules(&self) -> &Vec { 31 | &self.rules 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /test/integration/.rorshach.conf: -------------------------------------------------------------------------------- 1 | CREATE * echo "CREATED ${FULLPATH}" >> ${BASEDIR}/../output 2 | MODIFY *.cpp g++ ${FULLPATH} -o ${BASEDIR}/test 3 | DELETE * echo "DELETED ${FULLPATH}" >> ${BASEDIR}/../output 4 | RENAME * echo "RENAMED ${FULLPATH} ${NEWFULLPATH}" >> ${BASEDIR}/../output 5 | -------------------------------------------------------------------------------- /test/integration/golden.output: -------------------------------------------------------------------------------- 1 | CREATED /home/sam/code/rorshach/test/integration/test_data/new 2 | DELETED /home/sam/code/rorshach/test/integration/test_data/new 3 | CREATED /home/sam/code/rorshach/test/integration/test_data/test 4 | DELETED /home/sam/code/rorshach/test/integration/test_data/test 5 | RENAMED /home/sam/code/rorshach/test/integration/test_data/test.cpp /home/sam/code/rorshach/test/integration/test_data/temp.cpp 6 | CREATED /home/sam/code/rorshach/test/integration/test_data/test.cpp 7 | DELETED /home/sam/code/rorshach/test/integration/test_data/temp.cpp 8 | -------------------------------------------------------------------------------- /test/integration/start_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo build 4 | output="test/integration/output" 5 | golden="test/integration/golden.output" 6 | rm $output 7 | touch $output 8 | bin_path="./target/debug/rorshach" 9 | 10 | test_dir="./test/integration/test_data" 11 | config_file="./test/integration/.rorshach.conf" 12 | 13 | 14 | $bin_path -f $test_dir -c $config_file & 15 | pid="$!" 16 | sleep 2 17 | 18 | touch $test_dir/new 19 | sleep 2 20 | 21 | rm $test_dir/new 22 | sleep 2 23 | 24 | echo "}" >> $test_dir/test.cpp 25 | sleep 5 26 | 27 | rm $test_dir/test 28 | sleep 2 29 | 30 | mv $test_dir/test.cpp $test_dir/temp.cpp 31 | sleep 2 32 | 33 | sed '$d' $test_dir/temp.cpp > $test_dir/test.cpp 34 | sleep 2 35 | 36 | rm $test_dir/temp.cpp 37 | sleep 4 38 | 39 | kill -9 $pid 40 | cmp --silent $golden $output || echo "Test Failed" 41 | -------------------------------------------------------------------------------- /test/integration/test_data/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int main() { 5 | cout<<"Hello, World!\n"; 6 | --------------------------------------------------------------------------------