├── .DS_Store ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cargo-generate.toml └── src ├── app.rs ├── config_parser.rs └── main.rs /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noelzubin/crib/0526217dd3130823a6e9ab44fd606aa4c2f134c8/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for Cargo 9 | - package-ecosystem: "cargo" 10 | directory: "/" # Location of package manifests 11 | schedule: 12 | interval: "weekly" 13 | # Maintain dependencies for GitHub Actions 14 | - package-ecosystem: github-actions 15 | directory: "/" 16 | schedule: 17 | interval: weekly 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | - develop 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | # ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel 15 | # and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | fmt: 22 | name: fmt 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | - name: Install Rust stable 28 | uses: dtolnay/rust-toolchain@stable 29 | with: 30 | components: rustfmt 31 | - name: check formatting 32 | run: cargo fmt -- --check 33 | - name: Cache Cargo dependencies 34 | uses: Swatinem/rust-cache@v2 35 | clippy: 36 | name: clippy 37 | runs-on: ubuntu-latest 38 | permissions: 39 | checks: write 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | - name: Install Rust stable 44 | uses: dtolnay/rust-toolchain@stable 45 | with: 46 | components: clippy 47 | - name: Run clippy action 48 | uses: clechasseur/rs-clippy-check@v3 49 | - name: Cache Cargo dependencies 50 | uses: Swatinem/rust-cache@v2 51 | doc: 52 | # run docs generation on nightly rather than stable. This enables features like 53 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an 54 | # API be documented as only available in some specific platforms. 55 | name: doc 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v4 59 | - name: Install Rust nightly 60 | uses: dtolnay/rust-toolchain@nightly 61 | - name: Run cargo doc 62 | run: cargo doc --no-deps --all-features 63 | env: 64 | RUSTDOCFLAGS: --cfg docsrs 65 | test: 66 | runs-on: ${{ matrix.os }} 67 | name: test ${{ matrix.os }} 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | os: [macos-latest, windows-latest] 72 | steps: 73 | # if your project needs OpenSSL, uncomment this to fix Windows builds. 74 | # it's commented out by default as the install command takes 5-10m. 75 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 76 | # if: runner.os == 'Windows' 77 | # - run: vcpkg install openssl:x64-windows-static-md 78 | # if: runner.os == 'Windows' 79 | - uses: actions/checkout@v4 80 | - name: Install Rust 81 | uses: dtolnay/rust-toolchain@stable 82 | # enable this ci template to run regardless of whether the lockfile is checked in or not 83 | - name: cargo generate-lockfile 84 | if: hashFiles('Cargo.lock') == '' 85 | run: cargo generate-lockfile 86 | - name: cargo test --locked 87 | run: cargo test --locked --all-features --all-targets 88 | - name: Cache Cargo dependencies 89 | uses: Swatinem/rust-cache@v2 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "allocator-api2" 22 | version = "0.2.21" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 25 | 26 | [[package]] 27 | name = "ansi-to-tui" 28 | version = "7.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" 31 | dependencies = [ 32 | "nom", 33 | "ratatui", 34 | "simdutf8", 35 | "smallvec", 36 | "thiserror 1.0.69", 37 | ] 38 | 39 | [[package]] 40 | name = "ansi_term" 41 | version = "0.12.1" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 44 | dependencies = [ 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "atty" 50 | version = "0.2.14" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 53 | dependencies = [ 54 | "hermit-abi", 55 | "libc", 56 | "winapi", 57 | ] 58 | 59 | [[package]] 60 | name = "autocfg" 61 | version = "1.4.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 64 | 65 | [[package]] 66 | name = "backtrace" 67 | version = "0.3.71" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 70 | dependencies = [ 71 | "addr2line", 72 | "cc", 73 | "cfg-if", 74 | "libc", 75 | "miniz_oxide", 76 | "object", 77 | "rustc-demangle", 78 | ] 79 | 80 | [[package]] 81 | name = "bitflags" 82 | version = "1.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 85 | 86 | [[package]] 87 | name = "bitflags" 88 | version = "2.8.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 91 | 92 | [[package]] 93 | name = "bytecount" 94 | version = "0.6.8" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" 97 | 98 | [[package]] 99 | name = "byteorder" 100 | version = "1.5.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 103 | 104 | [[package]] 105 | name = "cassowary" 106 | version = "0.3.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 109 | 110 | [[package]] 111 | name = "castaway" 112 | version = "0.2.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 115 | dependencies = [ 116 | "rustversion", 117 | ] 118 | 119 | [[package]] 120 | name = "cc" 121 | version = "1.2.15" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" 124 | dependencies = [ 125 | "shlex", 126 | ] 127 | 128 | [[package]] 129 | name = "cfg-if" 130 | version = "1.0.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 133 | 134 | [[package]] 135 | name = "clap" 136 | version = "2.34.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 139 | dependencies = [ 140 | "ansi_term", 141 | "atty", 142 | "bitflags 1.3.2", 143 | "strsim 0.8.0", 144 | "textwrap", 145 | "unicode-width 0.1.14", 146 | "vec_map", 147 | ] 148 | 149 | [[package]] 150 | name = "color-eyre" 151 | version = "0.6.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 154 | dependencies = [ 155 | "backtrace", 156 | "color-spantrace", 157 | "eyre", 158 | "indenter", 159 | "once_cell", 160 | "owo-colors", 161 | "tracing-error", 162 | ] 163 | 164 | [[package]] 165 | name = "color-spantrace" 166 | version = "0.2.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 169 | dependencies = [ 170 | "once_cell", 171 | "owo-colors", 172 | "tracing-core", 173 | "tracing-error", 174 | ] 175 | 176 | [[package]] 177 | name = "colored" 178 | version = "3.0.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 181 | dependencies = [ 182 | "windows-sys 0.59.0", 183 | ] 184 | 185 | [[package]] 186 | name = "compact_str" 187 | version = "0.8.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 190 | dependencies = [ 191 | "castaway", 192 | "cfg-if", 193 | "itoa", 194 | "rustversion", 195 | "ryu", 196 | "static_assertions", 197 | ] 198 | 199 | [[package]] 200 | name = "crib" 201 | version = "0.1.0" 202 | dependencies = [ 203 | "ansi-to-tui", 204 | "color-eyre", 205 | "colored", 206 | "crossterm", 207 | "dirs", 208 | "rand", 209 | "ratatui", 210 | "serde", 211 | "serde_yaml", 212 | "structopt", 213 | "tabled", 214 | ] 215 | 216 | [[package]] 217 | name = "crossterm" 218 | version = "0.28.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 221 | dependencies = [ 222 | "bitflags 2.8.0", 223 | "crossterm_winapi", 224 | "mio", 225 | "parking_lot", 226 | "rustix", 227 | "signal-hook", 228 | "signal-hook-mio", 229 | "winapi", 230 | ] 231 | 232 | [[package]] 233 | name = "crossterm_winapi" 234 | version = "0.9.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 237 | dependencies = [ 238 | "winapi", 239 | ] 240 | 241 | [[package]] 242 | name = "darling" 243 | version = "0.20.10" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 246 | dependencies = [ 247 | "darling_core", 248 | "darling_macro", 249 | ] 250 | 251 | [[package]] 252 | name = "darling_core" 253 | version = "0.20.10" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 256 | dependencies = [ 257 | "fnv", 258 | "ident_case", 259 | "proc-macro2", 260 | "quote", 261 | "strsim 0.11.1", 262 | "syn 2.0.98", 263 | ] 264 | 265 | [[package]] 266 | name = "darling_macro" 267 | version = "0.20.10" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 270 | dependencies = [ 271 | "darling_core", 272 | "quote", 273 | "syn 2.0.98", 274 | ] 275 | 276 | [[package]] 277 | name = "dirs" 278 | version = "6.0.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 281 | dependencies = [ 282 | "dirs-sys", 283 | ] 284 | 285 | [[package]] 286 | name = "dirs-sys" 287 | version = "0.5.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 290 | dependencies = [ 291 | "libc", 292 | "option-ext", 293 | "redox_users", 294 | "windows-sys 0.59.0", 295 | ] 296 | 297 | [[package]] 298 | name = "either" 299 | version = "1.14.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" 302 | 303 | [[package]] 304 | name = "equivalent" 305 | version = "1.0.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 308 | 309 | [[package]] 310 | name = "errno" 311 | version = "0.3.10" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 314 | dependencies = [ 315 | "libc", 316 | "windows-sys 0.59.0", 317 | ] 318 | 319 | [[package]] 320 | name = "eyre" 321 | version = "0.6.12" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 324 | dependencies = [ 325 | "indenter", 326 | "once_cell", 327 | ] 328 | 329 | [[package]] 330 | name = "fnv" 331 | version = "1.0.7" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 334 | 335 | [[package]] 336 | name = "foldhash" 337 | version = "0.1.4" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 340 | 341 | [[package]] 342 | name = "getrandom" 343 | version = "0.2.15" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 346 | dependencies = [ 347 | "cfg-if", 348 | "libc", 349 | "wasi 0.11.0+wasi-snapshot-preview1", 350 | ] 351 | 352 | [[package]] 353 | name = "getrandom" 354 | version = "0.3.1" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 357 | dependencies = [ 358 | "cfg-if", 359 | "libc", 360 | "wasi 0.13.3+wasi-0.2.2", 361 | "windows-targets", 362 | ] 363 | 364 | [[package]] 365 | name = "gimli" 366 | version = "0.28.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 369 | 370 | [[package]] 371 | name = "hashbrown" 372 | version = "0.15.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 375 | dependencies = [ 376 | "allocator-api2", 377 | "equivalent", 378 | "foldhash", 379 | ] 380 | 381 | [[package]] 382 | name = "heck" 383 | version = "0.3.3" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 386 | dependencies = [ 387 | "unicode-segmentation", 388 | ] 389 | 390 | [[package]] 391 | name = "heck" 392 | version = "0.5.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 395 | 396 | [[package]] 397 | name = "hermit-abi" 398 | version = "0.1.19" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 401 | dependencies = [ 402 | "libc", 403 | ] 404 | 405 | [[package]] 406 | name = "ident_case" 407 | version = "1.0.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 410 | 411 | [[package]] 412 | name = "indenter" 413 | version = "0.3.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 416 | 417 | [[package]] 418 | name = "indexmap" 419 | version = "2.7.1" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 422 | dependencies = [ 423 | "equivalent", 424 | "hashbrown", 425 | ] 426 | 427 | [[package]] 428 | name = "indoc" 429 | version = "2.0.5" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 432 | 433 | [[package]] 434 | name = "instability" 435 | version = "0.3.7" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" 438 | dependencies = [ 439 | "darling", 440 | "indoc", 441 | "proc-macro2", 442 | "quote", 443 | "syn 2.0.98", 444 | ] 445 | 446 | [[package]] 447 | name = "itertools" 448 | version = "0.13.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 451 | dependencies = [ 452 | "either", 453 | ] 454 | 455 | [[package]] 456 | name = "itoa" 457 | version = "1.0.14" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 460 | 461 | [[package]] 462 | name = "lazy_static" 463 | version = "1.5.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 466 | 467 | [[package]] 468 | name = "libc" 469 | version = "0.2.170" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 472 | 473 | [[package]] 474 | name = "libredox" 475 | version = "0.1.3" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 478 | dependencies = [ 479 | "bitflags 2.8.0", 480 | "libc", 481 | ] 482 | 483 | [[package]] 484 | name = "linux-raw-sys" 485 | version = "0.4.15" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 488 | 489 | [[package]] 490 | name = "lock_api" 491 | version = "0.4.12" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 494 | dependencies = [ 495 | "autocfg", 496 | "scopeguard", 497 | ] 498 | 499 | [[package]] 500 | name = "log" 501 | version = "0.4.26" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 504 | 505 | [[package]] 506 | name = "lru" 507 | version = "0.12.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 510 | dependencies = [ 511 | "hashbrown", 512 | ] 513 | 514 | [[package]] 515 | name = "memchr" 516 | version = "2.7.4" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 519 | 520 | [[package]] 521 | name = "minimal-lexical" 522 | version = "0.2.1" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 525 | 526 | [[package]] 527 | name = "miniz_oxide" 528 | version = "0.7.4" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 531 | dependencies = [ 532 | "adler", 533 | ] 534 | 535 | [[package]] 536 | name = "mio" 537 | version = "1.0.3" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 540 | dependencies = [ 541 | "libc", 542 | "log", 543 | "wasi 0.11.0+wasi-snapshot-preview1", 544 | "windows-sys 0.52.0", 545 | ] 546 | 547 | [[package]] 548 | name = "nom" 549 | version = "7.1.3" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 552 | dependencies = [ 553 | "memchr", 554 | "minimal-lexical", 555 | ] 556 | 557 | [[package]] 558 | name = "object" 559 | version = "0.32.2" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 562 | dependencies = [ 563 | "memchr", 564 | ] 565 | 566 | [[package]] 567 | name = "once_cell" 568 | version = "1.20.3" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 571 | 572 | [[package]] 573 | name = "option-ext" 574 | version = "0.2.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 577 | 578 | [[package]] 579 | name = "owo-colors" 580 | version = "3.5.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 583 | 584 | [[package]] 585 | name = "papergrid" 586 | version = "0.14.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "b915f831b85d984193fdc3d3611505871dc139b2534530fa01c1a6a6707b6723" 589 | dependencies = [ 590 | "bytecount", 591 | "fnv", 592 | "unicode-width 0.2.0", 593 | ] 594 | 595 | [[package]] 596 | name = "parking_lot" 597 | version = "0.12.3" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 600 | dependencies = [ 601 | "lock_api", 602 | "parking_lot_core", 603 | ] 604 | 605 | [[package]] 606 | name = "parking_lot_core" 607 | version = "0.9.10" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 610 | dependencies = [ 611 | "cfg-if", 612 | "libc", 613 | "redox_syscall", 614 | "smallvec", 615 | "windows-targets", 616 | ] 617 | 618 | [[package]] 619 | name = "paste" 620 | version = "1.0.15" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 623 | 624 | [[package]] 625 | name = "pin-project-lite" 626 | version = "0.2.16" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 629 | 630 | [[package]] 631 | name = "ppv-lite86" 632 | version = "0.2.20" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 635 | dependencies = [ 636 | "zerocopy 0.7.35", 637 | ] 638 | 639 | [[package]] 640 | name = "proc-macro-error" 641 | version = "1.0.4" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 644 | dependencies = [ 645 | "proc-macro-error-attr", 646 | "proc-macro2", 647 | "quote", 648 | "syn 1.0.109", 649 | "version_check", 650 | ] 651 | 652 | [[package]] 653 | name = "proc-macro-error-attr" 654 | version = "1.0.4" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 657 | dependencies = [ 658 | "proc-macro2", 659 | "quote", 660 | "version_check", 661 | ] 662 | 663 | [[package]] 664 | name = "proc-macro-error-attr2" 665 | version = "2.0.0" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 668 | dependencies = [ 669 | "proc-macro2", 670 | "quote", 671 | ] 672 | 673 | [[package]] 674 | name = "proc-macro-error2" 675 | version = "2.0.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 678 | dependencies = [ 679 | "proc-macro-error-attr2", 680 | "proc-macro2", 681 | "quote", 682 | "syn 2.0.98", 683 | ] 684 | 685 | [[package]] 686 | name = "proc-macro2" 687 | version = "1.0.93" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 690 | dependencies = [ 691 | "unicode-ident", 692 | ] 693 | 694 | [[package]] 695 | name = "quote" 696 | version = "1.0.38" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 699 | dependencies = [ 700 | "proc-macro2", 701 | ] 702 | 703 | [[package]] 704 | name = "rand" 705 | version = "0.9.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 708 | dependencies = [ 709 | "rand_chacha", 710 | "rand_core", 711 | "zerocopy 0.8.20", 712 | ] 713 | 714 | [[package]] 715 | name = "rand_chacha" 716 | version = "0.9.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 719 | dependencies = [ 720 | "ppv-lite86", 721 | "rand_core", 722 | ] 723 | 724 | [[package]] 725 | name = "rand_core" 726 | version = "0.9.2" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" 729 | dependencies = [ 730 | "getrandom 0.3.1", 731 | "zerocopy 0.8.20", 732 | ] 733 | 734 | [[package]] 735 | name = "ratatui" 736 | version = "0.29.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 739 | dependencies = [ 740 | "bitflags 2.8.0", 741 | "cassowary", 742 | "compact_str", 743 | "crossterm", 744 | "indoc", 745 | "instability", 746 | "itertools", 747 | "lru", 748 | "paste", 749 | "strum", 750 | "unicode-segmentation", 751 | "unicode-truncate", 752 | "unicode-width 0.2.0", 753 | ] 754 | 755 | [[package]] 756 | name = "redox_syscall" 757 | version = "0.5.9" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" 760 | dependencies = [ 761 | "bitflags 2.8.0", 762 | ] 763 | 764 | [[package]] 765 | name = "redox_users" 766 | version = "0.5.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 769 | dependencies = [ 770 | "getrandom 0.2.15", 771 | "libredox", 772 | "thiserror 2.0.11", 773 | ] 774 | 775 | [[package]] 776 | name = "rustc-demangle" 777 | version = "0.1.24" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 780 | 781 | [[package]] 782 | name = "rustix" 783 | version = "0.38.44" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 786 | dependencies = [ 787 | "bitflags 2.8.0", 788 | "errno", 789 | "libc", 790 | "linux-raw-sys", 791 | "windows-sys 0.59.0", 792 | ] 793 | 794 | [[package]] 795 | name = "rustversion" 796 | version = "1.0.19" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 799 | 800 | [[package]] 801 | name = "ryu" 802 | version = "1.0.19" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 805 | 806 | [[package]] 807 | name = "scopeguard" 808 | version = "1.2.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 811 | 812 | [[package]] 813 | name = "serde" 814 | version = "1.0.218" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" 817 | dependencies = [ 818 | "serde_derive", 819 | ] 820 | 821 | [[package]] 822 | name = "serde_derive" 823 | version = "1.0.218" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" 826 | dependencies = [ 827 | "proc-macro2", 828 | "quote", 829 | "syn 2.0.98", 830 | ] 831 | 832 | [[package]] 833 | name = "serde_yaml" 834 | version = "0.9.34+deprecated" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 837 | dependencies = [ 838 | "indexmap", 839 | "itoa", 840 | "ryu", 841 | "serde", 842 | "unsafe-libyaml", 843 | ] 844 | 845 | [[package]] 846 | name = "sharded-slab" 847 | version = "0.1.7" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 850 | dependencies = [ 851 | "lazy_static", 852 | ] 853 | 854 | [[package]] 855 | name = "shlex" 856 | version = "1.3.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 859 | 860 | [[package]] 861 | name = "signal-hook" 862 | version = "0.3.17" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 865 | dependencies = [ 866 | "libc", 867 | "signal-hook-registry", 868 | ] 869 | 870 | [[package]] 871 | name = "signal-hook-mio" 872 | version = "0.2.4" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 875 | dependencies = [ 876 | "libc", 877 | "mio", 878 | "signal-hook", 879 | ] 880 | 881 | [[package]] 882 | name = "signal-hook-registry" 883 | version = "1.4.2" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 886 | dependencies = [ 887 | "libc", 888 | ] 889 | 890 | [[package]] 891 | name = "simdutf8" 892 | version = "0.1.5" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 895 | 896 | [[package]] 897 | name = "smallvec" 898 | version = "1.14.0" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 901 | 902 | [[package]] 903 | name = "static_assertions" 904 | version = "1.1.0" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 907 | 908 | [[package]] 909 | name = "strsim" 910 | version = "0.8.0" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 913 | 914 | [[package]] 915 | name = "strsim" 916 | version = "0.11.1" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 919 | 920 | [[package]] 921 | name = "structopt" 922 | version = "0.3.26" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 925 | dependencies = [ 926 | "clap", 927 | "lazy_static", 928 | "structopt-derive", 929 | ] 930 | 931 | [[package]] 932 | name = "structopt-derive" 933 | version = "0.4.18" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 936 | dependencies = [ 937 | "heck 0.3.3", 938 | "proc-macro-error", 939 | "proc-macro2", 940 | "quote", 941 | "syn 1.0.109", 942 | ] 943 | 944 | [[package]] 945 | name = "strum" 946 | version = "0.26.3" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 949 | dependencies = [ 950 | "strum_macros", 951 | ] 952 | 953 | [[package]] 954 | name = "strum_macros" 955 | version = "0.26.4" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 958 | dependencies = [ 959 | "heck 0.5.0", 960 | "proc-macro2", 961 | "quote", 962 | "rustversion", 963 | "syn 2.0.98", 964 | ] 965 | 966 | [[package]] 967 | name = "syn" 968 | version = "1.0.109" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 971 | dependencies = [ 972 | "proc-macro2", 973 | "quote", 974 | "unicode-ident", 975 | ] 976 | 977 | [[package]] 978 | name = "syn" 979 | version = "2.0.98" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 982 | dependencies = [ 983 | "proc-macro2", 984 | "quote", 985 | "unicode-ident", 986 | ] 987 | 988 | [[package]] 989 | name = "tabled" 990 | version = "0.18.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "121d8171ee5687a4978d1b244f7d99c43e7385a272185a2f1e1fa4dc0979d444" 993 | dependencies = [ 994 | "papergrid", 995 | "tabled_derive", 996 | ] 997 | 998 | [[package]] 999 | name = "tabled_derive" 1000 | version = "0.10.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "52d9946811baad81710ec921809e2af67ad77719418673b2a3794932d57b7538" 1003 | dependencies = [ 1004 | "heck 0.5.0", 1005 | "proc-macro-error2", 1006 | "proc-macro2", 1007 | "quote", 1008 | "syn 2.0.98", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "textwrap" 1013 | version = "0.11.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1016 | dependencies = [ 1017 | "unicode-width 0.1.14", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "thiserror" 1022 | version = "1.0.69" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1025 | dependencies = [ 1026 | "thiserror-impl 1.0.69", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "thiserror" 1031 | version = "2.0.11" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 1034 | dependencies = [ 1035 | "thiserror-impl 2.0.11", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "thiserror-impl" 1040 | version = "1.0.69" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1043 | dependencies = [ 1044 | "proc-macro2", 1045 | "quote", 1046 | "syn 2.0.98", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "thiserror-impl" 1051 | version = "2.0.11" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 1054 | dependencies = [ 1055 | "proc-macro2", 1056 | "quote", 1057 | "syn 2.0.98", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "thread_local" 1062 | version = "1.1.8" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1065 | dependencies = [ 1066 | "cfg-if", 1067 | "once_cell", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "tracing" 1072 | version = "0.1.41" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1075 | dependencies = [ 1076 | "pin-project-lite", 1077 | "tracing-core", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "tracing-core" 1082 | version = "0.1.33" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1085 | dependencies = [ 1086 | "once_cell", 1087 | "valuable", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "tracing-error" 1092 | version = "0.2.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 1095 | dependencies = [ 1096 | "tracing", 1097 | "tracing-subscriber", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "tracing-subscriber" 1102 | version = "0.3.19" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1105 | dependencies = [ 1106 | "sharded-slab", 1107 | "thread_local", 1108 | "tracing-core", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "unicode-ident" 1113 | version = "1.0.17" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" 1116 | 1117 | [[package]] 1118 | name = "unicode-segmentation" 1119 | version = "1.12.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1122 | 1123 | [[package]] 1124 | name = "unicode-truncate" 1125 | version = "1.1.0" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1128 | dependencies = [ 1129 | "itertools", 1130 | "unicode-segmentation", 1131 | "unicode-width 0.1.14", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "unicode-width" 1136 | version = "0.1.14" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1139 | 1140 | [[package]] 1141 | name = "unicode-width" 1142 | version = "0.2.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1145 | 1146 | [[package]] 1147 | name = "unsafe-libyaml" 1148 | version = "0.2.11" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1151 | 1152 | [[package]] 1153 | name = "valuable" 1154 | version = "0.1.1" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1157 | 1158 | [[package]] 1159 | name = "vec_map" 1160 | version = "0.8.2" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1163 | 1164 | [[package]] 1165 | name = "version_check" 1166 | version = "0.9.5" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1169 | 1170 | [[package]] 1171 | name = "wasi" 1172 | version = "0.11.0+wasi-snapshot-preview1" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1175 | 1176 | [[package]] 1177 | name = "wasi" 1178 | version = "0.13.3+wasi-0.2.2" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 1181 | dependencies = [ 1182 | "wit-bindgen-rt", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "winapi" 1187 | version = "0.3.9" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1190 | dependencies = [ 1191 | "winapi-i686-pc-windows-gnu", 1192 | "winapi-x86_64-pc-windows-gnu", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "winapi-i686-pc-windows-gnu" 1197 | version = "0.4.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1200 | 1201 | [[package]] 1202 | name = "winapi-x86_64-pc-windows-gnu" 1203 | version = "0.4.0" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1206 | 1207 | [[package]] 1208 | name = "windows-sys" 1209 | version = "0.52.0" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1212 | dependencies = [ 1213 | "windows-targets", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "windows-sys" 1218 | version = "0.59.0" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1221 | dependencies = [ 1222 | "windows-targets", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "windows-targets" 1227 | version = "0.52.6" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1230 | dependencies = [ 1231 | "windows_aarch64_gnullvm", 1232 | "windows_aarch64_msvc", 1233 | "windows_i686_gnu", 1234 | "windows_i686_gnullvm", 1235 | "windows_i686_msvc", 1236 | "windows_x86_64_gnu", 1237 | "windows_x86_64_gnullvm", 1238 | "windows_x86_64_msvc", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "windows_aarch64_gnullvm" 1243 | version = "0.52.6" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1246 | 1247 | [[package]] 1248 | name = "windows_aarch64_msvc" 1249 | version = "0.52.6" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1252 | 1253 | [[package]] 1254 | name = "windows_i686_gnu" 1255 | version = "0.52.6" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1258 | 1259 | [[package]] 1260 | name = "windows_i686_gnullvm" 1261 | version = "0.52.6" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1264 | 1265 | [[package]] 1266 | name = "windows_i686_msvc" 1267 | version = "0.52.6" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1270 | 1271 | [[package]] 1272 | name = "windows_x86_64_gnu" 1273 | version = "0.52.6" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1276 | 1277 | [[package]] 1278 | name = "windows_x86_64_gnullvm" 1279 | version = "0.52.6" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1282 | 1283 | [[package]] 1284 | name = "windows_x86_64_msvc" 1285 | version = "0.52.6" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1288 | 1289 | [[package]] 1290 | name = "wit-bindgen-rt" 1291 | version = "0.33.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 1294 | dependencies = [ 1295 | "bitflags 2.8.0", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "zerocopy" 1300 | version = "0.7.35" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1303 | dependencies = [ 1304 | "byteorder", 1305 | "zerocopy-derive 0.7.35", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "zerocopy" 1310 | version = "0.8.20" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" 1313 | dependencies = [ 1314 | "zerocopy-derive 0.8.20", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "zerocopy-derive" 1319 | version = "0.7.35" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1322 | dependencies = [ 1323 | "proc-macro2", 1324 | "quote", 1325 | "syn 2.0.98", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "zerocopy-derive" 1330 | version = "0.8.20" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" 1333 | dependencies = [ 1334 | "proc-macro2", 1335 | "quote", 1336 | "syn 2.0.98", 1337 | ] 1338 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crib" 3 | version = "0.1.0" 4 | authors = ["noelzubinvictor@gmail.com"] 5 | license = "MIT" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | crossterm = "0.28.1" 10 | ratatui = "0.29.0" 11 | color-eyre = "0.6.3" 12 | rand = "0.9.0" 13 | tabled = "0.18.0" 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_yaml = "0.9" 16 | colored = "3.0.0" 17 | ansi-to-tui = "7.0.0" 18 | dirs = "6.0.0" 19 | structopt = "0.3.26" 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) {{authors}} 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 | # Crib 2 | 3 | ![](https://i.imgur.com/saTPUrl.gif) 4 | 5 | Create and view your own custom hotkey cheatsheet in the terminal 6 | 7 | ## Motivation 8 | I had trouble remembering the various hotkeys that I sometimes use. It got 9 | annoying to look them up so I resorted to writing them down on a paper 10 | cheatsheet. Then, I thought: maybe there's a tool that does this better. I 11 | didn't find one I liked so I built crib. 12 | 13 | With crib, I can list: 14 | * Hotkeys that I occasionally forget or am new to 15 | * Custom key combinations that I defined for my own workflow 16 | It is best used as a popup cheatsheet. 17 | 18 | 19 | ## Install 20 | ``` 21 | cargo install --git https://github.com/noelzubin/crib.git 22 | ``` 23 | 24 | ## Build from source 25 | ``` 26 | cargo build --release 27 | ``` 28 | 29 | ## Usage 30 | To perform filtering on section headings, append `:` to the search term. For 31 | example, to search for all sections with vscode in the heading, type `vscode:`. 32 | 33 | To scroll use up/down arrow keys. 34 | 35 | ## Configuration syntax 36 | ``` yaml 37 | - name: yazi 38 | bindings: 39 | - name: Toggle hidden files 40 | key: "." 41 | - name: Run a shell command 42 | key: ":" 43 | children: 44 | - name: find files 45 | bindings: 46 | - name: zoxide 47 | key: z 48 | - name: fzf 49 | key: Z 50 | - name: fd 51 | key: s 52 | - name: misc 53 | bindings: 54 | - name: help 55 | key: F1 56 | - name: quit without writing CWD 57 | key: Q 58 | - name: vscode 59 | children: 60 | - name: lsp 61 | bindings: 62 | - name: format document 63 | key: = 64 | - name: focus explorer 65 | key: ge 66 | ``` 67 | 68 | #### Extra options 69 | ``` sh 70 | crib # run tui 71 | 72 | # crib 73 | crib vscode # filter app by query. 74 | 75 | crib --print # print to the console instead of running the tui. 76 | ``` 77 | 78 | 79 | # Similar tools 80 | Inspired by: 81 | * [showkeys](https://github.com/adamharmansky/showkeys) 82 | * [keyb](https://github.com/kencx/keyb) 83 | -------------------------------------------------------------------------------- /cargo-generate.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | cargo_generate_version = ">=0.10.0" 3 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::config_parser::TableConfig; 2 | use ansi_to_tui::IntoText; 3 | use color_eyre::Result; 4 | use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; 5 | use crossterm::terminal::size; 6 | use ratatui::buffer::Buffer; 7 | use ratatui::style::Style as RatatuiStyle; 8 | use ratatui::text::{Span, Text}; 9 | use ratatui::widgets::Widget; 10 | use ratatui::{ 11 | layout::Rect, 12 | style::{Color, Stylize}, 13 | text::Line, 14 | widgets::{Block, Borders, Paragraph}, 15 | DefaultTerminal, Frame, 16 | }; 17 | use std::collections::hash_map::DefaultHasher; 18 | use std::hash::{Hash, Hasher}; 19 | use tabled::settings::style::BorderColor; 20 | use tabled::{ 21 | settings::{ 22 | object::{Columns, Rows}, 23 | style::{HorizontalLine, Style}, 24 | Alignment, Modify, Panel, Width, 25 | }, 26 | Table, Tabled, 27 | }; 28 | 29 | fn hash_string(s: &str) -> u64 { 30 | let mut hasher = DefaultHasher::new(); 31 | s.hash(&mut hasher); 32 | hasher.finish() 33 | } 34 | 35 | #[derive(Debug)] 36 | pub struct App { 37 | /// Is the application running? 38 | running: bool, 39 | config: Vec, 40 | input: String, // Add a field to store the input text 41 | scroll_offset: u16, 42 | max_height: usize, 43 | } 44 | 45 | fn get_column_width(max: u16, min_col_width: u16) -> u16 { 46 | if max < min_col_width { 47 | return max; 48 | } 49 | let number_of_columns = max / min_col_width; 50 | let remaining = max % min_col_width; 51 | min_col_width + remaining / number_of_columns 52 | } 53 | 54 | #[derive(Tabled)] 55 | struct Row { 56 | h1: String, 57 | h2: String, 58 | } 59 | 60 | fn rgb(r: usize, g: usize, b: usize) -> tabled::settings::Color { 61 | tabled::settings::Color::rgb_fg(r as u8, g as u8, b as u8) 62 | } 63 | 64 | impl App { 65 | /// Construct a new instance of [`App`]. 66 | pub fn new(initial_query: String, config: Vec) -> Self { 67 | Self { 68 | config: config, 69 | running: false, 70 | input: initial_query, // Initialize the input field 71 | scroll_offset: 0, 72 | max_height: 0, 73 | } 74 | } 75 | 76 | /// Run the application's main loop. 77 | pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { 78 | self.running = true; 79 | while self.running { 80 | terminal.draw(|frame| self.draw(frame))?; 81 | self.handle_crossterm_events()?; 82 | } 83 | Ok(()) 84 | } 85 | 86 | pub fn draw_to_buffer(mut self) { 87 | let (width, height) = size().unwrap(); 88 | // Generate buffer 89 | let area = Rect::new(0, 0, width, height); 90 | let column_width = get_column_width(area.width, 40_u16) as usize; 91 | let config = filter_by_input(&self.config, &self.input); 92 | let buffer = self.generate_buffer(area, config, column_width); 93 | 94 | // Print the buffer 95 | print_buffer(&buffer); 96 | } 97 | 98 | pub fn create_simple_table(table_config: &TableConfig, width: usize) -> Table { 99 | let mut table = Vec::new(); 100 | 101 | for binding in table_config.bindings.iter() { 102 | let row = Row { 103 | h2: binding.key.to_string(), 104 | h1: binding.name.to_string(), 105 | }; 106 | 107 | table.push(row); 108 | } 109 | 110 | let mut table = Table::builder(table); 111 | table.remove_record(0); 112 | let mut table = table.build(); 113 | 114 | let app_colors: [tabled::settings::Color; 12] = [ 115 | rgb(242, 173, 159), 116 | rgb(240, 198, 198), 117 | rgb(245, 189, 230), 118 | rgb(198, 160, 246), 119 | rgb(238, 153, 160), 120 | rgb(245, 169, 127), 121 | rgb(238, 212, 159), 122 | rgb(166, 218, 149), 123 | rgb(139, 213, 202), 124 | rgb(145, 215, 227), 125 | rgb(125, 196, 228), 126 | rgb(138, 173, 244), 127 | ]; 128 | 129 | let color = app_colors[hash_string(table_config.name.split(":").collect::>()[0]) 130 | as usize 131 | % app_colors.len()] 132 | .clone(); 133 | 134 | let style = Style::modern() 135 | .horizontals([(1, HorizontalLine::inherit(Style::modern()))]) 136 | .remove_vertical() 137 | .remove_horizontal(); 138 | table 139 | .modify(Columns::first(), Alignment::left()) 140 | .modify(Columns::last(), Alignment::right()) 141 | .modify(Columns::first(), Width::wrap(25)) 142 | .modify(Columns::last(), Width::wrap(width - 25 - 4)) 143 | .with(Panel::header(table_config.name.clone())) 144 | .with(style) 145 | .with(Modify::new(Rows::new(..)).with(BorderColor::filled(color))) 146 | .with(Width::increase(width)); 147 | 148 | table 149 | } 150 | 151 | fn generate_buffer( 152 | &mut self, 153 | area: Rect, 154 | config: Vec, 155 | column_width: usize, 156 | ) -> Buffer { 157 | let mut y = vec![0; area.width as usize / column_width]; 158 | let mut buffer = Buffer::empty(Rect::new(0, 0, area.width, 0)); 159 | let mut max_height = 0; 160 | 161 | for table_config in config { 162 | let table = App::create_simple_table(&table_config, column_width - 2); 163 | let height = table.total_height() as u16; 164 | let column = y.iter().enumerate().min_by_key(|&(_, &h)| h).unwrap().0; 165 | let rect = Rect::new( 166 | (column * column_width) as u16, 167 | y[column], // Adjust y position to account for input field 168 | column_width as u16, 169 | height + 3, 170 | ); 171 | 172 | if (buffer.area().height as usize) < (y[column] + height) as usize { 173 | buffer.resize(Rect::new(0, 0, area.width, y[column] + height + 3)); 174 | } 175 | 176 | let widget = 177 | Text::from(table.to_string().into_text().unwrap()).style(RatatuiStyle::default()); 178 | widget.render(rect, &mut buffer); 179 | y[column] += height; 180 | max_height = max_height.max(y[column] as usize); 181 | } 182 | 183 | self.max_height = max_height; 184 | buffer 185 | } 186 | 187 | /// Renders the user interface. 188 | /// 189 | /// This is where you add new widgets. See the following resources for more information: 190 | /// - 191 | /// - 192 | fn draw(&mut self, frame: &mut Frame) { 193 | let area = frame.area(); 194 | let column_width = get_column_width(area.width, 40_u16) as usize; 195 | 196 | // Render input field at the top 197 | let input_field = Paragraph::new(Line::from(vec![ 198 | Span::raw(" Search: ") 199 | .fg(Color::Black) 200 | .bg(Color::Rgb(181, 232, 176)), 201 | Span::raw(format!(" {}", self.input.clone())).fg(Color::White), 202 | ])) 203 | .block(Block::default().borders(Borders::NONE)); 204 | frame.render_widget(input_field, Rect::new(0, 0, area.width, 1)); 205 | 206 | // Set the cursor position 207 | frame.set_cursor_position((10 + self.input.len() as u16, 0)); 208 | 209 | // Filter the configuration based on user input 210 | let config = filter_by_input(&self.config, &self.input); 211 | 212 | // Generate buffer 213 | let buffer = self.generate_buffer(area, config, column_width); 214 | 215 | let mut buffer_widget = CustomBufferWidget::new(&buffer); 216 | buffer_widget.set_scroll_offset(self.scroll_offset); 217 | frame.render_widget( 218 | buffer_widget, 219 | Rect::new(0, 2, frame.area().width, frame.area().height - 3), 220 | ); 221 | } 222 | 223 | /// Reads the crossterm events and updates the state of [`App`]. 224 | /// 225 | /// If your application needs to perform work in between handling events, you can use the 226 | /// [`event::poll`] function to check if there are any events available with a timeout. 227 | fn handle_crossterm_events(&mut self) -> Result<()> { 228 | match event::read()? { 229 | // it's important to check KeyEventKind::Press to avoid handling key release events 230 | Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), 231 | Event::Mouse(_) => {} 232 | Event::Resize(_, _) => {} 233 | _ => {} 234 | } 235 | Ok(()) 236 | } 237 | 238 | /// Handles the key events and updates the state of [`App`]. 239 | fn on_key_event(&mut self, key: KeyEvent) { 240 | match (key.modifiers, key.code) { 241 | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), 242 | (KeyModifiers::NONE, KeyCode::Char(ch)) => { 243 | self.input.push(ch); 244 | self.scroll_offset = 0; 245 | } 246 | (KeyModifiers::NONE, KeyCode::Backspace) => { 247 | self.input.pop(); 248 | } 249 | 250 | (KeyModifiers::NONE, KeyCode::Down) => { 251 | let screen_height = size().unwrap().1 as usize; 252 | if (self.scroll_offset as usize + screen_height - 5) < self.max_height { 253 | self.scroll_offset += 2; 254 | } 255 | } 256 | 257 | (KeyModifiers::NONE, KeyCode::Up) => { 258 | if self.scroll_offset > 0 { 259 | self.scroll_offset -= 2; 260 | } 261 | } 262 | // Add other key handlers here. 263 | _ => {} 264 | } 265 | } 266 | 267 | /// Set running to false to quit the application. 268 | fn quit(&mut self) { 269 | self.running = false; 270 | } 271 | } 272 | 273 | fn filter_by_input(config: &Vec, input: &str) -> Vec { 274 | let mut app_input = ""; 275 | let mut binding_input = input; 276 | if input.contains(":") { 277 | (app_input, binding_input) = input.split_once(":").unwrap(); 278 | } 279 | 280 | config 281 | .iter() 282 | .filter_map(|cfg| { 283 | if cfg.name.contains(app_input) { 284 | let bindings: Vec<_> = cfg 285 | .bindings 286 | .iter() 287 | .filter(|binding| binding.name.contains(binding_input)) 288 | .cloned() 289 | .collect(); 290 | if !bindings.is_empty() { 291 | return Some(TableConfig { 292 | name: cfg.name.clone(), 293 | bindings, 294 | }); 295 | } 296 | } 297 | None 298 | }) 299 | .collect() 300 | } 301 | 302 | fn print_buffer(buffer: &Buffer) { 303 | for y in 0..buffer.area.height { 304 | let mut row_is_empty = true; 305 | for x in 0..buffer.area.width { 306 | let cell = buffer.cell((x, y)).unwrap(); 307 | 308 | let symbol = cell.symbol(); 309 | print!("{}", &symbol); 310 | 311 | if !symbol.trim().is_empty() { 312 | row_is_empty = false; 313 | } 314 | } 315 | 316 | // Early exit after last line with content 317 | println!(); 318 | if row_is_empty { 319 | break; 320 | } 321 | } 322 | } 323 | 324 | struct CustomBufferWidget<'a> { 325 | buffer: &'a Buffer, 326 | scroll_offset: u16, 327 | } 328 | 329 | impl<'a> CustomBufferWidget<'a> { 330 | fn new(buffer: &'a Buffer) -> Self { 331 | CustomBufferWidget { 332 | buffer, 333 | scroll_offset: 0, 334 | } 335 | } 336 | 337 | fn set_scroll_offset(&mut self, offset: u16) { 338 | self.scroll_offset = offset; 339 | } 340 | } 341 | 342 | impl<'a> Widget for CustomBufferWidget<'a> { 343 | fn render(self, area: Rect, buf: &mut Buffer) { 344 | if buf.area.height < area.height + area.y { 345 | buf.resize(Rect::new( 346 | area.x, 347 | area.y, 348 | buf.area.width, 349 | area.height + area.y - buf.area.height, 350 | )); 351 | } 352 | 353 | for y in self.scroll_offset..self.buffer.area().height { 354 | for x in 0..self.buffer.area().width { 355 | let src_cell = self.buffer.cell((x, y)).unwrap(); 356 | let dest_x = area.x + x; 357 | let dest_y = area.y + y - self.scroll_offset; 358 | if dest_x < buf.area().width && dest_y < buf.area().height { 359 | buf.cell_mut((dest_x, dest_y)) 360 | .unwrap() 361 | .set_symbol(src_cell.symbol()) 362 | .set_style(src_cell.style()); 363 | } 364 | } 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/config_parser.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::error::Error; 3 | use std::fs; 4 | 5 | #[derive(Debug, Serialize, Deserialize, Clone)] 6 | pub struct Mapping { 7 | name: String, 8 | children: Option>, 9 | bindings: Vec, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize, Clone)] 13 | pub struct Binding { 14 | pub name: String, 15 | pub key: String, 16 | } 17 | 18 | #[derive(Debug, Serialize, Deserialize, Clone)] 19 | pub struct Config(Vec); 20 | 21 | pub fn parse_config(filename: &str) -> Result> { 22 | let contents = fs::read_to_string(filename)?; 23 | let config: Config = serde_yaml::from_str(&contents)?; 24 | Ok(config) 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct TableConfig { 29 | pub name: String, 30 | pub bindings: Vec, 31 | } 32 | 33 | fn add_mapping_to_list(prefix:&str, tables: &mut Vec, mapping: &Mapping) { 34 | let mapping_name = if prefix.is_empty() { mapping.name.to_string() } else { format!("{}:{}", prefix , &mapping.name) }; 35 | let table_config = TableConfig{ 36 | name: mapping_name.clone(), 37 | bindings: mapping.bindings.clone(), 38 | }; 39 | tables.push(table_config); 40 | 41 | if let Some(children) = &mapping.children { 42 | for child in children { 43 | add_mapping_to_list(&mapping_name, tables, child); 44 | } 45 | } 46 | } 47 | 48 | impl Config { 49 | pub fn get_table_configs(&self) -> Vec { 50 | let mut table_configs = Vec::new(); 51 | 52 | for mapping in self.0.iter() { 53 | add_mapping_to_list("", &mut table_configs, &mapping); 54 | } 55 | 56 | table_configs 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub use app::App; 2 | 3 | pub mod app; 4 | 5 | mod config_parser; 6 | 7 | use structopt::StructOpt; 8 | 9 | const DEFAULT_YAML_CONFIG: &str = r#" 10 | - name: app 11 | children: 12 | - name: subcategory 13 | bindings: 14 | - name: do something 15 | key: ctrl + a 16 | - name: do something else 17 | key: ctrl + b 18 | bindings: 19 | - name: one 20 | key: shift + c 21 | - name: tmux 22 | bindings: 23 | - name: split vertical 24 | key: ctrl + h 25 | - name: split horizontal 26 | key: ctrl + l 27 | "#; 28 | 29 | /// A simple CLI tool 30 | #[derive(StructOpt, Debug)] 31 | #[structopt(name = "crib")] 32 | struct Opt { 33 | /// The initial query 34 | query: Option, 35 | /// Print the configuration 36 | #[structopt(long)] 37 | print: bool, 38 | } 39 | 40 | fn main() -> color_eyre::Result<()> { 41 | color_eyre::install()?; 42 | use dirs::home_dir; 43 | use std::fs; 44 | 45 | let opt = Opt::from_args(); 46 | 47 | let initial_query = match opt.query { 48 | Some(query) => format!("{}:", query), 49 | None => String::new(), 50 | }; 51 | 52 | let config_path = home_dir() 53 | .map(|p| p.join(".config/crib/bindings.yaml")) 54 | .expect("Failed to determine home directory"); 55 | 56 | if !config_path.exists() { 57 | fs::create_dir_all(config_path.parent().unwrap())?; 58 | fs::write(&config_path, DEFAULT_YAML_CONFIG)?; 59 | } 60 | 61 | let config = config_parser::parse_config(config_path.to_str().unwrap()).unwrap(); 62 | let table_configs = config.get_table_configs(); 63 | let app = App::new(initial_query, table_configs); 64 | 65 | let result = if opt.print { 66 | app.draw_to_buffer(); 67 | Ok(()) 68 | } else { 69 | let terminal = ratatui::init(); 70 | let result = app.run(terminal); 71 | ratatui::restore(); 72 | result 73 | }; 74 | result 75 | } 76 | --------------------------------------------------------------------------------