├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── rust.yml │ └── deploy.yml ├── .glitterrc.broken ├── .cargo └── config ├── assets ├── glitter.png └── glitter.svg ├── rustfmt.toml ├── Cargo.toml ├── src ├── main.rs ├── lib.rs ├── get_and_parse.rs ├── config.rs └── cli.rs ├── .glitterrc ├── LICENSE ├── README.md ├── install.sh └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Milo123459 -------------------------------------------------------------------------------- /.glitterrc.broken: -------------------------------------------------------------------------------- 1 | glitter on top 2 | glitter > anything else -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | lint = "clippy --all-targets --all-features" -------------------------------------------------------------------------------- /assets/glitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Milo123459/glitter/HEAD/assets/glitter.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | reorder_modules = true 3 | use_field_init_shorthand = true 4 | hard_tabs = true -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "glitter" 3 | version = "1.6.6" 4 | authors = ["Milo123459"] 5 | edition = "2021" 6 | include = ["src/**/*", "LICENSE", "README.md"] 7 | 8 | [dependencies] 9 | serde_json = "1.0.115" 10 | serde = { version = "1.0.197", features = ["derive"] } 11 | structopt = "0.3.26" 12 | anyhow = "1.0.81" 13 | fancy-regex = "0.13.0" 14 | Inflector = { version = "0.11.4", default-features = false } 15 | colored = "2.1.0" 16 | which = "6.0.1" 17 | ms = "0.1.1" 18 | spinoff = "0.8.0" 19 | 20 | [profile.release] 21 | lto = "fat" 22 | panic = "abort" 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use glitter::config::Arguments; 2 | use structopt::StructOpt; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | match Arguments::from_args_safe() { 6 | Ok(args) => glitter::run(args), 7 | Err(err) => { 8 | if err.use_stderr() { 9 | Err(err.into()) 10 | } else { 11 | println!("{}", err.message); 12 | Ok(()) 13 | } 14 | } 15 | } 16 | } 17 | // tests 18 | #[cfg(test)] 19 | mod tests { 20 | use super::main; 21 | 22 | #[test] 23 | fn runs_correctly() { 24 | // main will always error as it doesnt get any args 25 | assert!(main().is_err()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.glitterrc: -------------------------------------------------------------------------------- 1 | { 2 | "commit_message": "$1: $2: $3+", 3 | "commit_message_arguments": [ 4 | { 5 | "argument": 1, 6 | "case": "lower", 7 | "type_enums": [ 8 | "fix", 9 | "feat", 10 | "chore", 11 | "refactor", 12 | "docs", 13 | "void", 14 | "deps", 15 | "ci" 16 | ] 17 | } 18 | ], 19 | "custom_tasks": [ 20 | { 21 | "name": "fmt", 22 | "execute": [ 23 | "cargo fmt" 24 | ] 25 | }, 26 | { 27 | "name": "lint", 28 | "execute": [ 29 | "cargo lint" 30 | ] 31 | } 32 | ], 33 | "hooks": [ 34 | "fmt", 35 | "lint" 36 | ] 37 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli; 2 | pub mod config; 3 | pub mod get_and_parse; 4 | use crate::cli::match_cmds; 5 | use config::Arguments; 6 | 7 | // this function will parse configuration from the get_and_parse file and pass it onto the cli 8 | pub fn run(args: Arguments) -> anyhow::Result<()> { 9 | let config = get_and_parse::parse(&args.rc_path)?; 10 | match_cmds(args, config)?; 11 | 12 | Ok(()) 13 | } 14 | // tests 15 | #[cfg(test)] 16 | mod tests { 17 | use std::path::PathBuf; 18 | 19 | use crate::{config::Arguments, run}; 20 | 21 | #[test] 22 | fn runs_correctly() { 23 | let args = Arguments { 24 | action: "push".to_string(), 25 | arguments: vec![ 26 | "feat".to_string(), 27 | "test".to_string(), 28 | "b".to_string(), 29 | "c".to_string(), 30 | ], 31 | rc_path: PathBuf::from(".glitterrc"), 32 | dry: Some(Some(true)), 33 | raw: Some(Some(false)), 34 | no_verify: Some(Some(false)), 35 | verbose: Some(Some(false)), 36 | yes: None, 37 | }; 38 | 39 | run(args).unwrap(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2021-Present 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | on: 3 | push: 4 | paths-ignore: 5 | - "**.md" 6 | pull_request: 7 | paths-ignore: 8 | - "**.md" 9 | 10 | env: 11 | CARGO_INCREMENTAL: 0 12 | 13 | jobs: 14 | rust: 15 | name: Rust CI 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Setup | Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup | Rust 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | override: true 26 | profile: minimal 27 | components: rustfmt 28 | 29 | - name: Setup | Cache 30 | uses: Swatinem/rust-cache@v1 31 | 32 | - name: Build | Format 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: fmt 36 | args: --all -- --check 37 | 38 | - name: Build | Check 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: check 42 | args: --workspace --locked 43 | 44 | - name: Build | Lint 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: clippy 48 | args: --workspace --locked --all-targets --all-features 49 | -------------------------------------------------------------------------------- /src/get_and_parse.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GlitterRc; 2 | use anyhow::Context; 3 | use std::fs::File; 4 | use std::path::Path; 5 | // parse the config file 6 | pub fn parse(path: &Path) -> anyhow::Result { 7 | let does_exist = Path::new(path).exists(); 8 | if !does_exist { 9 | Ok(GlitterRc { 10 | fetch: None, 11 | commit_message: "$1+".to_owned(), 12 | arguments: None, 13 | custom_tasks: None, 14 | commit_message_arguments: None, 15 | __default: Some(true), 16 | hooks: None, 17 | verbose: None, 18 | }) 19 | } else { 20 | let file = File::open(path)?; 21 | match serde_json::from_reader(file) { 22 | Ok(json) => Ok(json), 23 | Err(err) => Err(anyhow::Error::new(err)).with_context(|| "error parsing glitterrc"), 24 | } 25 | } 26 | } 27 | // tests 28 | #[cfg(test)] 29 | mod tests { 30 | use std::path::PathBuf; 31 | 32 | use crate::config::{CommitMessageArguments, CustomTaskOptions, GlitterRc}; 33 | 34 | use super::parse; 35 | 36 | #[test] 37 | fn parse_correctly() { 38 | assert_eq!( 39 | parse(&PathBuf::from(".glitterrc")).unwrap(), 40 | GlitterRc { 41 | commit_message: "$1: $2: $3+".to_string(), 42 | arguments: None, 43 | commit_message_arguments: Some(vec![CommitMessageArguments { 44 | argument: 1, 45 | case: Some("lower".to_string()), 46 | type_enums: Some(vec![ 47 | "fix".to_string(), 48 | "feat".to_string(), 49 | "chore".to_string(), 50 | "refactor".to_string(), 51 | "docs".to_string(), 52 | "void".to_string(), 53 | "deps".to_string(), 54 | "ci".to_string() 55 | ]) 56 | }]), 57 | fetch: None, 58 | custom_tasks: Some(vec![ 59 | CustomTaskOptions { 60 | name: String::from("fmt"), 61 | execute: Some(vec![String::from("cargo fmt")]) 62 | }, 63 | CustomTaskOptions { 64 | name: String::from("lint"), 65 | execute: Some(vec![String::from("cargo lint")]) 66 | } 67 | ]), 68 | __default: None, 69 | hooks: Some(vec![String::from("fmt"), String::from("lint")]), 70 | verbose: None 71 | } 72 | ) 73 | } 74 | 75 | #[test] 76 | fn non_existant_file() { 77 | assert!(parse(&PathBuf::from(".glitter")) 78 | .unwrap() 79 | .__default 80 | .is_some()) 81 | } 82 | 83 | #[test] 84 | fn broken_glitterrc() { 85 | assert!(parse(&PathBuf::from(".glitterrc.broken")).is_err()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Glitter 3 |

4 |

❯ Glitter

5 |

6 | Git tooling of the future 7 |

8 |

9 | 10 | ## Features 11 | - Config files 12 | - Simple errors 13 | - Glitter Hooks (Git hooks natively built into Glitter) 14 | 15 | ## What is glitter? 16 | 17 | Glitter is a tool for generating and structuring commit messages via arguments passed to the tool. It allows you to configure it extensively and easily. 18 | 19 | ## Installation 20 | 21 | **Windows** 22 | 23 | [Scoop](https://scoop.sh) 24 | 25 | ``` 26 | scoop install glitter 27 | ``` 28 | 29 | **Linux** 30 | 31 | *This is a bash script that can install on **any** platform other than **windows**, not just linux.* 32 | 33 | ``` 34 | curl -fsSL https://raw.githubusercontent.com/Milo123459/glitter/master/install.sh | bash 35 | ``` 36 | 37 | **Other** 38 | 39 | Check the [releases](https://github.com/Milo123459/glitter/releases) and download the appropriate binary. Or build from source. 40 | 41 | To build from source run this: 42 | 43 | *You need rust installed!* 44 | 45 | ``` 46 | cargo install --git https://github.com/Milo123459/glitter 47 | ``` 48 | 49 | ## Get started 50 | 51 | A simple example when using glitter would be a 3 step commit template. For example, something to turn `glitter push fix docs fix typo` into `fix: docs: fix typo`. 52 | 53 | This example covers using type_enums, hooks and how glitters argument system works. 54 | 55 | Firstly, we can define our `.glitterrc` to support 2 or more arguments. 56 | 57 | ```json 58 | { 59 | "commit_message": "$1: $2: $3+" 60 | } 61 | ``` 62 | This snippet alone now allows us to do `glitter push fix docs fix typo` and would template to `fix: docs: fix typo`. $1 is the first argument passed to glitter push, $2 is the second, and $3+ means that the third argument and anything after that should take it's place. 63 | 64 | Now, lets take a look at `type_enums` - a way of validating arguments. 65 | 66 | Let's add a `commit_message_arguments` to our `.glitterrc`: 67 | ```json 68 | { 69 | "commit_message": "$1: $2: $3+", 70 | "commit_message_arguments": [ 71 | { 72 | "argument": 1, 73 | "case": "lower", 74 | "type_enums": [ 75 | "fix", 76 | "feat" 77 | ] 78 | } 79 | ] 80 | } 81 | ``` 82 | This snippet now means that the first argument will: 83 | - be converted to lower-case 84 | - matched against the type_enums, and if it does not match, it fails 85 | 86 | For example, `glitter push fix docs fix typo` would work, but `glitter push chore docs fix typo` would not, because `chore` isn't in the type enums. 87 | 88 | Next: glitter hooks. 89 | 90 | Glitter hooks are like git hooks, but always run before `git add` - it allows you to run/make your own hooks with ease. 91 | 92 | An example of a hook to run `cargo fmt` would look like this: 93 | ```json 94 | { 95 | "custom_tasks": [ 96 | { 97 | "name": "fmt", 98 | "execute": [ 99 | "cargo fmt" 100 | ] 101 | }, 102 | ], 103 | "hooks": ["fmt"] 104 | } 105 | ``` 106 | This defines a custom task, which can also be run via `glitter cc` (for example `glitter cc fmt` would run `cargo fmt`). We then have a hooks array which specifies a custom task to run before running `git add`. 107 | 108 | ## FAQ 109 | 110 | > Does **"this hello"** count as 1 or 2 arguments? 111 | 112 | **This example counts as 1.** For example `glitter push hello "world how" are you` would give the following arguments: 113 | ``` 114 | 1: hello 115 | 2: world how 116 | 3: are 117 | 4: you 118 | ``` 119 | 120 | ## Available Cases 121 | 122 | - lower 123 | - upper 124 | - snake 125 | - screaming-snake 126 | - kebab 127 | - train 128 | - sentence 129 | - title 130 | - class 131 | - pascal 132 | 133 | ![Alt](https://repobeats.axiom.co/api/embed/94616a17e7b0081aad0b1634999ac54c23bd5e5c.svg "Repobeats analytics image") 134 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | env: 8 | CARGO_INCREMENTAL: 0 9 | 10 | jobs: 11 | # Build sources for every OS 12 | github_build: 13 | name: Build release binaries 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - target: x86_64-unknown-linux-gnu 19 | os: ubuntu-latest 20 | name: glitter-x86_64-unknown-linux-gnu.tar.gz 21 | 22 | - target: x86_64-unknown-linux-musl 23 | os: ubuntu-latest 24 | name: glitter-x86_64-unknown-linux-musl.tar.gz 25 | 26 | - target: i686-unknown-linux-musl 27 | os: ubuntu-latest 28 | name: glitter-i686-unknown-linux-musl.tar.gz 29 | 30 | - target: aarch64-unknown-linux-musl 31 | os: ubuntu-latest 32 | name: glitter-aarch64-unknown-linux-musl.tar.gz 33 | 34 | - target: arm-unknown-linux-musleabihf 35 | os: ubuntu-latest 36 | name: glitter-arm-unknown-linux-musleabihf.tar.gz 37 | 38 | - target: x86_64-apple-darwin 39 | os: macOS-latest 40 | name: glitter-x86_64-apple-darwin.tar.gz 41 | 42 | - target: aarch64-apple-darwin 43 | os: macOS-latest 44 | name: glitter-aarch64-apple-darwin.tar.gz 45 | 46 | - target: x86_64-pc-windows-msvc 47 | os: windows-latest 48 | name: glitter-x86_64-pc-windows-msvc.zip 49 | 50 | - target: i686-pc-windows-msvc 51 | os: windows-latest 52 | name: glitter-i686-pc-windows-msvc.zip 53 | 54 | - target: aarch64-pc-windows-msvc 55 | os: windows-latest 56 | name: glitter-aarch64-pc-windows-msvc.zip 57 | 58 | # - target: x86_64-unknown-freebsd 59 | # os: ubuntu-latest 60 | # name: glitter-x86_64-unknown-freebsd.tar.gz 61 | 62 | runs-on: ${{ matrix.os }} 63 | continue-on-error: true 64 | steps: 65 | - name: Setup | Checkout 66 | uses: actions/checkout@v3 67 | 68 | - name: Setup | Rust 69 | uses: actions-rs/toolchain@v1.0.7 70 | with: 71 | toolchain: stable 72 | override: true 73 | profile: minimal 74 | target: ${{ matrix.target }} 75 | 76 | - name: Setup | Cache 77 | uses: Swatinem/rust-cache@v1 78 | 79 | - name: Build | Build 80 | uses: actions-rs/cargo@v1.0.3 81 | with: 82 | command: build 83 | args: --release --locked --target ${{ matrix.target }} 84 | use-cross: ${{ matrix.os == 'ubuntu-latest' }} 85 | 86 | - name: Post Build | Prepare artifacts [Windows] 87 | if: matrix.os == 'windows-latest' 88 | run: | 89 | cd target/${{ matrix.target }}/release 90 | strip glitter.exe 91 | 7z a ../../../${{ matrix.name }} glitter.exe 92 | cd - 93 | 94 | - name: Post Build | Prepare artifacts [-nix] 95 | if: matrix.os != 'windows-latest' 96 | run: | 97 | cd target/${{ matrix.target }}/release 98 | # TODO: investigate better cross platform stripping 99 | strip glitter || true 100 | tar czvf ../../../${{ matrix.name }} glitter 101 | cd - 102 | 103 | - name: Deploy | Upload artifacts 104 | uses: actions/upload-artifact@v3 105 | with: 106 | name: ${{ matrix.name }} 107 | path: ${{ matrix.name }} 108 | 109 | # Create GitHub release with Rust build targets 110 | github_release: 111 | name: Create GitHub Release 112 | needs: github_build 113 | runs-on: ubuntu-latest 114 | steps: 115 | - name: Setup | Checkout 116 | uses: actions/checkout@v3 117 | with: 118 | fetch-depth: 0 119 | 120 | - name: Setup | Artifacts 121 | uses: actions/download-artifact@v3 122 | 123 | - name: Setup | Checksums 124 | run: for file in glitter-*/glitter-*; do openssl dgst -sha256 -r "$file" | awk '{print $1}' > "${file}.sha256"; done 125 | 126 | - name: Build | Publish 127 | uses: softprops/action-gh-release@v1 128 | with: 129 | files: glitter-*/glitter-* 130 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use structopt::StructOpt; 3 | 4 | fn commit_msg() -> String { 5 | "$1+".to_string() 6 | } 7 | 8 | #[derive(Serialize, Deserialize, Debug, StructOpt, Eq, PartialEq, Clone)] 9 | pub struct Arguments { 10 | /// type of action. run the `action` / `actions` action to see available actions. 11 | pub action: String, 12 | 13 | /// arguments to action 14 | pub arguments: Vec, 15 | 16 | /// path to glitterrc 17 | #[structopt( 18 | parse(from_os_str), 19 | default_value = ".glitterrc", 20 | long, 21 | visible_alias = "rc" 22 | )] 23 | pub rc_path: std::path::PathBuf, 24 | 25 | /// dry run. aka don't actually run the commands 26 | #[structopt(long, short, visible_alias = "d")] 27 | pub(crate) dry: Option>, 28 | 29 | /// don't follow the commit template specified and just use $1+ 30 | #[structopt(long, short)] 31 | pub(crate) raw: Option>, 32 | 33 | /// don't run any glitter hooks 34 | #[structopt(long = "no-verify", short = "n")] 35 | pub(crate) no_verify: Option>, 36 | 37 | /// verbose mode: log the output of all commands run 38 | #[structopt(long = "verbose", short = "v")] 39 | pub(crate) verbose: Option>, 40 | 41 | /// don't ask for confirmation 42 | #[structopt(long, short = "y")] 43 | pub(crate) yes: Option>, 44 | } 45 | 46 | pub struct VerboseResponse { 47 | pub provided: bool, 48 | pub value: bool, 49 | } 50 | 51 | // for the usage shorthand things, ie, without a value 52 | impl Arguments { 53 | pub fn dry(&self) -> bool { 54 | match self.dry { 55 | None => false, 56 | Some(None) => true, 57 | Some(Some(a)) => a, 58 | } 59 | } 60 | pub fn raw(&self) -> bool { 61 | match self.raw { 62 | None => false, 63 | Some(None) => true, 64 | Some(Some(a)) => a, 65 | } 66 | } 67 | pub fn no_verify(&self) -> bool { 68 | match self.no_verify { 69 | None => false, 70 | Some(None) => true, 71 | Some(Some(a)) => a, 72 | } 73 | } 74 | pub fn verbose(&self) -> VerboseResponse { 75 | match self.verbose { 76 | None => VerboseResponse { 77 | provided: false, 78 | value: false, 79 | }, 80 | Some(None) => VerboseResponse { 81 | provided: true, 82 | value: true, 83 | }, 84 | Some(Some(a)) => VerboseResponse { 85 | provided: true, 86 | value: a, 87 | }, 88 | } 89 | } 90 | pub fn yes(&self) -> bool { 91 | match self.yes { 92 | None => false, 93 | Some(None) => true, 94 | Some(Some(a)) => a, 95 | } 96 | } 97 | } 98 | 99 | #[derive(Deserialize, Debug, Eq, PartialEq)] 100 | pub struct CommitMessageArguments { 101 | pub argument: i32, 102 | pub case: Option, 103 | pub type_enums: Option>, 104 | } 105 | 106 | #[derive(Deserialize, Debug, Eq, PartialEq, Clone)] 107 | pub struct CustomTaskOptions { 108 | pub name: String, 109 | pub execute: Option>, 110 | } 111 | 112 | // main struct for the GlitterRc with defaults 113 | #[derive(Deserialize, Debug, Eq, PartialEq)] 114 | pub struct GlitterRc { 115 | #[serde(default = "commit_msg")] 116 | pub commit_message: String, 117 | pub arguments: Option>, 118 | pub commit_message_arguments: Option>, 119 | pub fetch: Option, 120 | pub custom_tasks: Option>, 121 | pub hooks: Option>, 122 | pub __default: Option, // this really shouldn't be provided by a user, but it's here for backwards compatibility 123 | pub verbose: Option, 124 | } 125 | // tests 126 | #[cfg(test)] 127 | mod tests { 128 | use std::path::PathBuf; 129 | 130 | use super::{commit_msg, Arguments, CommitMessageArguments, CustomTaskOptions, GlitterRc}; 131 | 132 | #[test] 133 | fn check_commit_message() { 134 | // getting 100% using this trick as we kinda cant test structs that dont have impls 135 | 136 | let args = Arguments { 137 | action: "actions".to_string(), 138 | arguments: vec![ 139 | "test".to_string(), 140 | "a".to_string(), 141 | "b".to_string(), 142 | "c".to_string(), 143 | ], 144 | rc_path: PathBuf::new(), 145 | dry: Some(Some(false)), 146 | raw: Some(Some(false)), 147 | no_verify: Some(Some(false)), 148 | verbose: Some(Some(false)), 149 | yes: None, 150 | }; 151 | 152 | let config = GlitterRc { 153 | commit_message: "$1($2): $3+".to_string(), 154 | arguments: None, 155 | commit_message_arguments: Some(vec![CommitMessageArguments { 156 | argument: 1, 157 | case: Some("snake".to_string()), 158 | type_enums: Some(vec![ 159 | "fix".to_owned(), 160 | "feat".to_owned(), 161 | "chore".to_owned(), 162 | ]), 163 | }]), 164 | fetch: None, 165 | custom_tasks: Some(vec![CustomTaskOptions { 166 | name: "fmt".to_owned(), 167 | execute: Some(vec!["cargo fmt".to_owned()]), 168 | }]), 169 | __default: None, 170 | hooks: None, 171 | verbose: None, 172 | }; 173 | 174 | assert_eq!(commit_msg(), "$1+".to_string()); 175 | assert_eq!( 176 | args, 177 | Arguments { 178 | action: "actions".to_string(), 179 | arguments: vec![ 180 | "test".to_string(), 181 | "a".to_string(), 182 | "b".to_string(), 183 | "c".to_string(), 184 | ], 185 | rc_path: PathBuf::new(), 186 | dry: Some(Some(false)), 187 | raw: Some(Some(false)), 188 | no_verify: Some(Some(false)), 189 | verbose: Some(Some(false)), 190 | yes: None 191 | } 192 | ); 193 | assert_eq!( 194 | config, 195 | GlitterRc { 196 | commit_message: "$1($2): $3+".to_string(), 197 | arguments: None, 198 | commit_message_arguments: Some(vec![CommitMessageArguments { 199 | argument: 1, 200 | case: Some("snake".to_string()), 201 | type_enums: Some(vec![ 202 | "fix".to_owned(), 203 | "feat".to_owned(), 204 | "chore".to_owned() 205 | ]) 206 | }]), 207 | fetch: None, 208 | custom_tasks: Some(vec![CustomTaskOptions { 209 | name: "fmt".to_owned(), 210 | execute: Some(vec!["cargo fmt".to_owned()]) 211 | }]), 212 | __default: None, 213 | hooks: None, 214 | verbose: None 215 | } 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Adapted from starships install file 4 | # shellcheck disable=SC2039 5 | 6 | help_text="# Options 7 | # 8 | # -V, --verbose 9 | # Enable verbose output for the installer 10 | # 11 | # -f, -y, --force, --yes 12 | # Skip the confirmation prompt during installation 13 | # 14 | # -p, --platform 15 | # Override the platform identified by the installer 16 | # 17 | # -b, --bin-dir 18 | # Override the bin installation directory 19 | # 20 | # -a, --arch 21 | # Override the architecture identified by the installer 22 | # 23 | # -B, --base-url 24 | # Override the base URL used for downloading releases 25 | # -r, --remove 26 | # Uninstall glitter 27 | # -h, --help 28 | # Get some help 29 | # 30 | " 31 | 32 | set -eu 33 | printf '\n' 34 | 35 | BOLD="$(tput bold 2>/dev/null || printf '')" 36 | GREY="$(tput setaf 0 2>/dev/null || printf '')" 37 | UNDERLINE="$(tput smul 2>/dev/null || printf '')" 38 | RED="$(tput setaf 1 2>/dev/null || printf '')" 39 | GREEN="$(tput setaf 2 2>/dev/null || printf '')" 40 | YELLOW="$(tput setaf 3 2>/dev/null || printf '')" 41 | BLUE="$(tput setaf 4 2>/dev/null || printf '')" 42 | MAGENTA="$(tput setaf 5 2>/dev/null || printf '')" 43 | NO_COLOR="$(tput sgr0 2>/dev/null || printf '')" 44 | 45 | SUPPORTED_TARGETS="x86_64-unknown-linux-gnu x86_64-unknown-linux-musl \ 46 | i686-unknown-linux-musl aarch64-unknown-linux-musl \ 47 | arm-unknown-linux-musleabihf x86_64-apple-darwin \ 48 | aarch64-apple-darwin x86_64-pc-windows-msvc \ 49 | i686-pc-windows-msvc aarch64-pc-windows-msvc \ 50 | x86_64-unknown-freebsd" 51 | 52 | info() { 53 | printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" 54 | } 55 | 56 | warn() { 57 | printf '%s\n' "${YELLOW}! $*${NO_COLOR}" 58 | } 59 | 60 | error() { 61 | printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 62 | } 63 | 64 | completed() { 65 | printf '%s\n' "${GREEN}✓${NO_COLOR} $*" 66 | } 67 | 68 | has() { 69 | command -v "$1" 1>/dev/null 2>&1 70 | } 71 | 72 | # Gets path to a temporary file, even if 73 | get_tmpfile() { 74 | local suffix 75 | suffix="$1" 76 | if has mktemp; then 77 | printf "%s%s.%s.%s" "$(mktemp)" "-glitter" "${RANDOM}" "${suffix}" 78 | else 79 | # No really good options here--let's pick a default + hope 80 | printf "/tmp/glitter.%s" "${suffix}" 81 | fi 82 | } 83 | 84 | # Test if a location is writeable by trying to write to it. Windows does not let 85 | # you test writeability other than by writing: https://stackoverflow.com/q/1999988 86 | test_writeable() { 87 | local path 88 | path="${1:-}/test.txt" 89 | if touch "${path}" 2>/dev/null; then 90 | rm "${path}" 91 | return 0 92 | else 93 | return 1 94 | fi 95 | } 96 | 97 | download() { 98 | file="$1" 99 | url="$2" 100 | touch "$file" 101 | printf "%s" "$file" 102 | 103 | if has curl; then 104 | cmd="curl --fail --silent --location --output $file $url" 105 | elif has wget; then 106 | cmd="wget --quiet --output-document=$file $url" 107 | elif has fetch; then 108 | cmd="fetch --quiet --output=$file $url" 109 | else 110 | error "No HTTP download program (curl, wget, fetch) found, exiting…" 111 | return 1 112 | fi 113 | 114 | $cmd && return 0 || rc=$? 115 | 116 | error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" 117 | printf "\n" >&2 118 | info "This is likely due to Glitter not yet supporting your configuration." 119 | info "If you would like to see a build for your configuration," 120 | info "please create an issue requesting a build for ${MAGENTA}${TARGET}${NO_COLOR}:" 121 | info "${BOLD}${UNDERLINE}https://github.com/Milo123459/glitter/issues/new/${NO_COLOR}" 122 | return $rc 123 | } 124 | 125 | unpack() { 126 | local archive=$1 127 | local bin_dir=$2 128 | local sudo=${3-} 129 | 130 | case "$archive" in 131 | *.tar.gz) 132 | flags=$(test -n "${VERBOSE-}" && echo "-v" || echo "") 133 | ${sudo} tar "${flags}" -xzf "${archive}" -C "${bin_dir}" 134 | return 0 135 | ;; 136 | *.zip) 137 | flags=$(test -z "${VERBOSE-}" && echo "-qq" || echo "") 138 | UNZIP="${flags}" ${sudo} unzip "${archive}" -d "${bin_dir}" 139 | return 0 140 | ;; 141 | esac 142 | 143 | error "Unknown package extension." 144 | printf "\n" 145 | info "This almost certainly results from a bug in this script--please file a" 146 | info "bug report at https://github.com/Milo123459/glitter/issues" 147 | return 1 148 | } 149 | 150 | elevate_priv() { 151 | if ! has sudo; then 152 | error 'Could not find the command "sudo", needed to get permissions for install.' 153 | info "If you are on Windows, please run your shell as an administrator, then" 154 | info "rerun this script. Otherwise, please run this script as root, or install" 155 | info "sudo." 156 | exit 1 157 | fi 158 | if ! sudo -v; then 159 | error "Superuser not granted, aborting installation" 160 | exit 1 161 | fi 162 | } 163 | 164 | install() { 165 | local msg 166 | local sudo 167 | local archive 168 | local ext="$1" 169 | 170 | if test_writeable "${BIN_DIR}"; then 171 | sudo="" 172 | msg="Installing Glitter, please wait…" 173 | else 174 | warn "Escalated permissions are required to install to ${BIN_DIR}" 175 | elevate_priv 176 | sudo="sudo" 177 | msg="Installing Glitter as root, please wait…" 178 | fi 179 | info "$msg" 180 | 181 | archive=$(get_tmpfile "$ext") 182 | 183 | # download to the temp file 184 | download "${archive}" "${URL}" 185 | 186 | # unpack the temp file to the bin dir, using sudo if required 187 | unpack "${archive}" "${BIN_DIR}" "${sudo}" 188 | 189 | # remove tempfile 190 | 191 | rm "${archive}" 192 | } 193 | 194 | # Currently supporting: 195 | # - win (Git Bash) 196 | # - darwin 197 | # - linux 198 | # - linux_musl (Alpine) 199 | # - freebsd 200 | detect_platform() { 201 | local platform 202 | platform="$(uname -s | tr '[:upper:]' '[:lower:]')" 203 | 204 | case "${platform}" in 205 | msys_nt*) platform="pc-windows-msvc" ;; 206 | cygwin_nt*) platform="pc-windows-msvc";; 207 | # mingw is Git-Bash 208 | mingw*) platform="pc-windows-msvc" ;; 209 | # use the statically compiled musl bins on linux to avoid linking issues. 210 | linux) platform="unknown-linux-musl" ;; 211 | darwin) platform="apple-darwin" ;; 212 | freebsd) platform="unknown-freebsd" ;; 213 | esac 214 | 215 | printf '%s' "${platform}" 216 | } 217 | 218 | # Currently supporting: 219 | # - x86_64 220 | # - i386 221 | detect_arch() { 222 | local arch 223 | arch="$(uname -m | tr '[:upper:]' '[:lower:]')" 224 | 225 | case "${arch}" in 226 | amd64) arch="x86_64" ;; 227 | armv*) arch="arm" ;; 228 | arm64) arch="aarch64" ;; 229 | esac 230 | 231 | # `uname -m` in some cases mis-reports 32-bit OS as 64-bit, so double check 232 | if [ "${arch}" = "x86_64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then 233 | arch=i686 234 | elif [ "${arch}" = "aarch64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then 235 | arch=arm 236 | fi 237 | 238 | printf '%s' "${arch}" 239 | } 240 | 241 | detect_target() { 242 | local arch="$1" 243 | local platform="$2" 244 | local target="$arch-$platform" 245 | 246 | if [ "${target}" = "arm-unknown-linux-musl" ]; then 247 | target="${target}eabihf" 248 | fi 249 | 250 | printf '%s' "${target}" 251 | } 252 | 253 | 254 | confirm() { 255 | if [ -z "${FORCE-}" ]; then 256 | printf "%s " "${MAGENTA}?${NO_COLOR} $* ${BOLD}[y/N]${NO_COLOR}" 257 | set +e 258 | read -r yn &2 318 | info "If you would like to see a build for your configuration," 319 | info "please create an issue requesting a build for ${MAGENTA}${target}${NO_COLOR}:" 320 | info "${BOLD}${UNDERLINE}https://github.com/Milo123459/glitter/issues/new/${NO_COLOR}" 321 | printf "\n" 322 | exit 1 323 | fi 324 | } 325 | UNINSTALL=0 326 | HELP=0 327 | CARGOTOML="$(curl -fsSL https://raw.githubusercontent.com/Milo123459/glitter/master/Cargo.toml)" 328 | ALL_VERSIONS="$(sed -n 's/.*version = "\([^"]*\)".*/\1/p' <<< "$CARGOTOML")" 329 | IFS=$'\n' read -r -a VERSION <<< "$ALL_VERSIONS" 330 | # defaults 331 | if [ -z "${PLATFORM-}" ]; then 332 | PLATFORM="$(detect_platform)" 333 | fi 334 | 335 | if [ -z "${BIN_DIR-}" ]; then 336 | BIN_DIR=/usr/local/bin 337 | fi 338 | 339 | if [ -z "${ARCH-}" ]; then 340 | ARCH="$(detect_arch)" 341 | fi 342 | 343 | if [ -z "${BASE_URL-}" ]; then 344 | BASE_URL="https://github.com/Milo123459/glitter/releases" 345 | fi 346 | 347 | # parse argv variables 348 | while [ "$#" -gt 0 ]; do 349 | case "$1" in 350 | -p | --platform) 351 | PLATFORM="$2" 352 | shift 2 353 | ;; 354 | -b | --bin-dir) 355 | BIN_DIR="$2" 356 | shift 2 357 | ;; 358 | -a | --arch) 359 | ARCH="$2" 360 | shift 2 361 | ;; 362 | -B | --base-url) 363 | BASE_URL="$2" 364 | shift 2 365 | ;; 366 | 367 | -V | --verbose) 368 | VERBOSE=1 369 | shift 1 370 | ;; 371 | -f | -y | --force | --yes) 372 | FORCE=1 373 | shift 1 374 | ;; 375 | -r | --remove | --uninstall) 376 | UNINSTALL=1 377 | shift 1 378 | ;; 379 | -h | --help) 380 | HELP=1 381 | shift 1 382 | ;; 383 | -p=* | --platform=*) 384 | PLATFORM="${1#*=}" 385 | shift 1 386 | ;; 387 | -b=* | --bin-dir=*) 388 | BIN_DIR="${1#*=}" 389 | shift 1 390 | ;; 391 | -a=* | --arch=*) 392 | ARCH="${1#*=}" 393 | shift 1 394 | ;; 395 | -B=* | --base-url=*) 396 | BASE_URL="${1#*=}" 397 | shift 1 398 | ;; 399 | -V=* | --verbose=*) 400 | VERBOSE="${1#*=}" 401 | shift 1 402 | ;; 403 | -f=* | -y=* | --force=* | --yes=*) 404 | FORCE="${1#*=}" 405 | shift 1 406 | ;; 407 | 408 | *) 409 | error "Unknown option: $1" 410 | exit 1 411 | ;; 412 | esac 413 | done 414 | if [ $UNINSTALL == 1 ]; then 415 | confirm "Are you sure you want to uninstall glitter?" 416 | 417 | msg="" 418 | sudo="" 419 | 420 | info "REMOVING GLITTER" 421 | 422 | if test_writeable "$(dirname "$(which glitter)")"; then 423 | sudo="" 424 | msg="Removing Glitter, please wait…" 425 | else 426 | warn "Escalated permissions are required to install to ${BIN_DIR}" 427 | elevate_priv 428 | sudo="sudo" 429 | msg="Removing Glitter as root, please wait…" 430 | fi 431 | 432 | info "$msg" 433 | ${sudo} rm "$(which glitter)" 434 | ${sudo} rm /tmp/glitter 435 | 436 | info "Removed glitter" 437 | exit 0 438 | 439 | fi 440 | if [ $HELP == 1 ]; then 441 | echo "${help_text}" 442 | exit 0 443 | fi 444 | TARGET="$(detect_target "${ARCH}" "${PLATFORM}")" 445 | 446 | is_build_available "${ARCH}" "${PLATFORM}" "${TARGET}" 447 | 448 | printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" 449 | info "${BOLD}Bin directory${NO_COLOR}: ${GREEN}${BIN_DIR}${NO_COLOR}" 450 | info "${BOLD}Platform${NO_COLOR}: ${GREEN}${PLATFORM}${NO_COLOR}" 451 | info "${BOLD}Arch${NO_COLOR}: ${GREEN}${ARCH}${NO_COLOR}" 452 | info "${BOLD}Version${NO_COLOR}: ${GREEN}${VERSION[0]}${NO_COLOR}" 453 | 454 | # non-empty VERBOSE enables verbose untarring 455 | if [ -n "${VERBOSE-}" ]; then 456 | VERBOSE=v 457 | info "${BOLD}Verbose${NO_COLOR}: yes" 458 | else 459 | VERBOSE= 460 | fi 461 | 462 | printf '\n' 463 | 464 | EXT=tar.gz 465 | if [ "${PLATFORM}" = "pc-windows-msvc" ]; then 466 | EXT=zip 467 | fi 468 | 469 | URL="${BASE_URL}/latest/download/glitter-${TARGET}.${EXT}" 470 | info "Tarball URL: ${UNDERLINE}${BLUE}${URL}${NO_COLOR}" 471 | confirm "Install Glitter ${GREEN}${VERSION[0]}${NO_COLOR} to ${BOLD}${GREEN}${BIN_DIR}${NO_COLOR}?" 472 | check_bin_dir "${BIN_DIR}" 473 | 474 | install "${EXT}" 475 | completed "Glitter installed" -------------------------------------------------------------------------------- /assets/glitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 200 | 201 | -------------------------------------------------------------------------------- /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 = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.1.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "ansi_term" 22 | version = "0.12.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 25 | dependencies = [ 26 | "winapi", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.81" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "bit-set" 48 | version = "0.5.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 51 | dependencies = [ 52 | "bit-vec", 53 | ] 54 | 55 | [[package]] 56 | name = "bit-vec" 57 | version = "0.6.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "2.5.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 72 | 73 | [[package]] 74 | name = "clap" 75 | version = "2.34.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 78 | dependencies = [ 79 | "ansi_term", 80 | "atty", 81 | "bitflags 1.3.2", 82 | "strsim", 83 | "textwrap", 84 | "unicode-width", 85 | "vec_map", 86 | ] 87 | 88 | [[package]] 89 | name = "colored" 90 | version = "2.1.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 93 | dependencies = [ 94 | "lazy_static", 95 | "windows-sys 0.48.0", 96 | ] 97 | 98 | [[package]] 99 | name = "either" 100 | version = "1.10.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 103 | 104 | [[package]] 105 | name = "errno" 106 | version = "0.3.8" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 109 | dependencies = [ 110 | "libc", 111 | "windows-sys 0.52.0", 112 | ] 113 | 114 | [[package]] 115 | name = "fancy-regex" 116 | version = "0.13.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" 119 | dependencies = [ 120 | "bit-set", 121 | "regex-automata", 122 | "regex-syntax", 123 | ] 124 | 125 | [[package]] 126 | name = "glitter" 127 | version = "1.6.6" 128 | dependencies = [ 129 | "Inflector", 130 | "anyhow", 131 | "colored", 132 | "fancy-regex", 133 | "ms", 134 | "serde", 135 | "serde_json", 136 | "spinoff", 137 | "structopt", 138 | "which", 139 | ] 140 | 141 | [[package]] 142 | name = "heck" 143 | version = "0.3.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 146 | dependencies = [ 147 | "unicode-segmentation", 148 | ] 149 | 150 | [[package]] 151 | name = "hermit-abi" 152 | version = "0.1.19" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 155 | dependencies = [ 156 | "libc", 157 | ] 158 | 159 | [[package]] 160 | name = "home" 161 | version = "0.5.9" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 164 | dependencies = [ 165 | "windows-sys 0.52.0", 166 | ] 167 | 168 | [[package]] 169 | name = "itoa" 170 | version = "1.0.11" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 173 | 174 | [[package]] 175 | name = "lazy_static" 176 | version = "1.4.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 179 | 180 | [[package]] 181 | name = "libc" 182 | version = "0.2.153" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 185 | 186 | [[package]] 187 | name = "linux-raw-sys" 188 | version = "0.4.13" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 191 | 192 | [[package]] 193 | name = "memchr" 194 | version = "2.7.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 197 | 198 | [[package]] 199 | name = "ms" 200 | version = "0.1.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "aa2357cf1951787260d78a39cbfefdda655196ccce6ed9e9697f950fa4dbea42" 203 | dependencies = [ 204 | "regex", 205 | ] 206 | 207 | [[package]] 208 | name = "once_cell" 209 | version = "1.19.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 212 | 213 | [[package]] 214 | name = "paste" 215 | version = "1.0.14" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 218 | 219 | [[package]] 220 | name = "proc-macro-error" 221 | version = "1.0.4" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 224 | dependencies = [ 225 | "proc-macro-error-attr", 226 | "proc-macro2", 227 | "quote", 228 | "syn 1.0.109", 229 | "version_check", 230 | ] 231 | 232 | [[package]] 233 | name = "proc-macro-error-attr" 234 | version = "1.0.4" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 237 | dependencies = [ 238 | "proc-macro2", 239 | "quote", 240 | "version_check", 241 | ] 242 | 243 | [[package]] 244 | name = "proc-macro2" 245 | version = "1.0.79" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 248 | dependencies = [ 249 | "unicode-ident", 250 | ] 251 | 252 | [[package]] 253 | name = "quote" 254 | version = "1.0.35" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 257 | dependencies = [ 258 | "proc-macro2", 259 | ] 260 | 261 | [[package]] 262 | name = "regex" 263 | version = "1.10.4" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 266 | dependencies = [ 267 | "aho-corasick", 268 | "memchr", 269 | "regex-automata", 270 | "regex-syntax", 271 | ] 272 | 273 | [[package]] 274 | name = "regex-automata" 275 | version = "0.4.6" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 278 | dependencies = [ 279 | "aho-corasick", 280 | "memchr", 281 | "regex-syntax", 282 | ] 283 | 284 | [[package]] 285 | name = "regex-syntax" 286 | version = "0.8.3" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 289 | 290 | [[package]] 291 | name = "rustix" 292 | version = "0.38.32" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 295 | dependencies = [ 296 | "bitflags 2.5.0", 297 | "errno", 298 | "libc", 299 | "linux-raw-sys", 300 | "windows-sys 0.52.0", 301 | ] 302 | 303 | [[package]] 304 | name = "ryu" 305 | version = "1.0.17" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 308 | 309 | [[package]] 310 | name = "serde" 311 | version = "1.0.197" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 314 | dependencies = [ 315 | "serde_derive", 316 | ] 317 | 318 | [[package]] 319 | name = "serde_derive" 320 | version = "1.0.197" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 323 | dependencies = [ 324 | "proc-macro2", 325 | "quote", 326 | "syn 2.0.58", 327 | ] 328 | 329 | [[package]] 330 | name = "serde_json" 331 | version = "1.0.115" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" 334 | dependencies = [ 335 | "itoa", 336 | "ryu", 337 | "serde", 338 | ] 339 | 340 | [[package]] 341 | name = "spinoff" 342 | version = "0.8.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "20aa2ed67fbb202e7b716ff8bfc6571dd9301617767380197d701c31124e88f6" 345 | dependencies = [ 346 | "colored", 347 | "once_cell", 348 | "paste", 349 | ] 350 | 351 | [[package]] 352 | name = "strsim" 353 | version = "0.8.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 356 | 357 | [[package]] 358 | name = "structopt" 359 | version = "0.3.26" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 362 | dependencies = [ 363 | "clap", 364 | "lazy_static", 365 | "structopt-derive", 366 | ] 367 | 368 | [[package]] 369 | name = "structopt-derive" 370 | version = "0.4.18" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 373 | dependencies = [ 374 | "heck", 375 | "proc-macro-error", 376 | "proc-macro2", 377 | "quote", 378 | "syn 1.0.109", 379 | ] 380 | 381 | [[package]] 382 | name = "syn" 383 | version = "1.0.109" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 386 | dependencies = [ 387 | "proc-macro2", 388 | "quote", 389 | "unicode-ident", 390 | ] 391 | 392 | [[package]] 393 | name = "syn" 394 | version = "2.0.58" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 397 | dependencies = [ 398 | "proc-macro2", 399 | "quote", 400 | "unicode-ident", 401 | ] 402 | 403 | [[package]] 404 | name = "textwrap" 405 | version = "0.11.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 408 | dependencies = [ 409 | "unicode-width", 410 | ] 411 | 412 | [[package]] 413 | name = "unicode-ident" 414 | version = "1.0.12" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 417 | 418 | [[package]] 419 | name = "unicode-segmentation" 420 | version = "1.11.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 423 | 424 | [[package]] 425 | name = "unicode-width" 426 | version = "0.1.11" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 429 | 430 | [[package]] 431 | name = "vec_map" 432 | version = "0.8.2" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 435 | 436 | [[package]] 437 | name = "version_check" 438 | version = "0.9.4" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 441 | 442 | [[package]] 443 | name = "which" 444 | version = "6.0.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" 447 | dependencies = [ 448 | "either", 449 | "home", 450 | "rustix", 451 | "winsafe", 452 | ] 453 | 454 | [[package]] 455 | name = "winapi" 456 | version = "0.3.9" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 459 | dependencies = [ 460 | "winapi-i686-pc-windows-gnu", 461 | "winapi-x86_64-pc-windows-gnu", 462 | ] 463 | 464 | [[package]] 465 | name = "winapi-i686-pc-windows-gnu" 466 | version = "0.4.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 469 | 470 | [[package]] 471 | name = "winapi-x86_64-pc-windows-gnu" 472 | version = "0.4.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 475 | 476 | [[package]] 477 | name = "windows-sys" 478 | version = "0.48.0" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 481 | dependencies = [ 482 | "windows-targets 0.48.5", 483 | ] 484 | 485 | [[package]] 486 | name = "windows-sys" 487 | version = "0.52.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 490 | dependencies = [ 491 | "windows-targets 0.52.4", 492 | ] 493 | 494 | [[package]] 495 | name = "windows-targets" 496 | version = "0.48.5" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 499 | dependencies = [ 500 | "windows_aarch64_gnullvm 0.48.5", 501 | "windows_aarch64_msvc 0.48.5", 502 | "windows_i686_gnu 0.48.5", 503 | "windows_i686_msvc 0.48.5", 504 | "windows_x86_64_gnu 0.48.5", 505 | "windows_x86_64_gnullvm 0.48.5", 506 | "windows_x86_64_msvc 0.48.5", 507 | ] 508 | 509 | [[package]] 510 | name = "windows-targets" 511 | version = "0.52.4" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 514 | dependencies = [ 515 | "windows_aarch64_gnullvm 0.52.4", 516 | "windows_aarch64_msvc 0.52.4", 517 | "windows_i686_gnu 0.52.4", 518 | "windows_i686_msvc 0.52.4", 519 | "windows_x86_64_gnu 0.52.4", 520 | "windows_x86_64_gnullvm 0.52.4", 521 | "windows_x86_64_msvc 0.52.4", 522 | ] 523 | 524 | [[package]] 525 | name = "windows_aarch64_gnullvm" 526 | version = "0.48.5" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 529 | 530 | [[package]] 531 | name = "windows_aarch64_gnullvm" 532 | version = "0.52.4" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 535 | 536 | [[package]] 537 | name = "windows_aarch64_msvc" 538 | version = "0.48.5" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 541 | 542 | [[package]] 543 | name = "windows_aarch64_msvc" 544 | version = "0.52.4" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 547 | 548 | [[package]] 549 | name = "windows_i686_gnu" 550 | version = "0.48.5" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 553 | 554 | [[package]] 555 | name = "windows_i686_gnu" 556 | version = "0.52.4" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 559 | 560 | [[package]] 561 | name = "windows_i686_msvc" 562 | version = "0.48.5" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 565 | 566 | [[package]] 567 | name = "windows_i686_msvc" 568 | version = "0.52.4" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 571 | 572 | [[package]] 573 | name = "windows_x86_64_gnu" 574 | version = "0.48.5" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 577 | 578 | [[package]] 579 | name = "windows_x86_64_gnu" 580 | version = "0.52.4" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 583 | 584 | [[package]] 585 | name = "windows_x86_64_gnullvm" 586 | version = "0.48.5" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 589 | 590 | [[package]] 591 | name = "windows_x86_64_gnullvm" 592 | version = "0.52.4" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 595 | 596 | [[package]] 597 | name = "windows_x86_64_msvc" 598 | version = "0.48.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 601 | 602 | [[package]] 603 | name = "windows_x86_64_msvc" 604 | version = "0.52.4" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 607 | 608 | [[package]] 609 | name = "winsafe" 610 | version = "0.0.19" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" 613 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Arguments, CustomTaskOptions, GlitterRc}; 2 | use colored::*; 3 | use fancy_regex::Regex; 4 | use inflector::Inflector; 5 | use ms::*; 6 | use spinoff::{spinners, Spinner}; 7 | use std::convert::TryInto; 8 | use std::io::{stdin, Error}; 9 | use std::process::Command; 10 | use std::time::{SystemTime, UNIX_EPOCH}; 11 | 12 | // this is a macro that will return the patterns in match's 13 | macro_rules! match_patterns { 14 | ($val:expr, $patterns_ident:ident, $($p:pat => $e:expr),*) => { 15 | let $patterns_ident = vec![$(stringify!($p)),*]; 16 | match $val { 17 | $($p => $e),* 18 | } 19 | } 20 | } 21 | 22 | fn get_commit_message(config: &GlitterRc, args: &Arguments) -> anyhow::Result { 23 | let splitted = config.commit_message.split('$').skip(1); 24 | 25 | let mut result = String::from(&config.commit_message); 26 | 27 | for val in splitted { 28 | if val.len() >= 2 && String::from(val.chars().nth(1).unwrap()) == *"+" { 29 | let idx = val.chars().next().unwrap().to_digit(10).unwrap() - 1; 30 | let rest = &args.arguments[idx as usize..]; 31 | 32 | if rest.is_empty() { 33 | return Err(anyhow::Error::new(Error::new( 34 | std::io::ErrorKind::InvalidInput, 35 | format!( 36 | "Argument {0} was not provided. Argument {0} is a rest argument.", 37 | val.split("").collect::>()[1] 38 | ), 39 | ))); 40 | } 41 | 42 | result = result.replace( 43 | &format!("${}+", val.split("").collect::>()[1]), 44 | &rest.join(" "), 45 | ); 46 | } else { 47 | let idx = val.split("").nth(1).unwrap().parse::().unwrap() - 1; 48 | 49 | if args.arguments.len() <= idx { 50 | return Err(anyhow::Error::new(Error::new( 51 | std::io::ErrorKind::InvalidInput, 52 | format!( 53 | "Argument {} was not provided.", 54 | val.split("").collect::>()[1] 55 | ), 56 | ))); 57 | } 58 | 59 | let mut val_ = args.arguments[idx].clone(); 60 | if let Some(ref args_) = config.commit_message_arguments { 61 | for arg in args_.iter().as_ref() { 62 | if arg.argument == ((idx + 1) as i32) { 63 | if let Some(v) = arg.case.as_deref() { 64 | // we do this to prevent binding errors 65 | let mut temp_val = val_.clone(); 66 | match v.to_lowercase().as_str() { 67 | "lower" => temp_val = temp_val.to_lowercase(), 68 | "upper" => temp_val = temp_val.to_uppercase(), 69 | "snake" => temp_val = temp_val.to_snake_case(), 70 | "screaming-snake" => temp_val = temp_val.to_screaming_snake_case(), 71 | "kebab" => temp_val = temp_val.to_kebab_case(), 72 | "train" => temp_val = temp_val.to_train_case(), 73 | "sentence" => temp_val = temp_val.to_sentence_case(), 74 | "title" => temp_val = temp_val.to_title_case(), 75 | "pascal" => temp_val = temp_val.to_pascal_case(), 76 | _ => println!("Found invalid case `{}`", v), 77 | } 78 | val_ = temp_val 79 | } 80 | } 81 | } 82 | } 83 | if let Some(ref args_) = config.commit_message_arguments { 84 | for arg in args_.iter().as_ref() { 85 | if arg.argument == ((idx + 1) as i32) { 86 | if let Some(valid_type_enums) = arg.type_enums.as_ref() { 87 | if !valid_type_enums.contains(&val_.to_owned()) { 88 | return Err(anyhow::Error::new(Error::new( 89 | std::io::ErrorKind::InvalidInput, 90 | format!( 91 | "Argument {} did not have a valid type enum. Valid type enums are {}", 92 | val.split("").collect::>()[1], 93 | valid_type_enums.join(", ").red() 94 | ), 95 | ))); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | if let Some(ref args_) = config.commit_message_arguments { 103 | for arg in args_.iter().as_ref() { 104 | if arg.argument == ((idx + 1) as i32) { 105 | if let Some(valid_type_enums) = arg.type_enums.as_ref() { 106 | if !valid_type_enums.contains(&val_.to_owned()) { 107 | return Err(anyhow::Error::new(Error::new( 108 | std::io::ErrorKind::InvalidInput, 109 | format!( 110 | "Argument {} did not have a valid type enum. Valid type enums are {}", 111 | val.split("").collect::>()[1], 112 | valid_type_enums.join(", ").red() 113 | ), 114 | ))); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | let captures = Regex::new(&format!( 122 | "\\${}(?!\\+)", 123 | val.split("").collect::>()[1] 124 | ))?; 125 | 126 | let res = result.clone(); 127 | 128 | // poor mans replace 129 | for _ in 0..captures.captures_iter(&res).count() { 130 | let res = result.clone(); 131 | 132 | let capture = captures 133 | // when we replace, the value changes, so we rerun the logic 134 | .captures_iter(&res) 135 | .collect::>() 136 | .first() 137 | .unwrap() 138 | .as_ref() 139 | .unwrap() 140 | .get(0) 141 | .unwrap(); 142 | result.replace_range(capture.range(), &val_); 143 | } 144 | } 145 | } 146 | Ok(result) 147 | } 148 | 149 | #[allow(clippy::too_many_arguments)] 150 | pub fn push( 151 | config: GlitterRc, 152 | args: Arguments, 153 | dry: bool, 154 | raw: bool, 155 | no_verify: bool, 156 | verbose: bool, 157 | yes: bool, 158 | ) -> anyhow::Result<()> { 159 | let is_git_folder = Command::new("git").arg("status").output()?.status.success(); 160 | if !is_git_folder { 161 | return Err(anyhow::Error::new(Error::new( 162 | std::io::ErrorKind::InvalidInput, 163 | format!("{} This is not a git repository.", "Fatal".red()), 164 | ))); 165 | } 166 | let current_branch = String::from_utf8( 167 | Command::new("git") 168 | .arg("branch") 169 | .arg("--show-current") 170 | .output() 171 | .unwrap() 172 | .stdout, 173 | ) 174 | .unwrap(); 175 | let mut _result = String::new(); 176 | if !raw { 177 | _result = get_commit_message(&config, &args)?; 178 | } else { 179 | let raw_args = args.clone(); 180 | _result = get_commit_message( 181 | &GlitterRc { 182 | commit_message: "$1+".to_owned(), 183 | arguments: Some(vec![args]), 184 | commit_message_arguments: None, 185 | fetch: None, 186 | custom_tasks: None, 187 | __default: None, 188 | hooks: None, 189 | verbose: None, 190 | }, 191 | &raw_args, 192 | )? 193 | } 194 | let mut warnings: Vec = Vec::new(); 195 | if no_verify { 196 | warnings.push("(no-verify)".yellow().to_string()); 197 | } 198 | if dry { 199 | warnings.push("(dry-run)".yellow().to_string()); 200 | } 201 | if config.__default.is_some() { 202 | warnings.push("(default-config)".yellow().to_string()) 203 | } 204 | if raw { 205 | warnings.push("(raw-commit-message)".yellow().to_string()) 206 | } 207 | if verbose { 208 | warnings.push("(verbose)".yellow().to_string()) 209 | } 210 | if yes { 211 | warnings.push("(yes)".yellow().to_string()) 212 | } 213 | println!( 214 | "Commit message: {} {}", 215 | format_args!( 216 | "{}{}{}", 217 | "`".green(), 218 | _result.underline().green(), 219 | "`".green() 220 | ), 221 | warnings.join(" ") 222 | ); 223 | // if they abort the process (cmd+c / ctrl+c), this will error and stop 224 | // if they press enter the command will then start executing git commands 225 | if !dry && !yes { 226 | let mut temp = String::new(); 227 | stdin().read_line(&mut temp)?; 228 | } 229 | 230 | let start = get_current_epoch(); 231 | 232 | if let Some(fetch) = config.fetch { 233 | if fetch { 234 | run_cmd("git", vec!["fetch"], dry, verbose, None); 235 | } 236 | } // glitter hooks 237 | if config.custom_tasks.is_some() 238 | && config.hooks.is_some() 239 | && !config.hooks.clone().unwrap().is_empty() 240 | { 241 | let tasks = &config.custom_tasks.unwrap(); 242 | let task_names = &tasks 243 | .iter() 244 | .map(|task| task.clone().name) 245 | .collect::>(); 246 | let hooks = &config.hooks.unwrap(); 247 | for hook in hooks.clone() { 248 | if !task_names.contains(&hook) { 249 | println!("{} Couldn't find the custom task `{}`", "Fatal".red(), hook); 250 | std::process::exit(1); 251 | } 252 | let custom_task = &tasks.iter().find(|task| task.name == hook); 253 | if let Some(task) = custom_task { 254 | for cmd in task.execute.clone().unwrap() { 255 | if !no_verify { 256 | let splitted = cmd.split(' ').collect::>(); 257 | run_cmd( 258 | splitted.first().unwrap(), 259 | splitted[1..].to_vec(), 260 | dry || no_verify, 261 | verbose, 262 | None, 263 | ); 264 | } 265 | } 266 | } else { 267 | println!("{} Couldn't find the custom task `{}`", "Fatal".red(), hook); 268 | std::process::exit(1); 269 | } 270 | } 271 | } 272 | run_cmd("git", vec!["add", "."], dry, verbose, None); 273 | let mut commit_args = vec!["commit", "-m", &_result]; 274 | if no_verify { 275 | commit_args.push("--no-verify") 276 | } 277 | run_cmd( 278 | "git", 279 | commit_args, 280 | dry, 281 | verbose, 282 | Some(&*format!( 283 | "git commit -m {}{}", 284 | format_args!("{}{}{0}", "`".green(), _result.underline().green()), 285 | if no_verify { " --no-verify" } else { "" } 286 | )), 287 | ); 288 | let mut args = vec!["pull", "origin"]; 289 | args.push(current_branch.split('\n').next().unwrap()); 290 | 291 | if no_verify { 292 | args.push("--no-verify") 293 | }; 294 | run_cmd( 295 | "git", 296 | args.clone(), 297 | dry, 298 | verbose, 299 | Some(&*format!( 300 | "git pull origin {}{}", 301 | args.clone().get(2).unwrap().green().underline(), 302 | if no_verify { " --no-verify" } else { "" } 303 | )), 304 | ); 305 | 306 | let mut args = vec!["push", "origin"]; 307 | args.push(current_branch.split('\n').next().unwrap()); 308 | 309 | if no_verify { 310 | args.push("--no-verify") 311 | }; 312 | run_cmd( 313 | "git", 314 | args.clone(), 315 | dry, 316 | verbose, 317 | Some(&*format!( 318 | "git push origin {}{}", 319 | args.clone().get(2).unwrap().green().underline(), 320 | if no_verify { " --no-verify" } else { "" } 321 | )), 322 | ); 323 | 324 | let end = get_current_epoch(); 325 | if !dry { 326 | println!( 327 | "Completed in {}", 328 | ms::ms!( 329 | (end - start).try_into().expect("Couldn't convert type"), 330 | true 331 | ) 332 | .green() 333 | ); 334 | } 335 | 336 | Ok(()) 337 | } 338 | 339 | pub fn action(input: Vec<&str>) -> anyhow::Result<()> { 340 | // this will sanitize the vec in a sense 341 | // the input has \" \" around the value we want so we remove it 342 | // we also filter out _ from the vec 343 | let actions = input 344 | .into_iter() 345 | .filter_map(|x| x.strip_prefix('"')?.strip_suffix('"')) 346 | .collect::>(); 347 | // log a nice message displaying all the actions 348 | println!( 349 | "Actions available:\n{}", 350 | actions.join(", ").underline().bold() 351 | ); 352 | Ok(()) 353 | } 354 | 355 | pub fn cc(config: GlitterRc, args: Arguments, dry: bool, verbose: bool) -> anyhow::Result<()> { 356 | if args.arguments.first().is_some() { 357 | match_patterns! { &*args.arguments.first().unwrap().to_lowercase(), patterns, 358 | "list" => { 359 | let mut cmds: Vec = vec![]; 360 | if let Some(v) = config.custom_tasks { 361 | cmds = v; 362 | } 363 | let cmd = cmds.into_iter().map(|x| x.name).collect::>(); 364 | println!( 365 | "Custom tasks specified:\n{}", 366 | cmd.join(", ").underline().bold() 367 | ); 368 | }, 369 | 370 | "help" => { 371 | let mut cmds: Vec = vec![]; 372 | if let Some(v) = config.custom_tasks { 373 | cmds = v; 374 | } 375 | let actions = patterns 376 | .into_iter() 377 | .filter_map(|x| x.strip_prefix('"')?.strip_suffix('"')) 378 | .collect::>(); 379 | let cmd = cmds.into_iter().map(|x| x.name).collect::>(); 380 | println!( 381 | "Runnable commands:\n{}\nCustom tasks specified:\n{}\nIf the output of custom tasks is valuable to you, please provide the -v flag when running.", 382 | actions.join(", ").underline().bold(), 383 | cmd.join(", ").underline().bold() 384 | ); 385 | }, 386 | _ => { 387 | let mut cmds: Vec = vec![]; 388 | let mut exec_cmds: Vec = vec![]; 389 | if let Some(v) = config.custom_tasks { 390 | cmds = v.clone(); 391 | exec_cmds = v; 392 | }; 393 | if dry { 394 | println!("{}", 395 | "(dry-run)".yellow() 396 | ); 397 | } 398 | 399 | if cmds.into_iter().map(|x| x.name).any(| 400 | s| s == args.arguments.first().unwrap().to_lowercase()) { 401 | let exec = exec_cmds.into_iter().filter(|x| x.name == args.arguments.first().unwrap().to_lowercase()).map(|x| x.execute); 402 | for task in exec { 403 | let e = task.to_owned().unwrap(); 404 | // because it is a vec, we must do a for loop to get each command & execute if dry is false 405 | for cmd in e { 406 | let splitted = cmd.split(' ').collect::>(); 407 | let command = which::which(splitted.first().unwrap()); 408 | if command.is_err() { 409 | println!("{} Cannot find binary `{}`", "Fatal".red(), splitted.first().unwrap()); 410 | std::process::exit(1); 411 | } 412 | if !dry { 413 | run_cmd( 414 | splitted.first().unwrap(), 415 | splitted[1..].to_vec(), 416 | dry, 417 | verbose, 418 | None, 419 | ); 420 | } 421 | 422 | } 423 | }; 424 | } else { 425 | return Err(anyhow::Error::new(Error::new( 426 | std::io::ErrorKind::InvalidInput, 427 | "That is not a valid custom command / sub command.", 428 | ))); 429 | }; 430 | } 431 | }; 432 | } else { 433 | println!("Try `cc help`") 434 | }; 435 | Ok(()) 436 | } 437 | 438 | pub fn undo(dry: bool, verbose: bool) -> anyhow::Result<()> { 439 | if dry { 440 | println!("{}", "(dry-run)".yellow()); 441 | } 442 | run_cmd("git", vec!["reset", "--soft", "HEAD~1"], dry, verbose, None); 443 | Ok(()) 444 | } 445 | // this is the function behind matching commands (as in actions) 446 | pub fn match_cmds(args: Arguments, config: GlitterRc) -> anyhow::Result<()> { 447 | let cmd = &args.action; 448 | let dry = args.dry(); 449 | let raw_mode = args.raw(); 450 | let no_verify = args.no_verify(); 451 | let verbose = args.verbose(); 452 | let yes = args.yes(); 453 | let verbose = if verbose.provided { 454 | verbose.value 455 | } else { 456 | config.verbose.unwrap_or(false) 457 | }; 458 | // custom macro for the patterns command 459 | match_patterns! { &*cmd.to_lowercase(), patterns, 460 | "push" => push(config, args, dry, raw_mode, no_verify, verbose, yes)?, 461 | "action" => action(patterns)?, 462 | "actions" => action(patterns)?, 463 | "cc" => cc(config, args, dry, verbose)?, 464 | "undo" => undo(dry, verbose)?, 465 | _ => { 466 | let mut cmds: Vec = vec![]; 467 | let mut exec_cmds: Vec = vec![]; 468 | if let Some(v) = config.custom_tasks { 469 | cmds = v.clone(); 470 | exec_cmds = v; 471 | }; 472 | if dry { 473 | println!( 474 | "{}", 475 | "(dry-run)".yellow(), 476 | ); 477 | } 478 | 479 | if cmds.into_iter().map(|x| x.name).any(| 480 | s| s == args.action.to_lowercase()) { 481 | let exec = exec_cmds.into_iter().filter(|x| x.name == args.action.to_lowercase()).map(|x| x.execute); 482 | for task in exec { 483 | let e = task.to_owned().unwrap(); 484 | // because it is a vec, we must do a for loop to get each command & execute if dry is false 485 | for cmd in e { 486 | let splitted = cmd.split(' ').collect::>(); 487 | let command = which::which(splitted.first().unwrap()); 488 | if command.is_err() { 489 | println!("{} Cannot find binary `{}`", "Fatal".red(), splitted.first().unwrap()); 490 | std::process::exit(1); 491 | } 492 | if !dry { 493 | run_cmd( 494 | splitted.first().unwrap(), 495 | splitted[1..].to_vec(), 496 | dry, 497 | verbose, 498 | None, 499 | ); 500 | } 501 | 502 | } 503 | }; 504 | } else { 505 | return Err(anyhow::Error::new(Error::new( 506 | std::io::ErrorKind::InvalidInput, 507 | "This is not a valid action or custom command.", 508 | ))); 509 | }; 510 | 511 | } 512 | }; 513 | Ok(()) 514 | } 515 | 516 | fn run_cmd( 517 | command_name: &str, 518 | args: Vec<&str>, 519 | dry: bool, 520 | verbose: bool, 521 | spinner_message: Option<&str>, 522 | ) { 523 | let start = get_current_epoch(); 524 | let text = if let Some(msg) = spinner_message { 525 | format!( 526 | "{}{}", 527 | "$".green().bold(), 528 | if msg.starts_with(' ') { 529 | msg.to_string() 530 | } else { 531 | format!(" {}", msg) 532 | } 533 | ) 534 | } else { 535 | format!( 536 | "{} {} {}", 537 | "$".bold().green(), 538 | command_name, 539 | &args.join(" ") 540 | ) 541 | }; 542 | let mut spinner = Spinner::new(spinners::Dots, text.clone(), None); 543 | 544 | if !dry { 545 | let cmd_path_result = which::which(command_name); 546 | if cmd_path_result.is_err() { 547 | spinner.fail("Cannot find binary"); 548 | println!("{} Cannot find binary `{}`", "Fatal".red(), command_name); 549 | std::process::exit(1); 550 | } 551 | let cmd_path = cmd_path_result.unwrap(); 552 | let mut command = Command::new(cmd_path); 553 | command.args(&args); 554 | command.envs(std::env::vars()); 555 | let output = command 556 | .stdout(std::process::Stdio::piped()) 557 | .output() 558 | .unwrap(); 559 | if output.status.success() && !dry { 560 | spinner.success( 561 | format!( 562 | "{} {}", 563 | text, 564 | ms::ms!( 565 | (get_current_epoch() - start) 566 | .try_into() 567 | .expect("MS conversion didn't work."), 568 | true 569 | ) 570 | .truecolor(79, 88, 109) 571 | ) 572 | .as_str(), 573 | ); 574 | } else { 575 | if let Some(p) = args.first() { 576 | if p == &"pull" 577 | && (String::from_utf8_lossy(&output.stdout) 578 | .contains("fatal: couldn't find remote ref") 579 | || String::from_utf8_lossy(&output.stderr) 580 | .contains("fatal: couldn't find remote ref")) 581 | { 582 | spinner.warn( 583 | format!( 584 | "{} {} {}", 585 | text, 586 | ms::ms!( 587 | (get_current_epoch() - start) 588 | .try_into() 589 | .expect("MS conversion didn't work."), 590 | true 591 | ) 592 | .truecolor(79, 88, 109), 593 | "| This branch does not exist on the remote repository." 594 | .truecolor(79, 88, 109) 595 | ) 596 | .as_str(), 597 | ); 598 | return; 599 | } 600 | } 601 | spinner.fail("Command failed to run"); 602 | println!("{}", String::from_utf8_lossy(&output.stdout)); 603 | println!("{}", String::from_utf8_lossy(&output.stderr)); 604 | 605 | std::process::exit(1); 606 | } 607 | if verbose { 608 | println!("{}", String::from_utf8_lossy(&output.stdout)); 609 | } 610 | } else { 611 | spinner.success(text.as_str()); 612 | } 613 | } 614 | 615 | fn get_current_epoch() -> u128 { 616 | let start = SystemTime::now(); 617 | let since_the_epoch = start 618 | .duration_since(UNIX_EPOCH) 619 | .expect("Time went backwards"); 620 | since_the_epoch.as_millis() 621 | } 622 | 623 | // tests 624 | #[cfg(test)] 625 | mod tests { 626 | use crate::cli::action; 627 | use crate::match_cmds; 628 | use std::path::PathBuf; 629 | 630 | use crate::config::{Arguments, CommitMessageArguments, CustomTaskOptions, GlitterRc}; 631 | 632 | use super::get_commit_message; 633 | 634 | #[test] 635 | fn basic() { 636 | let args = Arguments { 637 | action: "push".to_string(), 638 | arguments: vec![ 639 | "test".to_string(), 640 | "a".to_string(), 641 | "b".to_string(), 642 | "c".to_string(), 643 | ], 644 | rc_path: PathBuf::new(), 645 | dry: Some(Some(false)), 646 | raw: Some(Some(false)), 647 | no_verify: Some(Some(false)), 648 | verbose: Some(Some(false)), 649 | yes: None, 650 | }; 651 | 652 | let config = GlitterRc { 653 | commit_message: "$1($2): $3+".to_string(), 654 | arguments: None, 655 | commit_message_arguments: None, 656 | fetch: None, 657 | custom_tasks: Some(vec![CustomTaskOptions { 658 | name: "fmt".to_owned(), 659 | execute: Some(vec!["cargo fmt".to_owned()]), 660 | }]), 661 | __default: None, 662 | hooks: None, 663 | verbose: None, 664 | }; 665 | 666 | assert_eq!(get_commit_message(&config, &args).unwrap(), "test(a): b c") 667 | } 668 | 669 | #[test] 670 | fn reuse_arguments() { 671 | let args = Arguments { 672 | action: "push".to_string(), 673 | arguments: vec![ 674 | "test".to_string(), 675 | "a".to_string(), 676 | "b".to_string(), 677 | "c".to_string(), 678 | ], 679 | rc_path: PathBuf::new(), 680 | dry: Some(Some(false)), 681 | raw: Some(Some(false)), 682 | no_verify: Some(Some(false)), 683 | verbose: Some(Some(false)), 684 | yes: None, 685 | }; 686 | 687 | let config = GlitterRc { 688 | commit_message: "$1($2): $3+ : $2 | $1+".to_string(), 689 | arguments: None, 690 | commit_message_arguments: None, 691 | fetch: None, 692 | custom_tasks: Some(vec![CustomTaskOptions { 693 | name: "fmt".to_owned(), 694 | execute: Some(vec!["cargo fmt".to_owned()]), 695 | }]), 696 | __default: None, 697 | hooks: None, 698 | verbose: None, 699 | }; 700 | 701 | assert_eq!( 702 | get_commit_message(&config, &args).unwrap(), 703 | "test(a): b c : a | test a b c" 704 | ) 705 | } 706 | 707 | #[test] 708 | fn less_than_required_args() { 709 | let args = Arguments { 710 | action: "push".to_string(), 711 | arguments: vec!["test".to_string(), "a".to_string()], 712 | rc_path: PathBuf::new(), 713 | dry: Some(Some(false)), 714 | raw: Some(Some(false)), 715 | no_verify: Some(Some(false)), 716 | verbose: Some(Some(false)), 717 | yes: None, 718 | }; 719 | 720 | let args_2 = Arguments { 721 | action: "push".to_string(), 722 | arguments: vec!["test".to_string()], 723 | rc_path: PathBuf::new(), 724 | dry: Some(Some(false)), 725 | raw: Some(Some(false)), 726 | no_verify: Some(Some(false)), 727 | verbose: Some(Some(false)), 728 | yes: None, 729 | }; 730 | 731 | let config = GlitterRc { 732 | commit_message: "$1($2): $3+".to_string(), 733 | arguments: None, 734 | commit_message_arguments: None, 735 | fetch: None, 736 | custom_tasks: Some(vec![CustomTaskOptions { 737 | name: "fmt".to_owned(), 738 | execute: Some(vec!["cargo fmt".to_owned()]), 739 | }]), 740 | __default: None, 741 | hooks: None, 742 | verbose: None, 743 | }; 744 | 745 | let config_2 = GlitterRc { 746 | commit_message: "$1($2): $3+".to_string(), 747 | arguments: None, 748 | commit_message_arguments: None, 749 | fetch: None, 750 | custom_tasks: Some(vec![CustomTaskOptions { 751 | name: "fmt".to_owned(), 752 | execute: Some(vec!["cargo fmt".to_owned()]), 753 | }]), 754 | __default: None, 755 | hooks: None, 756 | verbose: None, 757 | }; 758 | 759 | assert!(get_commit_message(&config, &args).is_err()); 760 | assert!(get_commit_message(&config_2, &args_2).is_err()); 761 | } 762 | 763 | #[test] 764 | fn no_commit_message_format() { 765 | let args = Arguments { 766 | action: "push".to_string(), 767 | arguments: vec!["test".to_string(), "a".to_string()], 768 | rc_path: PathBuf::new(), 769 | dry: Some(Some(false)), 770 | raw: Some(Some(false)), 771 | no_verify: Some(Some(false)), 772 | verbose: Some(Some(false)), 773 | yes: None, 774 | }; 775 | 776 | let config = GlitterRc { 777 | // "$1+" is the default 778 | commit_message: "$1+".to_string(), 779 | arguments: None, 780 | commit_message_arguments: None, 781 | fetch: None, 782 | custom_tasks: Some(vec![CustomTaskOptions { 783 | name: "fmt".to_owned(), 784 | execute: Some(vec!["cargo fmt".to_owned()]), 785 | }]), 786 | __default: None, 787 | hooks: None, 788 | verbose: None, 789 | }; 790 | 791 | assert!(get_commit_message(&config, &args).is_ok()) 792 | } 793 | 794 | #[test] 795 | fn commit_message_arguments() { 796 | let args = Arguments { 797 | action: "push".to_string(), 798 | arguments: vec!["feat".to_string(), "test".to_string(), "tests".to_string()], 799 | rc_path: PathBuf::new(), 800 | dry: Some(Some(false)), 801 | raw: Some(Some(false)), 802 | no_verify: Some(Some(false)), 803 | verbose: Some(Some(false)), 804 | yes: None, 805 | }; 806 | 807 | let config = GlitterRc { 808 | commit_message: "$1: $2: $3+".to_string(), 809 | arguments: None, 810 | commit_message_arguments: Some(vec![CommitMessageArguments { 811 | argument: 1, 812 | case: Some("snake".to_string()), 813 | type_enums: Some(vec![ 814 | "fix".to_owned(), 815 | "feat".to_owned(), 816 | "chore".to_owned(), 817 | ]), 818 | }]), 819 | fetch: None, 820 | custom_tasks: Some(vec![CustomTaskOptions { 821 | name: "fmt".to_owned(), 822 | execute: Some(vec!["cargo fmt".to_owned()]), 823 | }]), 824 | __default: None, 825 | hooks: None, 826 | verbose: None, 827 | }; 828 | 829 | assert_eq!( 830 | get_commit_message(&config, &args).unwrap(), 831 | "feat: test: tests" 832 | ) 833 | } 834 | 835 | #[test] 836 | fn test_action() { 837 | assert!(action(vec!["test"]).is_ok()) 838 | } 839 | 840 | #[test] 841 | fn matching_cmds() { 842 | let args = Arguments { 843 | action: "action".to_string(), 844 | arguments: vec![ 845 | "test".to_string(), 846 | "a".to_string(), 847 | "b".to_string(), 848 | "c".to_string(), 849 | ], 850 | rc_path: PathBuf::new(), 851 | dry: Some(Some(false)), 852 | raw: Some(Some(false)), 853 | no_verify: Some(Some(false)), 854 | verbose: Some(Some(false)), 855 | yes: None, 856 | }; 857 | 858 | let config = GlitterRc { 859 | commit_message: "$1($2): $3+".to_string(), 860 | arguments: None, 861 | commit_message_arguments: None, 862 | fetch: None, 863 | custom_tasks: Some(vec![CustomTaskOptions { 864 | name: "fmt".to_owned(), 865 | execute: Some(vec!["cargo fmt".to_owned()]), 866 | }]), 867 | __default: None, 868 | hooks: None, 869 | verbose: None, 870 | }; 871 | 872 | assert!(match_cmds(args, config).is_ok()); 873 | 874 | let args = Arguments { 875 | action: "actions".to_string(), 876 | arguments: vec![ 877 | "test".to_string(), 878 | "a".to_string(), 879 | "b".to_string(), 880 | "c".to_string(), 881 | ], 882 | rc_path: PathBuf::new(), 883 | dry: Some(Some(false)), 884 | raw: Some(Some(false)), 885 | no_verify: Some(Some(false)), 886 | verbose: Some(Some(false)), 887 | yes: None, 888 | }; 889 | 890 | let config = GlitterRc { 891 | commit_message: "$1($2): $3+".to_string(), 892 | arguments: None, 893 | commit_message_arguments: None, 894 | fetch: None, 895 | custom_tasks: Some(vec![CustomTaskOptions { 896 | name: "fmt".to_owned(), 897 | execute: Some(vec!["cargo fmt".to_owned()]), 898 | }]), 899 | __default: None, 900 | hooks: None, 901 | verbose: None, 902 | }; 903 | 904 | assert!(match_cmds(args, config).is_ok()); 905 | 906 | let args = Arguments { 907 | action: "fasdafsfsa".to_string(), 908 | arguments: vec![ 909 | "test".to_string(), 910 | "a".to_string(), 911 | "b".to_string(), 912 | "c".to_string(), 913 | ], 914 | rc_path: PathBuf::new(), 915 | dry: Some(Some(false)), 916 | raw: Some(Some(false)), 917 | no_verify: Some(Some(false)), 918 | verbose: Some(Some(false)), 919 | yes: None, 920 | }; 921 | 922 | let config = GlitterRc { 923 | commit_message: "$1($2): $3+".to_string(), 924 | arguments: None, 925 | commit_message_arguments: None, 926 | fetch: None, 927 | custom_tasks: Some(vec![CustomTaskOptions { 928 | name: "fmt".to_owned(), 929 | execute: Some(vec!["cargo fmt".to_owned()]), 930 | }]), 931 | __default: None, 932 | hooks: None, 933 | verbose: None, 934 | }; 935 | 936 | assert!(match_cmds(args, config).is_err()); 937 | } 938 | } 939 | --------------------------------------------------------------------------------