├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── default.nix ├── flake.lock ├── flake.nix └── src ├── archived.rs ├── archived └── keymap.rs ├── cmd.rs ├── main.rs ├── sig.rs ├── sig └── keymap.rs ├── stdin.rs └── terminal.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ynqa 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | name: test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/cache@v2 12 | with: 13 | path: | 14 | ~/.cargo/registry 15 | ~/.cargo/git 16 | target 17 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | components: rustfmt, clippy 22 | - uses: actions-rs/cargo@v1 23 | with: 24 | command: fmt 25 | args: --all -- --check 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: clippy 29 | - uses: actions-rs/cargo@v1 30 | with: 31 | command: test 32 | args: -- --nocapture --format pretty 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: build 36 | args: --examples 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: build 40 | args: --bins 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023, axodotdev 2 | # SPDX-License-Identifier: MIT or Apache-2.0 3 | # 4 | # CI that: 5 | # 6 | # * checks for a Git Tag that looks like a release 7 | # * builds artifacts with cargo-dist (archives, installers, hashes) 8 | # * uploads those artifacts to temporary workflow zip 9 | # * on success, uploads the artifacts to a Github Release 10 | # 11 | # Note that the Github Release will be created with a generated 12 | # title/body based on your changelogs. 13 | 14 | name: Release 15 | 16 | permissions: 17 | contents: write 18 | 19 | # This task will run whenever you push a git tag that looks like a version 20 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 21 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 22 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 23 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 24 | # 25 | # If PACKAGE_NAME is specified, then the announcement will be for that 26 | # package (erroring out if it doesn't have the given version or isn't cargo-dist-able). 27 | # 28 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 29 | # (cargo-dist-able) packages in the workspace with that version (this mode is 30 | # intended for workspaces with only one dist-able package, or with all dist-able 31 | # packages versioned/released in lockstep). 32 | # 33 | # If you push multiple tags at once, separate instances of this workflow will 34 | # spin up, creating an independent announcement for each one. However Github 35 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 36 | # mistake. 37 | # 38 | # If there's a prerelease-style suffix to the version, then the release(s) 39 | # will be marked as a prerelease. 40 | on: 41 | push: 42 | tags: 43 | - '**[0-9]+.[0-9]+.[0-9]+*' 44 | pull_request: 45 | 46 | jobs: 47 | # Run 'cargo dist plan' (or host) to determine what tasks we need to do 48 | plan: 49 | runs-on: ubuntu-latest 50 | outputs: 51 | val: ${{ steps.plan.outputs.manifest }} 52 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 53 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 54 | publishing: ${{ !github.event.pull_request }} 55 | env: 56 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | submodules: recursive 61 | - name: Install cargo-dist 62 | # we specify bash to get pipefail; it guards against the `curl` command 63 | # failing. otherwise `sh` won't catch that `curl` returned non-0 64 | shell: bash 65 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 66 | # sure would be cool if github gave us proper conditionals... 67 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 68 | # functionality based on whether this is a pull_request, and whether it's from a fork. 69 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 70 | # but also really annoying to build CI around when it needs secrets to work right.) 71 | - id: plan 72 | run: | 73 | cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 74 | echo "cargo dist ran successfully" 75 | cat plan-dist-manifest.json 76 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 77 | - name: "Upload dist-manifest.json" 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: artifacts-plan-dist-manifest 81 | path: plan-dist-manifest.json 82 | 83 | # Build and packages all the platform-specific things 84 | build-local-artifacts: 85 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 86 | # Let the initial task tell us to not run (currently very blunt) 87 | needs: 88 | - plan 89 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 90 | strategy: 91 | fail-fast: false 92 | # Target platforms/runners are computed by cargo-dist in create-release. 93 | # Each member of the matrix has the following arguments: 94 | # 95 | # - runner: the github runner 96 | # - dist-args: cli flags to pass to cargo dist 97 | # - install-dist: expression to run to install cargo-dist on the runner 98 | # 99 | # Typically there will be: 100 | # - 1 "global" task that builds universal installers 101 | # - N "local" tasks that build each platform's binaries and platform-specific installers 102 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 103 | runs-on: ${{ matrix.runner }} 104 | env: 105 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 107 | steps: 108 | - uses: actions/checkout@v4 109 | with: 110 | submodules: recursive 111 | - uses: swatinem/rust-cache@v2 112 | - name: Install cargo-dist 113 | run: ${{ matrix.install_dist }} 114 | # Get the dist-manifest 115 | - name: Fetch local artifacts 116 | uses: actions/download-artifact@v4 117 | with: 118 | pattern: artifacts-* 119 | path: target/distrib/ 120 | merge-multiple: true 121 | - name: Install dependencies 122 | run: | 123 | ${{ matrix.packages_install }} 124 | - name: Build artifacts 125 | run: | 126 | # Actually do builds and make zips and whatnot 127 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 128 | echo "cargo dist ran successfully" 129 | - id: cargo-dist 130 | name: Post-build 131 | # We force bash here just because github makes it really hard to get values up 132 | # to "real" actions without writing to env-vars, and writing to env-vars has 133 | # inconsistent syntax between shell and powershell. 134 | shell: bash 135 | run: | 136 | # Parse out what we just built and upload it to scratch storage 137 | echo "paths<> "$GITHUB_OUTPUT" 138 | jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" 139 | echo "EOF" >> "$GITHUB_OUTPUT" 140 | 141 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 142 | - name: "Upload artifacts" 143 | uses: actions/upload-artifact@v4 144 | with: 145 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 146 | path: | 147 | ${{ steps.cargo-dist.outputs.paths }} 148 | ${{ env.BUILD_MANIFEST_NAME }} 149 | 150 | # Build and package all the platform-agnostic(ish) things 151 | build-global-artifacts: 152 | needs: 153 | - plan 154 | - build-local-artifacts 155 | runs-on: "ubuntu-20.04" 156 | env: 157 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 158 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 159 | steps: 160 | - uses: actions/checkout@v4 161 | with: 162 | submodules: recursive 163 | - name: Install cargo-dist 164 | shell: bash 165 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 166 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 167 | - name: Fetch local artifacts 168 | uses: actions/download-artifact@v4 169 | with: 170 | pattern: artifacts-* 171 | path: target/distrib/ 172 | merge-multiple: true 173 | - id: cargo-dist 174 | shell: bash 175 | run: | 176 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 177 | echo "cargo dist ran successfully" 178 | 179 | # Parse out what we just built and upload it to scratch storage 180 | echo "paths<> "$GITHUB_OUTPUT" 181 | jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" 182 | echo "EOF" >> "$GITHUB_OUTPUT" 183 | 184 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 185 | - name: "Upload artifacts" 186 | uses: actions/upload-artifact@v4 187 | with: 188 | name: artifacts-build-global 189 | path: | 190 | ${{ steps.cargo-dist.outputs.paths }} 191 | ${{ env.BUILD_MANIFEST_NAME }} 192 | # Determines if we should publish/announce 193 | host: 194 | needs: 195 | - plan 196 | - build-local-artifacts 197 | - build-global-artifacts 198 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 199 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 200 | env: 201 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 202 | runs-on: "ubuntu-20.04" 203 | outputs: 204 | val: ${{ steps.host.outputs.manifest }} 205 | steps: 206 | - uses: actions/checkout@v4 207 | with: 208 | submodules: recursive 209 | - name: Install cargo-dist 210 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 211 | # Fetch artifacts from scratch-storage 212 | - name: Fetch artifacts 213 | uses: actions/download-artifact@v4 214 | with: 215 | pattern: artifacts-* 216 | path: target/distrib/ 217 | merge-multiple: true 218 | # This is a harmless no-op for Github Releases, hosting for that happens in "announce" 219 | - id: host 220 | shell: bash 221 | run: | 222 | cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 223 | echo "artifacts uploaded and released successfully" 224 | cat dist-manifest.json 225 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 226 | - name: "Upload dist-manifest.json" 227 | uses: actions/upload-artifact@v4 228 | with: 229 | # Overwrite the previous copy 230 | name: artifacts-dist-manifest 231 | path: dist-manifest.json 232 | 233 | publish-homebrew-formula: 234 | needs: 235 | - plan 236 | - host 237 | runs-on: "ubuntu-20.04" 238 | env: 239 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 240 | PLAN: ${{ needs.plan.outputs.val }} 241 | GITHUB_USER: "axo bot" 242 | GITHUB_EMAIL: "admin+bot@axo.dev" 243 | if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} 244 | steps: 245 | - uses: actions/checkout@v4 246 | with: 247 | repository: "ynqa/homebrew-tap" 248 | token: ${{ secrets.HOMEBREW_TAP_TOKEN }} 249 | # So we have access to the formula 250 | - name: Fetch local artifacts 251 | uses: actions/download-artifact@v4 252 | with: 253 | pattern: artifacts-* 254 | path: Formula/ 255 | merge-multiple: true 256 | # This is extra complex because you can make your Formula name not match your app name 257 | # so we need to find releases with a *.rb file, and publish with that filename. 258 | - name: Commit formula files 259 | run: | 260 | git config --global user.name "${GITHUB_USER}" 261 | git config --global user.email "${GITHUB_EMAIL}" 262 | 263 | for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do 264 | filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) 265 | name=$(echo "$filename" | sed "s/\.rb$//") 266 | version=$(echo "$release" | jq .app_version --raw-output) 267 | 268 | git add "Formula/${filename}" 269 | git commit -m "${name} ${version}" 270 | done 271 | git push 272 | 273 | # Create a Github Release while uploading all files to it 274 | announce: 275 | needs: 276 | - plan 277 | - host 278 | - publish-homebrew-formula 279 | # use "always() && ..." to allow us to wait for all publish jobs while 280 | # still allowing individual publish jobs to skip themselves (for prereleases). 281 | # "host" however must run to completion, no skipping allowed! 282 | if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') }} 283 | runs-on: "ubuntu-20.04" 284 | env: 285 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 286 | steps: 287 | - uses: actions/checkout@v4 288 | with: 289 | submodules: recursive 290 | - name: "Download Github Artifacts" 291 | uses: actions/download-artifact@v4 292 | with: 293 | pattern: artifacts-* 294 | path: artifacts 295 | merge-multiple: true 296 | - name: Cleanup 297 | run: | 298 | # Remove the granular manifests 299 | rm -f artifacts/*-dist-manifest.json 300 | - name: Create Github Release 301 | uses: ncipollo/release-action@v1 302 | with: 303 | tag: ${{ needs.plan.outputs.tag }} 304 | name: ${{ fromJson(needs.host.outputs.val).announcement_title }} 305 | body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }} 306 | prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }} 307 | artifacts: "artifacts/*" 308 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | email (un.pensiero.vano@gmail.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to sig 2 | 3 | We welcome contributions to "sig" and greatly appreciate your help in making 4 | this project even better. Here's a quick guide to get you started. 5 | 6 | ## How to Contribute 7 | 8 | 1. **Fork the Repository**: Click the "Fork" button at the top right of the 9 | [sig repository](https://github.com/ynqa/sig) to create a copy of the 10 | project in your GitHub account. 11 | 12 | 2. **Clone the Repository**: On your local machine, open a terminal and run the 13 | following command, replacing `` with your GitHub username: 14 | 15 | ```bash 16 | git clone https://github.com//sig.git 17 | ``` 18 | 19 | 3. **Create a Branch**: Before making any changes, create a new branch for your 20 | work: 21 | 22 | ```bash 23 | git checkout -b your-branch-name 24 | ``` 25 | 26 | 4. **Make Changes**: Make your desired code changes, bug fixes, or feature 27 | additions. 28 | 29 | 5. **Commit Your Changes**: Commit your changes with a clear and concise message 30 | explaining the purpose of your contribution: 31 | 32 | ```bash 33 | git commit -m "Your commit message here" 34 | ``` 35 | 36 | 6. **Push to Your Fork**: Push your changes to your forked repository on GitHub: 37 | 38 | ```bash 39 | git push origin your-branch-name 40 | ``` 41 | 42 | 7. **Create a Pull Request (PR)**: Open the 43 | [sig Pull Request page](https://github.com/ynqa/sig/pulls) and click the 44 | "New Pull Request" button. Compare and create your PR by following the prompts. 45 | 46 | 8. **Review and Discuss**: Your PR will be reviewed by project maintainers, who 47 | may provide feedback or request further changes. Be prepared for discussion and 48 | updates. 49 | 50 | 9. **Merging**: Once your PR is approved and passes any necessary tests, a 51 | project maintainer will merge it into the main repository. 52 | 53 | ## Code of Conduct 54 | 55 | Please adhere to our [Code of Conduct](CODE_OF_CONDUCT.md) when participating in 56 | this project. We aim to create a respectful and inclusive community for all 57 | contributors. 58 | 59 | Thank you for considering contributing to "sig"! 60 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.4" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.0.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 64 | dependencies = [ 65 | "windows-sys 0.52.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.3" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys 0.52.0", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.83" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" 83 | 84 | [[package]] 85 | name = "autocfg" 86 | version = "1.2.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 89 | 90 | [[package]] 91 | name = "backtrace" 92 | version = "0.3.71" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 95 | dependencies = [ 96 | "addr2line", 97 | "cc", 98 | "cfg-if", 99 | "libc", 100 | "miniz_oxide", 101 | "object", 102 | "rustc-demangle", 103 | ] 104 | 105 | [[package]] 106 | name = "bitflags" 107 | version = "2.5.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 110 | 111 | [[package]] 112 | name = "bstr" 113 | version = "1.9.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 116 | dependencies = [ 117 | "memchr", 118 | "regex-automata", 119 | "serde", 120 | ] 121 | 122 | [[package]] 123 | name = "bytes" 124 | version = "1.6.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 127 | 128 | [[package]] 129 | name = "cc" 130 | version = "1.0.96" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" 133 | 134 | [[package]] 135 | name = "cfg-if" 136 | version = "1.0.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 139 | 140 | [[package]] 141 | name = "clap" 142 | version = "4.5.4" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 145 | dependencies = [ 146 | "clap_builder", 147 | "clap_derive", 148 | ] 149 | 150 | [[package]] 151 | name = "clap_builder" 152 | version = "4.5.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 155 | dependencies = [ 156 | "anstream", 157 | "anstyle", 158 | "clap_lex", 159 | "strsim", 160 | ] 161 | 162 | [[package]] 163 | name = "clap_derive" 164 | version = "4.5.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 167 | dependencies = [ 168 | "heck", 169 | "proc-macro2", 170 | "quote", 171 | "syn", 172 | ] 173 | 174 | [[package]] 175 | name = "clap_lex" 176 | version = "0.7.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 179 | 180 | [[package]] 181 | name = "colorchoice" 182 | version = "1.0.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 185 | 186 | [[package]] 187 | name = "crossbeam-deque" 188 | version = "0.8.5" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 191 | dependencies = [ 192 | "crossbeam-epoch", 193 | "crossbeam-utils", 194 | ] 195 | 196 | [[package]] 197 | name = "crossbeam-epoch" 198 | version = "0.9.18" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 201 | dependencies = [ 202 | "crossbeam-utils", 203 | ] 204 | 205 | [[package]] 206 | name = "crossbeam-utils" 207 | version = "0.8.19" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 210 | 211 | [[package]] 212 | name = "crossterm" 213 | version = "0.28.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 216 | dependencies = [ 217 | "bitflags", 218 | "crossterm_winapi", 219 | "filedescriptor", 220 | "mio 1.0.3", 221 | "parking_lot", 222 | "rustix", 223 | "signal-hook", 224 | "signal-hook-mio", 225 | "winapi", 226 | ] 227 | 228 | [[package]] 229 | name = "crossterm_winapi" 230 | version = "0.9.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 233 | dependencies = [ 234 | "winapi", 235 | ] 236 | 237 | [[package]] 238 | name = "either" 239 | version = "1.11.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" 242 | 243 | [[package]] 244 | name = "encoding_rs" 245 | version = "0.8.34" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 248 | dependencies = [ 249 | "cfg-if", 250 | ] 251 | 252 | [[package]] 253 | name = "encoding_rs_io" 254 | version = "0.1.7" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" 257 | dependencies = [ 258 | "encoding_rs", 259 | ] 260 | 261 | [[package]] 262 | name = "endian-type" 263 | version = "0.1.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 266 | 267 | [[package]] 268 | name = "equivalent" 269 | version = "1.0.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 272 | 273 | [[package]] 274 | name = "errno" 275 | version = "0.3.10" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 278 | dependencies = [ 279 | "libc", 280 | "windows-sys 0.52.0", 281 | ] 282 | 283 | [[package]] 284 | name = "filedescriptor" 285 | version = "0.8.2" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" 288 | dependencies = [ 289 | "libc", 290 | "thiserror", 291 | "winapi", 292 | ] 293 | 294 | [[package]] 295 | name = "futures" 296 | version = "0.3.30" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 299 | dependencies = [ 300 | "futures-channel", 301 | "futures-core", 302 | "futures-executor", 303 | "futures-io", 304 | "futures-sink", 305 | "futures-task", 306 | "futures-util", 307 | ] 308 | 309 | [[package]] 310 | name = "futures-channel" 311 | version = "0.3.30" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 314 | dependencies = [ 315 | "futures-core", 316 | "futures-sink", 317 | ] 318 | 319 | [[package]] 320 | name = "futures-core" 321 | version = "0.3.30" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 324 | 325 | [[package]] 326 | name = "futures-executor" 327 | version = "0.3.30" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 330 | dependencies = [ 331 | "futures-core", 332 | "futures-task", 333 | "futures-util", 334 | ] 335 | 336 | [[package]] 337 | name = "futures-io" 338 | version = "0.3.30" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 341 | 342 | [[package]] 343 | name = "futures-macro" 344 | version = "0.3.30" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 347 | dependencies = [ 348 | "proc-macro2", 349 | "quote", 350 | "syn", 351 | ] 352 | 353 | [[package]] 354 | name = "futures-sink" 355 | version = "0.3.30" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 358 | 359 | [[package]] 360 | name = "futures-task" 361 | version = "0.3.30" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 364 | 365 | [[package]] 366 | name = "futures-timer" 367 | version = "3.0.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 370 | 371 | [[package]] 372 | name = "futures-util" 373 | version = "0.3.30" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 376 | dependencies = [ 377 | "futures-channel", 378 | "futures-core", 379 | "futures-io", 380 | "futures-macro", 381 | "futures-sink", 382 | "futures-task", 383 | "memchr", 384 | "pin-project-lite", 385 | "pin-utils", 386 | "slab", 387 | ] 388 | 389 | [[package]] 390 | name = "gimli" 391 | version = "0.28.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 394 | 395 | [[package]] 396 | name = "globset" 397 | version = "0.4.14" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" 400 | dependencies = [ 401 | "aho-corasick", 402 | "bstr", 403 | "log", 404 | "regex-automata", 405 | "regex-syntax", 406 | ] 407 | 408 | [[package]] 409 | name = "grep" 410 | version = "0.3.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "6e2b024ec1e686cb64d78beb852030b0e632af93817f1ed25be0173af0e94939" 413 | dependencies = [ 414 | "grep-cli", 415 | "grep-matcher", 416 | "grep-printer", 417 | "grep-regex", 418 | "grep-searcher", 419 | ] 420 | 421 | [[package]] 422 | name = "grep-cli" 423 | version = "0.1.10" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "ea40788c059ab8b622c4d074732750bfb3bd2912e2dd58eabc11798a4d5ad725" 426 | dependencies = [ 427 | "bstr", 428 | "globset", 429 | "libc", 430 | "log", 431 | "termcolor", 432 | "winapi-util", 433 | ] 434 | 435 | [[package]] 436 | name = "grep-matcher" 437 | version = "0.1.7" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" 440 | dependencies = [ 441 | "memchr", 442 | ] 443 | 444 | [[package]] 445 | name = "grep-printer" 446 | version = "0.2.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "743c12a03c8aee38b6e5bd0168d8ebb09345751323df4a01c56e792b1f38ceb2" 449 | dependencies = [ 450 | "bstr", 451 | "grep-matcher", 452 | "grep-searcher", 453 | "log", 454 | "serde", 455 | "serde_json", 456 | "termcolor", 457 | ] 458 | 459 | [[package]] 460 | name = "grep-regex" 461 | version = "0.1.12" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d" 464 | dependencies = [ 465 | "bstr", 466 | "grep-matcher", 467 | "log", 468 | "regex-automata", 469 | "regex-syntax", 470 | ] 471 | 472 | [[package]] 473 | name = "grep-searcher" 474 | version = "0.1.13" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54" 477 | dependencies = [ 478 | "bstr", 479 | "encoding_rs", 480 | "encoding_rs_io", 481 | "grep-matcher", 482 | "log", 483 | "memchr", 484 | "memmap2", 485 | ] 486 | 487 | [[package]] 488 | name = "hashbrown" 489 | version = "0.15.2" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 492 | 493 | [[package]] 494 | name = "heck" 495 | version = "0.5.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 498 | 499 | [[package]] 500 | name = "hermit-abi" 501 | version = "0.3.9" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 504 | 505 | [[package]] 506 | name = "indexmap" 507 | version = "2.6.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 510 | dependencies = [ 511 | "equivalent", 512 | "hashbrown", 513 | ] 514 | 515 | [[package]] 516 | name = "is_terminal_polyfill" 517 | version = "1.70.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 520 | 521 | [[package]] 522 | name = "itoa" 523 | version = "1.0.11" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 526 | 527 | [[package]] 528 | name = "libc" 529 | version = "0.2.167" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 532 | 533 | [[package]] 534 | name = "linux-raw-sys" 535 | version = "0.4.14" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 538 | 539 | [[package]] 540 | name = "lock_api" 541 | version = "0.4.12" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 544 | dependencies = [ 545 | "autocfg", 546 | "scopeguard", 547 | ] 548 | 549 | [[package]] 550 | name = "log" 551 | version = "0.4.21" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 554 | 555 | [[package]] 556 | name = "memchr" 557 | version = "2.7.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 560 | 561 | [[package]] 562 | name = "memmap2" 563 | version = "0.9.4" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 566 | dependencies = [ 567 | "libc", 568 | ] 569 | 570 | [[package]] 571 | name = "miniz_oxide" 572 | version = "0.7.2" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 575 | dependencies = [ 576 | "adler", 577 | ] 578 | 579 | [[package]] 580 | name = "mio" 581 | version = "0.8.11" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 584 | dependencies = [ 585 | "libc", 586 | "wasi", 587 | "windows-sys 0.48.0", 588 | ] 589 | 590 | [[package]] 591 | name = "mio" 592 | version = "1.0.3" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 595 | dependencies = [ 596 | "libc", 597 | "log", 598 | "wasi", 599 | "windows-sys 0.52.0", 600 | ] 601 | 602 | [[package]] 603 | name = "nibble_vec" 604 | version = "0.1.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 607 | dependencies = [ 608 | "smallvec", 609 | ] 610 | 611 | [[package]] 612 | name = "num_cpus" 613 | version = "1.16.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 616 | dependencies = [ 617 | "hermit-abi", 618 | "libc", 619 | ] 620 | 621 | [[package]] 622 | name = "object" 623 | version = "0.32.2" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 626 | dependencies = [ 627 | "memchr", 628 | ] 629 | 630 | [[package]] 631 | name = "parking_lot" 632 | version = "0.12.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 635 | dependencies = [ 636 | "lock_api", 637 | "parking_lot_core", 638 | ] 639 | 640 | [[package]] 641 | name = "parking_lot_core" 642 | version = "0.9.10" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 645 | dependencies = [ 646 | "cfg-if", 647 | "libc", 648 | "redox_syscall", 649 | "smallvec", 650 | "windows-targets 0.52.5", 651 | ] 652 | 653 | [[package]] 654 | name = "pin-project-lite" 655 | version = "0.2.14" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 658 | 659 | [[package]] 660 | name = "pin-utils" 661 | version = "0.1.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 664 | 665 | [[package]] 666 | name = "proc-macro2" 667 | version = "1.0.81" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 670 | dependencies = [ 671 | "unicode-ident", 672 | ] 673 | 674 | [[package]] 675 | name = "promkit" 676 | version = "0.5.1" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "4b5b2f4352dbc2c7879f58af5585cc8ab30d1d68157d65ffda0ac80210420a1b" 679 | dependencies = [ 680 | "anyhow", 681 | "crossterm", 682 | "indexmap", 683 | "radix_trie", 684 | "serde", 685 | "serde_json", 686 | "unicode-width", 687 | ] 688 | 689 | [[package]] 690 | name = "quote" 691 | version = "1.0.36" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 694 | dependencies = [ 695 | "proc-macro2", 696 | ] 697 | 698 | [[package]] 699 | name = "radix_trie" 700 | version = "0.2.1" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 703 | dependencies = [ 704 | "endian-type", 705 | "nibble_vec", 706 | ] 707 | 708 | [[package]] 709 | name = "rayon" 710 | version = "1.10.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 713 | dependencies = [ 714 | "either", 715 | "rayon-core", 716 | ] 717 | 718 | [[package]] 719 | name = "rayon-core" 720 | version = "1.12.1" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 723 | dependencies = [ 724 | "crossbeam-deque", 725 | "crossbeam-utils", 726 | ] 727 | 728 | [[package]] 729 | name = "redox_syscall" 730 | version = "0.5.1" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 733 | dependencies = [ 734 | "bitflags", 735 | ] 736 | 737 | [[package]] 738 | name = "regex" 739 | version = "1.10.4" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 742 | dependencies = [ 743 | "aho-corasick", 744 | "memchr", 745 | "regex-automata", 746 | "regex-syntax", 747 | ] 748 | 749 | [[package]] 750 | name = "regex-automata" 751 | version = "0.4.6" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 754 | dependencies = [ 755 | "aho-corasick", 756 | "memchr", 757 | "regex-syntax", 758 | ] 759 | 760 | [[package]] 761 | name = "regex-syntax" 762 | version = "0.8.3" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 765 | 766 | [[package]] 767 | name = "rustc-demangle" 768 | version = "0.1.23" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 771 | 772 | [[package]] 773 | name = "rustix" 774 | version = "0.38.41" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 777 | dependencies = [ 778 | "bitflags", 779 | "errno", 780 | "libc", 781 | "linux-raw-sys", 782 | "windows-sys 0.52.0", 783 | ] 784 | 785 | [[package]] 786 | name = "ryu" 787 | version = "1.0.17" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 790 | 791 | [[package]] 792 | name = "scopeguard" 793 | version = "1.2.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 796 | 797 | [[package]] 798 | name = "serde" 799 | version = "1.0.200" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" 802 | dependencies = [ 803 | "serde_derive", 804 | ] 805 | 806 | [[package]] 807 | name = "serde_derive" 808 | version = "1.0.200" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" 811 | dependencies = [ 812 | "proc-macro2", 813 | "quote", 814 | "syn", 815 | ] 816 | 817 | [[package]] 818 | name = "serde_json" 819 | version = "1.0.116" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" 822 | dependencies = [ 823 | "indexmap", 824 | "itoa", 825 | "ryu", 826 | "serde", 827 | ] 828 | 829 | [[package]] 830 | name = "signal-hook" 831 | version = "0.3.17" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 834 | dependencies = [ 835 | "libc", 836 | "signal-hook-registry", 837 | ] 838 | 839 | [[package]] 840 | name = "signal-hook-mio" 841 | version = "0.2.4" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 844 | dependencies = [ 845 | "libc", 846 | "mio 1.0.3", 847 | "signal-hook", 848 | ] 849 | 850 | [[package]] 851 | name = "signal-hook-registry" 852 | version = "1.4.2" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 855 | dependencies = [ 856 | "libc", 857 | ] 858 | 859 | [[package]] 860 | name = "sigrs" 861 | version = "0.1.4" 862 | dependencies = [ 863 | "anyhow", 864 | "clap", 865 | "futures", 866 | "futures-timer", 867 | "grep", 868 | "promkit", 869 | "rayon", 870 | "regex", 871 | "strip-ansi-escapes", 872 | "tokio", 873 | "tokio-util", 874 | ] 875 | 876 | [[package]] 877 | name = "slab" 878 | version = "0.4.9" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 881 | dependencies = [ 882 | "autocfg", 883 | ] 884 | 885 | [[package]] 886 | name = "smallvec" 887 | version = "1.13.2" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 890 | 891 | [[package]] 892 | name = "socket2" 893 | version = "0.5.7" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 896 | dependencies = [ 897 | "libc", 898 | "windows-sys 0.52.0", 899 | ] 900 | 901 | [[package]] 902 | name = "strip-ansi-escapes" 903 | version = "0.2.0" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" 906 | dependencies = [ 907 | "vte", 908 | ] 909 | 910 | [[package]] 911 | name = "strsim" 912 | version = "0.11.1" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 915 | 916 | [[package]] 917 | name = "syn" 918 | version = "2.0.60" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 921 | dependencies = [ 922 | "proc-macro2", 923 | "quote", 924 | "unicode-ident", 925 | ] 926 | 927 | [[package]] 928 | name = "termcolor" 929 | version = "1.4.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 932 | dependencies = [ 933 | "winapi-util", 934 | ] 935 | 936 | [[package]] 937 | name = "thiserror" 938 | version = "1.0.65" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 941 | dependencies = [ 942 | "thiserror-impl", 943 | ] 944 | 945 | [[package]] 946 | name = "thiserror-impl" 947 | version = "1.0.65" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 950 | dependencies = [ 951 | "proc-macro2", 952 | "quote", 953 | "syn", 954 | ] 955 | 956 | [[package]] 957 | name = "tokio" 958 | version = "1.37.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 961 | dependencies = [ 962 | "backtrace", 963 | "bytes", 964 | "libc", 965 | "mio 0.8.11", 966 | "num_cpus", 967 | "parking_lot", 968 | "pin-project-lite", 969 | "signal-hook-registry", 970 | "socket2", 971 | "tokio-macros", 972 | "windows-sys 0.48.0", 973 | ] 974 | 975 | [[package]] 976 | name = "tokio-macros" 977 | version = "2.2.0" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 980 | dependencies = [ 981 | "proc-macro2", 982 | "quote", 983 | "syn", 984 | ] 985 | 986 | [[package]] 987 | name = "tokio-util" 988 | version = "0.7.11" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 991 | dependencies = [ 992 | "bytes", 993 | "futures-core", 994 | "futures-sink", 995 | "pin-project-lite", 996 | "tokio", 997 | ] 998 | 999 | [[package]] 1000 | name = "unicode-ident" 1001 | version = "1.0.12" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1004 | 1005 | [[package]] 1006 | name = "unicode-width" 1007 | version = "0.1.14" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1010 | 1011 | [[package]] 1012 | name = "utf8parse" 1013 | version = "0.2.1" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1016 | 1017 | [[package]] 1018 | name = "vte" 1019 | version = "0.11.1" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" 1022 | dependencies = [ 1023 | "utf8parse", 1024 | "vte_generate_state_changes", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "vte_generate_state_changes" 1029 | version = "0.1.1" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" 1032 | dependencies = [ 1033 | "proc-macro2", 1034 | "quote", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "wasi" 1039 | version = "0.11.0+wasi-snapshot-preview1" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1042 | 1043 | [[package]] 1044 | name = "winapi" 1045 | version = "0.3.9" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1048 | dependencies = [ 1049 | "winapi-i686-pc-windows-gnu", 1050 | "winapi-x86_64-pc-windows-gnu", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "winapi-i686-pc-windows-gnu" 1055 | version = "0.4.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1058 | 1059 | [[package]] 1060 | name = "winapi-util" 1061 | version = "0.1.8" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 1064 | dependencies = [ 1065 | "windows-sys 0.52.0", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "winapi-x86_64-pc-windows-gnu" 1070 | version = "0.4.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1073 | 1074 | [[package]] 1075 | name = "windows-sys" 1076 | version = "0.48.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1079 | dependencies = [ 1080 | "windows-targets 0.48.5", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "windows-sys" 1085 | version = "0.52.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1088 | dependencies = [ 1089 | "windows-targets 0.52.5", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "windows-targets" 1094 | version = "0.48.5" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1097 | dependencies = [ 1098 | "windows_aarch64_gnullvm 0.48.5", 1099 | "windows_aarch64_msvc 0.48.5", 1100 | "windows_i686_gnu 0.48.5", 1101 | "windows_i686_msvc 0.48.5", 1102 | "windows_x86_64_gnu 0.48.5", 1103 | "windows_x86_64_gnullvm 0.48.5", 1104 | "windows_x86_64_msvc 0.48.5", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "windows-targets" 1109 | version = "0.52.5" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1112 | dependencies = [ 1113 | "windows_aarch64_gnullvm 0.52.5", 1114 | "windows_aarch64_msvc 0.52.5", 1115 | "windows_i686_gnu 0.52.5", 1116 | "windows_i686_gnullvm", 1117 | "windows_i686_msvc 0.52.5", 1118 | "windows_x86_64_gnu 0.52.5", 1119 | "windows_x86_64_gnullvm 0.52.5", 1120 | "windows_x86_64_msvc 0.52.5", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "windows_aarch64_gnullvm" 1125 | version = "0.48.5" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1128 | 1129 | [[package]] 1130 | name = "windows_aarch64_gnullvm" 1131 | version = "0.52.5" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1134 | 1135 | [[package]] 1136 | name = "windows_aarch64_msvc" 1137 | version = "0.48.5" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1140 | 1141 | [[package]] 1142 | name = "windows_aarch64_msvc" 1143 | version = "0.52.5" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1146 | 1147 | [[package]] 1148 | name = "windows_i686_gnu" 1149 | version = "0.48.5" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1152 | 1153 | [[package]] 1154 | name = "windows_i686_gnu" 1155 | version = "0.52.5" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1158 | 1159 | [[package]] 1160 | name = "windows_i686_gnullvm" 1161 | version = "0.52.5" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1164 | 1165 | [[package]] 1166 | name = "windows_i686_msvc" 1167 | version = "0.48.5" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1170 | 1171 | [[package]] 1172 | name = "windows_i686_msvc" 1173 | version = "0.52.5" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1176 | 1177 | [[package]] 1178 | name = "windows_x86_64_gnu" 1179 | version = "0.48.5" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1182 | 1183 | [[package]] 1184 | name = "windows_x86_64_gnu" 1185 | version = "0.52.5" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1188 | 1189 | [[package]] 1190 | name = "windows_x86_64_gnullvm" 1191 | version = "0.48.5" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1194 | 1195 | [[package]] 1196 | name = "windows_x86_64_gnullvm" 1197 | version = "0.52.5" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1200 | 1201 | [[package]] 1202 | name = "windows_x86_64_msvc" 1203 | version = "0.48.5" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1206 | 1207 | [[package]] 1208 | name = "windows_x86_64_msvc" 1209 | version = "0.52.5" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1212 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sigrs" 3 | version = "0.1.4" 4 | authors = ["ynqa "] 5 | edition = "2021" 6 | description = "Interactive grep (for streaming)" 7 | repository = "https://github.com/ynqa/sig" 8 | license = "MIT" 9 | readme = "README.md" 10 | 11 | [[bin]] 12 | name = "sig" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | anyhow = "1.0.83" 17 | clap = { version = "4.5.4", features = ["derive"] } 18 | futures = "0.3.30" 19 | futures-timer = "3.0.3" 20 | grep = "0.3.1" 21 | promkit = "0.5.1" 22 | rayon = "1.5.0" 23 | regex = "1.10.4" 24 | strip-ansi-escapes = "0.2.0" 25 | tokio = { version = "1.37.0", features = ["full"] } 26 | tokio-util = "0.7.11" 27 | 28 | # The profile that 'cargo dist' will build with 29 | [profile.dist] 30 | inherits = "release" 31 | lto = "thin" 32 | 33 | # Config for 'cargo dist' 34 | [workspace.metadata.dist] 35 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 36 | cargo-dist-version = "0.11.1" 37 | # CI backends to support 38 | ci = ["github"] 39 | # The installers to generate for each app 40 | installers = ["homebrew"] 41 | # A GitHub repo to push Homebrew formulas to 42 | tap = "ynqa/homebrew-tap" 43 | # Target platforms to build apps for (Rust target-triple syntax) 44 | targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] 45 | # Publish jobs to run in CI 46 | publish-jobs = ["homebrew"] 47 | # Publish jobs to run in CI 48 | pr-run-mode = "plan" 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 sig authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sig 2 | 3 | [![ci](https://github.com/ynqa/sig/actions/workflows/ci.yml/badge.svg)](https://github.com/ynqa/sig/actions/workflows/ci.yml) 4 | 5 | Interactive grep 6 | 7 | |![sig.gif](https://github.com/ynqa/ynqa/blob/master/demo/sig.gif)|![sig_archived.gif](https://github.com/ynqa/ynqa/blob/master/demo/sig_archived.gif)| 8 | |---|---| 9 | 10 | ## Features 11 | 12 | - Interactive grep (for streaming) 13 | - *sig* allows users to interactively search through (streaming) data, 14 | updating results in real-time. 15 | - Re-execute command 16 | - If `--cmd` is specified instread of piping data to *sig*, 17 | the command will be executed on initial and retries. 18 | - This feature is designed to address the issue where data streams 19 | past while the user is fine-tuning the search criteria. 20 | In other words, even if the data has already passed, 21 | executing the command again allows 22 | the retrieval of the data for re-evaluation. 23 | - Archived mode 24 | - In archived mode, since there is no seeking capability 25 | for streaming data received through a pipe, 26 | it is not possible to search backwards without exiting the process. 27 | Therefore, in *sig*, the latest N entries of streaming data are saved, 28 | and it is possible to switch to a mode 29 | where you can grep through these N entries 30 | based on key inputs at any given moment. 31 | - Additionally, by starting in this mode, 32 | it is also possible to grep through static data such as files. 33 | - like [ugrep](https://github.com/Genivia/ugrep) with `-Q` option. 34 | 35 | ## Installation 36 | 37 | ### Homebrew 38 | 39 | ```bash 40 | brew install ynqa/tap/sigrs 41 | ``` 42 | 43 | ### Cargo 44 | 45 | ```bash 46 | cargo install sigrs 47 | ``` 48 | 49 | ### Arch Linux 50 | 51 | ```bash 52 | pacman -S sig 53 | ``` 54 | 55 | ### Nix (flakes) 56 | 57 | Add it as an input to your flake: 58 | ```nix 59 | inputs = { 60 | sig.url = 'github:ynqa/sig/' 61 | } 62 | ``` 63 | 64 | Create a shell with it: 65 | ```nix 66 | nix shell github:ynqa/sig 67 | ``` 68 | 69 | Or run it directly: 70 | ```nix 71 | cat README.md | nix run github:ynqa/sig -- --archived 72 | ``` 73 | 74 | ### Nix (classic) 75 | 76 | Fetch the source and use it, e.g. in your shell: 77 | 78 | ```nix 79 | let 80 | # At the time of writing this, pkgs need to be unstable for the package to build properly 81 | # (requires Rust 1.74, stable has 1.73) 82 | pkgs = import {}; 83 | 84 | sig = pkgs.callPackage (pkgs.fetchFromGitHub { 85 | owner = "ynqa"; 86 | repo = "sig"; 87 | rev = ""; 88 | hash = ""; # Build first, put proper hash in place 89 | }) {}; 90 | in 91 | pkgs.mkShell { 92 | packages = [sig]; 93 | } 94 | ``` 95 | 96 | ## Examples 97 | 98 | ```bash 99 | stern --context kind-kind etcd |& sig 100 | # or 101 | sig --cmd "stern --context kind-kind etcd" # this is able to retry command by ctrl+r. 102 | ``` 103 | 104 | ### Archived mode 105 | 106 | ```bash 107 | cat README.md |& sig -a 108 | # or 109 | sig -a --cmd "cat README.md" 110 | ``` 111 | 112 | ## Keymap 113 | 114 | | Key | Action 115 | | :- | :- 116 | | Ctrl + C | Exit `sig` 117 | | Ctrl + R | Retry command if `--cmd` is specified 118 | | Ctrl + F | Enter Archived mode 119 | | | Move the cursor one character to the left 120 | | | Move the cursor one character to the right 121 | | Ctrl + A | Move the cursor to the start of the filter 122 | | Ctrl + E | Move the cursor to the end of the filter 123 | | Backspace | Delete a character of filter at the cursor position 124 | | Ctrl + U | Delete all characters of filter 125 | 126 | (Archived mode) 127 | 128 | | Key | Action 129 | | :- | :- 130 | | Ctrl + C | Exit Archived mode 131 | | | Move the cursor one character to the left 132 | | | Move the cursor one character to the right 133 | | Ctrl + A | Move the cursor to the start of the filter 134 | | Ctrl + E | Move the cursor to the end of the filter 135 | | Backspace | Delete a character of filter at the cursor position 136 | | Ctrl + U | Delete all characters of filter 137 | 138 | ## Usage 139 | 140 | ```bash 141 | Interactive grep (for streaming) 142 | 143 | Usage: sig [OPTIONS] 144 | 145 | Examples: 146 | 147 | $ stern --context kind-kind etcd |& sig 148 | Or the method to retry command by pressing ctrl+r: 149 | $ sig --cmd "stern --context kind-kind etcd" 150 | 151 | Archived mode: 152 | $ cat README.md |& sig -a 153 | Or 154 | $ sig -a --cmd "cat README.md" 155 | 156 | Options: 157 | --retrieval-timeout 158 | Timeout to read a next line from the stream in milliseconds. [default: 10] 159 | --render-interval 160 | Interval to render a line in milliseconds. [default: 10] 161 | -q, --queue-capacity 162 | Queue capacity to store lines. [default: 1000] 163 | -a, --archived 164 | Archived mode to grep through static data. 165 | -i, --ignore-case 166 | Case insensitive search. 167 | --cmd 168 | Command to execute on initial and retries. 169 | -h, --help 170 | Print help (see more with '--help') 171 | -V, --version 172 | Print version 173 | ``` 174 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | rustPlatform, 4 | }: let 5 | version = "0.1.0"; 6 | owner = "ynqa"; 7 | repo = "sig"; 8 | in 9 | rustPlatform.buildRustPackage { 10 | pname = "sig"; 11 | inherit version; 12 | 13 | src = builtins.path { 14 | path = ./.; 15 | name = repo; 16 | }; 17 | 18 | cargoLock = { 19 | lockFile = ./Cargo.lock; 20 | allowBuiltinFetchGit = true; 21 | }; 22 | 23 | meta = { 24 | description = "Interactive grep (for streaming)"; 25 | homepage = "https://github.com/${owner}/${repo}"; 26 | license = [lib.licenses.mit]; 27 | mainProgram = "sig"; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1716619601, 6 | "narHash": "sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "47e03a624662ce399e55c45a5f6da698fc72c797", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "systems": "systems" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Interactive grep (for streaming)"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 6 | systems.url = "github:nix-systems/default"; 7 | }; 8 | 9 | outputs = { 10 | self, 11 | nixpkgs, 12 | systems, 13 | }: let 14 | forEachSystem = f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system}); 15 | in { 16 | formatter = forEachSystem (pkgs: pkgs.alejandra); 17 | 18 | packages = forEachSystem (pkgs: { 19 | default = pkgs.callPackage ./default.nix {}; 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/archived.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | 3 | use promkit::{ 4 | crossterm::{event::Event, style::ContentStyle}, 5 | grapheme::StyledGraphemes, 6 | listbox, 7 | pane::Pane, 8 | snapshot::Snapshot, 9 | switch::ActiveKeySwitcher, 10 | text_editor, PaneFactory, Prompt, PromptSignal, 11 | }; 12 | 13 | use crate::sig; 14 | 15 | mod keymap; 16 | 17 | struct Archived { 18 | keymap: ActiveKeySwitcher, 19 | text_editor_snapshot: Snapshot, 20 | lines: Snapshot, 21 | highlight_style: ContentStyle, 22 | case_insensitive: bool, 23 | cmd: Option, 24 | } 25 | 26 | impl promkit::Finalizer for Archived { 27 | type Return = (); 28 | 29 | fn finalize(&mut self) -> anyhow::Result { 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl promkit::Renderer for Archived { 35 | fn create_panes(&self, width: u16, height: u16) -> Vec { 36 | vec![ 37 | self.lines.create_pane(width, height), 38 | self.text_editor_snapshot.create_pane(width, height), 39 | ] 40 | } 41 | 42 | fn evaluate(&mut self, event: &Event) -> anyhow::Result { 43 | let signal = self.keymap.get()( 44 | event, 45 | &mut self.text_editor_snapshot, 46 | &mut self.lines, 47 | self.cmd.clone(), 48 | ); 49 | if self 50 | .text_editor_snapshot 51 | .after() 52 | .texteditor 53 | .text_without_cursor() 54 | != self 55 | .text_editor_snapshot 56 | .borrow_before() 57 | .texteditor 58 | .text_without_cursor() 59 | { 60 | let query = self 61 | .text_editor_snapshot 62 | .after() 63 | .texteditor 64 | .text_without_cursor() 65 | .to_string(); 66 | 67 | let list: Vec = self 68 | .lines 69 | .init() 70 | .listbox 71 | .items() 72 | .par_iter() 73 | .filter_map(|line| { 74 | sig::styled( 75 | &query, 76 | &line.to_string(), 77 | self.highlight_style, 78 | self.case_insensitive, 79 | ) 80 | }) 81 | .collect(); 82 | 83 | self.lines.after_mut().listbox = listbox::Listbox::from_styled_graphemes(list); 84 | } 85 | signal 86 | } 87 | } 88 | 89 | pub fn run( 90 | text_editor: text_editor::State, 91 | lines: listbox::State, 92 | highlight_style: ContentStyle, 93 | case_insensitive: bool, 94 | cmd: Option, 95 | ) -> anyhow::Result<()> { 96 | Prompt { 97 | renderer: Archived { 98 | keymap: ActiveKeySwitcher::new("default", keymap::default), 99 | text_editor_snapshot: Snapshot::new(text_editor), 100 | lines: Snapshot::new(lines), 101 | highlight_style, 102 | case_insensitive, 103 | cmd, 104 | }, 105 | } 106 | .run() 107 | } 108 | -------------------------------------------------------------------------------- /src/archived/keymap.rs: -------------------------------------------------------------------------------- 1 | use promkit::{ 2 | crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, 3 | listbox, 4 | snapshot::Snapshot, 5 | text_editor, PromptSignal, 6 | }; 7 | 8 | pub type Keymap = fn( 9 | &Event, 10 | &mut Snapshot, 11 | &mut Snapshot, 12 | Option, 13 | ) -> anyhow::Result; 14 | 15 | pub fn default( 16 | event: &Event, 17 | text_editor_snapshot: &mut Snapshot, 18 | logs_snapshot: &mut Snapshot, 19 | cmd: Option, 20 | ) -> anyhow::Result { 21 | let text_editor_state = text_editor_snapshot.after_mut(); 22 | let logs_state = logs_snapshot.after_mut(); 23 | 24 | match event { 25 | Event::Key(KeyEvent { 26 | code: KeyCode::Char('r'), 27 | modifiers: KeyModifiers::CONTROL, 28 | kind: KeyEventKind::Press, 29 | state: KeyEventState::NONE, 30 | }) => { 31 | if cmd.is_some() { 32 | // Exiting archive mode here allows 33 | // the caller to re-enter streaming mode, 34 | // as it is running in an infinite loop. 35 | return Ok(PromptSignal::Quit); 36 | } 37 | } 38 | 39 | Event::Key(KeyEvent { 40 | code: KeyCode::Char('c'), 41 | modifiers: KeyModifiers::CONTROL, 42 | kind: KeyEventKind::Press, 43 | state: KeyEventState::NONE, 44 | }) => return Err(anyhow::anyhow!("ctrl+c")), 45 | 46 | // Move cursor (text editor) 47 | Event::Key(KeyEvent { 48 | code: KeyCode::Left, 49 | modifiers: KeyModifiers::NONE, 50 | kind: KeyEventKind::Press, 51 | state: KeyEventState::NONE, 52 | }) => { 53 | text_editor_state.texteditor.backward(); 54 | } 55 | Event::Key(KeyEvent { 56 | code: KeyCode::Right, 57 | modifiers: KeyModifiers::NONE, 58 | kind: KeyEventKind::Press, 59 | state: KeyEventState::NONE, 60 | }) => { 61 | text_editor_state.texteditor.forward(); 62 | } 63 | Event::Key(KeyEvent { 64 | code: KeyCode::Char('a'), 65 | modifiers: KeyModifiers::CONTROL, 66 | kind: KeyEventKind::Press, 67 | state: KeyEventState::NONE, 68 | }) => text_editor_state.texteditor.move_to_head(), 69 | Event::Key(KeyEvent { 70 | code: KeyCode::Char('e'), 71 | modifiers: KeyModifiers::CONTROL, 72 | kind: KeyEventKind::Press, 73 | state: KeyEventState::NONE, 74 | }) => text_editor_state.texteditor.move_to_tail(), 75 | 76 | // Move cursor (listbox). 77 | Event::Key(KeyEvent { 78 | code: KeyCode::Up, 79 | modifiers: KeyModifiers::NONE, 80 | kind: KeyEventKind::Press, 81 | state: KeyEventState::NONE, 82 | }) => { 83 | logs_state.listbox.backward(); 84 | } 85 | Event::Key(KeyEvent { 86 | code: KeyCode::Down, 87 | modifiers: KeyModifiers::NONE, 88 | kind: KeyEventKind::Press, 89 | state: KeyEventState::NONE, 90 | }) => { 91 | logs_state.listbox.forward(); 92 | } 93 | 94 | // Erase char(s). 95 | Event::Key(KeyEvent { 96 | code: KeyCode::Backspace, 97 | modifiers: KeyModifiers::NONE, 98 | kind: KeyEventKind::Press, 99 | state: KeyEventState::NONE, 100 | }) => text_editor_state.texteditor.erase(), 101 | Event::Key(KeyEvent { 102 | code: KeyCode::Char('u'), 103 | modifiers: KeyModifiers::CONTROL, 104 | kind: KeyEventKind::Press, 105 | state: KeyEventState::NONE, 106 | }) => text_editor_state.texteditor.erase_all(), 107 | 108 | // Input char. 109 | Event::Key(KeyEvent { 110 | code: KeyCode::Char(ch), 111 | modifiers: KeyModifiers::NONE, 112 | kind: KeyEventKind::Press, 113 | state: KeyEventState::NONE, 114 | }) 115 | | Event::Key(KeyEvent { 116 | code: KeyCode::Char(ch), 117 | modifiers: KeyModifiers::SHIFT, 118 | kind: KeyEventKind::Press, 119 | state: KeyEventState::NONE, 120 | }) => match text_editor_state.edit_mode { 121 | text_editor::Mode::Insert => text_editor_state.texteditor.insert(*ch), 122 | text_editor::Mode::Overwrite => text_editor_state.texteditor.overwrite(*ch), 123 | }, 124 | 125 | _ => (), 126 | } 127 | Ok(PromptSignal::Continue) 128 | } 129 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | 3 | use tokio::{ 4 | io::{AsyncBufReadExt, BufReader}, 5 | process::Command, 6 | sync::mpsc, 7 | time::{timeout, Duration}, 8 | }; 9 | use tokio_util::sync::CancellationToken; 10 | 11 | pub async fn execute( 12 | cmdstr: &str, 13 | tx: mpsc::Sender, 14 | retrieval_timeout: Duration, 15 | canceled: CancellationToken, 16 | ) -> anyhow::Result<()> { 17 | let args: Vec<&str> = cmdstr.split_whitespace().collect(); 18 | let mut child = Command::new(args[0]) 19 | .args(&args[1..]) 20 | .stdout(Stdio::piped()) 21 | .stderr(Stdio::piped()) 22 | .spawn()?; 23 | 24 | let stdout = child 25 | .stdout 26 | .take() 27 | .ok_or_else(|| anyhow::anyhow!("stdout is not available"))?; 28 | let stderr = child 29 | .stderr 30 | .take() 31 | .ok_or_else(|| anyhow::anyhow!("stderr is not available"))?; 32 | let mut stdout_reader = BufReader::new(stdout).lines(); 33 | let mut stderr_reader = BufReader::new(stderr).lines(); 34 | 35 | while !canceled.is_cancelled() { 36 | tokio::select! { 37 | stdout_res = timeout(retrieval_timeout, stdout_reader.next_line()) => { 38 | if let Ok(Ok(Some(line))) = stdout_res { 39 | let escaped = strip_ansi_escapes::strip_str(line.replace(['\n', '\t'], " ")); 40 | tx.send(escaped).await?; 41 | } 42 | }, 43 | stderr_res = timeout(retrieval_timeout, stderr_reader.next_line()) => { 44 | if let Ok(Ok(Some(line))) = stderr_res { 45 | let escaped = strip_ansi_escapes::strip_str(line.replace(['\n', '\t'], " ")); 46 | tx.send(escaped).await?; 47 | } 48 | } 49 | } 50 | } 51 | 52 | child.kill().await?; 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, io}; 2 | 3 | use clap::Parser; 4 | use tokio::{ 5 | sync::mpsc, 6 | time::{timeout, Duration}, 7 | }; 8 | use tokio_util::sync::CancellationToken; 9 | 10 | use promkit::{ 11 | crossterm::{ 12 | self, cursor, execute, 13 | style::Color, 14 | terminal::{disable_raw_mode, enable_raw_mode}, 15 | }, 16 | listbox, 17 | style::StyleBuilder, 18 | text_editor, 19 | }; 20 | 21 | mod archived; 22 | mod cmd; 23 | mod sig; 24 | mod stdin; 25 | mod terminal; 26 | 27 | #[derive(Eq, PartialEq)] 28 | pub enum Signal { 29 | Continue, 30 | GotoArchived, 31 | GotoStreaming, 32 | } 33 | 34 | /// Interactive grep (for streaming) 35 | #[derive(Parser)] 36 | #[command( 37 | name = "sig", 38 | version, 39 | help_template = " 40 | {about} 41 | 42 | Usage: {usage} 43 | 44 | Examples: 45 | 46 | $ stern --context kind-kind etcd |& sig 47 | Or the method to retry command by pressing ctrl+r: 48 | $ sig --cmd \"stern --context kind-kind etcd\" 49 | 50 | Archived mode: 51 | $ cat README.md |& sig -a 52 | Or 53 | $ sig -a --cmd \"cat README.md\" 54 | 55 | Options: 56 | {options} 57 | " 58 | )] 59 | pub struct Args { 60 | #[arg( 61 | long = "retrieval-timeout", 62 | default_value = "10", 63 | help = "Timeout to read a next line from the stream in milliseconds." 64 | )] 65 | pub retrieval_timeout_millis: u64, 66 | 67 | #[arg( 68 | long = "render-interval", 69 | default_value = "10", 70 | help = "Interval to render a line in milliseconds.", 71 | long_help = "Adjust this value to prevent screen flickering 72 | when a large volume of lines is rendered in a short period." 73 | )] 74 | pub render_interval_millis: u64, 75 | 76 | #[arg( 77 | short = 'q', 78 | long = "queue-capacity", 79 | default_value = "1000", 80 | help = "Queue capacity to store lines.", 81 | long_help = "Queue capacity for storing lines. 82 | This value is used for temporary storage of lines 83 | and should be adjusted based on the system's memory capacity. 84 | Increasing this value allows for more lines to be stored temporarily, 85 | which can be beneficial when digging deeper into lines with the digger." 86 | )] 87 | pub queue_capacity: usize, 88 | 89 | #[arg( 90 | short = 'a', 91 | long = "archived", 92 | default_value = "false", 93 | help = "Archived mode to grep through static data." 94 | )] 95 | pub archived: bool, 96 | 97 | #[arg( 98 | short = 'i', 99 | long = "ignore-case", 100 | default_value = "false", 101 | help = "Case insensitive search." 102 | )] 103 | pub case_insensitive: bool, 104 | 105 | #[arg( 106 | long = "cmd", 107 | help = "Command to execute on initial and retries.", 108 | long_help = "This command is invoked initially and 109 | whenever a retry is triggered according to key mappings." 110 | )] 111 | pub cmd: Option, 112 | } 113 | 114 | impl Drop for Args { 115 | fn drop(&mut self) { 116 | disable_raw_mode().ok(); 117 | execute!(io::stdout(), cursor::Show).ok(); 118 | } 119 | } 120 | 121 | #[tokio::main] 122 | async fn main() -> anyhow::Result<()> { 123 | let args = Args::parse(); 124 | 125 | enable_raw_mode()?; 126 | execute!(io::stdout(), cursor::Hide)?; 127 | 128 | let highlight_style = StyleBuilder::new().fgc(Color::Red).build(); 129 | 130 | if args.archived { 131 | let (tx, mut rx) = mpsc::channel(1); 132 | 133 | if let Some(cmd) = args.cmd.clone() { 134 | tokio::spawn(async move { 135 | cmd::execute( 136 | &cmd, 137 | tx, 138 | Duration::from_millis(args.retrieval_timeout_millis), 139 | CancellationToken::new(), 140 | ) 141 | .await 142 | }); 143 | } else { 144 | tokio::spawn(async move { 145 | stdin::streaming( 146 | tx, 147 | Duration::from_millis(args.retrieval_timeout_millis), 148 | CancellationToken::new(), 149 | ) 150 | .await 151 | }); 152 | } 153 | 154 | let mut queue = VecDeque::with_capacity(args.queue_capacity); 155 | loop { 156 | match timeout( 157 | Duration::from_millis(args.retrieval_timeout_millis), 158 | rx.recv(), 159 | ) 160 | .await 161 | { 162 | Ok(Some(line)) => { 163 | if queue.len() > args.queue_capacity { 164 | queue.pop_front().unwrap(); 165 | } 166 | queue.push_back(line.clone()); 167 | } 168 | Ok(None) => break, 169 | Err(_) => break, 170 | } 171 | } 172 | 173 | crossterm::execute!( 174 | io::stdout(), 175 | crossterm::terminal::Clear(crossterm::terminal::ClearType::All), 176 | crossterm::terminal::Clear(crossterm::terminal::ClearType::Purge), 177 | cursor::MoveTo(0, 0), 178 | )?; 179 | 180 | archived::run( 181 | text_editor::State { 182 | texteditor: Default::default(), 183 | history: Default::default(), 184 | prefix: String::from("❯❯❯ "), 185 | mask: Default::default(), 186 | prefix_style: StyleBuilder::new().fgc(Color::DarkBlue).build(), 187 | active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(), 188 | inactive_char_style: StyleBuilder::new().build(), 189 | edit_mode: Default::default(), 190 | word_break_chars: Default::default(), 191 | lines: Default::default(), 192 | }, 193 | listbox::State { 194 | listbox: listbox::Listbox::from_displayable(queue), 195 | cursor: String::from("❯ "), 196 | active_item_style: None, 197 | inactive_item_style: None, 198 | lines: Default::default(), 199 | }, 200 | highlight_style, 201 | args.case_insensitive, 202 | // In archived mode, command for retry is meaningless. 203 | None, 204 | )?; 205 | } else { 206 | while let Ok((signal, queue)) = sig::run( 207 | text_editor::State { 208 | texteditor: Default::default(), 209 | history: Default::default(), 210 | prefix: String::from("❯❯ "), 211 | mask: Default::default(), 212 | prefix_style: StyleBuilder::new().fgc(Color::DarkGreen).build(), 213 | active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(), 214 | inactive_char_style: StyleBuilder::new().build(), 215 | edit_mode: Default::default(), 216 | word_break_chars: Default::default(), 217 | lines: Default::default(), 218 | }, 219 | highlight_style, 220 | Duration::from_millis(args.retrieval_timeout_millis), 221 | Duration::from_millis(args.render_interval_millis), 222 | args.queue_capacity, 223 | args.case_insensitive, 224 | args.cmd.clone(), 225 | ) 226 | .await 227 | { 228 | crossterm::execute!( 229 | io::stdout(), 230 | crossterm::terminal::Clear(crossterm::terminal::ClearType::All), 231 | crossterm::terminal::Clear(crossterm::terminal::ClearType::Purge), 232 | cursor::MoveTo(0, 0), 233 | )?; 234 | 235 | match signal { 236 | Signal::GotoArchived => { 237 | archived::run( 238 | text_editor::State { 239 | texteditor: Default::default(), 240 | history: Default::default(), 241 | prefix: String::from("❯❯❯ "), 242 | mask: Default::default(), 243 | prefix_style: StyleBuilder::new().fgc(Color::DarkBlue).build(), 244 | active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(), 245 | inactive_char_style: StyleBuilder::new().build(), 246 | edit_mode: Default::default(), 247 | word_break_chars: Default::default(), 248 | lines: Default::default(), 249 | }, 250 | listbox::State { 251 | listbox: listbox::Listbox::from_displayable(queue), 252 | cursor: String::from("❯ "), 253 | active_item_style: None, 254 | inactive_item_style: None, 255 | lines: Default::default(), 256 | }, 257 | highlight_style, 258 | args.case_insensitive, 259 | args.cmd.clone(), 260 | )?; 261 | 262 | // Re-enable raw mode and hide the cursor again here 263 | // because they are disabled and shown, respectively, by promkit. 264 | enable_raw_mode()?; 265 | execute!(io::stdout(), cursor::Hide)?; 266 | 267 | crossterm::execute!( 268 | io::stdout(), 269 | crossterm::terminal::Clear(crossterm::terminal::ClearType::All), 270 | crossterm::terminal::Clear(crossterm::terminal::ClearType::Purge), 271 | cursor::MoveTo(0, 0), 272 | )?; 273 | } 274 | Signal::GotoStreaming => { 275 | continue; 276 | } 277 | _ => {} 278 | } 279 | } 280 | } 281 | 282 | Ok(()) 283 | } 284 | -------------------------------------------------------------------------------- /src/sig.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, sync::Arc}; 2 | 3 | use grep::{ 4 | matcher::{Match, Matcher}, 5 | regex::RegexMatcherBuilder, 6 | }; 7 | use tokio::{ 8 | sync::{mpsc, RwLock}, 9 | task::JoinHandle, 10 | time::{self, Duration}, 11 | }; 12 | use tokio_util::sync::CancellationToken; 13 | 14 | use promkit::{ 15 | crossterm::{self, event, style::ContentStyle}, 16 | grapheme::StyledGraphemes, 17 | switch::ActiveKeySwitcher, 18 | text_editor, PaneFactory, 19 | }; 20 | 21 | mod keymap; 22 | use crate::{cmd, stdin, terminal::Terminal, Signal}; 23 | 24 | fn matched(queries: &[&str], line: &str, case_insensitive: bool) -> anyhow::Result> { 25 | let mut matched = Vec::new(); 26 | RegexMatcherBuilder::new() 27 | .case_insensitive(case_insensitive) 28 | .build_many(queries)? 29 | .find_iter_at(line.as_bytes(), 0, |m| { 30 | if m.start() >= line.as_bytes().len() { 31 | return false; 32 | } 33 | matched.push(m); 34 | true 35 | })?; 36 | Ok(matched) 37 | } 38 | 39 | pub fn styled( 40 | query: &str, 41 | line: &str, 42 | highlight_style: ContentStyle, 43 | case_insensitive: bool, 44 | ) -> Option { 45 | let piped = &query 46 | .split('|') 47 | .map(|s| s.trim()) 48 | .filter(|s| !s.is_empty()) 49 | .collect::>(); 50 | 51 | let mut styled = StyledGraphemes::from(line); 52 | 53 | if query.is_empty() { 54 | Some(styled) 55 | } else { 56 | match matched(piped, line, case_insensitive) { 57 | Ok(matches) => { 58 | if matches.is_empty() { 59 | None 60 | } else { 61 | for m in matches { 62 | for i in m.start()..m.end() { 63 | styled = styled.apply_style_at(i, highlight_style); 64 | } 65 | } 66 | Some(styled) 67 | } 68 | } 69 | _ => None, 70 | } 71 | } 72 | } 73 | 74 | pub async fn run( 75 | text_editor: text_editor::State, 76 | highlight_style: ContentStyle, 77 | retrieval_timeout: Duration, 78 | render_interval: Duration, 79 | queue_capacity: usize, 80 | case_insensitive: bool, 81 | cmd: Option, 82 | ) -> anyhow::Result<(Signal, VecDeque)> { 83 | let keymap = ActiveKeySwitcher::new("default", keymap::default); 84 | let size = crossterm::terminal::size()?; 85 | 86 | let pane = text_editor.create_pane(size.0, size.1); 87 | let mut term = Terminal::new(&pane)?; 88 | term.draw_pane(&pane)?; 89 | 90 | let shared_term = Arc::new(RwLock::new(term)); 91 | let shared_text_editor = Arc::new(RwLock::new(text_editor)); 92 | let readonly_term = Arc::clone(&shared_term); 93 | let readonly_text_editor = Arc::clone(&shared_text_editor); 94 | 95 | let (tx, mut rx) = mpsc::channel(1); 96 | let canceler = CancellationToken::new(); 97 | 98 | let canceled = canceler.clone(); 99 | let streaming = if let Some(cmd) = cmd.clone() { 100 | tokio::spawn(async move { cmd::execute(&cmd, tx, retrieval_timeout, canceled).await }) 101 | } else { 102 | tokio::spawn(async move { stdin::streaming(tx, retrieval_timeout, canceled).await }) 103 | }; 104 | 105 | let keeping: JoinHandle>> = tokio::spawn(async move { 106 | let mut queue = VecDeque::with_capacity(queue_capacity); 107 | let interval = time::interval(render_interval); 108 | futures::pin_mut!(interval); 109 | 110 | loop { 111 | interval.tick().await; 112 | match rx.recv().await { 113 | Some(line) => { 114 | let text_editor = readonly_text_editor.read().await; 115 | let size = crossterm::terminal::size()?; 116 | 117 | if queue.len() > queue_capacity { 118 | queue.pop_front().unwrap(); 119 | } 120 | queue.push_back(line.clone()); 121 | 122 | if let Some(styled) = styled( 123 | &text_editor.texteditor.text_without_cursor().to_string(), 124 | &line, 125 | highlight_style, 126 | case_insensitive, 127 | ) { 128 | let matrix = styled.matrixify(size.0 as usize, size.1 as usize, 0).0; 129 | let term = readonly_term.read().await; 130 | term.draw_stream_and_pane( 131 | matrix, 132 | &text_editor.create_pane(size.0, size.1), 133 | )?; 134 | } 135 | } 136 | None => break, 137 | } 138 | } 139 | Ok(queue) 140 | }); 141 | 142 | let mut signal: Signal; 143 | loop { 144 | let event = event::read()?; 145 | let mut text_editor = shared_text_editor.write().await; 146 | signal = keymap.get()(&event, &mut text_editor, cmd.clone())?; 147 | if signal == Signal::GotoArchived || signal == Signal::GotoStreaming { 148 | break; 149 | } 150 | 151 | let size = crossterm::terminal::size()?; 152 | let pane = text_editor.create_pane(size.0, size.1); 153 | let mut term = shared_term.write().await; 154 | term.draw_pane(&pane)?; 155 | } 156 | 157 | canceler.cancel(); 158 | let _: anyhow::Result<(), anyhow::Error> = streaming.await?; 159 | 160 | Ok((signal, keeping.await??)) 161 | } 162 | -------------------------------------------------------------------------------- /src/sig/keymap.rs: -------------------------------------------------------------------------------- 1 | use promkit::{ 2 | crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, 3 | text_editor, 4 | }; 5 | 6 | use crate::Signal; 7 | 8 | pub fn default( 9 | event: &Event, 10 | state: &mut text_editor::State, 11 | cmd: Option, 12 | ) -> anyhow::Result { 13 | match event { 14 | Event::Key(KeyEvent { 15 | code: KeyCode::Char('f'), 16 | modifiers: KeyModifiers::CONTROL, 17 | kind: KeyEventKind::Press, 18 | state: KeyEventState::NONE, 19 | }) => return Ok(Signal::GotoArchived), 20 | 21 | Event::Key(KeyEvent { 22 | code: KeyCode::Char('r'), 23 | modifiers: KeyModifiers::CONTROL, 24 | kind: KeyEventKind::Press, 25 | state: KeyEventState::NONE, 26 | }) => { 27 | if cmd.is_some() { 28 | return Ok(Signal::GotoStreaming); 29 | } 30 | } 31 | 32 | Event::Key(KeyEvent { 33 | code: KeyCode::Char('c'), 34 | modifiers: KeyModifiers::CONTROL, 35 | kind: KeyEventKind::Press, 36 | state: KeyEventState::NONE, 37 | }) => return Err(anyhow::anyhow!("ctrl+c")), 38 | 39 | // Move cursor. 40 | Event::Key(KeyEvent { 41 | code: KeyCode::Left, 42 | modifiers: KeyModifiers::NONE, 43 | kind: KeyEventKind::Press, 44 | state: KeyEventState::NONE, 45 | }) => { 46 | state.texteditor.backward(); 47 | } 48 | Event::Key(KeyEvent { 49 | code: KeyCode::Right, 50 | modifiers: KeyModifiers::NONE, 51 | kind: KeyEventKind::Press, 52 | state: KeyEventState::NONE, 53 | }) => { 54 | state.texteditor.forward(); 55 | } 56 | Event::Key(KeyEvent { 57 | code: KeyCode::Char('a'), 58 | modifiers: KeyModifiers::CONTROL, 59 | kind: KeyEventKind::Press, 60 | state: KeyEventState::NONE, 61 | }) => state.texteditor.move_to_head(), 62 | Event::Key(KeyEvent { 63 | code: KeyCode::Char('e'), 64 | modifiers: KeyModifiers::CONTROL, 65 | kind: KeyEventKind::Press, 66 | state: KeyEventState::NONE, 67 | }) => state.texteditor.move_to_tail(), 68 | 69 | // Erase char(s). 70 | Event::Key(KeyEvent { 71 | code: KeyCode::Backspace, 72 | modifiers: KeyModifiers::NONE, 73 | kind: KeyEventKind::Press, 74 | state: KeyEventState::NONE, 75 | }) => state.texteditor.erase(), 76 | Event::Key(KeyEvent { 77 | code: KeyCode::Char('u'), 78 | modifiers: KeyModifiers::CONTROL, 79 | kind: KeyEventKind::Press, 80 | state: KeyEventState::NONE, 81 | }) => state.texteditor.erase_all(), 82 | 83 | // Input char. 84 | Event::Key(KeyEvent { 85 | code: KeyCode::Char(ch), 86 | modifiers: KeyModifiers::NONE, 87 | kind: KeyEventKind::Press, 88 | state: KeyEventState::NONE, 89 | }) 90 | | Event::Key(KeyEvent { 91 | code: KeyCode::Char(ch), 92 | modifiers: KeyModifiers::SHIFT, 93 | kind: KeyEventKind::Press, 94 | state: KeyEventState::NONE, 95 | }) => match state.edit_mode { 96 | text_editor::Mode::Insert => state.texteditor.insert(*ch), 97 | text_editor::Mode::Overwrite => state.texteditor.overwrite(*ch), 98 | }, 99 | 100 | _ => (), 101 | } 102 | Ok(Signal::Continue) 103 | } 104 | -------------------------------------------------------------------------------- /src/stdin.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | io::{self, AsyncBufReadExt, BufReader}, 3 | sync::mpsc, 4 | time::{timeout, Duration}, 5 | }; 6 | 7 | use tokio_util::sync::CancellationToken; 8 | 9 | pub async fn streaming( 10 | tx: mpsc::Sender, 11 | retrieval_timeout: Duration, 12 | canceled: CancellationToken, 13 | ) -> Result<(), anyhow::Error> { 14 | let mut reader = BufReader::new(io::stdin()).lines(); 15 | 16 | while !canceled.is_cancelled() { 17 | // Set a timeout to ensure non-blocking behavior, 18 | // especially responsive to user inputs like ctrl+c. 19 | // Continuously retry until cancellation to prevent loss of logs. 20 | let ret = timeout(retrieval_timeout, reader.next_line()).await; 21 | if ret.is_err() { 22 | continue; 23 | } 24 | 25 | let ret = ret?; 26 | 27 | match ret { 28 | Ok(Some(line)) => { 29 | let escaped = strip_ansi_escapes::strip_str(line.replace(['\n', '\t'], " ")); 30 | tx.send(escaped).await?; 31 | } 32 | _ => break, 33 | } 34 | } 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use promkit::{ 4 | crossterm::{self, cursor, style, terminal}, 5 | grapheme::StyledGraphemes, 6 | pane::Pane, 7 | }; 8 | 9 | pub struct Terminal { 10 | anchor_position: (u16, u16), 11 | } 12 | 13 | impl Terminal { 14 | pub fn new(pane: &Pane) -> anyhow::Result { 15 | let mut offset_from_bottom = terminal::size()?; 16 | offset_from_bottom.1 = offset_from_bottom 17 | .1 18 | .saturating_sub(1 + pane.visible_row_count() as u16); 19 | 20 | Ok(Self { 21 | anchor_position: (0, offset_from_bottom.1), 22 | }) 23 | } 24 | 25 | pub fn draw_stream_and_pane( 26 | &self, 27 | items: Vec, 28 | pane: &Pane, 29 | ) -> anyhow::Result<()> { 30 | let coefficient = items.len().saturating_sub(1) as u16; 31 | crossterm::queue!( 32 | io::stdout(), 33 | cursor::MoveTo( 34 | self.anchor_position.0, 35 | self.anchor_position.1.saturating_sub(coefficient) 36 | ), 37 | terminal::ScrollUp(1 + coefficient), 38 | terminal::Clear(terminal::ClearType::FromCursorDown), 39 | )?; 40 | 41 | for item in items.iter() { 42 | crossterm::queue!( 43 | io::stdout(), 44 | style::Print(item.styled_display()), 45 | cursor::MoveToNextLine(1) 46 | )?; 47 | } 48 | 49 | io::stdout().flush()?; 50 | self.draw(pane) 51 | } 52 | 53 | pub fn draw_pane(&mut self, pane: &Pane) -> anyhow::Result<()> { 54 | let size = terminal::size()?; 55 | crossterm::queue!( 56 | io::stdout(), 57 | cursor::MoveTo(self.anchor_position.0, self.anchor_position.1 + 1), 58 | terminal::Clear(terminal::ClearType::FromCursorDown), 59 | )?; 60 | self.anchor_position.1 = size.1.saturating_sub(1 + pane.visible_row_count() as u16); 61 | self.draw(pane) 62 | } 63 | 64 | fn draw(&self, pane: &Pane) -> anyhow::Result<()> { 65 | crossterm::queue!( 66 | io::stdout(), 67 | cursor::MoveTo(self.anchor_position.0, self.anchor_position.1 + 1), 68 | terminal::Clear(terminal::ClearType::FromCursorDown), 69 | )?; 70 | 71 | for row in pane.extract(pane.visible_row_count()) { 72 | crossterm::queue!(io::stdout(), style::Print(row.styled_display()))?; 73 | } 74 | 75 | io::stdout().flush()?; 76 | Ok(()) 77 | } 78 | } 79 | --------------------------------------------------------------------------------