├── .github └── workflows │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Taskfile.yaml ├── doc └── demo.gif ├── readme.md ├── scripts ├── install.ps1 ├── install.sh └── version.sh └── src ├── cli.rs ├── commands ├── create.rs ├── mod.rs └── search │ ├── framework │ ├── event.rs │ ├── mod.rs │ └── tui.rs │ ├── handlers.rs │ ├── mod.rs │ ├── state │ ├── collection.rs │ ├── mod.rs │ └── view_preview.rs │ └── views │ ├── home │ ├── footer.rs │ ├── header.rs │ ├── main_list.rs │ ├── main_side.rs │ └── mod.rs │ ├── mod.rs │ ├── preview │ ├── footer.rs │ └── mod.rs │ └── util.rs ├── main.rs ├── template ├── collection │ ├── github.rs │ ├── mod.rs │ └── toptal.rs ├── item │ ├── cache.rs │ └── mod.rs ├── list.rs └── mod.rs ├── tests.rs └── util ├── fs.rs ├── http.rs ├── mod.rs ├── package.rs └── string.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Inspired by: 2 | # https://github.com/open-contracting/cardinal-rs/blob/main/.github/workflows/release.yml 3 | 4 | name: Test & Release 5 | # The name is either "Release vX.X.X" on tag push or the commit message 6 | run-name: ${{ github.ref_name != 'main' && format('Release - {0}', github.ref_name) || github.event.head_commit.message }} 7 | on: 8 | push: 9 | branches: [ "main" ] 10 | tags: [ "v[0-9]+.[0-9]+.[0-9]+" ] 11 | env: 12 | APP_NAME: gitnr 13 | CARGO_TERM_COLOR: always 14 | jobs: 15 | test: 16 | name: Test 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | - name: Install Rust 23 | uses: dtolnay/rust-toolchain@stable 24 | with: { components: "clippy" } 25 | 26 | - name: Setup Cache 27 | uses: Swatinem/rust-cache@v2 28 | if: github.ref_name == 'main' # Caching doesn't work on tags, so skip it 29 | with: 30 | cache-targets: "true" 31 | cache-on-failure: "true" 32 | cache-all-crates: "true" 33 | 34 | - name: Run Clippy 35 | run: cargo clippy --all-targets -- -D warnings 36 | 37 | - name: Run Tests 38 | run: cargo test --verbose 39 | 40 | setup: 41 | name: Setup 42 | needs: test 43 | runs-on: ubuntu-latest 44 | if: github.ref_name != 'main' # Only run on tag push 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v3 48 | 49 | - name: Create Draft Release 50 | run: gh release create ${{ github.ref_name }} --verify-tag --draft=true --title="${{ github.ref_name }}" 51 | env: { GH_TOKEN: "${{ secrets.GH_ACTIONS_PAT }}" } 52 | 53 | build: 54 | name: Build 55 | needs: setup 56 | runs-on: ${{ matrix.runner }} 57 | if: github.ref_name != 'main' # Only run on tag push 58 | strategy: 59 | matrix: 60 | include: 61 | # == Linux == # 62 | - name: linux-amd64 63 | runner: ubuntu-latest 64 | target: x86_64-unknown-linux-gnu 65 | # == Windows == # 66 | - name: win-amd64 67 | runner: windows-latest 68 | target: x86_64-pc-windows-msvc 69 | # == MacOS == # 70 | - name: macos-amd64 71 | runner: macos-latest 72 | target: x86_64-apple-darwin 73 | - name: macos-arm64 74 | runner: macos-latest 75 | target: aarch64-apple-darwin 76 | 77 | steps: 78 | - name: Checkout 79 | uses: actions/checkout@v3 80 | 81 | - name: Install Rust 82 | uses: dtolnay/rust-toolchain@stable 83 | with: { targets: "${{ matrix.target }}" } 84 | 85 | - name: Build Binary 86 | run: cargo build --verbose --locked --release --target ${{ matrix.target }} 87 | 88 | - name: Release Upload Binary 89 | shell: bash 90 | env: { GH_TOKEN: "${{ secrets.GH_ACTIONS_PAT }}" } 91 | run: | 92 | BIN_SUFFIX="" 93 | if [[ "${{ matrix.runner }}" == "windows-latest" ]]; then 94 | BIN_SUFFIX=".exe" 95 | fi 96 | 97 | BIN_OUTPUT="target/${{ matrix.target }}/release/${APP_NAME}${BIN_SUFFIX}" 98 | BIN_RELEASE="${APP_NAME}-${{ matrix.name }}${BIN_SUFFIX}" 99 | 100 | # Not used as makes it hard to download binary for script setup 101 | BIN_RELEASE_VERSIONED="${APP_NAME}-${{ github.ref_name }}-${{ matrix.name }}${BIN_SUFFIX}" 102 | 103 | cp "${BIN_OUTPUT}" "${BIN_RELEASE}" 104 | gh release upload ${{ github.ref_name }} "${BIN_RELEASE}" 105 | 106 | release: 107 | name: Release 108 | needs: build 109 | runs-on: ubuntu-latest 110 | if: github.ref_name != 'main' # Only run on tag push 111 | steps: 112 | - name: Checkout 113 | uses: actions/checkout@v3 114 | 115 | - name: Publish GitHub Release 116 | run: gh release edit ${{ github.ref_name }} --verify-tag --draft=false 117 | env: { GH_TOKEN: "${{ secrets.GH_ACTIONS_PAT }}" } 118 | 119 | - name: Install Rust 120 | uses: dtolnay/rust-toolchain@stable 121 | 122 | # This step takes place only after GH release to ensure binaries are available via cargo-binstall 123 | - name: Publish Crates.io Release 124 | run: cargo publish --verbose --locked --token ${{ secrets.CARGO_REGISTRY_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###-------------### 2 | ### JetBrains ### 3 | ###-------------### 4 | 5 | .idea/ 6 | *.iml 7 | 8 | ###----------------### 9 | ### GitHub: Rust ### 10 | ###----------------### 11 | 12 | # Generated by Cargo 13 | # will have compiled files and executables 14 | debug/ 15 | target/ 16 | 17 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 18 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 19 | # Cargo.lock 20 | 21 | # These are backup files generated by rustfmt 22 | **/*.rs.bk 23 | 24 | # MSVC Windows builds of rustc generate these, which store debugging information 25 | *.pdb 26 | -------------------------------------------------------------------------------- /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.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "allocator-api2" 22 | version = "0.2.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.15" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "is_terminal_polyfill", 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle" 43 | version = "1.0.8" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 46 | 47 | [[package]] 48 | name = "anstyle-parse" 49 | version = "0.2.5" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 52 | dependencies = [ 53 | "utf8parse", 54 | ] 55 | 56 | [[package]] 57 | name = "anstyle-query" 58 | version = "1.1.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 61 | dependencies = [ 62 | "windows-sys 0.52.0", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-wincon" 67 | version = "3.0.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 70 | dependencies = [ 71 | "anstyle", 72 | "windows-sys 0.52.0", 73 | ] 74 | 75 | [[package]] 76 | name = "anyhow" 77 | version = "1.0.90" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" 80 | 81 | [[package]] 82 | name = "autocfg" 83 | version = "1.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 86 | 87 | [[package]] 88 | name = "backtrace" 89 | version = "0.3.74" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 92 | dependencies = [ 93 | "addr2line", 94 | "cfg-if", 95 | "libc", 96 | "miniz_oxide", 97 | "object", 98 | "rustc-demangle", 99 | "windows-targets 0.52.6", 100 | ] 101 | 102 | [[package]] 103 | name = "base64" 104 | version = "0.22.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "1.3.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 113 | 114 | [[package]] 115 | name = "bitflags" 116 | version = "2.6.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 119 | 120 | [[package]] 121 | name = "block" 122 | version = "0.1.6" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 125 | 126 | [[package]] 127 | name = "cassowary" 128 | version = "0.3.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 131 | 132 | [[package]] 133 | name = "castaway" 134 | version = "0.2.3" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 137 | dependencies = [ 138 | "rustversion", 139 | ] 140 | 141 | [[package]] 142 | name = "cc" 143 | version = "1.1.31" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" 146 | dependencies = [ 147 | "shlex", 148 | ] 149 | 150 | [[package]] 151 | name = "cfg-if" 152 | version = "1.0.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 155 | 156 | [[package]] 157 | name = "clap" 158 | version = "4.5.20" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 161 | dependencies = [ 162 | "clap_builder", 163 | "clap_derive", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_builder" 168 | version = "4.5.20" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 171 | dependencies = [ 172 | "anstream", 173 | "anstyle", 174 | "clap_lex", 175 | "strsim", 176 | ] 177 | 178 | [[package]] 179 | name = "clap_derive" 180 | version = "4.5.18" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 183 | dependencies = [ 184 | "heck", 185 | "proc-macro2", 186 | "quote", 187 | "syn", 188 | ] 189 | 190 | [[package]] 191 | name = "clap_lex" 192 | version = "0.7.2" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 195 | 196 | [[package]] 197 | name = "clipboard-win" 198 | version = "3.1.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" 201 | dependencies = [ 202 | "lazy-bytes-cast", 203 | "winapi", 204 | ] 205 | 206 | [[package]] 207 | name = "colorchoice" 208 | version = "1.0.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 211 | 212 | [[package]] 213 | name = "compact_str" 214 | version = "0.8.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" 217 | dependencies = [ 218 | "castaway", 219 | "cfg-if", 220 | "itoa", 221 | "rustversion", 222 | "ryu", 223 | "static_assertions", 224 | ] 225 | 226 | [[package]] 227 | name = "copypasta" 228 | version = "0.8.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "133fc8675ee3a4ec9aa513584deda9aa0faeda3586b87f7f0f2ba082c66fb172" 231 | dependencies = [ 232 | "clipboard-win", 233 | "objc", 234 | "objc-foundation", 235 | "objc_id", 236 | "smithay-clipboard", 237 | "x11-clipboard", 238 | ] 239 | 240 | [[package]] 241 | name = "core-foundation" 242 | version = "0.9.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 245 | dependencies = [ 246 | "core-foundation-sys", 247 | "libc", 248 | ] 249 | 250 | [[package]] 251 | name = "core-foundation-sys" 252 | version = "0.8.7" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 255 | 256 | [[package]] 257 | name = "crc32fast" 258 | version = "1.4.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 261 | dependencies = [ 262 | "cfg-if", 263 | ] 264 | 265 | [[package]] 266 | name = "crossterm" 267 | version = "0.28.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 270 | dependencies = [ 271 | "bitflags 2.6.0", 272 | "crossterm_winapi", 273 | "mio", 274 | "parking_lot", 275 | "rustix", 276 | "signal-hook", 277 | "signal-hook-mio", 278 | "winapi", 279 | ] 280 | 281 | [[package]] 282 | name = "crossterm_winapi" 283 | version = "0.9.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 286 | dependencies = [ 287 | "winapi", 288 | ] 289 | 290 | [[package]] 291 | name = "dirs" 292 | version = "5.0.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 295 | dependencies = [ 296 | "dirs-sys", 297 | ] 298 | 299 | [[package]] 300 | name = "dirs-sys" 301 | version = "0.4.1" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 304 | dependencies = [ 305 | "libc", 306 | "option-ext", 307 | "redox_users", 308 | "windows-sys 0.48.0", 309 | ] 310 | 311 | [[package]] 312 | name = "dlib" 313 | version = "0.5.2" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 316 | dependencies = [ 317 | "libloading", 318 | ] 319 | 320 | [[package]] 321 | name = "downcast-rs" 322 | version = "1.2.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 325 | 326 | [[package]] 327 | name = "either" 328 | version = "1.13.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 331 | 332 | [[package]] 333 | name = "equivalent" 334 | version = "1.0.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 337 | 338 | [[package]] 339 | name = "errno" 340 | version = "0.3.9" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 343 | dependencies = [ 344 | "libc", 345 | "windows-sys 0.52.0", 346 | ] 347 | 348 | [[package]] 349 | name = "fastrand" 350 | version = "2.1.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 353 | 354 | [[package]] 355 | name = "flate2" 356 | version = "1.0.34" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 359 | dependencies = [ 360 | "crc32fast", 361 | "miniz_oxide", 362 | ] 363 | 364 | [[package]] 365 | name = "foldhash" 366 | version = "0.1.3" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" 369 | 370 | [[package]] 371 | name = "foreign-types" 372 | version = "0.3.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 375 | dependencies = [ 376 | "foreign-types-shared", 377 | ] 378 | 379 | [[package]] 380 | name = "foreign-types-shared" 381 | version = "0.1.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 384 | 385 | [[package]] 386 | name = "form_urlencoded" 387 | version = "1.2.1" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 390 | dependencies = [ 391 | "percent-encoding", 392 | ] 393 | 394 | [[package]] 395 | name = "gethostname" 396 | version = "0.2.3" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" 399 | dependencies = [ 400 | "libc", 401 | "winapi", 402 | ] 403 | 404 | [[package]] 405 | name = "getrandom" 406 | version = "0.2.15" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 409 | dependencies = [ 410 | "cfg-if", 411 | "libc", 412 | "wasi", 413 | ] 414 | 415 | [[package]] 416 | name = "gimli" 417 | version = "0.31.1" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 420 | 421 | [[package]] 422 | name = "gitnr" 423 | version = "0.2.2" 424 | dependencies = [ 425 | "anyhow", 426 | "clap", 427 | "copypasta", 428 | "dirs", 429 | "human-panic", 430 | "indoc", 431 | "native-tls", 432 | "once_cell", 433 | "ratatui", 434 | "serde", 435 | "serde_json", 436 | "tui-input", 437 | "ureq", 438 | "url", 439 | "yansi", 440 | ] 441 | 442 | [[package]] 443 | name = "hashbrown" 444 | version = "0.15.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 447 | dependencies = [ 448 | "allocator-api2", 449 | "equivalent", 450 | "foldhash", 451 | ] 452 | 453 | [[package]] 454 | name = "heck" 455 | version = "0.5.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 458 | 459 | [[package]] 460 | name = "hermit-abi" 461 | version = "0.3.9" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 464 | 465 | [[package]] 466 | name = "human-panic" 467 | version = "2.0.2" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "80b84a66a325082740043a6c28bbea400c129eac0d3a27673a1de971e44bf1f7" 470 | dependencies = [ 471 | "anstream", 472 | "anstyle", 473 | "backtrace", 474 | "os_info", 475 | "serde", 476 | "serde_derive", 477 | "toml", 478 | "uuid", 479 | ] 480 | 481 | [[package]] 482 | name = "idna" 483 | version = "0.5.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 486 | dependencies = [ 487 | "unicode-bidi", 488 | "unicode-normalization", 489 | ] 490 | 491 | [[package]] 492 | name = "indexmap" 493 | version = "2.6.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 496 | dependencies = [ 497 | "equivalent", 498 | "hashbrown", 499 | ] 500 | 501 | [[package]] 502 | name = "indoc" 503 | version = "2.0.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 506 | 507 | [[package]] 508 | name = "instability" 509 | version = "0.3.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" 512 | dependencies = [ 513 | "quote", 514 | "syn", 515 | ] 516 | 517 | [[package]] 518 | name = "is_terminal_polyfill" 519 | version = "1.70.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 522 | 523 | [[package]] 524 | name = "itertools" 525 | version = "0.13.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 528 | dependencies = [ 529 | "either", 530 | ] 531 | 532 | [[package]] 533 | name = "itoa" 534 | version = "1.0.11" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 537 | 538 | [[package]] 539 | name = "lazy-bytes-cast" 540 | version = "5.0.1" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" 543 | 544 | [[package]] 545 | name = "lazy_static" 546 | version = "1.5.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 549 | 550 | [[package]] 551 | name = "libc" 552 | version = "0.2.161" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 555 | 556 | [[package]] 557 | name = "libloading" 558 | version = "0.8.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 561 | dependencies = [ 562 | "cfg-if", 563 | "windows-targets 0.52.6", 564 | ] 565 | 566 | [[package]] 567 | name = "libredox" 568 | version = "0.1.3" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 571 | dependencies = [ 572 | "bitflags 2.6.0", 573 | "libc", 574 | ] 575 | 576 | [[package]] 577 | name = "linux-raw-sys" 578 | version = "0.4.14" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 581 | 582 | [[package]] 583 | name = "lock_api" 584 | version = "0.4.12" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 587 | dependencies = [ 588 | "autocfg", 589 | "scopeguard", 590 | ] 591 | 592 | [[package]] 593 | name = "log" 594 | version = "0.4.22" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 597 | 598 | [[package]] 599 | name = "lru" 600 | version = "0.12.5" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 603 | dependencies = [ 604 | "hashbrown", 605 | ] 606 | 607 | [[package]] 608 | name = "malloc_buf" 609 | version = "0.0.6" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 612 | dependencies = [ 613 | "libc", 614 | ] 615 | 616 | [[package]] 617 | name = "memchr" 618 | version = "2.7.4" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 621 | 622 | [[package]] 623 | name = "memmap2" 624 | version = "0.5.10" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" 627 | dependencies = [ 628 | "libc", 629 | ] 630 | 631 | [[package]] 632 | name = "memoffset" 633 | version = "0.6.5" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 636 | dependencies = [ 637 | "autocfg", 638 | ] 639 | 640 | [[package]] 641 | name = "miniz_oxide" 642 | version = "0.8.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 645 | dependencies = [ 646 | "adler2", 647 | ] 648 | 649 | [[package]] 650 | name = "mio" 651 | version = "1.0.2" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 654 | dependencies = [ 655 | "hermit-abi", 656 | "libc", 657 | "log", 658 | "wasi", 659 | "windows-sys 0.52.0", 660 | ] 661 | 662 | [[package]] 663 | name = "native-tls" 664 | version = "0.2.12" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 667 | dependencies = [ 668 | "libc", 669 | "log", 670 | "openssl", 671 | "openssl-probe", 672 | "openssl-sys", 673 | "schannel", 674 | "security-framework", 675 | "security-framework-sys", 676 | "tempfile", 677 | ] 678 | 679 | [[package]] 680 | name = "nix" 681 | version = "0.24.3" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" 684 | dependencies = [ 685 | "bitflags 1.3.2", 686 | "cfg-if", 687 | "libc", 688 | "memoffset", 689 | ] 690 | 691 | [[package]] 692 | name = "objc" 693 | version = "0.2.7" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 696 | dependencies = [ 697 | "malloc_buf", 698 | ] 699 | 700 | [[package]] 701 | name = "objc-foundation" 702 | version = "0.1.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 705 | dependencies = [ 706 | "block", 707 | "objc", 708 | "objc_id", 709 | ] 710 | 711 | [[package]] 712 | name = "objc_id" 713 | version = "0.1.1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 716 | dependencies = [ 717 | "objc", 718 | ] 719 | 720 | [[package]] 721 | name = "object" 722 | version = "0.36.5" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 725 | dependencies = [ 726 | "memchr", 727 | ] 728 | 729 | [[package]] 730 | name = "once_cell" 731 | version = "1.20.2" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 734 | 735 | [[package]] 736 | name = "openssl" 737 | version = "0.10.68" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 740 | dependencies = [ 741 | "bitflags 2.6.0", 742 | "cfg-if", 743 | "foreign-types", 744 | "libc", 745 | "once_cell", 746 | "openssl-macros", 747 | "openssl-sys", 748 | ] 749 | 750 | [[package]] 751 | name = "openssl-macros" 752 | version = "0.1.1" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 755 | dependencies = [ 756 | "proc-macro2", 757 | "quote", 758 | "syn", 759 | ] 760 | 761 | [[package]] 762 | name = "openssl-probe" 763 | version = "0.1.5" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 766 | 767 | [[package]] 768 | name = "openssl-sys" 769 | version = "0.9.104" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 772 | dependencies = [ 773 | "cc", 774 | "libc", 775 | "pkg-config", 776 | "vcpkg", 777 | ] 778 | 779 | [[package]] 780 | name = "option-ext" 781 | version = "0.2.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 784 | 785 | [[package]] 786 | name = "os_info" 787 | version = "3.8.2" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" 790 | dependencies = [ 791 | "log", 792 | "serde", 793 | "windows-sys 0.52.0", 794 | ] 795 | 796 | [[package]] 797 | name = "parking_lot" 798 | version = "0.12.3" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 801 | dependencies = [ 802 | "lock_api", 803 | "parking_lot_core", 804 | ] 805 | 806 | [[package]] 807 | name = "parking_lot_core" 808 | version = "0.9.10" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 811 | dependencies = [ 812 | "cfg-if", 813 | "libc", 814 | "redox_syscall", 815 | "smallvec", 816 | "windows-targets 0.52.6", 817 | ] 818 | 819 | [[package]] 820 | name = "paste" 821 | version = "1.0.15" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 824 | 825 | [[package]] 826 | name = "percent-encoding" 827 | version = "2.3.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 830 | 831 | [[package]] 832 | name = "pkg-config" 833 | version = "0.3.31" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 836 | 837 | [[package]] 838 | name = "proc-macro2" 839 | version = "1.0.88" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" 842 | dependencies = [ 843 | "unicode-ident", 844 | ] 845 | 846 | [[package]] 847 | name = "quote" 848 | version = "1.0.37" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 851 | dependencies = [ 852 | "proc-macro2", 853 | ] 854 | 855 | [[package]] 856 | name = "ratatui" 857 | version = "0.28.1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" 860 | dependencies = [ 861 | "bitflags 2.6.0", 862 | "cassowary", 863 | "compact_str", 864 | "crossterm", 865 | "instability", 866 | "itertools", 867 | "lru", 868 | "paste", 869 | "strum", 870 | "strum_macros", 871 | "unicode-segmentation", 872 | "unicode-truncate", 873 | "unicode-width", 874 | ] 875 | 876 | [[package]] 877 | name = "redox_syscall" 878 | version = "0.5.7" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 881 | dependencies = [ 882 | "bitflags 2.6.0", 883 | ] 884 | 885 | [[package]] 886 | name = "redox_users" 887 | version = "0.4.6" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 890 | dependencies = [ 891 | "getrandom", 892 | "libredox", 893 | "thiserror", 894 | ] 895 | 896 | [[package]] 897 | name = "rustc-demangle" 898 | version = "0.1.24" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 901 | 902 | [[package]] 903 | name = "rustix" 904 | version = "0.38.37" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 907 | dependencies = [ 908 | "bitflags 2.6.0", 909 | "errno", 910 | "libc", 911 | "linux-raw-sys", 912 | "windows-sys 0.52.0", 913 | ] 914 | 915 | [[package]] 916 | name = "rustversion" 917 | version = "1.0.18" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 920 | 921 | [[package]] 922 | name = "ryu" 923 | version = "1.0.18" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 926 | 927 | [[package]] 928 | name = "schannel" 929 | version = "0.1.26" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 932 | dependencies = [ 933 | "windows-sys 0.59.0", 934 | ] 935 | 936 | [[package]] 937 | name = "scoped-tls" 938 | version = "1.0.1" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 941 | 942 | [[package]] 943 | name = "scopeguard" 944 | version = "1.2.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 947 | 948 | [[package]] 949 | name = "security-framework" 950 | version = "2.11.1" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 953 | dependencies = [ 954 | "bitflags 2.6.0", 955 | "core-foundation", 956 | "core-foundation-sys", 957 | "libc", 958 | "security-framework-sys", 959 | ] 960 | 961 | [[package]] 962 | name = "security-framework-sys" 963 | version = "2.12.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 966 | dependencies = [ 967 | "core-foundation-sys", 968 | "libc", 969 | ] 970 | 971 | [[package]] 972 | name = "serde" 973 | version = "1.0.210" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 976 | dependencies = [ 977 | "serde_derive", 978 | ] 979 | 980 | [[package]] 981 | name = "serde_derive" 982 | version = "1.0.210" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 985 | dependencies = [ 986 | "proc-macro2", 987 | "quote", 988 | "syn", 989 | ] 990 | 991 | [[package]] 992 | name = "serde_json" 993 | version = "1.0.132" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 996 | dependencies = [ 997 | "itoa", 998 | "memchr", 999 | "ryu", 1000 | "serde", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "serde_spanned" 1005 | version = "0.6.8" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1008 | dependencies = [ 1009 | "serde", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "shlex" 1014 | version = "1.3.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1017 | 1018 | [[package]] 1019 | name = "signal-hook" 1020 | version = "0.3.17" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1023 | dependencies = [ 1024 | "libc", 1025 | "signal-hook-registry", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "signal-hook-mio" 1030 | version = "0.2.4" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1033 | dependencies = [ 1034 | "libc", 1035 | "mio", 1036 | "signal-hook", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "signal-hook-registry" 1041 | version = "1.4.2" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1044 | dependencies = [ 1045 | "libc", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "smallvec" 1050 | version = "1.13.2" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1053 | 1054 | [[package]] 1055 | name = "smithay-client-toolkit" 1056 | version = "0.16.1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" 1059 | dependencies = [ 1060 | "bitflags 1.3.2", 1061 | "dlib", 1062 | "lazy_static", 1063 | "log", 1064 | "memmap2", 1065 | "nix", 1066 | "pkg-config", 1067 | "wayland-client", 1068 | "wayland-cursor", 1069 | "wayland-protocols", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "smithay-clipboard" 1074 | version = "0.6.6" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" 1077 | dependencies = [ 1078 | "smithay-client-toolkit", 1079 | "wayland-client", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "static_assertions" 1084 | version = "1.1.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1087 | 1088 | [[package]] 1089 | name = "strsim" 1090 | version = "0.11.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1093 | 1094 | [[package]] 1095 | name = "strum" 1096 | version = "0.26.3" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1099 | dependencies = [ 1100 | "strum_macros", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "strum_macros" 1105 | version = "0.26.4" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1108 | dependencies = [ 1109 | "heck", 1110 | "proc-macro2", 1111 | "quote", 1112 | "rustversion", 1113 | "syn", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "syn" 1118 | version = "2.0.82" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" 1121 | dependencies = [ 1122 | "proc-macro2", 1123 | "quote", 1124 | "unicode-ident", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "tempfile" 1129 | version = "3.13.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1132 | dependencies = [ 1133 | "cfg-if", 1134 | "fastrand", 1135 | "once_cell", 1136 | "rustix", 1137 | "windows-sys 0.59.0", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "thiserror" 1142 | version = "1.0.64" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1145 | dependencies = [ 1146 | "thiserror-impl", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "thiserror-impl" 1151 | version = "1.0.64" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1154 | dependencies = [ 1155 | "proc-macro2", 1156 | "quote", 1157 | "syn", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "tinyvec" 1162 | version = "1.8.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1165 | dependencies = [ 1166 | "tinyvec_macros", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "tinyvec_macros" 1171 | version = "0.1.1" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1174 | 1175 | [[package]] 1176 | name = "toml" 1177 | version = "0.8.19" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1180 | dependencies = [ 1181 | "serde", 1182 | "serde_spanned", 1183 | "toml_datetime", 1184 | "toml_edit", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "toml_datetime" 1189 | version = "0.6.8" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1192 | dependencies = [ 1193 | "serde", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "toml_edit" 1198 | version = "0.22.22" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1201 | dependencies = [ 1202 | "indexmap", 1203 | "serde", 1204 | "serde_spanned", 1205 | "toml_datetime", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "tui-input" 1210 | version = "0.10.1" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "bd137780d743c103a391e06fe952487f914b299a4fe2c3626677f6a6339a7c6b" 1213 | dependencies = [ 1214 | "ratatui", 1215 | "unicode-width", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "unicode-bidi" 1220 | version = "0.3.17" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1223 | 1224 | [[package]] 1225 | name = "unicode-ident" 1226 | version = "1.0.13" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1229 | 1230 | [[package]] 1231 | name = "unicode-normalization" 1232 | version = "0.1.24" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1235 | dependencies = [ 1236 | "tinyvec", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "unicode-segmentation" 1241 | version = "1.12.0" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1244 | 1245 | [[package]] 1246 | name = "unicode-truncate" 1247 | version = "1.1.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1250 | dependencies = [ 1251 | "itertools", 1252 | "unicode-segmentation", 1253 | "unicode-width", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "unicode-width" 1258 | version = "0.1.14" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1261 | 1262 | [[package]] 1263 | name = "ureq" 1264 | version = "2.10.1" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" 1267 | dependencies = [ 1268 | "base64", 1269 | "flate2", 1270 | "log", 1271 | "native-tls", 1272 | "once_cell", 1273 | "url", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "url" 1278 | version = "2.5.2" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1281 | dependencies = [ 1282 | "form_urlencoded", 1283 | "idna", 1284 | "percent-encoding", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "utf8parse" 1289 | version = "0.2.2" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1292 | 1293 | [[package]] 1294 | name = "uuid" 1295 | version = "1.11.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 1298 | dependencies = [ 1299 | "getrandom", 1300 | ] 1301 | 1302 | [[package]] 1303 | name = "vcpkg" 1304 | version = "0.2.15" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1307 | 1308 | [[package]] 1309 | name = "wasi" 1310 | version = "0.11.0+wasi-snapshot-preview1" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1313 | 1314 | [[package]] 1315 | name = "wayland-client" 1316 | version = "0.29.5" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" 1319 | dependencies = [ 1320 | "bitflags 1.3.2", 1321 | "downcast-rs", 1322 | "libc", 1323 | "nix", 1324 | "scoped-tls", 1325 | "wayland-commons", 1326 | "wayland-scanner", 1327 | "wayland-sys", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "wayland-commons" 1332 | version = "0.29.5" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" 1335 | dependencies = [ 1336 | "nix", 1337 | "once_cell", 1338 | "smallvec", 1339 | "wayland-sys", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "wayland-cursor" 1344 | version = "0.29.5" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" 1347 | dependencies = [ 1348 | "nix", 1349 | "wayland-client", 1350 | "xcursor", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "wayland-protocols" 1355 | version = "0.29.5" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" 1358 | dependencies = [ 1359 | "bitflags 1.3.2", 1360 | "wayland-client", 1361 | "wayland-commons", 1362 | "wayland-scanner", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "wayland-scanner" 1367 | version = "0.29.5" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" 1370 | dependencies = [ 1371 | "proc-macro2", 1372 | "quote", 1373 | "xml-rs", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "wayland-sys" 1378 | version = "0.29.5" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" 1381 | dependencies = [ 1382 | "dlib", 1383 | "lazy_static", 1384 | "pkg-config", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "winapi" 1389 | version = "0.3.9" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1392 | dependencies = [ 1393 | "winapi-i686-pc-windows-gnu", 1394 | "winapi-x86_64-pc-windows-gnu", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "winapi-i686-pc-windows-gnu" 1399 | version = "0.4.0" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1402 | 1403 | [[package]] 1404 | name = "winapi-wsapoll" 1405 | version = "0.1.2" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" 1408 | dependencies = [ 1409 | "winapi", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "winapi-x86_64-pc-windows-gnu" 1414 | version = "0.4.0" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1417 | 1418 | [[package]] 1419 | name = "windows-sys" 1420 | version = "0.48.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1423 | dependencies = [ 1424 | "windows-targets 0.48.5", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "windows-sys" 1429 | version = "0.52.0" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1432 | dependencies = [ 1433 | "windows-targets 0.52.6", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "windows-sys" 1438 | version = "0.59.0" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1441 | dependencies = [ 1442 | "windows-targets 0.52.6", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "windows-targets" 1447 | version = "0.48.5" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1450 | dependencies = [ 1451 | "windows_aarch64_gnullvm 0.48.5", 1452 | "windows_aarch64_msvc 0.48.5", 1453 | "windows_i686_gnu 0.48.5", 1454 | "windows_i686_msvc 0.48.5", 1455 | "windows_x86_64_gnu 0.48.5", 1456 | "windows_x86_64_gnullvm 0.48.5", 1457 | "windows_x86_64_msvc 0.48.5", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "windows-targets" 1462 | version = "0.52.6" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1465 | dependencies = [ 1466 | "windows_aarch64_gnullvm 0.52.6", 1467 | "windows_aarch64_msvc 0.52.6", 1468 | "windows_i686_gnu 0.52.6", 1469 | "windows_i686_gnullvm", 1470 | "windows_i686_msvc 0.52.6", 1471 | "windows_x86_64_gnu 0.52.6", 1472 | "windows_x86_64_gnullvm 0.52.6", 1473 | "windows_x86_64_msvc 0.52.6", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "windows_aarch64_gnullvm" 1478 | version = "0.48.5" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1481 | 1482 | [[package]] 1483 | name = "windows_aarch64_gnullvm" 1484 | version = "0.52.6" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1487 | 1488 | [[package]] 1489 | name = "windows_aarch64_msvc" 1490 | version = "0.48.5" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1493 | 1494 | [[package]] 1495 | name = "windows_aarch64_msvc" 1496 | version = "0.52.6" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1499 | 1500 | [[package]] 1501 | name = "windows_i686_gnu" 1502 | version = "0.48.5" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1505 | 1506 | [[package]] 1507 | name = "windows_i686_gnu" 1508 | version = "0.52.6" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1511 | 1512 | [[package]] 1513 | name = "windows_i686_gnullvm" 1514 | version = "0.52.6" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1517 | 1518 | [[package]] 1519 | name = "windows_i686_msvc" 1520 | version = "0.48.5" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1523 | 1524 | [[package]] 1525 | name = "windows_i686_msvc" 1526 | version = "0.52.6" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1529 | 1530 | [[package]] 1531 | name = "windows_x86_64_gnu" 1532 | version = "0.48.5" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1535 | 1536 | [[package]] 1537 | name = "windows_x86_64_gnu" 1538 | version = "0.52.6" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1541 | 1542 | [[package]] 1543 | name = "windows_x86_64_gnullvm" 1544 | version = "0.48.5" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1547 | 1548 | [[package]] 1549 | name = "windows_x86_64_gnullvm" 1550 | version = "0.52.6" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1553 | 1554 | [[package]] 1555 | name = "windows_x86_64_msvc" 1556 | version = "0.48.5" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1559 | 1560 | [[package]] 1561 | name = "windows_x86_64_msvc" 1562 | version = "0.52.6" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1565 | 1566 | [[package]] 1567 | name = "x11-clipboard" 1568 | version = "0.7.1" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" 1571 | dependencies = [ 1572 | "x11rb", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "x11rb" 1577 | version = "0.10.1" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" 1580 | dependencies = [ 1581 | "gethostname", 1582 | "nix", 1583 | "winapi", 1584 | "winapi-wsapoll", 1585 | "x11rb-protocol", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "x11rb-protocol" 1590 | version = "0.10.0" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" 1593 | dependencies = [ 1594 | "nix", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "xcursor" 1599 | version = "0.3.8" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" 1602 | 1603 | [[package]] 1604 | name = "xml-rs" 1605 | version = "0.8.22" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" 1608 | 1609 | [[package]] 1610 | name = "yansi" 1611 | version = "1.0.1" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 1614 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitnr" 3 | version = "0.2.2" 4 | edition = "2021" 5 | license = "MIT" 6 | readme = "readme.md" 7 | authors = ["Tarun Ramesh "] 8 | homepage = "https://github.com/reemus-dev/gitnr" 9 | repository = "https://github.com/reemus-dev/gitnr" 10 | description = "A CLI to generate '.gitignore' files using one or more templates" 11 | include = ["src/**/*", "LICENSE", "readme.md"] 12 | keywords = ["git", "gitignore", "cli"] 13 | categories = ["development-tools", "command-line-utilities"] 14 | 15 | [dependencies] 16 | anyhow = "1.0.89" 17 | clap = { version = "4.5.20", features = ["derive"] } 18 | copypasta = "0.8.2" 19 | dirs = "5.0.1" 20 | human-panic = "2.0.2" 21 | indoc = "2.0.5" 22 | native-tls = { version = "0.2.12", features = [] } 23 | once_cell = "1.20.2" 24 | ratatui = "0.28.1" 25 | serde = { version = "1.0.210", features = ["default", "derive"] } 26 | serde_json = "1.0.128" 27 | tui-input = "0.10.1" 28 | ureq = { version = "2.10.1", features = ["gzip", "native-tls"], default-features = false } 29 | url = "2.5.2" 30 | yansi = "1.0.1" 31 | 32 | [profile.release] 33 | lto = true 34 | strip = true 35 | opt-level = "z" 36 | 37 | [package.metadata.binstall] 38 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }{ archive-suffix }" 39 | bin-dir = "{ name }-{ target }-v{ version }/{ name }{ binary-ext }" 40 | pkg-fmt = "bin" 41 | 42 | [package.metadata.binstall.overrides.x86_64-unknown-linux-gnu] 43 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-amd64" 44 | 45 | [package.metadata.binstall.overrides.x86_64-pc-windows-msvc] 46 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-win-amd64.exe" 47 | 48 | [package.metadata.binstall.overrides.x86_64-apple-darwin] 49 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-amd64" 50 | 51 | [package.metadata.binstall.overrides.aarch64-apple-darwin] 52 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-arm64" 53 | 54 | [package.metadata.cross.target.x86_64-unknown-linux-gnu] 55 | pre-build = [ 56 | "dpkg --add-architecture $CROSS_DEB_ARCH", 57 | "apt-get update && apt-get --assume-yes install pkg-config libssl-dev:$CROSS_DEB_ARCH" 58 | ] 59 | [package.metadata.cross.target.i686-unknown-linux-gnu] 60 | pre-build = [ 61 | "dpkg --add-architecture $CROSS_DEB_ARCH", 62 | "apt-get update && apt-get --assume-yes install pkg-config libssl-dev:$CROSS_DEB_ARCH" 63 | ] 64 | [package.metadata.cross.target.aarch64-unknown-linux-gnu] 65 | pre-build = [ 66 | "dpkg --add-architecture $CROSS_DEB_ARCH", 67 | "apt-get update && apt-get --assume-yes install pkg-config libssl-dev:$CROSS_DEB_ARCH" 68 | ] 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 reemus.dev 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. -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | # Versioning 5 | version:patch: bash ./scripts/version.sh patch 6 | version:minor: bash ./scripts/version.sh minor 7 | version:major: bash ./scripts/version.sh major 8 | 9 | # Run app with different options 10 | run:create:one: cargo run -- create gh:Rust 11 | run:create:many: cargo run -- create gh:Rust gh:Python tt:Django tt:JetBrains 12 | run:search: cargo run -- search 13 | run:search:watch: task ps:watch -- cargo run -- search 14 | run:help: cargo run -- help 15 | 16 | # Run tests and lints 17 | test: cargo test 18 | lint: cargo clippy --all-targets 19 | lint:fix: cargo clippy --all-targets --fix 20 | 21 | # Build the project 22 | build:clean: rm -rf ./target/release 23 | build:release: cargo build --release && ls -lh target/release/gitnr 24 | 25 | # Analyze project size 26 | analyze:fns: cargo bloat --release 27 | analyze:deps: cargo bloat --release --crates 28 | analyze:compile-time: cargo bloat --time -j 1 29 | 30 | # Utility file watching 31 | ps:watch: 32 | silent: true 33 | cmd: | 34 | watchexec \ 35 | --shell=bash \ 36 | --restart \ 37 | --stop-signal SIGTERM \ 38 | "{{.CLI_ARGS}}" 39 | -------------------------------------------------------------------------------- /doc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reemus-dev/gitnr/34efb722068b1a9c466d8e2a05d6330d0d567485/doc/demo.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gitnr 2 | 3 | A cross-platform CLI utility to create `.gitignore` files using templates. 4 | 5 | - Use templates from the [GitHub](https://github.com/github/gitignore) & [TopTal](https://github.com/toptal/gitignore) collections 6 | - Use local files and remote URLs as templates 7 | - Filter out duplicate ignore lines when using multiple templates 8 | - Interactive mode to search and select templates 9 | - Cross-platform support (Windows, macOS, Linux, NetBSD) 10 | 11 | ```sh 12 | # Create an ignore file for Rust with CLion IDE 13 | gitnr create gh:Rust tt:clion+all 14 | 15 | # Interactively search and select templates 16 | gitnr search 17 | ``` 18 | 19 | ![gitnr demo](/doc/demo.gif) 20 | 21 | ## Table of Contents 22 | 23 | - [Installation](#install--update) 24 | - [Linux & Mac](#linux--mac) 25 | - [Windows](#windows) 26 | - [Cargo](#cargo) 27 | - [Cargo Binstall](#cargo-binstall) 28 | - [Binary Download](#binary-download) 29 | - [NetBSD](#NetBSD) 30 | - [From Source](#from-source) 31 | - [Usage](#usage) 32 | - [Create](#create) 33 | - [Search](#search) 34 | - [Why This Exists](#why-this-exists) 35 | - [Contributing](#contributing) 36 | 37 | ## Install & Update 38 | 39 | ### Linux & Mac 40 | 41 | Run any of the commands below in your terminal to get the latest version of `gitnr`. 42 | 43 | **Install system-wide** 44 | ```sh 45 | curl -s https://raw.githubusercontent.com/reemus-dev/gitnr/main/scripts/install.sh | sudo bash -s 46 | ``` 47 | 48 | **Install for current user** 49 | ```sh 50 | curl -s https://raw.githubusercontent.com/reemus-dev/gitnr/main/scripts/install.sh | bash -s -- -u 51 | ``` 52 | 53 | _On Linux this defaults to `$HOME/.local/bin` and on macOS to `$HOME/bin`. The script will fail if the directory doesn't exist or is not in your system path._ 54 | 55 | **Install in specific directory** 56 | ```sh 57 | curl -s https://raw.githubusercontent.com/reemus-dev/gitnr/main/scripts/install.sh | bash -s -- -d 58 | ``` 59 | 60 | ### Windows 61 | 62 | Run the command below in a PowerShell terminal to install the latest version of `gitnr`. 63 | 64 | ```powershell 65 | Set-ExecutionPolicy Unrestricted -Scope Process; iex (iwr "https://raw.githubusercontent.com/reemus-dev/gitnr/main/scripts/install.ps1").Content 66 | ``` 67 | 68 | ### Cargo 69 | 70 | Install and compile the latest version from crates.io with the command below. 71 | 72 | ```sh 73 | cargo install gitnr 74 | ``` 75 | 76 | ### Cargo Binstall 77 | 78 | Install the binary directly using `cargo-binstall`. 79 | 80 | ```sh 81 | cargo-binstall gitnr 82 | ``` 83 | 84 | ### Binary Download 85 | 86 | See the [releases page](https://github.com/reemus-dev/gitnr/releases) to download a binary and then add it to a directory in your system path. 87 | 88 | ### NetBSD 89 | 90 | On NetBSD a pre-compiled binary is available from the official repositories. To install it, simply run: 91 | ```sh 92 | pkgin install gitnr 93 | ``` 94 | 95 | ### From Source 96 | 97 | ```sh 98 | git clone --depth=1 github.com/reemus-dev/gitnr 99 | cd gitnr 100 | cargo install --path . 101 | ``` 102 | 103 | _Note: This requires that you have Rust and cargo installed on your system._ 104 | 105 | ## Usage 106 | 107 | There are 3 commands available 108 | 109 | | Command | Description | 110 | |----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 111 | | `create` | Create a .gitignore file and print the content to `stdout` or save it to a file | 112 | | `search` | Interactive mode to search and select templates from the GitHub and TopTal collections. You can then copy the result to your clipboard or copy the relevant `create` command to generate your ignore file | 113 | | `help` | Display the CLI help message with available flags & commands | 114 | 115 | ## Create 116 | 117 | The create command accepts a list of templates to generate a `.gitignore` file with. 118 | 119 | ```sh 120 | gitnr create [FLAGS] [TEMPLATES]... 121 | ``` 122 | 123 | ### Create Template Arguments 124 | 125 | Templates can be provided to the CLI as: 126 | - Space separated or comma separated values 127 | - With or without their source prefix 128 | 129 | The following template sources are available: 130 | 131 | | Template Sources | Prefix | 132 | |---------------------------------------------------------------------------------------|---------| 133 | | URL | `url:` | 134 | | File | `file:` | 135 | | GitHub (a file from any public repo) | `repo:` | 136 | | [GitHub Templates](https://github.com/github/gitignore/tree/main) | `gh:` | 137 | | [GitHub Community Templates](https://github.com/github/gitignore/tree/main/community) | `ghc:` | 138 | | [GitHub Global Templates](https://github.com/github/gitignore/tree/main/Global) | `ghg:` | 139 | | [TopTal Templates](https://github.com/toptal/gitignore/tree/master/templates) | `tt:` | 140 | 141 | For example: 142 | 143 | ```sh 144 | # With prefix 145 | gitnr create gh:Node 146 | 147 | # Without prefix 148 | gitnr create Node 149 | 150 | # Combining templates - a project using Node.js + Vue in WebStorm 151 | gitnr create gh:Node ghc:JavaScript/Vue tt:webstorm+all 152 | 153 | # Using a remote URL and local file 154 | gitnr create url:https://domain.com/template.gitignore file:path/to/local.template.gitignore 155 | 156 | # Using a file from a GitHub repo 157 | gitnr create repo:github/gitignore/main/Rust.gitignore 158 | ``` 159 | 160 | If you do not prefix the template, the CLI will try to automatically detect the template source. If it can't match the template name to a source, it defaults to checking the GitHub template collection. It's advised to be explicit about the source prefix to avoid any ambiguity. 161 | 162 | Templates from the GitHub and TopTal collections do not need to have the `.gitignore`, `.stack` or `.patch` suffixes. Meaning you can use `gh:Rust` instead of `gh:Rust.gitignore` or `tt:webstorm+all` instead of `tt:webstorm+all.patch`. 163 | 164 | The generated template will be created in the order of the template arguments supplied. 165 | 166 | > [!NOTE] 167 | > The TopTal template collection includes `stacks` and `patches`. A stack specifies multiple ignore templates that are combined, e.g. `Angular.stack`. The patch extension add modifications to the original template from GitHub's collection. 168 | 169 | ### Create Flags 170 | 171 | By default, the resulting .gitignore template is printed to `stdout`. You can customize this behaviour using the CLI flags available: 172 | 173 | | Flag | Short | Description | 174 | |-----------------|-------------|---------------------------------------------------------------------------------------| 175 | | `--save` | `-s` | Write template to .gitignore file in current directory (overwriting any exiting file) | 176 | | `--file ` | `-f ` | Write template to the specified file path overwriting any exiting file | 177 | | --refresh | -r | Refresh the template cache (templates are cached for 1 hour by default) | 178 | 179 | 180 | ## Search 181 | 182 | The search command allows you to interactively browse, filter and select templates from the GitHub and Toptal collections. 183 | 184 | ```bash 185 | gitnr search 186 | ``` 187 | 188 | This is useful when you want to see what's available and preview different template combinations. You will be able to preview an individual template as well as preview a template combination. 189 | 190 | The search command only has one flag, which is `--refresh | -r`. This allows you to refresh the template cache which by default caches the template collections for 1 hour. This is to avoid hitting the API rate-limits. 191 | 192 | ## Why This Exists? 193 | 194 | - I wanted a way to template my `.gitignore` files for all projects to keep things consistent. 195 | - I wanted to build a `.gitignore` from multiple templates to keep things modular 196 | - I wanted to build something with Rust and learn the language 197 | - And apparently I wanted to waste more time than I'd like to on a simple side project 😅 198 | 199 | ## Contributing 200 | 201 | Open a PR or create an issue with any suggestions. Given this is my first Rust application, veterans will probably spot a lot of things that can be improved, refactored or removed. So feel free to open a PR or issue with any suggestions. 202 | 203 | --- 204 | 205 | Improve your software dev skills by learning from my programming struggles at https://reemus.dev 206 | -------------------------------------------------------------------------------- /scripts/install.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # Define app-specific details 4 | $name = "gitnr" 5 | $binary="$name.exe" 6 | $version="latest" 7 | $githubRepo="reemus-dev/$name" 8 | $downloadBaseUrl="https://github.com/$githubRepo/releases/download/$version" 9 | 10 | if ($version -eq "latest") { 11 | # The latest version is accessible from a slightly different URL 12 | $downloadBaseUrl="https://github.com/$githubRepo/releases/latest/download" 13 | } 14 | 15 | # Determine system architecture 16 | $type = (Get-ComputerInfo).CsSystemType.ToLower() 17 | if ($type.StartsWith("x64")) { 18 | $downloadFile = "$name-win-amd64.exe" 19 | } else { 20 | Write-Host "[Error]" -ForegroundColor Red 21 | Write-Host "Unsupported Architecture: $type" -ForegroundColor Red 22 | [Environment]::Exit(1) 23 | } 24 | 25 | # Create app directory 26 | $destDir = "$env:USERPROFILE\AppData\Local\$name" 27 | $destBin = "$destDir\$binary" 28 | Write-Host "Creating Install Directory" -ForegroundColor White 29 | Write-Host " $destDir" 30 | if (-Not (Test-Path $destDir)) { 31 | New-Item -ItemType Directory -Path $destDir 32 | } 33 | 34 | # Download the executable 35 | $downloadUrl = "$downloadBaseUrl/$downloadFile" 36 | Write-Host "Downloading Binary" -ForegroundColor White 37 | Write-Host " From: $downloadUrl" 38 | Write-Host " Path: $destBin" 39 | Invoke-WebRequest -Uri $downloadUrl -OutFile "$destBin" 40 | 41 | # Add to user PATH if not already present 42 | $currentPath = [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::User) 43 | if (-Not ($currentPath -like "*$destDir*")) { 44 | Write-Host "Adding Install Directory To System Path" -ForegroundColor White 45 | Write-Host " $destBin" 46 | [System.Environment]::SetEnvironmentVariable('Path', "$currentPath;$destDir", [System.EnvironmentVariableTarget]::User) 47 | } 48 | 49 | 50 | Write-Host "Installation Complete" -ForegroundColor Green 51 | Write-Host " Restart your shell to starting using '$binary'. Run '$binary --help' for more information" -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # ============================================================================= 6 | # Define helper functions 7 | # ============================================================================= 8 | 9 | text_bold() { 10 | echo -e "\033[1m$1\033[0m" 11 | } 12 | text_title() { 13 | echo "" 14 | text_bold "$1" 15 | if [ "$2" != "" ]; then echo "$2"; fi 16 | } 17 | text_title_error() { 18 | echo "" 19 | echo -e "\033[1;31m$1\033[00m" 20 | } 21 | 22 | # ============================================================================= 23 | # Define base variables 24 | # ============================================================================= 25 | 26 | NAME="gitnr" 27 | REPO="reemus-dev/$NAME" 28 | VERSION="latest" 29 | DOWNLOAD_BASE_URL="https://github.com/$REPO/releases/download/$VERSION" 30 | 31 | if [ "$VERSION" == "latest" ]; then 32 | # The latest version is accessible from a slightly different URL 33 | DOWNLOAD_BASE_URL="https://github.com/$REPO/releases/latest/download" 34 | fi 35 | 36 | # ============================================================================= 37 | # Define binary list for supported OS & Arch 38 | # ============================================================================= 39 | 40 | declare -A BINARIES=( 41 | ["Linux:x86_64"]="$NAME-linux-amd64" 42 | ["Darwin:x86_64"]="$NAME-macos-amd64" 43 | ["Darwin:arm64"]="$NAME-macos-arm64" 44 | ) 45 | 46 | # ============================================================================= 47 | # Get the user's OS and Arch 48 | # ============================================================================= 49 | 50 | OS="$(uname -s)" 51 | ARCH="$(uname -m)" 52 | SYSTEM="${OS}:${ARCH}" 53 | 54 | # ============================================================================= 55 | # Match a binary to check if the system is supported 56 | # ============================================================================= 57 | 58 | if [[ ! ${BINARIES["$SYSTEM"]+_} ]]; then 59 | text_title_error "Error" 60 | echo " Unsupported OS or arch: ${SYSTEM}" 61 | echo "" 62 | exit 1 63 | fi 64 | 65 | # ============================================================================= 66 | # Set the default installation variables 67 | # ============================================================================= 68 | 69 | INSTALL_DIR="/usr/local/bin" 70 | BINARY="${BINARIES["$SYSTEM"]}" 71 | DOWNLOAD_URL="$DOWNLOAD_BASE_URL/$BINARY" 72 | 73 | # ============================================================================= 74 | # Handle script arguments if passed 75 | # -u: install to user bin directory 76 | # -d : specify installation directory 77 | # ============================================================================= 78 | 79 | if [ $# -gt 0 ]; then 80 | while getopts ":ud:" opt; do 81 | case $opt in 82 | u) 83 | # Set default install dir based on OS 84 | [ "$OS" == "Darwin" ] && INSTALL_DIR="$HOME/bin" || INSTALL_DIR="$HOME/.local/bin" 85 | 86 | # Check that the user bin directory is in their PATH 87 | IFS=':' read -ra PATHS <<< "$PATH" 88 | INSTALL_DIR_IN_PATH="false" 89 | for P in "${PATHS[@]}"; do 90 | if [[ "$P" == "$INSTALL_DIR" ]]; then 91 | INSTALL_DIR_IN_PATH="true" 92 | fi 93 | done 94 | 95 | # If user bin directory doesn't exist or not in PATH, exit 96 | if [ ! -d "$INSTALL_DIR" ] || [ "$INSTALL_DIR_IN_PATH" == "false" ]; then 97 | text_title_error "Error" 98 | echo " The user bin directory '$INSTALL_DIR' does not exist or is not in your environment PATH variable" 99 | echo " To fix this error:" 100 | echo " - Omit the '-u' option and to install system-wide" 101 | echo " - Specify an installation directory with -d " 102 | echo "" 103 | exit 1 104 | fi 105 | 106 | ;; 107 | d) 108 | # Get absolute path in case a relative path is provided 109 | INSTALL_DIR=$(cd "$OPTARG"; pwd) 110 | 111 | if [ ! -d "$INSTALL_DIR" ]; then 112 | text_title_error "Error" 113 | echo " The installation directory '$INSTALL_DIR' does not exist or is not a directory" 114 | echo "" 115 | exit 1 116 | fi 117 | 118 | ;; 119 | \?) 120 | text_title_error "Error" 121 | echo " Invalid option: -$OPTARG" >&2 122 | echo "" 123 | exit 1 124 | ;; 125 | :) 126 | text_title_error "Error" 127 | echo " Option -$OPTARG requires an argument." >&2 128 | echo "" 129 | exit 1 130 | ;; 131 | esac 132 | done 133 | fi 134 | 135 | # ============================================================================= 136 | # Create and change to temp directory 137 | # ============================================================================= 138 | 139 | cd "$(mktemp -d)" 140 | 141 | # ============================================================================= 142 | # Download binary 143 | # ============================================================================= 144 | 145 | text_title "Downloading Binary" " $DOWNLOAD_URL" 146 | curl -LO --proto '=https' --tlsv1.2 -sSf "$DOWNLOAD_URL" 147 | 148 | # ============================================================================= 149 | # Make binary executable and move to install directory with appropriate name 150 | # ============================================================================= 151 | 152 | text_title "Installing Binary" " $INSTALL_DIR/$NAME" 153 | chmod +x "$BINARY" 154 | mv "$BINARY" "$INSTALL_DIR/$NAME" 155 | 156 | # ============================================================================= 157 | # Display post install message 158 | # ============================================================================= 159 | 160 | text_title "Installation Complete" " Run $NAME --help for more information" 161 | echo "" 162 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ================================================================= # 4 | # Version Script: Bump the project version, create a git tag and push the changes: 5 | # - check repo is clean 6 | # - get latest version from git tags 7 | # - increment version based on VERSION_TYPE 8 | # - update version in Cargo.toml & Cargo.lock 9 | # - tag commit & push changes 10 | # ================================================================= # 11 | 12 | set -euo pipefail 13 | 14 | if [ -n "$(git status --porcelain)" ]; then 15 | printf "\nError: repo has uncommitted changes\n\n" 16 | exit 1 17 | fi 18 | 19 | # ================================================================= 20 | # Get the latest git tag version and increment it 21 | # ================================================================= 22 | 23 | GIT_TAG_LATEST=$(git tag --sort=v:refname | tail -n 1) 24 | 25 | # If no tags found, default to v0.0.0 26 | if [ -z "$GIT_TAG_LATEST" ]; then 27 | GIT_TAG_LATEST="v0.0.0" 28 | fi 29 | 30 | GIT_TAG_LATEST=$(echo "$GIT_TAG_LATEST" | sed 's/^v//') # Remove prefix "v" from tag 31 | 32 | VERSION_TYPE="${1-}" # From the first argument passed 33 | VERSION_NEXT="" 34 | 35 | if [ "$VERSION_TYPE" = "patch" ]; then 36 | VERSION_NEXT="$(echo "$GIT_TAG_LATEST" | awk -F. '{$NF++; print $1"."$2"."$NF}')" 37 | elif [ "$VERSION_TYPE" = "minor" ]; then 38 | VERSION_NEXT="$(echo "$GIT_TAG_LATEST" | awk -F. '{$2++; $3=0; print $1"."$2"."$3}')" 39 | elif [ "$VERSION_TYPE" = "major" ]; then 40 | VERSION_NEXT="$(echo "$GIT_TAG_LATEST" | awk -F. '{$1++; $2=0; $3=0; print $1"."$2"."$3}')" 41 | else 42 | printf "\nError: invalid VERSION_TYPE arg passed, must be patch, minor or major\n\n" 43 | exit 1 44 | fi 45 | 46 | # ================================================================= 47 | # Update version in Cargo.toml and commit it 48 | # ================================================================= 49 | 50 | # echo "Next: $VERSION_NEXT" 51 | # sed -i "s/^version = .*/version = \"$VERSION_NEXT\"/" Cargo.toml 52 | sed -i '' "s/^version = .*/version = \"$VERSION_NEXT\"/" Cargo.toml 53 | cargo generate-lockfile # Update version in Cargo.lock 54 | git add . 55 | git commit -m "build: bump version to v$VERSION_NEXT" 56 | 57 | # ================================================================= 58 | # Create new git tag and push it 59 | # ================================================================= 60 | 61 | echo "Tagging Commit & Pushing Changes: v$VERSION_NEXT" 62 | git tag -a "v$VERSION_NEXT" -m "Release: v$VERSION_NEXT" 63 | git push -u origin main --follow-tags 64 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::template::item::Template; 2 | use crate::template::list::TemplateList; 3 | use anyhow::{bail, Context, Result}; 4 | use clap::{Args, Parser, Subcommand}; 5 | use once_cell::sync::Lazy; 6 | 7 | const LONG_ABOUT: &str = r" 8 | 9 | ―――――――――――――――――――――――――――――――――――――――――――― 10 | gitnr 11 | ―――――――――――――――――――――――――――――――――――――――――――― 12 | 13 | Generate a '.gitignore' file using one or more templates from 14 | the GitHub & TopTal collections along with your own templates 15 | from local files or remote URLs. 16 | 17 | You can also browse the available templates at the GitHub & 18 | TopTal collections using the `search` command."; 19 | 20 | #[derive(Parser, Debug)] 21 | #[command(author, version, about, long_about = LONG_ABOUT)] 22 | pub struct Cli { 23 | /// Refresh the cache (templates are cached for 1h) 24 | #[arg(short = 'r', long = "refresh", global = true)] 25 | pub refresh: bool, 26 | #[command(subcommand)] 27 | pub command: Option, 28 | } 29 | 30 | #[derive(Args, Debug)] 31 | pub struct CommandCreate { 32 | /// Write template to .gitignore file in current directory 33 | #[arg(short = 's', long = "save")] 34 | pub out_gitignore: bool, 35 | /// Write template to the specified file path 36 | #[arg(short = 'f', long = "file")] 37 | pub out_file: Option, 38 | /// Space or comma separated list of templates to use. Templates can be prefixed with 39 | /// the provider name to avoid any ambiguity. 40 | /// 41 | /// Providers: 42 | /// - "gh:" GitHub templates 43 | /// - "ghc:" GitHub community templates 44 | /// - "ghg:" GitHub global templates 45 | /// - "tt:" TopTal templates 46 | /// - "url:" Remote URL to text file template 47 | /// - "file:" Local file path to a .gitignore file 48 | /// - "repo:" File from a any public GitHub repo 49 | /// 50 | /// If no prefix is specified, program will attempt to guess the provider if possible 51 | /// otherwise it will default to a GitHub template. The template name is case-sensitive. 52 | /// Meaning "Rust" is not the same as "rust". The order in which the templates are provided 53 | /// is the order in output content will be. 54 | /// 55 | /// Examples: 56 | /// - gitnr create Rust 57 | /// - gitnr create gh:Rust 58 | /// - gitnr create gh:Rust tt:jetbrains+all 59 | #[arg(verbatim_doc_comment)] 60 | pub templates: Vec, 61 | } 62 | 63 | #[derive(Subcommand, Debug)] 64 | pub enum Commands { 65 | /// Create a .gitignore file from one or more templates 66 | Create(CommandCreate), 67 | /// Choose templates interactively from the GitHub & TopTal collections 68 | Search, 69 | } 70 | 71 | impl Cli { 72 | /// Get ignore templates passed in from the CLI arguments 73 | pub fn templates(&self) -> Result { 74 | let templates = match &self.command { 75 | Some(Commands::Create(args)) => &args.templates, 76 | Some(Commands::Search) => { 77 | bail!("Cannot provide template arguments to 'search' command") 78 | } 79 | None => bail!("Cannot provide template arguments to an unknown command"), 80 | }; 81 | 82 | // Split templates seperated by commas and spaces 83 | let templates = templates 84 | .iter() 85 | .flat_map(|f| f.split(',').map(|s| s.to_string())) 86 | .collect::>(); 87 | 88 | // Create the template structs from the templates provided 89 | let templates = templates 90 | .iter() 91 | .map(|name| Template::new(name)) 92 | .collect::>>() 93 | .with_context(|| "Failed to parse provided template arguments".to_string())?; 94 | 95 | Ok(TemplateList::new(templates)) 96 | } 97 | } 98 | 99 | static CLI: Lazy = Lazy::new(Cli::parse); 100 | 101 | pub fn get_cli() -> &'static Cli { 102 | &CLI 103 | } 104 | 105 | #[test] 106 | fn verify_cli() { 107 | use clap::CommandFactory; 108 | Cli::command().debug_assert() 109 | } 110 | -------------------------------------------------------------------------------- /src/commands/create.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{get_cli, CommandCreate}; 2 | use anyhow::{bail, Context, Result}; 3 | use indoc::{formatdoc, printdoc}; 4 | use std::path::PathBuf; 5 | use std::{env, fs}; 6 | use yansi::Paint; 7 | 8 | pub fn command(cmd: &CommandCreate) -> Result<()> { 9 | let cli = get_cli(); 10 | let templates = cli.templates()?; 11 | 12 | if templates.is_empty() { 13 | bail!(formatdoc! {" 14 | No template arguments provided 15 | 16 | Provide templates to the create command using the following syntax: 17 | gitnr create [TEMPLATE]... 18 | 19 | For more information, see the help: 20 | gitnr create --help"}) 21 | } 22 | 23 | let output = templates.content()?; 24 | 25 | // Write template to .gitignore file in current directory 26 | if cmd.out_gitignore { 27 | let cwd = env::current_dir().with_context(|| "Failed to get current directory")?; 28 | let path = cwd.join(".gitignore"); 29 | fs::write(&path, output + "\n").with_context(|| { 30 | format!( 31 | "Failed to write template to .gitignore file at path\n{}", 32 | path.to_str().unwrap_or("...unknown path") 33 | ) 34 | })?; 35 | 36 | success_msg(path); 37 | return Ok(()); 38 | } 39 | 40 | // Write template to file path 41 | if let Some(path) = &cmd.out_file { 42 | let path = PathBuf::from(path); 43 | let path = if path.is_relative() { 44 | path.canonicalize().with_context(|| { 45 | format!( 46 | "Failed to get absolute path for relative path\n{}", 47 | path.to_str().unwrap_or("...unknown path") 48 | ) 49 | })? 50 | } else { 51 | path 52 | }; 53 | 54 | if path.is_dir() { 55 | bail!( 56 | "The output path provided is a directory.\n\ 57 | Provide a file path to write the template to a file.\n\ 58 | Path: {}", 59 | path.to_str().unwrap_or("...unknown path") 60 | ) 61 | } 62 | 63 | fs::write(&path, output + "\n").with_context(|| { 64 | format!( 65 | "Failed to write template to file at path\n{}", 66 | path.to_str().unwrap_or("...unknown path") 67 | ) 68 | })?; 69 | 70 | success_msg(path); 71 | return Ok(()); 72 | } 73 | 74 | // Print template to stdout 75 | println!("{}", output); 76 | 77 | Ok(()) 78 | } 79 | 80 | fn success_msg(path: PathBuf) { 81 | printdoc! {"\n{title} {path}\n\n", 82 | title=" Success ".on_green().dim().white().bold(), 83 | path=format!("Template written to path: {}", path.to_str().unwrap_or("...unknown path")), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create; 2 | pub mod search; 3 | -------------------------------------------------------------------------------- /src/commands/search/framework/event.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ratatui::crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent}; 3 | use std::sync::mpsc; 4 | use std::thread; 5 | use std::time::{Duration, Instant}; 6 | 7 | /// Terminal events. 8 | #[derive(Clone, Copy, Debug)] 9 | pub enum Event { 10 | /// Terminal tick. 11 | Tick, 12 | /// Key press. 13 | Key(KeyEvent), 14 | /// Mouse click/scroll. 15 | Mouse(MouseEvent), 16 | /// Terminal resize. 17 | Resize(u16, u16), 18 | } 19 | 20 | /// Terminal event handler. 21 | #[allow(dead_code)] 22 | #[derive(Debug)] 23 | pub struct EventHandler { 24 | /// Event sender channel. 25 | sender: mpsc::Sender, 26 | /// Event receiver channel. 27 | receiver: mpsc::Receiver, 28 | /// Event handler thread. 29 | handler: thread::JoinHandle<()>, 30 | } 31 | 32 | impl EventHandler { 33 | /// Constructs a new instance of [`EventHandler`]. 34 | pub fn new(tick_rate: u64) -> Self { 35 | let tick_rate = Duration::from_millis(tick_rate); 36 | let (sender, receiver) = mpsc::channel(); 37 | let handler = { 38 | let sender = sender.clone(); 39 | thread::spawn(move || { 40 | let mut last_tick = Instant::now(); 41 | loop { 42 | let timeout = tick_rate 43 | .checked_sub(last_tick.elapsed()) 44 | .unwrap_or(tick_rate); 45 | 46 | if event::poll(timeout).expect("no events available") { 47 | match event::read().expect("unable to read event") { 48 | CrosstermEvent::Key(e) => sender.send(Event::Key(e)), 49 | CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), 50 | CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), 51 | _ => Ok(()), 52 | } 53 | .expect("failed to send terminal event") 54 | } 55 | 56 | if last_tick.elapsed() >= tick_rate { 57 | sender.send(Event::Tick).expect("failed to send tick event"); 58 | last_tick = Instant::now(); 59 | } 60 | } 61 | }) 62 | }; 63 | Self { 64 | sender, 65 | receiver, 66 | handler, 67 | } 68 | } 69 | 70 | /// Receive the next event from the handler thread. 71 | /// 72 | /// This function will always block the current thread if 73 | /// there is no data available and it's possible for more data to be sent. 74 | pub fn next(&self) -> Result { 75 | Ok(self.receiver.recv()?) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/commands/search/framework/mod.rs: -------------------------------------------------------------------------------- 1 | /// From: https://github.com/ratatui-org/rust-tui-template 2 | 3 | /// Terminal UI events handler 4 | pub mod event; 5 | 6 | /// Terminal UI setup and teardown 7 | pub mod tui; 8 | -------------------------------------------------------------------------------- /src/commands/search/framework/tui.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::search::framework::event::EventHandler; 2 | use crate::commands::search::state::UIState; 3 | use crate::commands::search::views::render; 4 | use anyhow::Result; 5 | use ratatui::backend::Backend; 6 | use ratatui::crossterm::event::{ 7 | DisableMouseCapture, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, 8 | PushKeyboardEnhancementFlags, 9 | }; 10 | use ratatui::crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; 11 | use ratatui::Terminal; 12 | use std::io; 13 | use std::panic; 14 | 15 | /// Representation of a terminal user interface. 16 | /// 17 | /// It is responsible for setting up the terminal, 18 | /// initializing the interface and handling the draw events. 19 | #[derive(Debug)] 20 | pub struct Tui { 21 | /// Interface to the Terminal. 22 | terminal: Terminal, 23 | /// Terminal event handler. 24 | pub events: EventHandler, 25 | } 26 | 27 | impl Tui { 28 | /// Constructs a new instance of [`Tui`]. 29 | pub fn new(terminal: Terminal, events: EventHandler) -> Self { 30 | Self { terminal, events } 31 | } 32 | 33 | /// Initializes the terminal interface. 34 | /// 35 | /// It enables the raw mode and sets terminal properties. 36 | pub fn init(&mut self) -> Result<()> { 37 | terminal::enable_raw_mode()?; 38 | 39 | if cfg!(target_family = "unix") { 40 | // Keyboard event types are only reported on Windows by default, enable on Unix too 41 | ratatui::crossterm::execute!( 42 | io::stderr(), 43 | EnterAlternateScreen, 44 | EnableMouseCapture, 45 | PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES) 46 | )?; 47 | } else { 48 | // Don't add the keyboard enhancement flags on Windows, as it produces error: 49 | // - Keyboard progressive enhancement not implemented for the legacy Windows API. 50 | ratatui::crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?; 51 | } 52 | 53 | // Define a custom panic hook to reset the terminal properties. 54 | // This way, you won't have your terminal messed up if an unexpected error happens. 55 | let panic_hook = panic::take_hook(); 56 | panic::set_hook(Box::new(move |panic| { 57 | Self::reset().expect("failed to reset the terminal"); 58 | panic_hook(panic); 59 | })); 60 | 61 | self.terminal.hide_cursor()?; 62 | self.clear()?; 63 | Ok(()) 64 | } 65 | 66 | /// [`Draw`] the terminal interface by [`rendering`] the widgets. 67 | /// 68 | /// [`Draw`]: tui::Terminal::draw 69 | /// [`rendering`]: crate::ui:render 70 | pub fn draw(&mut self, app: &mut UIState) -> Result<()> { 71 | self.terminal.draw(|frame| render(app, frame).unwrap())?; 72 | Ok(()) 73 | } 74 | 75 | /// Clears the terminal screen. 76 | pub fn clear(&mut self) -> Result<()> { 77 | self.terminal.clear()?; 78 | Ok(()) 79 | } 80 | /// Resets the terminal interface. 81 | /// 82 | /// This function is also used for the panic hook to revert 83 | /// the terminal properties if unexpected errors occur. 84 | fn reset() -> Result<()> { 85 | terminal::disable_raw_mode()?; 86 | if cfg!(target_family = "unix") { 87 | ratatui::crossterm::execute!( 88 | io::stdout(), 89 | LeaveAlternateScreen, 90 | DisableMouseCapture, 91 | PopKeyboardEnhancementFlags 92 | )?; 93 | } else { 94 | ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; 95 | } 96 | Ok(()) 97 | } 98 | 99 | /// Exits the terminal interface. 100 | /// 101 | /// It disables the raw mode and reverts back the terminal properties. 102 | pub fn exit(&mut self) -> Result<()> { 103 | Self::reset()?; 104 | self.terminal.show_cursor()?; 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/commands/search/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::search::framework::tui::Tui; 2 | use crate::commands::search::state::view_preview::{UIStatePreview, UIStatePreviewState}; 3 | use crate::commands::search::state::{UIState, UIStateView}; 4 | use crate::template::list::TemplateList; 5 | use anyhow::Result; 6 | use ratatui::backend::CrosstermBackend; 7 | use ratatui::crossterm::event::{ 8 | Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind, 9 | }; 10 | use std::io::Stderr; 11 | use std::time::{Duration, Instant}; 12 | use tui_input::backend::crossterm::EventHandler; 13 | 14 | /// Handles the keyboard events for the UI 15 | pub fn handle_key_events( 16 | event: KeyEvent, 17 | app: &mut UIState, 18 | tui: &mut Tui>, 19 | ) -> Result<()> { 20 | // Ignore release events to prevent executing the same action twice 21 | // [Issue] https://github.com/reemus-dev/gitnr/issues/3 22 | if event.kind == KeyEventKind::Release { 23 | return Ok(()); 24 | } 25 | 26 | let is_shift = event.modifiers == KeyModifiers::SHIFT; 27 | let is_ctrl = event.modifiers == KeyModifiers::CONTROL; 28 | let is_c = event.code == KeyCode::Char('c') || event.code == KeyCode::Char('C'); 29 | 30 | // --------------- 31 | // Application exit: Ctrl + C 32 | // --------------- 33 | let is_quit_ctrl_c = is_ctrl && is_c; 34 | if is_quit_ctrl_c { 35 | app.quit(); 36 | return Ok(()); 37 | } 38 | 39 | // --------------- 40 | // Handle shift modified keys presses 41 | // --------------- 42 | if is_shift { 43 | match &mut app.view { 44 | UIStateView::Home => match event.code { 45 | // Shift + S: previews the currently selected templates 46 | KeyCode::Char('s') | KeyCode::Char('S') => { 47 | // Clear the terminal before changing the view to prevent weird left overs 48 | tui.clear()?; 49 | let templates = app 50 | .selected 51 | .lock() 52 | .unwrap() 53 | .iter() 54 | .map(|s| s.template.clone()) 55 | .collect::>(); 56 | let templates = TemplateList::new(templates); 57 | let preview_state = UIStatePreview::new(templates)?; 58 | app.view = UIStateView::Preview(preview_state); 59 | return Ok(()); 60 | } 61 | // Shift + C: previews the currently highlighted template in the list 62 | KeyCode::Char('c') | KeyCode::Char('C') => { 63 | let index = app.collection().state.lock().unwrap().selected(); 64 | match index { 65 | None => {} 66 | Some(index) => { 67 | let template = app.collection().values.get(index).cloned(); 68 | if let Some(template) = template { 69 | // Clear the terminal before changing the view to prevent weird left overs 70 | tui.clear()?; 71 | let templates = TemplateList::new(vec![template]); 72 | let preview_state = UIStatePreview::new(templates)?; 73 | app.view = UIStateView::Preview(preview_state); 74 | } 75 | return Ok(()); 76 | } 77 | } 78 | } 79 | _ => {} 80 | }, 81 | UIStateView::Preview(ref mut p) => match event.code { 82 | KeyCode::Char('c') | KeyCode::Char('C') => p.copy_content()?, 83 | KeyCode::Char('x') | KeyCode::Char('X') => p.copy_command()?, 84 | _ => {} 85 | }, 86 | } 87 | } 88 | 89 | // --------------- 90 | // View: Home 91 | // --------------- 92 | if let UIStateView::Home = &mut app.view { 93 | match event.code { 94 | // --------------- 95 | // Change tabs: left & right arrow 96 | // --------------- 97 | KeyCode::Right => { 98 | app.collection_next(); 99 | app.list_filter_update(); 100 | } 101 | KeyCode::Left => { 102 | app.collection_prev(); 103 | app.list_filter_update(); 104 | } 105 | // --------------- 106 | // Select template: Enter 107 | // --------------- 108 | KeyCode::Enter => app.list_select(), 109 | // --------------- 110 | // Change template 111 | // - Up & Down Arrow (next / previous) 112 | // - Home & End (top / bottom) 113 | // --------------- 114 | KeyCode::Up => { 115 | app.list_previous(if is_shift { Some(10) } else { Some(1) }); 116 | } 117 | KeyCode::Down => { 118 | app.list_next(if is_shift { Some(10) } else { Some(1) }); 119 | } 120 | KeyCode::Home => { 121 | let list = app.collection(); 122 | list.state.lock().unwrap().select(Some(0)); 123 | } 124 | KeyCode::End => { 125 | let list = app.collection(); 126 | list.state 127 | .lock() 128 | .unwrap() 129 | .select(Some(list.values.len().saturating_sub(1))); 130 | } 131 | // --------------- 132 | // Filtering collection 133 | // - Space only if value is not empty 134 | // - Any character/number 135 | // --------------- 136 | KeyCode::Char(' ') => { 137 | if !app.collection_filter.value().is_empty() { 138 | app.collection_filter.handle_event(&Event::Key(event)); 139 | app.list_filter_update(); 140 | } 141 | } 142 | _ => { 143 | app.collection_filter.handle_event(&Event::Key(event)); 144 | app.list_filter_update(); 145 | } 146 | } 147 | } 148 | 149 | // --------------- 150 | // View: Preview 151 | // --------------- 152 | if let UIStateView::Preview(ref mut p) = &mut app.view { 153 | match &p.state { 154 | UIStatePreviewState::Default => match event.code { 155 | // Return to home view with ESC 156 | KeyCode::Esc => { 157 | app.view = UIStateView::Home; 158 | } 159 | // Scroll content up / down 160 | KeyCode::Up => p.scroll_up(), 161 | KeyCode::Down => p.scroll_down(), 162 | KeyCode::Home => p.scroll_to_top(), 163 | KeyCode::End => p.scroll_to_bottom(), 164 | _ => {} 165 | }, 166 | // Allow closing success message with any key except the keys that triggered the copy action 167 | // Without this, the success message closes when the user presses the key that triggered the copy action 168 | UIStatePreviewState::CopiedContent => match event.code { 169 | KeyCode::Char('c') | KeyCode::Char('C') => {} 170 | _ => p.copy_done(), 171 | }, 172 | UIStatePreviewState::CopiedCommand => match event.code { 173 | KeyCode::Char('x') | KeyCode::Char('X') => {} 174 | _ => p.copy_done(), 175 | }, 176 | } 177 | } 178 | 179 | Ok(()) 180 | } 181 | 182 | /// Handles mouse events for the UI 183 | pub fn handle_mouse_events(event: MouseEvent, app: &mut UIState) -> Result<()> { 184 | let now = Instant::now(); 185 | 186 | // For whatever reason these event modifiers aren't detected 187 | let is_alt = event.modifiers == KeyModifiers::ALT; 188 | let is_shift = event.modifiers == KeyModifiers::SHIFT; 189 | 190 | // --------------- 191 | // View: Home 192 | // --------------- 193 | if let UIStateView::Home = &app.view { 194 | match event.kind { 195 | // Select a template with left-click 196 | MouseEventKind::Down(MouseButton::Left) => app.list_select(), 197 | // Scrolling on the collection templates list 198 | MouseEventKind::ScrollUp => { 199 | if now.duration_since(app.last_scroll_time) > Duration::from_millis(15) { 200 | app.list_previous(if is_shift || is_alt { 201 | Some(10) 202 | } else { 203 | Some(1) 204 | }); 205 | app.last_scroll_time = now; 206 | } 207 | } 208 | // Scrolling on the collection templates list 209 | MouseEventKind::ScrollDown => { 210 | if now.duration_since(app.last_scroll_time) > Duration::from_millis(15) { 211 | app.list_next(if is_shift || is_alt { 212 | println!("Scrolling {} {}", is_shift, is_alt); 213 | Some(10) 214 | } else { 215 | Some(1) 216 | }); 217 | app.last_scroll_time = now; 218 | } 219 | } 220 | _ => {} 221 | } 222 | } 223 | 224 | // --------------- 225 | // View: Preview 226 | // --------------- 227 | if let UIStateView::Preview(ref mut p) = &mut app.view { 228 | match &p.state { 229 | // Scrolling content up / down with mouse wheel 230 | UIStatePreviewState::Default => match event.kind { 231 | MouseEventKind::ScrollUp => p.scroll_up(), 232 | MouseEventKind::ScrollDown => p.scroll_down(), 233 | _ => {} 234 | }, 235 | // Closing success message with mouse click 236 | _ => { 237 | if let MouseEventKind::Down(MouseButton::Left) = event.kind { 238 | p.copy_done() 239 | } 240 | } 241 | } 242 | } 243 | 244 | Ok(()) 245 | } 246 | -------------------------------------------------------------------------------- /src/commands/search/mod.rs: -------------------------------------------------------------------------------- 1 | mod framework; 2 | mod handlers; 3 | mod state; 4 | mod views; 5 | 6 | use crate::commands::search::framework::event::{Event, EventHandler}; 7 | use crate::commands::search::framework::tui::Tui; 8 | use crate::commands::search::handlers::{handle_key_events, handle_mouse_events}; 9 | use crate::commands::search::state::UIState; 10 | use anyhow::Result; 11 | use ratatui::backend::CrosstermBackend; 12 | use ratatui::Terminal; 13 | use std::io; 14 | 15 | pub fn command() -> Result<()> { 16 | // Create an application state 17 | let mut app = UIState::new()?; 18 | 19 | // Initialize the terminal user interface. 20 | let backend = CrosstermBackend::new(io::stderr()); 21 | let terminal = Terminal::new(backend)?; 22 | let events = EventHandler::new(250); 23 | let mut tui = Tui::new(terminal, events); 24 | tui.init()?; 25 | 26 | // Start the main loop. 27 | while app.running { 28 | // Render the user interface. 29 | tui.draw(&mut app)?; 30 | 31 | // Handle events. 32 | match tui.events.next()? { 33 | Event::Tick => app.tick(), 34 | Event::Key(key_event) => handle_key_events(key_event, &mut app, &mut tui)?, 35 | Event::Mouse(mouse_event) => handle_mouse_events(mouse_event, &mut app)?, 36 | Event::Resize(_, _) => {} 37 | } 38 | } 39 | 40 | // Exit the user interface. 41 | tui.exit()?; 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/search/state/collection.rs: -------------------------------------------------------------------------------- 1 | use crate::template::collection::{TemplateCollection, TemplateCollectionKind}; 2 | use crate::template::item::Template; 3 | use anyhow::Result; 4 | use ratatui::widgets::ListState; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | /// Stores the data and state for a template collection list 8 | #[derive(Debug)] 9 | pub struct UICollection { 10 | /// The underlying template collection 11 | pub collection: TemplateCollection, 12 | /// The state of the list 13 | pub state: Arc>, 14 | /// The current values of the list to display depending on any filters 15 | pub values: Vec