├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── clippy.yml │ ├── release.yml │ └── upload_asset.sh ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── INSTALL.md ├── LICENSE ├── README.md ├── commit.json ├── icon.png ├── pre-commit.sh └── src ├── commit.rs ├── commit_message ├── message_build │ ├── mod.rs │ └── tests.rs ├── mod.rs └── prompt.rs ├── commit_pattern ├── config.rs ├── messages.rs └── mod.rs ├── config ├── mod.rs └── tests.rs └── main.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: alt-art 2 | github: alt-art 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions and SO's** 27 | - OS: [e.g. iOS] 28 | - Git version: [e.g 3.4.5] 29 | - Version [e.g. 2.2.1] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | windows: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: Swatinem/rust-cache@v2 14 | - name: Run tests 15 | run: cargo test --verbose 16 | linux: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: Swatinem/rust-cache@v2 21 | - name: Run cargo fmt 22 | run: cargo fmt --all -- --check 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: pull_request 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | clippy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - run: rustup component add clippy 14 | - uses: Swatinem/rust-cache@v2 15 | - uses: actions-rs/clippy-check@v1 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | args: --all-features -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["[0-9]+.[0-9]+.[0-9]+*"] 6 | workflow_dispatch: 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | linux: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: Swatinem/rust-cache@v1 19 | - name: Install cargo-deb 20 | run: cargo install cargo-deb 21 | - name: Install cargo-generate-rpm 22 | run: cargo install cargo-generate-rpm 23 | - name: Test 24 | run: cargo test --release 25 | - name: Build .deb 26 | run: cargo deb -v -o ./commit_${GITHUB_REF##*/}_amd64.deb 27 | - name: Upload .deb package 28 | run: | 29 | chmod +x ./.github/workflows/upload_asset.sh 30 | ./.github/workflows/upload_asset.sh \ 31 | "alt-art/commit" ./commit_${GITHUB_REF##*/}_amd64.deb $GITHUB_TOKEN 32 | - name: Build .rpm 33 | run: cargo generate-rpm -o ./commit_${GITHUB_REF##*/}_x86_64.rpm 34 | - name: Upload .rpm package 35 | run: | 36 | chmod +x ./.github/workflows/upload_asset.sh 37 | ./.github/workflows/upload_asset.sh \ 38 | "alt-art/commit" ./commit_${GITHUB_REF##*/}_x86_64.rpm $GITHUB_TOKEN 39 | windows: 40 | runs-on: windows-latest 41 | 42 | defaults: 43 | run: 44 | shell: bash 45 | 46 | steps: 47 | - uses: actions/checkout@v2 48 | - name: Test 49 | run: cargo test --release 50 | - name: Build 51 | run: cargo build --release 52 | - name: Upload portable executable 53 | run: | 54 | cp ./target/release/commit.exe ./commit-${GITHUB_REF##*/}-portable.exe 55 | ./.github/workflows/upload_asset.sh \ 56 | "alt-art/commit" ./commit-${GITHUB_REF##*/}-portable.exe $GITHUB_TOKEN 57 | -------------------------------------------------------------------------------- /.github/workflows/upload_asset.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Author: Christian Duerr 4 | # License: Apache-2.0 5 | # https://github.com/alacritty/alacritty/blob/master/.github/workflows/upload_asset.sh 6 | # 7 | # 8 | # This script accepts the following parameters: 9 | # 10 | # * "owner/repo" 11 | # * file_path 12 | # * token 13 | # 14 | # Script to upload a release asset 15 | # 16 | # Example: 17 | # 18 | # upload_asset.sh "alt-art/lyrs" ./build.zip $GITHUB_TOKEN 19 | 20 | repo=$1 21 | file_path=$2 22 | file_name=${file_path##*/} 23 | token=$3 24 | 25 | echo "Starting asset upload from $file_name to $repo." 26 | # Define variables. 27 | TAG="$(git describe --tags --abbrev=0)" 28 | if [ -z "$TAG" ]; then 29 | printf "\e[31mError: Unable to find git tag\e[0m\n" 30 | exit 1 31 | fi 32 | echo "Git tag: $TAG" 33 | GH_API="https://api.github.com" 34 | GH_REPO="$GH_API/repos/$repo" 35 | AUTH="Authorization: Bearer $token" 36 | 37 | # Validate token. 38 | curl -o /dev/null -sH "$AUTH" "$GH_REPO" || { echo "Error: Invalid repo, token or network issue!"; exit 1; } 39 | 40 | echo "Checking for existing release..." 41 | upload_url=$(\ 42 | curl \ 43 | -H "$AUTH" \ 44 | "$GH_REPO/releases" \ 45 | 2> /dev/null \ 46 | | grep -E "(upload_url|tag_name)" \ 47 | | paste - - \ 48 | | grep -e "tag_name\": \"$TAG\"" \ 49 | | head -n 1 \ 50 | | sed 's/.*\(https.*assets\).*/\1/' \ 51 | ) 52 | 53 | # Create a new release if we didn't find one for this tag. 54 | if [ -z "$upload_url" ]; then 55 | echo "No release found." 56 | echo "Creating new release..." 57 | 58 | # Create new release. 59 | response=$( 60 | curl -f \ 61 | -X POST \ 62 | -H "$AUTH" \ 63 | -d "{\"tag_name\":\"$TAG\",\"draft\":true}" \ 64 | "$GH_REPO/releases" \ 65 | 2> /dev/null\ 66 | ) 67 | 68 | # Abort if the release could not be created. 69 | if [ $? -ne 0 ]; then 70 | printf "\e[31mError: Unable to create new release.\e[0m\n" 71 | exit 1; 72 | fi 73 | 74 | # Extract upload URL from new release. 75 | upload_url=$(\ 76 | echo "$response" \ 77 | | grep "upload_url" \ 78 | | sed 's/.*: "\(.*\){.*/\1/' \ 79 | ) 80 | fi 81 | 82 | if [ -z "$upload_url" ]; then 83 | printf "\e[31mError: Unable to find release upload url.\e[0m\n" 84 | exit 2 85 | fi 86 | 87 | # Upload asset 88 | 89 | echo "Uploading asset $file_name to $TAG..." 90 | curl -f \ 91 | -X POST \ 92 | -H "$AUTH" \ 93 | -H "Content-Type: application/octet-stream" \ 94 | --data-binary @"$file_path" \ 95 | "$upload_url?name=$file_name" \ 96 | &> /dev/null \ 97 | || { \ 98 | printf "\e[31mError: Unable to upload asset.\e[0m\n" \ 99 | && exit 3; \ 100 | } 101 | 102 | printf "\e[32mSuccess\e[0m\n" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 | e-mail. 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 commit 2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | 9 | ## Write bug reports with detail 10 | 11 | **Great Bug Reports** tend to have: 12 | 13 | - A quick summary and/or background 14 | - Steps to reproduce 15 | - Be specific! 16 | - What you expected would happen 17 | - What actually happens 18 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 19 | 20 | ## Contributing with code 21 | 22 | 1. Fork the repo and create your branch from `main`. 23 | 2. If you've added code that should be tested, add tests. 24 | 3. If you've changed user interface or configuration file code, update the documentation 25 | 4. Ensure the test suite passes, run `cargo test` to run the tests 26 | 5. Make sure your code lints, run `cargo fmt` to format the code and follow the lint rules with `cargo clippy`. 27 | 6. Issue that pull request! 28 | 29 | ## License 30 | By contributing, you agree that your contributions will be licensed under its MIT License. 31 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.8" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 49 | dependencies = [ 50 | "windows-sys 0.48.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.59.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.95" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 68 | 69 | [[package]] 70 | name = "assert_fs" 71 | version = "1.1.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" 74 | dependencies = [ 75 | "anstyle", 76 | "doc-comment", 77 | "globwalk", 78 | "predicates", 79 | "predicates-core", 80 | "predicates-tree", 81 | "tempfile", 82 | ] 83 | 84 | [[package]] 85 | name = "autocfg" 86 | version = "1.1.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "1.3.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 95 | 96 | [[package]] 97 | name = "bitflags" 98 | version = "2.4.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 101 | 102 | [[package]] 103 | name = "bstr" 104 | version = "1.7.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" 107 | dependencies = [ 108 | "memchr", 109 | "serde", 110 | ] 111 | 112 | [[package]] 113 | name = "byteorder" 114 | version = "1.5.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 117 | 118 | [[package]] 119 | name = "cfg-if" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 123 | 124 | [[package]] 125 | name = "clap" 126 | version = "4.5.23" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 129 | dependencies = [ 130 | "clap_builder", 131 | "clap_derive", 132 | ] 133 | 134 | [[package]] 135 | name = "clap_builder" 136 | version = "4.5.23" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 139 | dependencies = [ 140 | "anstream", 141 | "anstyle", 142 | "clap_lex", 143 | "strsim", 144 | ] 145 | 146 | [[package]] 147 | name = "clap_derive" 148 | version = "4.5.18" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 151 | dependencies = [ 152 | "heck", 153 | "proc-macro2", 154 | "quote", 155 | "syn", 156 | ] 157 | 158 | [[package]] 159 | name = "clap_lex" 160 | version = "0.7.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 163 | 164 | [[package]] 165 | name = "colorchoice" 166 | version = "1.0.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 169 | 170 | [[package]] 171 | name = "commit" 172 | version = "0.7.0" 173 | dependencies = [ 174 | "anyhow", 175 | "assert_fs", 176 | "clap", 177 | "dirs", 178 | "inquire", 179 | "serde", 180 | "serde_json", 181 | "serial_test", 182 | ] 183 | 184 | [[package]] 185 | name = "crossterm" 186 | version = "0.25.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 189 | dependencies = [ 190 | "bitflags 1.3.2", 191 | "crossterm_winapi", 192 | "libc", 193 | "mio", 194 | "parking_lot", 195 | "signal-hook", 196 | "signal-hook-mio", 197 | "winapi", 198 | ] 199 | 200 | [[package]] 201 | name = "crossterm_winapi" 202 | version = "0.9.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 205 | dependencies = [ 206 | "winapi", 207 | ] 208 | 209 | [[package]] 210 | name = "difflib" 211 | version = "0.4.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 214 | 215 | [[package]] 216 | name = "dirs" 217 | version = "5.0.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 220 | dependencies = [ 221 | "dirs-sys", 222 | ] 223 | 224 | [[package]] 225 | name = "dirs-sys" 226 | version = "0.4.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 229 | dependencies = [ 230 | "libc", 231 | "option-ext", 232 | "redox_users", 233 | "windows-sys 0.48.0", 234 | ] 235 | 236 | [[package]] 237 | name = "doc-comment" 238 | version = "0.3.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 241 | 242 | [[package]] 243 | name = "dyn-clone" 244 | version = "1.0.14" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" 247 | 248 | [[package]] 249 | name = "either" 250 | version = "1.9.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 253 | 254 | [[package]] 255 | name = "errno" 256 | version = "0.3.5" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" 259 | dependencies = [ 260 | "libc", 261 | "windows-sys 0.48.0", 262 | ] 263 | 264 | [[package]] 265 | name = "fastrand" 266 | version = "2.0.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 269 | 270 | [[package]] 271 | name = "fnv" 272 | version = "1.0.7" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 275 | 276 | [[package]] 277 | name = "futures" 278 | version = "0.3.28" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 281 | dependencies = [ 282 | "futures-channel", 283 | "futures-core", 284 | "futures-executor", 285 | "futures-io", 286 | "futures-sink", 287 | "futures-task", 288 | "futures-util", 289 | ] 290 | 291 | [[package]] 292 | name = "futures-channel" 293 | version = "0.3.28" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 296 | dependencies = [ 297 | "futures-core", 298 | "futures-sink", 299 | ] 300 | 301 | [[package]] 302 | name = "futures-core" 303 | version = "0.3.28" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 306 | 307 | [[package]] 308 | name = "futures-executor" 309 | version = "0.3.28" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 312 | dependencies = [ 313 | "futures-core", 314 | "futures-task", 315 | "futures-util", 316 | ] 317 | 318 | [[package]] 319 | name = "futures-io" 320 | version = "0.3.28" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 323 | 324 | [[package]] 325 | name = "futures-sink" 326 | version = "0.3.28" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 329 | 330 | [[package]] 331 | name = "futures-task" 332 | version = "0.3.28" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 335 | 336 | [[package]] 337 | name = "futures-util" 338 | version = "0.3.28" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 341 | dependencies = [ 342 | "futures-channel", 343 | "futures-core", 344 | "futures-io", 345 | "futures-sink", 346 | "futures-task", 347 | "memchr", 348 | "pin-project-lite", 349 | "pin-utils", 350 | "slab", 351 | ] 352 | 353 | [[package]] 354 | name = "fuzzy-matcher" 355 | version = "0.3.7" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 358 | dependencies = [ 359 | "thread_local", 360 | ] 361 | 362 | [[package]] 363 | name = "fxhash" 364 | version = "0.2.1" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 367 | dependencies = [ 368 | "byteorder", 369 | ] 370 | 371 | [[package]] 372 | name = "getrandom" 373 | version = "0.2.10" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 376 | dependencies = [ 377 | "cfg-if", 378 | "libc", 379 | "wasi", 380 | ] 381 | 382 | [[package]] 383 | name = "globset" 384 | version = "0.4.13" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" 387 | dependencies = [ 388 | "aho-corasick", 389 | "bstr", 390 | "fnv", 391 | "log", 392 | "regex", 393 | ] 394 | 395 | [[package]] 396 | name = "globwalk" 397 | version = "0.9.1" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 400 | dependencies = [ 401 | "bitflags 2.4.1", 402 | "ignore", 403 | "walkdir", 404 | ] 405 | 406 | [[package]] 407 | name = "heck" 408 | version = "0.5.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 411 | 412 | [[package]] 413 | name = "ignore" 414 | version = "0.4.20" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" 417 | dependencies = [ 418 | "globset", 419 | "lazy_static", 420 | "log", 421 | "memchr", 422 | "regex", 423 | "same-file", 424 | "thread_local", 425 | "walkdir", 426 | "winapi-util", 427 | ] 428 | 429 | [[package]] 430 | name = "inquire" 431 | version = "0.7.5" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 434 | dependencies = [ 435 | "bitflags 2.4.1", 436 | "crossterm", 437 | "dyn-clone", 438 | "fuzzy-matcher", 439 | "fxhash", 440 | "newline-converter", 441 | "once_cell", 442 | "unicode-segmentation", 443 | "unicode-width", 444 | ] 445 | 446 | [[package]] 447 | name = "is_terminal_polyfill" 448 | version = "1.70.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 451 | 452 | [[package]] 453 | name = "itertools" 454 | version = "0.11.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 457 | dependencies = [ 458 | "either", 459 | ] 460 | 461 | [[package]] 462 | name = "itoa" 463 | version = "1.0.9" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 466 | 467 | [[package]] 468 | name = "lazy_static" 469 | version = "1.4.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 472 | 473 | [[package]] 474 | name = "libc" 475 | version = "0.2.149" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 478 | 479 | [[package]] 480 | name = "linux-raw-sys" 481 | version = "0.4.10" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" 484 | 485 | [[package]] 486 | name = "lock_api" 487 | version = "0.4.11" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 490 | dependencies = [ 491 | "autocfg", 492 | "scopeguard", 493 | ] 494 | 495 | [[package]] 496 | name = "log" 497 | version = "0.4.20" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 500 | 501 | [[package]] 502 | name = "memchr" 503 | version = "2.6.4" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 506 | 507 | [[package]] 508 | name = "mio" 509 | version = "0.8.11" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 512 | dependencies = [ 513 | "libc", 514 | "log", 515 | "wasi", 516 | "windows-sys 0.48.0", 517 | ] 518 | 519 | [[package]] 520 | name = "newline-converter" 521 | version = "0.3.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 524 | dependencies = [ 525 | "unicode-segmentation", 526 | ] 527 | 528 | [[package]] 529 | name = "once_cell" 530 | version = "1.19.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 533 | 534 | [[package]] 535 | name = "option-ext" 536 | version = "0.2.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 539 | 540 | [[package]] 541 | name = "parking_lot" 542 | version = "0.12.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 545 | dependencies = [ 546 | "lock_api", 547 | "parking_lot_core", 548 | ] 549 | 550 | [[package]] 551 | name = "parking_lot_core" 552 | version = "0.9.9" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 555 | dependencies = [ 556 | "cfg-if", 557 | "libc", 558 | "redox_syscall 0.4.1", 559 | "smallvec", 560 | "windows-targets 0.48.5", 561 | ] 562 | 563 | [[package]] 564 | name = "pin-project-lite" 565 | version = "0.2.13" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 568 | 569 | [[package]] 570 | name = "pin-utils" 571 | version = "0.1.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 574 | 575 | [[package]] 576 | name = "predicates" 577 | version = "3.0.4" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" 580 | dependencies = [ 581 | "anstyle", 582 | "difflib", 583 | "itertools", 584 | "predicates-core", 585 | ] 586 | 587 | [[package]] 588 | name = "predicates-core" 589 | version = "1.0.6" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" 592 | 593 | [[package]] 594 | name = "predicates-tree" 595 | version = "1.0.9" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" 598 | dependencies = [ 599 | "predicates-core", 600 | "termtree", 601 | ] 602 | 603 | [[package]] 604 | name = "proc-macro2" 605 | version = "1.0.89" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 608 | dependencies = [ 609 | "unicode-ident", 610 | ] 611 | 612 | [[package]] 613 | name = "quote" 614 | version = "1.0.35" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 617 | dependencies = [ 618 | "proc-macro2", 619 | ] 620 | 621 | [[package]] 622 | name = "redox_syscall" 623 | version = "0.2.16" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 626 | dependencies = [ 627 | "bitflags 1.3.2", 628 | ] 629 | 630 | [[package]] 631 | name = "redox_syscall" 632 | version = "0.3.5" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 635 | dependencies = [ 636 | "bitflags 1.3.2", 637 | ] 638 | 639 | [[package]] 640 | name = "redox_syscall" 641 | version = "0.4.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 644 | dependencies = [ 645 | "bitflags 1.3.2", 646 | ] 647 | 648 | [[package]] 649 | name = "redox_users" 650 | version = "0.4.3" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 653 | dependencies = [ 654 | "getrandom", 655 | "redox_syscall 0.2.16", 656 | "thiserror", 657 | ] 658 | 659 | [[package]] 660 | name = "regex" 661 | version = "1.10.2" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 664 | dependencies = [ 665 | "aho-corasick", 666 | "memchr", 667 | "regex-automata", 668 | "regex-syntax", 669 | ] 670 | 671 | [[package]] 672 | name = "regex-automata" 673 | version = "0.4.3" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 676 | dependencies = [ 677 | "aho-corasick", 678 | "memchr", 679 | "regex-syntax", 680 | ] 681 | 682 | [[package]] 683 | name = "regex-syntax" 684 | version = "0.8.2" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 687 | 688 | [[package]] 689 | name = "rustix" 690 | version = "0.38.20" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" 693 | dependencies = [ 694 | "bitflags 2.4.1", 695 | "errno", 696 | "libc", 697 | "linux-raw-sys", 698 | "windows-sys 0.48.0", 699 | ] 700 | 701 | [[package]] 702 | name = "ryu" 703 | version = "1.0.15" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 706 | 707 | [[package]] 708 | name = "same-file" 709 | version = "1.0.6" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 712 | dependencies = [ 713 | "winapi-util", 714 | ] 715 | 716 | [[package]] 717 | name = "scc" 718 | version = "2.1.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" 721 | dependencies = [ 722 | "sdd", 723 | ] 724 | 725 | [[package]] 726 | name = "scopeguard" 727 | version = "1.2.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 730 | 731 | [[package]] 732 | name = "sdd" 733 | version = "0.2.0" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" 736 | 737 | [[package]] 738 | name = "serde" 739 | version = "1.0.217" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 742 | dependencies = [ 743 | "serde_derive", 744 | ] 745 | 746 | [[package]] 747 | name = "serde_derive" 748 | version = "1.0.217" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 751 | dependencies = [ 752 | "proc-macro2", 753 | "quote", 754 | "syn", 755 | ] 756 | 757 | [[package]] 758 | name = "serde_json" 759 | version = "1.0.134" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" 762 | dependencies = [ 763 | "itoa", 764 | "memchr", 765 | "ryu", 766 | "serde", 767 | ] 768 | 769 | [[package]] 770 | name = "serial_test" 771 | version = "3.2.0" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" 774 | dependencies = [ 775 | "futures", 776 | "log", 777 | "once_cell", 778 | "parking_lot", 779 | "scc", 780 | "serial_test_derive", 781 | ] 782 | 783 | [[package]] 784 | name = "serial_test_derive" 785 | version = "3.2.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" 788 | dependencies = [ 789 | "proc-macro2", 790 | "quote", 791 | "syn", 792 | ] 793 | 794 | [[package]] 795 | name = "signal-hook" 796 | version = "0.3.17" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 799 | dependencies = [ 800 | "libc", 801 | "signal-hook-registry", 802 | ] 803 | 804 | [[package]] 805 | name = "signal-hook-mio" 806 | version = "0.2.3" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 809 | dependencies = [ 810 | "libc", 811 | "mio", 812 | "signal-hook", 813 | ] 814 | 815 | [[package]] 816 | name = "signal-hook-registry" 817 | version = "1.4.1" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 820 | dependencies = [ 821 | "libc", 822 | ] 823 | 824 | [[package]] 825 | name = "slab" 826 | version = "0.4.9" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 829 | dependencies = [ 830 | "autocfg", 831 | ] 832 | 833 | [[package]] 834 | name = "smallvec" 835 | version = "1.11.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 838 | 839 | [[package]] 840 | name = "strsim" 841 | version = "0.11.0" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 844 | 845 | [[package]] 846 | name = "syn" 847 | version = "2.0.87" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 850 | dependencies = [ 851 | "proc-macro2", 852 | "quote", 853 | "unicode-ident", 854 | ] 855 | 856 | [[package]] 857 | name = "tempfile" 858 | version = "3.8.0" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 861 | dependencies = [ 862 | "cfg-if", 863 | "fastrand", 864 | "redox_syscall 0.3.5", 865 | "rustix", 866 | "windows-sys 0.48.0", 867 | ] 868 | 869 | [[package]] 870 | name = "termtree" 871 | version = "0.4.1" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 874 | 875 | [[package]] 876 | name = "thiserror" 877 | version = "1.0.50" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 880 | dependencies = [ 881 | "thiserror-impl", 882 | ] 883 | 884 | [[package]] 885 | name = "thiserror-impl" 886 | version = "1.0.50" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 889 | dependencies = [ 890 | "proc-macro2", 891 | "quote", 892 | "syn", 893 | ] 894 | 895 | [[package]] 896 | name = "thread_local" 897 | version = "1.1.7" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 900 | dependencies = [ 901 | "cfg-if", 902 | "once_cell", 903 | ] 904 | 905 | [[package]] 906 | name = "unicode-ident" 907 | version = "1.0.12" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 910 | 911 | [[package]] 912 | name = "unicode-segmentation" 913 | version = "1.10.1" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 916 | 917 | [[package]] 918 | name = "unicode-width" 919 | version = "0.1.11" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 922 | 923 | [[package]] 924 | name = "utf8parse" 925 | version = "0.2.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 928 | 929 | [[package]] 930 | name = "walkdir" 931 | version = "2.4.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" 934 | dependencies = [ 935 | "same-file", 936 | "winapi-util", 937 | ] 938 | 939 | [[package]] 940 | name = "wasi" 941 | version = "0.11.0+wasi-snapshot-preview1" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 944 | 945 | [[package]] 946 | name = "winapi" 947 | version = "0.3.9" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 950 | dependencies = [ 951 | "winapi-i686-pc-windows-gnu", 952 | "winapi-x86_64-pc-windows-gnu", 953 | ] 954 | 955 | [[package]] 956 | name = "winapi-i686-pc-windows-gnu" 957 | version = "0.4.0" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 960 | 961 | [[package]] 962 | name = "winapi-util" 963 | version = "0.1.6" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 966 | dependencies = [ 967 | "winapi", 968 | ] 969 | 970 | [[package]] 971 | name = "winapi-x86_64-pc-windows-gnu" 972 | version = "0.4.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 975 | 976 | [[package]] 977 | name = "windows-sys" 978 | version = "0.48.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 981 | dependencies = [ 982 | "windows-targets 0.48.5", 983 | ] 984 | 985 | [[package]] 986 | name = "windows-sys" 987 | version = "0.59.0" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 990 | dependencies = [ 991 | "windows-targets 0.52.6", 992 | ] 993 | 994 | [[package]] 995 | name = "windows-targets" 996 | version = "0.48.5" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 999 | dependencies = [ 1000 | "windows_aarch64_gnullvm 0.48.5", 1001 | "windows_aarch64_msvc 0.48.5", 1002 | "windows_i686_gnu 0.48.5", 1003 | "windows_i686_msvc 0.48.5", 1004 | "windows_x86_64_gnu 0.48.5", 1005 | "windows_x86_64_gnullvm 0.48.5", 1006 | "windows_x86_64_msvc 0.48.5", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "windows-targets" 1011 | version = "0.52.6" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1014 | dependencies = [ 1015 | "windows_aarch64_gnullvm 0.52.6", 1016 | "windows_aarch64_msvc 0.52.6", 1017 | "windows_i686_gnu 0.52.6", 1018 | "windows_i686_gnullvm", 1019 | "windows_i686_msvc 0.52.6", 1020 | "windows_x86_64_gnu 0.52.6", 1021 | "windows_x86_64_gnullvm 0.52.6", 1022 | "windows_x86_64_msvc 0.52.6", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "windows_aarch64_gnullvm" 1027 | version = "0.48.5" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1030 | 1031 | [[package]] 1032 | name = "windows_aarch64_gnullvm" 1033 | version = "0.52.6" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1036 | 1037 | [[package]] 1038 | name = "windows_aarch64_msvc" 1039 | version = "0.48.5" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1042 | 1043 | [[package]] 1044 | name = "windows_aarch64_msvc" 1045 | version = "0.52.6" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1048 | 1049 | [[package]] 1050 | name = "windows_i686_gnu" 1051 | version = "0.48.5" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1054 | 1055 | [[package]] 1056 | name = "windows_i686_gnu" 1057 | version = "0.52.6" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1060 | 1061 | [[package]] 1062 | name = "windows_i686_gnullvm" 1063 | version = "0.52.6" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1066 | 1067 | [[package]] 1068 | name = "windows_i686_msvc" 1069 | version = "0.48.5" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1072 | 1073 | [[package]] 1074 | name = "windows_i686_msvc" 1075 | version = "0.52.6" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1078 | 1079 | [[package]] 1080 | name = "windows_x86_64_gnu" 1081 | version = "0.48.5" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1084 | 1085 | [[package]] 1086 | name = "windows_x86_64_gnu" 1087 | version = "0.52.6" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1090 | 1091 | [[package]] 1092 | name = "windows_x86_64_gnullvm" 1093 | version = "0.48.5" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1096 | 1097 | [[package]] 1098 | name = "windows_x86_64_gnullvm" 1099 | version = "0.52.6" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1102 | 1103 | [[package]] 1104 | name = "windows_x86_64_msvc" 1105 | version = "0.48.5" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1108 | 1109 | [[package]] 1110 | name = "windows_x86_64_msvc" 1111 | version = "0.52.6" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1114 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "commit" 3 | version = "0.7.0" 4 | edition = "2021" 5 | homepage = "https://github.com/alt-art/commit" 6 | repository = "https://github.com/alt-art/commit" 7 | documentation = "https://github.com/alt-art/commit/wiki" 8 | description = "A tool to make patterned (conventional) commit messages" 9 | categories = ["development-tools", "command-line-utilities"] 10 | keywords = ["git", "commit","message", "conventional"] 11 | authors = ["Pedro H. M. "] 12 | readme = "README.md" 13 | license = "MIT" 14 | 15 | [lints.clippy] 16 | all = "warn" 17 | pedantic = "warn" 18 | nursery = "warn" 19 | cargo = "warn" 20 | str_to_string = "warn" 21 | 22 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 23 | [dependencies] 24 | inquire = "0.7.5" 25 | serde = { version = "1.0.217", features = ["derive"] } 26 | serde_json = "1.0.134" 27 | clap = { version = "4.5.23", features = ["derive"] } 28 | dirs = "5.0.1" 29 | anyhow = "1.0.95" 30 | 31 | [dev-dependencies] 32 | assert_fs = "1.1.2" 33 | serial_test = "3.2.0" 34 | 35 | [package.metadata.deb] 36 | name = "commit" 37 | maintainer = "Pedro Henrique Mendes " 38 | copyright = "2020, Pedro Henrique Mendes " 39 | license-file = ["LICENSE", "0"] 40 | extended-description = "A tool to make patterned (conventional) commit messages and customize the commit pattern" 41 | depends = "git" 42 | section = "utils" 43 | priority = "optional" 44 | assets = [ 45 | # Binary 46 | ["target/release/commit", "/usr/bin/", "111"], 47 | ] 48 | 49 | [package.metadata.generate-rpm] 50 | assets = [ 51 | # Binary 52 | { source = "target/release/commit", dest = "/usr/bin/commit", mode = "111" }, 53 | ] 54 | 55 | [package.metadata.generate-rpm.requires] 56 | git = "*" 57 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # How to install 2 | 3 | ## Summary 4 | 5 | - [AUR](#aur) 6 | - [Debian based](#debian-based) 7 | - [Fedora](#fedora) 8 | - [Install from source](#install-from-source) 9 | - [Windows portable binary](#windows-portable-binary) 10 | - [Add to programs on windows](#add-to-programs-on-windows) 11 | - [Add program to PATH on windows](#add-program-to-path-on-windows) 12 | 13 | ### AUR 14 | 15 | ```bash 16 | yay -S commits 17 | ``` 18 | 19 | ### Debian based 20 | 21 | > Verify what is the latest release [here](https://github.com/alt-art/commit/releases) 22 | 23 | ```bash 24 | wget https://github.com/alt-art/commit/releases/download/0.7.0/commit_0.7.0_amd64.deb 25 | apt install ./commit_0.7.0_amd64.deb 26 | ``` 27 | 28 | ### Fedora 29 | 30 | > Verify what is the latest release [here](https://github.com/alt-art/commit/releases) 31 | 32 | ```bash 33 | wget https://github.com/alt-art/commit/releases/download/0.7.0/commit_0.7.0_x86_64.rpm 34 | dnf install ./commit_0.7.0_x86_64.rpm 35 | ``` 36 | 37 | ### Install from source 38 | 39 | Requires rust and cargo 40 | 41 | ```bash 42 | git clone https://github.com/alt-art/commit.git 43 | cd commit 44 | cargo build --release 45 | ``` 46 | 47 | ```bash 48 | cp target/release/commit /usr/local/bin 49 | ``` 50 | 51 | ### Windows portable binary 52 | 53 | > Verify what is the latest release [here](https://github.com/alt-art/commit/releases) 54 | 55 | #### Add to programs on windows 56 | 57 | ```shell 58 | mkdir "C:\Program Files\commit" 59 | copy commit.exe "C:\Program Files\commit" 60 | ``` 61 | 62 | #### Add program to PATH on windows 63 | 64 | ```shell 65 | setx /M PATH "%PATH%;C:\Program Files\commit" 66 | ``` 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pedro Henrique Mendes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo 3 |

4 | 5 | # Commit 6 | 7 | See [wiki page](https://github.com/alt-art/commit/wiki) for usage and configuration. 8 | 9 | See [Installation guide](INSTALL.md) for installation. 10 | 11 | This command-line interface makes it possible to make patterned (conventional) commit messages to organize your repository. 12 | 13 | This project is based on [cz-cli](https://github.com/commitizen/cz-cli) but do everything best than the original. 14 | 15 | I made this project for my own use, because I don't want to mess with the original cz-cli. 16 | 17 | ## Principal changes in this project 18 | 19 | - Don't need to make your project commitizen friendly 20 | - Can be used with any project and any language 21 | - Custom conventional commit message style can be used without any other packages 22 | - Plug and play just needs git installed 23 | 24 | ## Contributing 25 | 26 | See [CONTRIBUTING.md](https://github.com/alt-art/commit/blob/main/CONTRIBUTING.md) 27 | -------------------------------------------------------------------------------- /commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "pre_commit": "./pre-commit.sh" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-art/commit/ee0ced1d755eb6ab0363e7dd0a2d92598cefc609/icon.png -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | YELLOW='\e[1;33m' 4 | NC='\e[0m' 5 | 6 | if ! cargo fmt -- --check; then 7 | printf "%bHelp: run 'cargo fmt' to fix formatting issues%b" "$YELLOW" "$NC" 8 | exit 1 9 | fi 10 | if ! cargo clippy --all-targets --all-features -- -D warnings; then 11 | printf "%bHelp: run 'cargo clippy' and fix the issues%b" "$YELLOW" "$NC" 12 | exit 1 13 | fi 14 | if ! cargo test; then 15 | printf "%bHelp: run 'cargo test' and fix the issues%b" "$YELLOW" "$NC" 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /src/commit.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | 3 | use std::io::{BufRead, BufReader, Write}; 4 | use std::path::PathBuf; 5 | use std::process::{exit, Command, Output, Stdio}; 6 | use std::{fs, thread}; 7 | 8 | pub fn git_exec(args: &[&str]) -> Result { 9 | let output = Command::new("git").args(args).output(); 10 | match output { 11 | Ok(output) => Ok(output), 12 | Err(e) => Err(anyhow!( 13 | "Failed to run git. Make sure git is installed\nAdditional info: {}", 14 | e 15 | )), 16 | } 17 | } 18 | 19 | pub fn get_git_path() -> Result { 20 | let output = git_exec(&["rev-parse", "--absolute-git-dir"])?; 21 | if !output.status.success() { 22 | return Err(anyhow!( 23 | "Failed to get git path. Make sure you are in a git repository" 24 | )); 25 | } 26 | let path = String::from_utf8(output.stdout)?; 27 | Ok(PathBuf::from(path.trim())) 28 | } 29 | 30 | pub fn commit(commit_message: &str) -> Result<()> { 31 | let output = git_exec(&["commit", "-m", commit_message])?; 32 | std::io::stdout().write_all(&output.stdout)?; 33 | std::io::stderr().write_all(&output.stderr)?; 34 | exit( 35 | output 36 | .status 37 | .code() 38 | .ok_or_else(|| anyhow!("Signal terminated"))?, 39 | ); 40 | } 41 | 42 | pub fn check_staged_files() -> Result<()> { 43 | let output = git_exec(&["diff", "--cached", "--quiet"])?; 44 | if output.status.code() == Some(0) { 45 | return Err(anyhow!("You have not added anything please do `git add`")); 46 | } 47 | Ok(()) 48 | } 49 | 50 | pub fn read_cached_commit() -> Result { 51 | let commit_file_path = get_git_path()?.join("COMMIT_EDITMSG"); 52 | let commit_message = fs::read_to_string(commit_file_path)?; 53 | Ok(commit_message) 54 | } 55 | 56 | pub fn write_cached_commit(commit_message: &str) -> Result<()> { 57 | fs::write(get_git_path()?.join("COMMIT_EDITMSG"), commit_message)?; 58 | Ok(()) 59 | } 60 | 61 | pub fn pre_commit_check(pre_commit_command: Option, message: &str) -> Result<()> { 62 | if let Some(command) = pre_commit_command { 63 | println!("Running pre-commit command..."); 64 | let mut process = Command::new(command) 65 | .env("MSG", message) 66 | .stdout(Stdio::piped()) 67 | .stderr(Stdio::piped()) 68 | .spawn()?; 69 | let stdout = process.stdout.take().expect("Unable to get stdout"); 70 | let stderr = process.stderr.take().expect("Unable to get stderr"); 71 | thread::spawn(move || { 72 | let lines = BufReader::new(stdout).lines(); 73 | for line in lines { 74 | println!("{}", line.expect("Unable to get line")); 75 | } 76 | }); 77 | thread::spawn(move || { 78 | let lines = BufReader::new(stderr).lines(); 79 | for line in lines { 80 | eprintln!("{}", line.expect("Unable to get line")); 81 | } 82 | }); 83 | let status = process.wait()?; 84 | if !status.success() { 85 | return Err(anyhow!("Pre-commit command failed with {}", status)); 86 | } 87 | } 88 | Ok(()) 89 | } 90 | 91 | pub fn git_add_all_modified() -> Result<()> { 92 | let output = git_exec(&["add", "-u"])?; 93 | std::io::stdout().write_all(&output.stdout)?; 94 | std::io::stderr().write_all(&output.stderr)?; 95 | 96 | if !output.status.success() { 97 | return Err(anyhow!("Could not add files to staged area")); 98 | } 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /src/commit_message/message_build/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use crate::commit_pattern::Config; 5 | 6 | pub struct MessageBuilder { 7 | config: Config, 8 | pub message: String, 9 | } 10 | 11 | impl MessageBuilder { 12 | pub const fn new(config: Config) -> Self { 13 | Self { 14 | config, 15 | message: String::new(), 16 | } 17 | } 18 | 19 | pub fn set_type(&mut self, commit_type: &str) { 20 | if let Some(prefix) = &self.config.type_prefix { 21 | self.message.push_str(prefix); 22 | } 23 | self.message.push_str(commit_type); 24 | if let Some(suffix) = &self.config.type_suffix { 25 | self.message.push_str(suffix); 26 | } 27 | } 28 | 29 | pub fn set_scope(&mut self, scope: &str) { 30 | self.message.push_str(&self.config.scope_prefix); 31 | self.message.push_str(scope); 32 | self.message.push_str(&self.config.scope_suffix); 33 | } 34 | 35 | pub fn set_description(&mut self, description: &str) { 36 | self.message.push_str(&self.config.subject_separator); 37 | self.message.push_str(description); 38 | } 39 | 40 | pub fn set_body(&mut self, body: &str) { 41 | self.message.push_str(format!("\n\n{body}").as_str()); 42 | } 43 | 44 | pub fn set_footer(&mut self, footer: &str) { 45 | self.message.push_str(format!("\n\n{footer}").as_str()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/commit_message/message_build/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::commit_message::message_build::MessageBuilder; 2 | use crate::commit_pattern::Config; 3 | 4 | fn message_with_config(config: Config) -> String { 5 | let mut builder = MessageBuilder::new(config); 6 | builder.set_type("feat"); 7 | builder.set_scope("test"); 8 | builder.set_description("description"); 9 | builder.set_body("body"); 10 | builder.set_footer("footer"); 11 | builder.message 12 | } 13 | 14 | #[test] 15 | fn message_builder_config_test() { 16 | let mut config = Config { 17 | scope_prefix: "(".to_owned(), 18 | scope_suffix: ")".to_owned(), 19 | subject_separator: ": ".to_owned(), 20 | type_prefix: None, 21 | type_suffix: None, 22 | pre_commit: None, 23 | }; 24 | 25 | assert_eq!( 26 | message_with_config(config.clone()), 27 | "feat(test): description\n\nbody\n\nfooter" 28 | ); 29 | 30 | config.type_prefix = Some("[".to_owned()); 31 | config.type_suffix = Some("]".to_owned()); 32 | 33 | assert_eq!( 34 | message_with_config(config.clone()), 35 | "[feat](test): description\n\nbody\n\nfooter" 36 | ); 37 | config.subject_separator = " ".to_owned(); 38 | 39 | assert_eq!( 40 | message_with_config(config.clone()), 41 | "[feat](test) description\n\nbody\n\nfooter" 42 | ); 43 | 44 | config.scope_prefix = String::new(); 45 | config.scope_suffix = String::new(); 46 | 47 | assert_eq!( 48 | message_with_config(config), 49 | "[feat]test description\n\nbody\n\nfooter" 50 | ); 51 | } 52 | 53 | #[test] 54 | fn message_builder_test() { 55 | let config = Config { 56 | scope_prefix: "(".to_owned(), 57 | scope_suffix: ")".to_owned(), 58 | subject_separator: ": ".to_owned(), 59 | type_prefix: None, 60 | type_suffix: None, 61 | pre_commit: None, 62 | }; 63 | let mut builder = MessageBuilder::new(config); 64 | 65 | builder.set_type("feat"); 66 | assert_eq!(builder.message, "feat"); 67 | 68 | builder.set_scope("test"); 69 | assert_eq!(builder.message, "feat(test)"); 70 | 71 | builder.set_description("description"); 72 | assert_eq!(builder.message, "feat(test): description"); 73 | 74 | builder.set_body("body"); 75 | assert_eq!(builder.message, "feat(test): description\n\nbody"); 76 | 77 | builder.set_footer("footer"); 78 | assert_eq!(builder.message, "feat(test): description\n\nbody\n\nfooter"); 79 | } 80 | -------------------------------------------------------------------------------- /src/commit_message/mod.rs: -------------------------------------------------------------------------------- 1 | mod message_build; 2 | mod prompt; 3 | 4 | use anyhow::{anyhow, Result}; 5 | use message_build::MessageBuilder; 6 | use prompt::Prompt; 7 | 8 | use crate::commit_pattern::CommitPattern; 9 | 10 | pub fn make_message_commit(pattern: CommitPattern) -> Result { 11 | let mut message_inquirer = MessageInquirer::new(pattern.clone()); 12 | let skip_commit = pattern.skip_commit; 13 | if !skip_commit.contains(&"commit_type".to_owned()) { 14 | message_inquirer.type_choice()?; 15 | } 16 | if !skip_commit.contains(&"commit_scope".to_owned()) { 17 | message_inquirer.scope_choice()?; 18 | } 19 | if !skip_commit.contains(&"commit_description".to_owned()) { 20 | message_inquirer.description()?; 21 | } 22 | if !skip_commit.contains(&"commit_body".to_owned()) { 23 | message_inquirer.body()?; 24 | } 25 | if !skip_commit.contains(&"commit_footer".to_owned()) { 26 | message_inquirer.footer()?; 27 | } 28 | message_inquirer.message() 29 | } 30 | 31 | struct MessageInquirer { 32 | commit_builder: MessageBuilder, 33 | prompt: Prompt, 34 | pattern: CommitPattern, 35 | } 36 | 37 | impl MessageInquirer { 38 | fn new(pattern: CommitPattern) -> Self { 39 | Self { 40 | commit_builder: MessageBuilder::new(pattern.config.clone()), 41 | prompt: Prompt::new(), 42 | pattern, 43 | } 44 | } 45 | 46 | fn type_choice(&mut self) -> Result<()> { 47 | let type_choice = self.prompt.select( 48 | &self.pattern.msg.commit_type, 49 | self.pattern.commit_types.clone(), 50 | )?; 51 | self.commit_builder.set_type(&type_choice); 52 | Ok(()) 53 | } 54 | 55 | fn scope_choice(&mut self) -> Result<()> { 56 | let scope_choice = self.prompt.select( 57 | &self.pattern.msg.commit_scope, 58 | self.pattern.commit_scopes.clone(), 59 | )?; 60 | if scope_choice == "custom" { 61 | let custom_scope = self 62 | .prompt 63 | .required_input("Enter custom scope:", "Custom scope")?; 64 | self.commit_builder.set_scope(&custom_scope); 65 | } else if scope_choice != "none" { 66 | self.commit_builder.set_scope(&scope_choice); 67 | } 68 | Ok(()) 69 | } 70 | 71 | fn description(&mut self) -> Result<()> { 72 | let description = self 73 | .prompt 74 | .required_input(&self.pattern.msg.commit_description, "Description")?; 75 | self.commit_builder.set_description(&description); 76 | Ok(()) 77 | } 78 | 79 | fn body(&mut self) -> Result<()> { 80 | let body = self 81 | .prompt 82 | .optional_input(&self.pattern.msg.commit_body, "Commit body")?; 83 | if !body.is_empty() { 84 | self.commit_builder.set_body(&body); 85 | } 86 | Ok(()) 87 | } 88 | 89 | fn footer(&mut self) -> Result<()> { 90 | let footer = self 91 | .prompt 92 | .optional_input(&self.pattern.msg.commit_footer, "Commit footer")?; 93 | if !footer.is_empty() { 94 | self.commit_builder.set_footer(&footer); 95 | } 96 | Ok(()) 97 | } 98 | 99 | fn message(&mut self) -> Result { 100 | println!( 101 | "\nThe commit message is:\n\n{}\n", 102 | self.commit_builder.message 103 | ); 104 | let confirm = self.prompt.confirm("Do you want to apply the commit?")?; 105 | if !confirm { 106 | return Err(anyhow!("Operation was canceled by the user")); 107 | } 108 | Ok(self.commit_builder.message.clone()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/commit_message/prompt.rs: -------------------------------------------------------------------------------- 1 | use crate::commit_pattern::Type; 2 | 3 | use inquire::{ 4 | required, 5 | ui::{Color, RenderConfig, Styled}, 6 | Confirm, Select, Text, 7 | }; 8 | 9 | use anyhow::Result; 10 | 11 | pub struct Prompt { 12 | config: RenderConfig<'static>, 13 | } 14 | 15 | impl Prompt { 16 | pub fn new() -> Self { 17 | let default = RenderConfig::default(); 18 | let prompt_prefix = Styled::new("-").with_fg(Color::LightGreen); 19 | let current_config = default.with_prompt_prefix(prompt_prefix); 20 | Self { 21 | config: current_config, 22 | } 23 | } 24 | 25 | pub fn optional_input(&self, prompt: &str, label: &str) -> Result { 26 | let input = Text::new(prompt) 27 | .with_render_config(self.config) 28 | .with_help_message(format!("{label}. Press Enter to skip").as_str()) 29 | .prompt()?; 30 | Ok(input) 31 | } 32 | 33 | pub fn required_input(&self, prompt: &str, label: &str) -> Result { 34 | let input = Text::new(prompt) 35 | .with_render_config(self.config) 36 | .with_validator(required!(format!("{label} can't be empty").as_str())) 37 | .prompt()?; 38 | Ok(input) 39 | } 40 | 41 | pub fn select(&self, prompt: &str, choices: Vec) -> Result { 42 | let choice = Select::new(prompt, choices) 43 | .with_render_config(self.config) 44 | .prompt()?; 45 | Ok(choice.name) 46 | } 47 | 48 | pub fn confirm(&self, prompt: &str) -> Result { 49 | let confirm = Confirm::new(prompt) 50 | .with_render_config(self.config) 51 | .with_default(true) 52 | .prompt()?; 53 | Ok(confirm) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/commit_pattern/config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Clone)] 4 | pub struct Config { 5 | pub type_prefix: Option, 6 | pub type_suffix: Option, 7 | #[serde(default = "Config::subject_separator")] 8 | pub subject_separator: String, 9 | #[serde(default = "Config::scope_prefix")] 10 | pub scope_prefix: String, 11 | #[serde(default = "Config::scope_suffix")] 12 | pub scope_suffix: String, 13 | pub pre_commit: Option, 14 | } 15 | 16 | impl Config { 17 | fn subject_separator() -> String { 18 | ": ".to_owned() 19 | } 20 | fn scope_prefix() -> String { 21 | "(".to_owned() 22 | } 23 | fn scope_suffix() -> String { 24 | ")".to_owned() 25 | } 26 | } 27 | 28 | impl Default for Config { 29 | fn default() -> Self { 30 | Self { 31 | type_prefix: None, 32 | type_suffix: None, 33 | subject_separator: Self::subject_separator(), 34 | scope_prefix: Self::scope_prefix(), 35 | scope_suffix: Self::scope_suffix(), 36 | pre_commit: None, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/commit_pattern/messages.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Clone)] 4 | pub struct Messages { 5 | #[serde(default = "Messages::commit_type")] 6 | pub commit_type: String, 7 | #[serde(default = "Messages::commit_scope")] 8 | pub commit_scope: String, 9 | #[serde(default = "Messages::commit_description")] 10 | pub commit_description: String, 11 | #[serde(default = "Messages::commit_body")] 12 | pub commit_body: String, 13 | #[serde(default = "Messages::commit_footer")] 14 | pub commit_footer: String, 15 | } 16 | 17 | impl Messages { 18 | fn commit_type() -> String { 19 | "What type of commit you will made?".to_owned() 20 | } 21 | fn commit_scope() -> String { 22 | "What scope of commit you will made? (Optional)".to_owned() 23 | } 24 | fn commit_description() -> String { 25 | "Write a SHORT, IMPERATIVE tense description of the change:".to_owned() 26 | } 27 | fn commit_body() -> String { 28 | "Provide a LONGER description of the change (Optional):".to_owned() 29 | } 30 | 31 | fn commit_footer() -> String { 32 | "List any ISSUES CLOSED by this change E.g.: #31, #34 (Optional):".to_owned() 33 | } 34 | } 35 | 36 | impl Default for Messages { 37 | fn default() -> Self { 38 | Self { 39 | commit_type: Self::commit_type(), 40 | commit_scope: Self::commit_scope(), 41 | commit_description: Self::commit_description(), 42 | commit_body: Self::commit_body(), 43 | commit_footer: Self::commit_footer(), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/commit_pattern/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod messages; 3 | 4 | use serde::Deserialize; 5 | use std::fmt::{Display, Formatter, Result as FmtResult}; 6 | 7 | pub use config::Config; 8 | use messages::Messages; 9 | 10 | impl Display for Type { 11 | fn fmt(&self, formatter: &mut Formatter) -> FmtResult { 12 | write!(formatter, "{} - {}", self.name, self.description) 13 | } 14 | } 15 | 16 | #[derive(Clone, Deserialize)] 17 | pub struct Type { 18 | pub name: String, 19 | pub description: String, 20 | } 21 | 22 | #[derive(Deserialize, Clone)] 23 | pub struct CommitPattern { 24 | #[serde(default)] 25 | pub config: Config, 26 | #[serde(default = "CommitPattern::commit_types")] 27 | pub commit_types: Vec, 28 | #[serde(default = "CommitPattern::commit_scopes")] 29 | pub commit_scopes: Vec, 30 | #[serde(default)] 31 | pub skip_commit: Vec, 32 | #[serde(default)] 33 | pub msg: Messages, 34 | } 35 | 36 | impl CommitPattern { 37 | fn commit_types() -> Vec { 38 | vec![ 39 | Type { 40 | name: "feat".to_owned(), 41 | description: "A new feature".to_owned(), 42 | }, 43 | Type { 44 | name: "fix".to_owned(), 45 | description: "A bug fix".to_owned(), 46 | }, 47 | Type { 48 | name: "docs".to_owned(), 49 | description: "Documentation only changes".to_owned(), 50 | }, 51 | Type { 52 | name: "style".to_owned(), 53 | description: "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)".to_owned(), 54 | }, 55 | Type { 56 | name: "refactor".to_owned(), 57 | description: "A code change that neither fixes a bug nor adds a feature".to_owned(), 58 | }, 59 | Type { 60 | name: "perf".to_owned(), 61 | description: "A code change that improves performance".to_owned(), 62 | }, 63 | Type { 64 | name: "test".to_owned(), 65 | description: "Adding missing tests or correcting existing tests".to_owned(), 66 | }, 67 | Type { 68 | name: "chore".to_owned(), 69 | description: "Other changes that don't modify src or test files".to_owned(), 70 | }, 71 | ] 72 | } 73 | fn commit_scopes() -> Vec { 74 | vec![ 75 | Type { 76 | name: "custom".to_owned(), 77 | description: "Custom scope".to_owned(), 78 | }, 79 | Type { 80 | name: "none".to_owned(), 81 | description: "No scope".to_owned(), 82 | }, 83 | ] 84 | } 85 | } 86 | 87 | impl Default for CommitPattern { 88 | fn default() -> Self { 89 | Self { 90 | config: Config::default(), 91 | commit_types: Self::commit_types(), 92 | commit_scopes: Self::commit_scopes(), 93 | skip_commit: vec![], 94 | msg: Messages::default(), 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use anyhow::{anyhow, Context, Result}; 5 | use std::fs::File; 6 | use std::path::PathBuf; 7 | 8 | use crate::commit_pattern::CommitPattern; 9 | 10 | fn select_custom_config_path(config: Option) -> Result { 11 | match config { 12 | Some(config_path) => { 13 | if config_path.exists() { 14 | if !config_path.is_file() { 15 | return Err(anyhow!( 16 | "Config file path is not a file: {}", 17 | config_path.display() 18 | )); 19 | } 20 | Ok(config_path) 21 | } else { 22 | Err(anyhow!( 23 | "Config file does not exist: {}", 24 | config_path.display() 25 | )) 26 | } 27 | } 28 | None => get_config_path(), 29 | } 30 | } 31 | 32 | fn search_config_file_on_parents() -> Result> { 33 | let current_dir = std::env::current_dir()?; 34 | let mut current_dir = current_dir.as_path(); 35 | loop { 36 | let config_file = current_dir.join("commit.json"); 37 | if config_file.is_file() { 38 | return Ok(Some(config_file)); 39 | } 40 | if let Some(parent) = current_dir.parent() { 41 | current_dir = parent; 42 | } else { 43 | break; 44 | } 45 | } 46 | Ok(None) 47 | } 48 | 49 | fn get_config_path() -> Result { 50 | Ok(search_config_file_on_parents()?.unwrap_or_else(|| { 51 | dirs::config_dir() 52 | .expect("Could not find config directory") 53 | .join("commit/commit.json") 54 | })) 55 | } 56 | 57 | pub fn get_pattern(config_path: Option) -> Result { 58 | let selected_config_path = select_custom_config_path(config_path)?; 59 | if selected_config_path.exists() { 60 | let file = File::open(&selected_config_path).context(format!( 61 | "Could not open config file: {}", 62 | selected_config_path.display() 63 | ))?; 64 | let reader = std::io::BufReader::new(file); 65 | Ok(serde_json::from_reader(reader).context(format!( 66 | "Could not parse config file: {}", 67 | selected_config_path.display() 68 | ))?) 69 | } else { 70 | Ok(CommitPattern::default()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/config/tests.rs: -------------------------------------------------------------------------------- 1 | use std::{env::set_current_dir, fs::remove_file}; 2 | 3 | use super::*; 4 | use assert_fs::prelude::*; 5 | use serial_test::serial; 6 | 7 | #[test] 8 | #[serial] 9 | fn select_custom_config_path_test() -> Result<()> { 10 | let temp_dir = assert_fs::TempDir::new()?; 11 | let config_file = temp_dir.child("config.json"); 12 | config_file.touch()?; 13 | 14 | let config_path = config_file.path().to_owned(); 15 | let selected_config_path = select_custom_config_path(Some(config_path.clone()))?; 16 | assert_eq!(config_path.to_str(), selected_config_path.to_str()); 17 | 18 | set_current_dir(temp_dir.path())?; 19 | let config_path_default = dirs::config_dir().unwrap().join("commit/commit.json"); 20 | let selected_config_path = select_custom_config_path(None)?; 21 | assert_eq!(selected_config_path.to_str(), config_path_default.to_str()); 22 | 23 | let selected_config_path = select_custom_config_path(Some(PathBuf::new())); 24 | match selected_config_path { 25 | Err(err) => assert_eq!(err.to_string(), "Config file does not exist: "), 26 | _ => unreachable!(), 27 | } 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | #[serial] 33 | fn get_config_path_test() -> Result<()> { 34 | let temp_dir = assert_fs::TempDir::new()?; 35 | temp_dir.child("commit.json").touch()?; 36 | temp_dir.child("some/sub/dir").create_dir_all()?; 37 | set_current_dir(temp_dir.path().join("some/sub/dir"))?; 38 | let config_path = get_config_path()?; 39 | assert_eq!( 40 | config_path.to_str(), 41 | temp_dir.path().join("commit.json").to_str() 42 | ); 43 | remove_file(temp_dir.path().join("commit.json"))?; 44 | let config_file = dirs::config_dir() 45 | .ok_or_else(|| anyhow!("Could not find config directory"))? 46 | .join("commit/commit.json"); 47 | let config_path = get_config_path(); 48 | assert_eq!(config_file.to_str(), config_path?.to_str()); 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | #[serial] 54 | fn get_pattern_test() -> Result<()> { 55 | let temp_dir = assert_fs::TempDir::new()?; 56 | set_current_dir(temp_dir.path())?; 57 | let pattern = get_pattern(None)?; 58 | assert_eq!(pattern.config.type_prefix, None); 59 | assert_eq!(pattern.config.type_suffix, None); 60 | assert_eq!(pattern.config.subject_separator, ": "); 61 | assert_eq!(pattern.config.scope_prefix, "("); 62 | assert_eq!(pattern.config.scope_suffix, ")"); 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::struct_field_names, 3 | clippy::multiple_crate_versions, 4 | clippy::module_name_repetitions 5 | )] 6 | 7 | mod commit; 8 | mod commit_message; 9 | mod commit_pattern; 10 | mod config; 11 | 12 | use anyhow::Result; 13 | use clap::Parser; 14 | use std::path::PathBuf; 15 | 16 | use commit::{ 17 | check_staged_files, commit, git_add_all_modified, pre_commit_check, read_cached_commit, 18 | write_cached_commit, 19 | }; 20 | use commit_message::make_message_commit; 21 | 22 | #[derive(Parser, Debug)] 23 | #[command(about, author, version)] 24 | struct Args { 25 | /// Custom configuration file path 26 | #[arg(short, long)] 27 | config: Option, 28 | /// Use as hook 29 | #[arg(long)] 30 | hook: bool, 31 | /// Retry commit with the same message as the last one 32 | #[arg(short, long)] 33 | retry: bool, 34 | /// Add all modified files into staging 35 | #[arg(short, long)] 36 | all: bool, 37 | } 38 | 39 | fn main() -> Result<()> { 40 | let args = Args::parse(); 41 | 42 | if args.all { 43 | git_add_all_modified()?; 44 | } 45 | 46 | check_staged_files()?; 47 | 48 | let pattern = config::get_pattern(args.config)?; 49 | 50 | if args.retry { 51 | let commit_message = read_cached_commit()?; 52 | pre_commit_check(pattern.config.pre_commit, &commit_message)?; 53 | commit(&commit_message)?; 54 | return Ok(()); 55 | } 56 | 57 | let commit_message = make_message_commit(pattern.clone())?; 58 | write_cached_commit(&commit_message)?; 59 | 60 | pre_commit_check(pattern.config.pre_commit, &commit_message)?; 61 | 62 | if args.hook { 63 | return Ok(()); 64 | } 65 | 66 | commit(&commit_message)?; 67 | Ok(()) 68 | } 69 | --------------------------------------------------------------------------------