├── .envrc ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── default.nix ├── flake.lock ├── flake.nix ├── images └── scrot.png └── src ├── args.rs ├── display.rs ├── hist_file.rs └── main.rs /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 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@v3 19 | - name: Run check 20 | run: cargo check --verbose 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .direnv 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.1.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.3.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 25 | 26 | [[package]] 27 | name = "cc" 28 | version = "1.0.78" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 31 | 32 | [[package]] 33 | name = "cfg-if" 34 | version = "1.0.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 37 | 38 | [[package]] 39 | name = "clap" 40 | version = "4.0.32" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" 43 | dependencies = [ 44 | "bitflags", 45 | "clap_derive", 46 | "clap_lex", 47 | "is-terminal", 48 | "once_cell", 49 | "strsim", 50 | "termcolor", 51 | ] 52 | 53 | [[package]] 54 | name = "clap_derive" 55 | version = "4.0.21" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 58 | dependencies = [ 59 | "heck", 60 | "proc-macro-error", 61 | "proc-macro2", 62 | "quote", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "clap_lex" 68 | version = "0.3.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 71 | dependencies = [ 72 | "os_str_bytes", 73 | ] 74 | 75 | [[package]] 76 | name = "crossterm" 77 | version = "0.26.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" 80 | dependencies = [ 81 | "bitflags", 82 | "crossterm_winapi", 83 | "libc", 84 | "mio", 85 | "parking_lot", 86 | "signal-hook", 87 | "signal-hook-mio", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "crossterm_winapi" 93 | version = "0.9.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" 96 | dependencies = [ 97 | "winapi", 98 | ] 99 | 100 | [[package]] 101 | name = "errno" 102 | version = "0.2.8" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 105 | dependencies = [ 106 | "errno-dragonfly", 107 | "libc", 108 | "winapi", 109 | ] 110 | 111 | [[package]] 112 | name = "errno-dragonfly" 113 | version = "0.1.2" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 116 | dependencies = [ 117 | "cc", 118 | "libc", 119 | ] 120 | 121 | [[package]] 122 | name = "heck" 123 | version = "0.4.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 126 | 127 | [[package]] 128 | name = "hermit-abi" 129 | version = "0.2.6" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 132 | dependencies = [ 133 | "libc", 134 | ] 135 | 136 | [[package]] 137 | name = "io-lifetimes" 138 | version = "1.0.3" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" 141 | dependencies = [ 142 | "libc", 143 | "windows-sys 0.42.0", 144 | ] 145 | 146 | [[package]] 147 | name = "is-terminal" 148 | version = "0.4.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 151 | dependencies = [ 152 | "hermit-abi", 153 | "io-lifetimes", 154 | "rustix", 155 | "windows-sys 0.42.0", 156 | ] 157 | 158 | [[package]] 159 | name = "libc" 160 | version = "0.2.139" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 163 | 164 | [[package]] 165 | name = "linux-raw-sys" 166 | version = "0.1.4" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 169 | 170 | [[package]] 171 | name = "lock_api" 172 | version = "0.4.9" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 175 | dependencies = [ 176 | "autocfg", 177 | "scopeguard", 178 | ] 179 | 180 | [[package]] 181 | name = "log" 182 | version = "0.4.17" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 185 | dependencies = [ 186 | "cfg-if", 187 | ] 188 | 189 | [[package]] 190 | name = "memchr" 191 | version = "2.5.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 194 | 195 | [[package]] 196 | name = "mio" 197 | version = "0.8.6" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 200 | dependencies = [ 201 | "libc", 202 | "log", 203 | "wasi", 204 | "windows-sys 0.45.0", 205 | ] 206 | 207 | [[package]] 208 | name = "muc" 209 | version = "0.1.0" 210 | dependencies = [ 211 | "clap", 212 | "crossterm", 213 | "regex", 214 | "utf8_slice", 215 | ] 216 | 217 | [[package]] 218 | name = "once_cell" 219 | version = "1.16.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 222 | 223 | [[package]] 224 | name = "os_str_bytes" 225 | version = "6.4.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 228 | 229 | [[package]] 230 | name = "parking_lot" 231 | version = "0.12.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 234 | dependencies = [ 235 | "lock_api", 236 | "parking_lot_core", 237 | ] 238 | 239 | [[package]] 240 | name = "parking_lot_core" 241 | version = "0.9.7" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 244 | dependencies = [ 245 | "cfg-if", 246 | "libc", 247 | "redox_syscall", 248 | "smallvec", 249 | "windows-sys 0.45.0", 250 | ] 251 | 252 | [[package]] 253 | name = "proc-macro-error" 254 | version = "1.0.4" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 257 | dependencies = [ 258 | "proc-macro-error-attr", 259 | "proc-macro2", 260 | "quote", 261 | "syn", 262 | "version_check", 263 | ] 264 | 265 | [[package]] 266 | name = "proc-macro-error-attr" 267 | version = "1.0.4" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 270 | dependencies = [ 271 | "proc-macro2", 272 | "quote", 273 | "version_check", 274 | ] 275 | 276 | [[package]] 277 | name = "proc-macro2" 278 | version = "1.0.49" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 281 | dependencies = [ 282 | "unicode-ident", 283 | ] 284 | 285 | [[package]] 286 | name = "quote" 287 | version = "1.0.23" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 290 | dependencies = [ 291 | "proc-macro2", 292 | ] 293 | 294 | [[package]] 295 | name = "redox_syscall" 296 | version = "0.2.16" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 299 | dependencies = [ 300 | "bitflags", 301 | ] 302 | 303 | [[package]] 304 | name = "regex" 305 | version = "1.7.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 308 | dependencies = [ 309 | "aho-corasick", 310 | "memchr", 311 | "regex-syntax", 312 | ] 313 | 314 | [[package]] 315 | name = "regex-syntax" 316 | version = "0.6.28" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 319 | 320 | [[package]] 321 | name = "rustix" 322 | version = "0.36.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" 325 | dependencies = [ 326 | "bitflags", 327 | "errno", 328 | "io-lifetimes", 329 | "libc", 330 | "linux-raw-sys", 331 | "windows-sys 0.42.0", 332 | ] 333 | 334 | [[package]] 335 | name = "scopeguard" 336 | version = "1.1.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 339 | 340 | [[package]] 341 | name = "signal-hook" 342 | version = "0.3.15" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" 345 | dependencies = [ 346 | "libc", 347 | "signal-hook-registry", 348 | ] 349 | 350 | [[package]] 351 | name = "signal-hook-mio" 352 | version = "0.2.3" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 355 | dependencies = [ 356 | "libc", 357 | "mio", 358 | "signal-hook", 359 | ] 360 | 361 | [[package]] 362 | name = "signal-hook-registry" 363 | version = "1.4.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 366 | dependencies = [ 367 | "libc", 368 | ] 369 | 370 | [[package]] 371 | name = "smallvec" 372 | version = "1.10.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 375 | 376 | [[package]] 377 | name = "strsim" 378 | version = "0.10.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 381 | 382 | [[package]] 383 | name = "syn" 384 | version = "1.0.107" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 387 | dependencies = [ 388 | "proc-macro2", 389 | "quote", 390 | "unicode-ident", 391 | ] 392 | 393 | [[package]] 394 | name = "termcolor" 395 | version = "1.1.3" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 398 | dependencies = [ 399 | "winapi-util", 400 | ] 401 | 402 | [[package]] 403 | name = "unicode-ident" 404 | version = "1.0.6" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 407 | 408 | [[package]] 409 | name = "utf8_slice" 410 | version = "1.0.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "44109e280ecf0f5d8e6ee671c0ee650008275c51dc9e494badd11fb39d785408" 413 | 414 | [[package]] 415 | name = "version_check" 416 | version = "0.9.4" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 419 | 420 | [[package]] 421 | name = "wasi" 422 | version = "0.11.0+wasi-snapshot-preview1" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 425 | 426 | [[package]] 427 | name = "winapi" 428 | version = "0.3.9" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 431 | dependencies = [ 432 | "winapi-i686-pc-windows-gnu", 433 | "winapi-x86_64-pc-windows-gnu", 434 | ] 435 | 436 | [[package]] 437 | name = "winapi-i686-pc-windows-gnu" 438 | version = "0.4.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 441 | 442 | [[package]] 443 | name = "winapi-util" 444 | version = "0.1.5" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 447 | dependencies = [ 448 | "winapi", 449 | ] 450 | 451 | [[package]] 452 | name = "winapi-x86_64-pc-windows-gnu" 453 | version = "0.4.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 456 | 457 | [[package]] 458 | name = "windows-sys" 459 | version = "0.42.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 462 | dependencies = [ 463 | "windows_aarch64_gnullvm", 464 | "windows_aarch64_msvc", 465 | "windows_i686_gnu", 466 | "windows_i686_msvc", 467 | "windows_x86_64_gnu", 468 | "windows_x86_64_gnullvm", 469 | "windows_x86_64_msvc", 470 | ] 471 | 472 | [[package]] 473 | name = "windows-sys" 474 | version = "0.45.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 477 | dependencies = [ 478 | "windows-targets", 479 | ] 480 | 481 | [[package]] 482 | name = "windows-targets" 483 | version = "0.42.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 486 | dependencies = [ 487 | "windows_aarch64_gnullvm", 488 | "windows_aarch64_msvc", 489 | "windows_i686_gnu", 490 | "windows_i686_msvc", 491 | "windows_x86_64_gnu", 492 | "windows_x86_64_gnullvm", 493 | "windows_x86_64_msvc", 494 | ] 495 | 496 | [[package]] 497 | name = "windows_aarch64_gnullvm" 498 | version = "0.42.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 501 | 502 | [[package]] 503 | name = "windows_aarch64_msvc" 504 | version = "0.42.2" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 507 | 508 | [[package]] 509 | name = "windows_i686_gnu" 510 | version = "0.42.2" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 513 | 514 | [[package]] 515 | name = "windows_i686_msvc" 516 | version = "0.42.2" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 519 | 520 | [[package]] 521 | name = "windows_x86_64_gnu" 522 | version = "0.42.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 525 | 526 | [[package]] 527 | name = "windows_x86_64_gnullvm" 528 | version = "0.42.2" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 531 | 532 | [[package]] 533 | name = "windows_x86_64_msvc" 534 | version = "0.42.2" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 537 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "muc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.0.32", features = ["derive"] } 8 | utf8_slice = "1.0.0" 9 | regex = "1.7.0" 10 | crossterm = "0.26.1" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The GPLv3 License (GPLv3) 2 | 3 | Copyright (c) 2022 Nathan Dawit 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MUC 2 | Visualize your most used commands 3 | 4 | ![scrot](images/scrot.png) 5 | 6 | ## Usage 7 | 8 | ### Installing 9 | 10 | #### Arch users (AUR) 11 | 12 | Use your favorite AUR helper to install [muc-git](https://aur.archlinux.org/packages/muc-git) package (or build manually using `git` and `makepkg -si`), for example: `paru -S muc-git` 13 | 14 | #### Nix 15 | 16 | You can use the outputs provided by the `flake.nix` inside this repository to install `muc`. Either with the `overlays.default` output for your system configuration, or the package output to imperatively install it with `nix install github:nate-sys/muc` or create an ad-hoc shell with `nix shell github:nate-sys/muc`. 17 | 18 | To quicky run muc use following command. 19 | ```sh 20 | nix run github:nate-sys/muc 21 | ``` 22 | 23 | #### Other distros 24 | 25 | ```sh 26 | cargo install --git=https://github.com/nate-sys/muc 27 | ``` 28 | 29 | ### Running 30 | 31 | muc uses your $HISTFILE environment variable to get your history 32 | ```sh 33 | muc # Bash or Vanilla zsh 34 | muc --shell ohmyzsh # ohmyzsh 35 | muc --shell fish # Fish 36 | muc --regexp # parse the histfile yourself (this overrides shell) 37 | 38 | muc -c 5 # show top 5 instead of the default 10 39 | 40 | muc --bar "=,*,-,=" # change the appearance of the bar =*****-----= 41 | ``` 42 | 43 | 44 | ### Roadmap 45 | - [X] Colors 46 | - [X] Customizable bar 47 | - [ ] Resolve aliases 48 | - [ ] Recognize leader commands (sudo, doas, git, etc) 49 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { 2 | src = builtins.fetchGit ./.; 3 | }).defaultNix 4 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1668681692, 7 | "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "009399224d5e398d03b22badca40a37ac85412a1", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "locked": { 21 | "lastModified": 1667395993, 22 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 23 | "owner": "numtide", 24 | "repo": "flake-utils", 25 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "type": "github" 32 | } 33 | }, 34 | "nixpkgs": { 35 | "locked": { 36 | "lastModified": 1671722432, 37 | "narHash": "sha256-ojcZUekIQeOZkHHzR81st7qxX99dB1Eaaq6PU5MNeKc=", 38 | "owner": "nixos", 39 | "repo": "nixpkgs", 40 | "rev": "652e92b8064949a11bc193b90b74cb727f2a1405", 41 | "type": "github" 42 | }, 43 | "original": { 44 | "owner": "nixos", 45 | "ref": "nixos-unstable", 46 | "repo": "nixpkgs", 47 | "type": "github" 48 | } 49 | }, 50 | "root": { 51 | "inputs": { 52 | "flake-compat": "flake-compat", 53 | "flake-utils": "flake-utils", 54 | "nixpkgs": "nixpkgs" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | flake-compat = { 6 | url = "github:edolstra/flake-compat"; 7 | flake = false; 8 | }; 9 | 10 | }; 11 | 12 | outputs = { self, nixpkgs, flake-utils, flake-compat }: { 13 | overlays.default = _: prev: 14 | let 15 | inherit (prev.rustPlatform) buildRustPackage; 16 | toml = builtins.fromTOML (builtins.readFile ./Cargo.toml); 17 | in 18 | { 19 | muc = buildRustPackage { 20 | pname = "muc"; 21 | src = self; 22 | inherit (toml.package) version; 23 | cargoHash = "sha256-w/b4qps4z1HrtSlSklflU6i1QfSFQw20+uALIpCIk8I="; 24 | }; 25 | }; 26 | } // 27 | (flake-utils.lib.eachDefaultSystem (system: 28 | let 29 | pkgs = import nixpkgs { 30 | inherit system; 31 | overlays = [ self.overlays.default ]; 32 | }; 33 | inherit (pkgs) muc; 34 | in 35 | { 36 | packages = { 37 | inherit muc; 38 | default = muc; 39 | }; 40 | })); 41 | } 42 | -------------------------------------------------------------------------------- /images/scrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthnd/muc/d8336bc2f387c79b33c87cf2aba28d3c93486af2/images/scrot.png -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use std::{str::FromStr, path::PathBuf}; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Parser, Debug)] 6 | #[command(author, version, about, long_about = None)] 7 | pub struct Args { 8 | /// The path to the file to be parsed 9 | #[arg(short, long)] 10 | pub file: Option, 11 | 12 | /// Display top n commands 13 | #[arg(short, long, default_value_t = 10)] 14 | pub count: usize, 15 | 16 | /// Show debug messages 17 | #[arg(long, default_value_t = false)] 18 | pub debug: bool, 19 | 20 | /// Change how the bar looks --bar [,▮, ,] 21 | #[arg(long, default_value_t = Default::default())] 22 | pub bar: Bar, 23 | 24 | 25 | /// Preset regular expressions for common shells: Bash, ZSH, Fish. 26 | #[arg(long, default_value_t = String::from(""))] 27 | pub shell: String, 28 | 29 | /// Regular expression to allow for the removal of prefixes in shells like zsh. Default value is for zsh. NOTE: overrides the shell arg 30 | #[arg(short, long, default_value_t = String::from(""))] 31 | pub regexp: String, 32 | } 33 | 34 | #[derive(Debug, Clone)] 35 | pub struct Bar { 36 | pub opening: String, 37 | pub closing: String, 38 | pub fill: String, 39 | pub empty: String, 40 | } 41 | impl Default for Bar { 42 | fn default() -> Self { 43 | Bar { 44 | opening: "[".to_owned(), 45 | fill: "▮".to_owned(), 46 | empty: " ".to_owned(), 47 | closing: "]".to_owned(), 48 | } 49 | } 50 | } 51 | 52 | impl FromStr for Bar { 53 | type Err = String; 54 | 55 | fn from_str(s: &str) -> Result { 56 | let chars = s.split(',').collect::>(); 57 | match chars.len() { 58 | 4 => Ok(Bar { 59 | opening: chars[0].to_string(), 60 | fill: chars[1].to_string(), 61 | empty: chars[2].to_string(), 62 | closing: chars[3].to_string(), 63 | }), 64 | _ => Err("Invalid bar length".to_string()), 65 | } 66 | } 67 | } 68 | 69 | impl std::fmt::Display for Bar { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | write!( 72 | f, 73 | "{},{},{},{}", 74 | self.opening, self.fill, self.empty, self.closing 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use crossterm::{ 2 | execute, queue, 3 | style::{Color, Print, PrintStyledContent, ResetColor, SetForegroundColor, Stylize}, 4 | }; 5 | use std::{ 6 | collections::{BTreeMap, HashMap}, 7 | io::{stdout, Stdout, Write}, 8 | usize, 9 | }; 10 | use utf8_slice::slice; 11 | 12 | use crate::{hist_file::CommandMap, Args}; 13 | 14 | type VeryComplexType = (String, Option, HashMap); 15 | pub fn print(data: CommandMap, args: Args) { 16 | let tree: BTreeMap = data 17 | .into_iter() 18 | .map(|(s, (f, o, h))| (f, (s, o, h))) 19 | .collect(); 20 | 21 | let total: usize = tree.keys().sum(); 22 | if total == 0 { 23 | println!("No commands found"); 24 | return; 25 | } 26 | let max = *tree.last_key_value().unwrap().0; 27 | 28 | let reversed_tree: Vec<(usize, VeryComplexType)> = tree.into_iter().rev().collect(); 29 | let limited_tree = reversed_tree[..(usize::min(args.count, reversed_tree.len()))].to_vec(); 30 | 31 | let mut stdout = stdout(); 32 | 33 | for (freq, elem) in limited_tree.iter() { 34 | let (s, _o, h) = elem; 35 | let mut sub_commands = h.iter().collect::>(); 36 | sub_commands.sort_by(|a, b| b.1.cmp(a.1)); 37 | 38 | let sub_commands = if sub_commands.is_empty() { 39 | None 40 | } else { 41 | Some( 42 | sub_commands[..(usize::min(3, sub_commands.len()))] 43 | .iter() 44 | .map(|x| x.0.to_owned()) 45 | .collect(), 46 | ) 47 | }; 48 | 49 | print_command(s, *freq, max, total, &args, sub_commands, &mut stdout); 50 | } 51 | 52 | stdout.flush().unwrap(); 53 | 54 | let others = total - limited_tree.iter().fold(0, |acc, x| acc + x.0); 55 | let other_percentage = (others as f64 / total as f64) * 100.; 56 | execute! { 57 | stdout, 58 | SetForegroundColor(Color::Grey), 59 | Print(format!("...{} ({:.2}%) others\n", others, other_percentage)), 60 | ResetColor, 61 | } 62 | .unwrap(); 63 | execute! { 64 | stdout, 65 | Print(format!("Total: {} commands\n", total)) 66 | } 67 | .unwrap(); 68 | } 69 | 70 | pub fn print_command( 71 | command: &str, 72 | invocations: usize, 73 | max: usize, 74 | total: usize, 75 | args: &Args, 76 | sub_commands: Option>, 77 | stdout: &mut Stdout, 78 | ) { 79 | let percentage = (invocations as f32 / total as f32) * 100.0; 80 | let num_of_bars = ((invocations as f32 / max as f32) * 10.) as usize; 81 | let bar: String = format!( 82 | "{}{}", 83 | args.bar.fill.repeat(num_of_bars), 84 | args.bar.empty.repeat(10 - num_of_bars) 85 | ); 86 | let pretty_sub_commands = if let Some(sub_commands) = sub_commands { 87 | format!("{} ...", sub_commands[..(sub_commands.len().min(3))].join(", ")) 88 | } else { 89 | "".to_string() 90 | }; 91 | 92 | queue!( 93 | stdout, 94 | SetForegroundColor(Color::Reset), 95 | Print(&args.bar.opening), 96 | PrintStyledContent(format!("{: <2}", slice(&bar, 0, 2)).red()), 97 | PrintStyledContent(format!("{: <3}", slice(&bar, 2, 5)).yellow()), 98 | PrintStyledContent(format!("{: <5}", slice(&bar, 5, 10)).green()), 99 | PrintStyledContent(format!("{} ", args.bar.closing).reset()), 100 | Print(format!("{: >5.2}% ", percentage)), 101 | PrintStyledContent(format!("{:5}", invocations).grey()), 102 | PrintStyledContent(format!(" {} ", command).reset().bold()), 103 | PrintStyledContent(format!("{}\n", pretty_sub_commands).reset().grey()), 104 | SetForegroundColor(Color::Reset), 105 | ) 106 | .unwrap(); 107 | } 108 | -------------------------------------------------------------------------------- /src/hist_file.rs: -------------------------------------------------------------------------------- 1 | use crate::Args; 2 | use crossterm::execute; 3 | use crossterm::style::{Attribute, PrintStyledContent, SetAttribute, Stylize}; 4 | use regex::Regex; 5 | use std::collections::HashMap; 6 | use std::io::stdout; 7 | use std::io::{BufRead, BufReader}; 8 | 9 | pub(crate) type CommandMap = HashMap, HashMap)>; 10 | 11 | pub fn get_contents(hist_file: std::fs::File, args: &Args) -> String { 12 | let reader = BufReader::new(hist_file); 13 | let mut contents = String::new(); 14 | 15 | for (index, line) in reader.lines().enumerate() { 16 | if let Ok(line) = line { 17 | contents.push_str(&line); 18 | contents.push('\n'); 19 | } else if args.debug { 20 | execute!{ 21 | stdout(), 22 | PrintStyledContent(format!("[Error] Could not read line : {index} = {line:#?}\n").yellow().bold()), 23 | SetAttribute(Attribute::Reset), 24 | }.unwrap(); 25 | } 26 | } 27 | 28 | contents 29 | } 30 | 31 | fn get_commands(line: String) -> Vec { 32 | line.split(&['&', '|', ';']) 33 | .filter(|x| !x.is_empty()) 34 | .map(str::to_string) 35 | .collect() 36 | } 37 | 38 | /// Takes contents of a file and returns a vector of valid commands 39 | pub fn parse_contents(contents: String, args: &Args) -> Vec { 40 | let mut lines: Vec = contents 41 | .lines() 42 | .filter(|line| !line.is_empty()) 43 | .map(str::trim) 44 | .map(str::to_string) 45 | .collect(); 46 | 47 | let shell_strat = match args.shell.as_str() { 48 | "fish" => { 49 | |line: String| -> String { line.strip_prefix("- cmd: ").unwrap_or("").to_owned() } 50 | } 51 | "ohmyzsh" => |line: String| -> String { line[7..].to_owned() }, 52 | _ => |line: String| -> String { line }, 53 | }; 54 | 55 | let regex_strat = |line: String, re: Regex| -> String { 56 | if let Some(cap) = re.captures(&line) { 57 | cap[0].to_owned() 58 | } else { 59 | String::new() 60 | } 61 | }; 62 | 63 | if args.regexp.is_empty() { 64 | lines = lines.into_iter().map(shell_strat).collect(); 65 | } else { 66 | let re = Regex::new(&args.regexp).unwrap(); 67 | lines = lines 68 | .into_iter() 69 | .map(move |line| regex_strat(line, re.clone())) 70 | .collect(); 71 | }; 72 | 73 | let reg = Regex::new("('(?:.|[^'\n])*'|\"(?:.|[^\"\n])*\")").unwrap(); 74 | let unquoted_lines = lines 75 | .into_iter() 76 | .map(|line| reg.replace_all(&line, "").to_string()); 77 | unquoted_lines.flat_map(get_commands).collect() 78 | } 79 | 80 | pub fn process_lines(lines: Vec, _args: &Args) -> CommandMap { 81 | let leaders = ["sudo", "doas"]; 82 | let super_commands = ["git", "entr", "time"]; 83 | 84 | let mut output: CommandMap = HashMap::new(); 85 | 86 | for line in lines.into_iter() { 87 | let words = line.split_whitespace().collect::>(); 88 | 89 | let (first, second) = (words.first().unwrap().to_string(), words.get(1)); 90 | 91 | output 92 | .entry(first.clone()) 93 | .or_insert((0, None, HashMap::new())) 94 | .0 += 1; 95 | 96 | if let Some(second) = second { 97 | let mut parent_entry = output.get_mut(&first).unwrap(); 98 | 99 | if parent_entry.1.is_some() { 100 | *parent_entry.2.entry(second.to_string()).or_insert(0) += 1; 101 | } 102 | 103 | if leaders.contains(&first.as_str()) { 104 | parent_entry.1 = Some(true); 105 | output 106 | .entry((*second).to_string()) 107 | .or_insert((0, None, HashMap::new())) 108 | .0 += 1; 109 | } else if super_commands.contains(&first.as_str()) { 110 | parent_entry.1 = Some(false); 111 | } 112 | } 113 | } 114 | output 115 | } 116 | 117 | #[cfg(test)] 118 | mod parsing { 119 | #[test] 120 | #[ignore = "reformat pending ..."] 121 | fn tood() { 122 | todo!() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod display; 3 | mod hist_file; 4 | use std::{env, path::PathBuf}; 5 | 6 | use args::Args; 7 | 8 | use clap::Parser; 9 | 10 | fn main() -> Result<(), Box> { 11 | let args = Args::parse(); 12 | 13 | let file_path = match args.file.as_ref() { 14 | Some(p) => p.to_owned(), 15 | None => match env::var("HISTFILE") { 16 | Ok(p) => PathBuf::from(p), 17 | Err(_e) => { 18 | return Err("Could not find HISTFILE environment variable. Supply the file path with the --file option".into()); 19 | } 20 | }, 21 | }; 22 | 23 | let file = std::fs::File::open(file_path).expect("Failed to get histfile"); 24 | 25 | let contents = hist_file::get_contents(file, &args); 26 | let command_lines = hist_file::parse_contents(contents, &args); 27 | let commands = hist_file::process_lines(command_lines, &args); 28 | 29 | display::print(commands, args); 30 | 31 | Ok(()) 32 | } 33 | --------------------------------------------------------------------------------