Compare commits
37 Commits
Author | SHA1 | Date |
---|---|---|
Bauke | 87c53d5e09 | |
Bauke | dff366f9a0 | |
Bauke | 595b8c25c1 | |
Bauke | caa158ce9f | |
Bauke | b98426a993 | |
Bauke | d12895184b | |
Bauke | 9a4299e5a1 | |
Bauke | 9e7dd2e3dd | |
Bauke | e7df35a765 | |
Bauke | a9ee3c20fe | |
Bauke | 6b6c48c476 | |
Bauke | 0634e86b3d | |
Bauke | f0030fd57f | |
Bauke | df397b2cda | |
Bauke | 260bcb7ca5 | |
Bauke | ba6d4fb0d1 | |
Bauke | d031374360 | |
Bauke | 762051116e | |
Bauke | 15dcca32b0 | |
Bauke | 3da03abe16 | |
Bauke | 6512eaac5a | |
Bauke | 0d09e2e086 | |
Bauke | 47b1b7ec51 | |
Bauke | 5d26a72c8b | |
Bauke | 4a4974fa35 | |
Bauke | e4ed623e64 | |
Bauke | 651699c40a | |
Bauke | da0e38e3bb | |
Bauke | 4ba723d5cc | |
Bauke | a8f587f43a | |
Bauke | 82cbc580dc | |
Bauke | 5bb3a282c3 | |
Bauke | a4d0431a32 | |
Bauke | 8d1baaf57f | |
Bauke | 1a79f9e051 | |
Bauke | 9c11ae3db6 | |
Bauke | aed22fad90 |
|
@ -1,9 +1,7 @@
|
||||||
# Generated by Cargo
|
.direnv/
|
||||||
debug/
|
.vscode/
|
||||||
target/
|
|
||||||
|
|
||||||
# Code coverage results
|
|
||||||
coverage/
|
coverage/
|
||||||
|
debug/
|
||||||
# mdBook output
|
|
||||||
hooked-book/book/
|
hooked-book/book/
|
||||||
|
outputs/
|
||||||
|
target/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,3 +3,11 @@ members = [
|
||||||
"hooked-cli",
|
"hooked-cli",
|
||||||
"hooked-config"
|
"hooked-config"
|
||||||
]
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
missing_docs_in_private_items = "warn"
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
missing_docs = "warn"
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
name = "Cargo Complete Check"
|
name = "Cargo Complete Check"
|
||||||
command = "cargo make complete-check"
|
command = "cargo make complete-check"
|
||||||
|
|
||||||
|
[[pre_commit]]
|
||||||
|
name = "Typos"
|
||||||
|
command = "typos"
|
||||||
|
|
|
@ -1,32 +1,18 @@
|
||||||
[env]
|
[env]
|
||||||
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||||
|
|
||||||
[tasks.fmt]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["fmt", "${@}"]
|
|
||||||
|
|
||||||
[tasks.check]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["check", "${@}"]
|
|
||||||
|
|
||||||
[tasks.clippy]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["clippy", "${@}"]
|
|
||||||
|
|
||||||
[tasks.test]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["test", "${@}"]
|
|
||||||
|
|
||||||
[tasks.doc]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["doc", "${@}"]
|
|
||||||
|
|
||||||
[tasks.build]
|
|
||||||
command = "cargo"
|
|
||||||
args = ["build", "${@}"]
|
|
||||||
|
|
||||||
[tasks.complete-check]
|
[tasks.complete-check]
|
||||||
dependencies = ["fmt", "check", "clippy", "test", "doc", "build"]
|
dependencies = [
|
||||||
|
"format",
|
||||||
|
"check",
|
||||||
|
"clippy",
|
||||||
|
"test",
|
||||||
|
"code-coverage",
|
||||||
|
"docs",
|
||||||
|
"build",
|
||||||
|
"audit-flow",
|
||||||
|
"outdated-flow",
|
||||||
|
]
|
||||||
|
|
||||||
[tasks.code-coverage]
|
[tasks.code-coverage]
|
||||||
workspace = false
|
workspace = false
|
||||||
|
@ -38,7 +24,7 @@ args = [
|
||||||
"--out=html",
|
"--out=html",
|
||||||
"--output-dir=coverage",
|
"--output-dir=coverage",
|
||||||
"--skip-clean",
|
"--skip-clean",
|
||||||
"--target-dir=target/tarpaulin"
|
"--target-dir=target/tarpaulin",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks.book]
|
[tasks.book]
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
> **Git hooks manager.**
|
> **Git hooks manager.**
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
See [hooked.holllo.org](https://hooked.holllo.org) for documentation.
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
|
|
||||||
Found a problem or want to request a new feature? Email [helllo@holllo.org](mailto:helllo@holllo.org) and I'll see what I can do for you.
|
Found a problem or want to request a new feature? Email [helllo@holllo.org](mailto:helllo@holllo.org) and I'll see what I can do for you.
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705309234,
|
||||||
|
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681202837,
|
||||||
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705635624,
|
||||||
|
"narHash": "sha256-DU0schxQOtBNO1c9hUsgYl+QMOXQMfRT7Qw/mg+ayno=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "4471857c0a4a8a0ffc7bdbeaf1b998746ce12a82",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681358109,
|
||||||
|
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705630663,
|
||||||
|
"narHash": "sha256-f+kcR17ZtwMyCEtNAfpD0Mv6qObNKoJ41l+6deoaXi8=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "47cac072a313d9cce884b9ea418d2bf712fa23dd",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
overlays = [ (import rust-overlay) ];
|
||||||
|
pkgs = import nixpkgs { inherit system overlays; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = import ./shell.nix { inherit pkgs; };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
This file is automatically generated using the cli-reference subcommand.
|
||||||
|
Use `cargo run -- cli-reference` to generate it.
|
||||||
|
|
||||||
|
// ANCHOR: install
|
||||||
|
$ hooked install --help
|
||||||
|
Install Hooked into ".git/hooks"
|
||||||
|
|
||||||
|
Usage: hooked install [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--overwrite Overwrite existing files
|
||||||
|
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
||||||
|
-h, --help Print help information
|
||||||
|
-V, --version Print version information
|
||||||
|
// ANCHOR_END: install
|
||||||
|
// ANCHOR: run
|
||||||
|
$ hooked run --help
|
||||||
|
Manually run hooks
|
||||||
|
|
||||||
|
Usage: hooked run [OPTIONS] <HOOK_TYPE>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<HOOK_TYPE> The hook type to run [possible values: pre-commit]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
||||||
|
-h, --help Print help information
|
||||||
|
-V, --version Print version information
|
||||||
|
// ANCHOR_END: run
|
||||||
|
// ANCHOR: uninstall
|
||||||
|
$ hooked uninstall --help
|
||||||
|
Remove installed hooks
|
||||||
|
|
||||||
|
Usage: hooked uninstall [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--all Remove hooks not installed by Hooked
|
||||||
|
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
||||||
|
-h, --help Print help information
|
||||||
|
-V, --version Print version information
|
||||||
|
// ANCHOR_END: uninstall
|
||||||
|
|
|
@ -3,16 +3,7 @@
|
||||||
The `install` command creates the scripts inside `.git/hooks`.
|
The `install` command creates the scripts inside `.git/hooks`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ hooked install --help
|
{{#include ../cli-reference.txt:install}}
|
||||||
Install Hooked into ".git/hooks"
|
|
||||||
|
|
||||||
Usage: hooked install [OPTIONS]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--overwrite Overwrite existing files
|
|
||||||
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
|
||||||
-h, --help Print help information
|
|
||||||
-V, --version Print version information
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Below is the default script template that Hooked uses, where `hook_type` is the type of hook to run (like `pre-commit`) and `config_path` is the `general.config` field from the parsed configuration.
|
Below is the default script template that Hooked uses, where `hook_type` is the type of hook to run (like `pre-commit`) and `config_path` is the `general.config` field from the parsed configuration.
|
||||||
|
@ -20,3 +11,7 @@ Below is the default script template that Hooked uses, where `hook_type` is the
|
||||||
```sh
|
```sh
|
||||||
{{#include ../../../hooked-cli/source/templates/default.sh}}
|
{{#include ../../../hooked-cli/source/templates/default.sh}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can provide your own template by using the `general.template` configuration setting. If you do, make sure you include a line somewhere that says `# Installed by Hooked.` for the [uninstall CLI command][cli-uninstall].
|
||||||
|
|
||||||
|
[cli-uninstall]: ./uninstall.md
|
||||||
|
|
|
@ -2,17 +2,6 @@
|
||||||
|
|
||||||
The `run` command manually runs configured hooks.
|
The `run` command manually runs configured hooks.
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ hooked run --help
|
{{#include ../cli-reference.txt:run}}
|
||||||
Manually run hooks
|
|
||||||
|
|
||||||
Usage: hooked run [OPTIONS] <HOOK_TYPE>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<HOOK_TYPE> The hook type to run [possible values: pre-commit]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
|
||||||
-h, --help Print help information
|
|
||||||
-V, --version Print version information
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,16 +3,7 @@
|
||||||
The `uninstall` command removes script files inside `.git/hooks`.
|
The `uninstall` command removes script files inside `.git/hooks`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
hooked uninstall --help
|
{{#include ../cli-reference.txt:uninstall}}
|
||||||
Remove installed hooks
|
|
||||||
|
|
||||||
Usage: hooked uninstall [OPTIONS]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--all Remove hooks not installed by Hooked
|
|
||||||
-c, --config <CONFIG> Path to a Hooked configuration [default: Hooked.toml]
|
|
||||||
-h, --help Print help information
|
|
||||||
-V, --version Print version information
|
|
||||||
```
|
```
|
||||||
|
|
||||||
By default Hooked will only remove scripts that have a `# Installed by Hooked.` line in them, using `--all` however will remove all script files.
|
By default Hooked will only remove scripts that have a `# Installed by Hooked.` line in them, using `--all` however will remove all script files.
|
||||||
|
|
|
@ -10,11 +10,13 @@ The `general` [table][toml-table] is for main Hooked configuration.
|
||||||
|-----|------|---------|-------------|
|
|-----|------|---------|-------------|
|
||||||
| config | String | Hooked.toml | The configuration file to use. If your configuration file isn't `Hooked.toml` you should set this accordingly. |
|
| config | String | Hooked.toml | The configuration file to use. If your configuration file isn't `Hooked.toml` you should set this accordingly. |
|
||||||
| directory | String | hooks | The directory Hooked looks in for anything related to files. For example: scripts, templates, etc. |
|
| directory | String | hooks | The directory Hooked looks in for anything related to files. For example: scripts, templates, etc. |
|
||||||
|
| template | Optional string | | Path to a custom template to be used when installing scripts. See the [install CLI command][cli-install] for more details. |
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[general]
|
[general]
|
||||||
config = "Hooked.toml"
|
config = "Hooked.toml"
|
||||||
directory = "hooks"
|
directory = "hooks"
|
||||||
|
template = "template.sh"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pre-commit
|
## Pre-commit
|
||||||
|
@ -27,6 +29,7 @@ Pre-commit hooks are defined using `pre_commit` [arrays of tables][toml-arrays-o
|
||||||
| command[^command-and-script] | String | | A command to run when the hook is called. |
|
| command[^command-and-script] | String | | A command to run when the hook is called. |
|
||||||
| script[^command-and-script] | String | | A script to run when the hook is called. This script should be executable and be located inside the configured general directory. |
|
| script[^command-and-script] | String | | A script to run when the hook is called. This script should be executable and be located inside the configured general directory. |
|
||||||
| on_failure | String | stop | What to do when the hook task returns a non-zero status code. Can be either "continue" or "stop". |
|
| on_failure | String | stop | What to do when the hook task returns a non-zero status code. Can be either "continue" or "stop". |
|
||||||
|
| staged | Optional list of strings | | A list of [globs][globset-docs] that will be checked against staged files. If none of the globs match the hook will be skipped. With no globs defined at all the hook will always run. |
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
|
@ -37,11 +40,14 @@ command = "echo \"Hey, $USER!\""
|
||||||
name = "Script Example"
|
name = "Script Example"
|
||||||
script = "example.sh"
|
script = "example.sh"
|
||||||
on_failure = "continue"
|
on_failure = "continue"
|
||||||
|
staged = ["*.txt"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Footnotes
|
## Footnotes
|
||||||
|
|
||||||
[^command-and-script]: When both a command and script are defined in a hook, *only* the command will be run.
|
[^command-and-script]: When both a command and script are defined in a hook, *only* the command will be run.
|
||||||
|
|
||||||
|
[cli-install]: ../cli/install.md
|
||||||
|
[globset-docs]: https://docs.rs/globset/0.4.9/globset/#syntax
|
||||||
[toml-table]: https://toml.io/en/v1.0.0#table
|
[toml-table]: https://toml.io/en/v1.0.0#table
|
||||||
[toml-arrays-of-tables]: https://toml.io/en/v1.0.0#array-of-tables
|
[toml-arrays-of-tables]: https://toml.io/en/v1.0.0#array-of-tables
|
||||||
|
|
|
@ -15,4 +15,13 @@ cargo install hooked-cli
|
||||||
|
|
||||||
Precompiled `x86_64-unknown-linux-gnu` binaries are available on the [Releases page][releases].
|
Precompiled `x86_64-unknown-linux-gnu` binaries are available on the [Releases page][releases].
|
||||||
|
|
||||||
|
## Debian & Derivatives
|
||||||
|
|
||||||
|
An amd64 `.deb` file is available on the [Releases page][releases].
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Don't forget to change <version>.
|
||||||
|
dpkg --install hooked-cli_<version>_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
[releases]: https://git.bauke.xyz/Holllo/hooked/releases
|
[releases]: https://git.bauke.xyz/Holllo/hooked/releases
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hooked-cli"
|
name = "hooked-cli"
|
||||||
description = "Git hooks manager."
|
description = "Git hooks manager."
|
||||||
|
documentation = "https://hooked.holllo.org"
|
||||||
|
homepage = "https://hooked.holllo.org"
|
||||||
repository = "https://git.bauke.xyz/Holllo/hooked"
|
repository = "https://git.bauke.xyz/Holllo/hooked"
|
||||||
|
readme = "../README.md"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Holllo <helllo@holllo.org>"]
|
authors = ["Holllo <helllo@holllo.org>"]
|
||||||
|
@ -11,22 +14,27 @@ edition = "2021"
|
||||||
name = "hooked"
|
name = "hooked"
|
||||||
path = "source/main.rs"
|
path = "source/main.rs"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
owo-colors = "3.5.0"
|
globset = "0.4.14"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
owo-colors = "4.0.0"
|
||||||
subprocess = "0.2.9"
|
subprocess = "0.2.9"
|
||||||
supports-color = "1.3.0"
|
supports-color = "2.1.0"
|
||||||
tera = "1.17.1"
|
tera = "1.19.1"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
version = "4.0.18"
|
version = "4.4.18"
|
||||||
|
|
||||||
[dependencies.hooked-config]
|
[dependencies.hooked-config]
|
||||||
path = "../hooked-config"
|
path = "../hooked-config"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0.5"
|
assert_cmd = "2.0.13"
|
||||||
insta = "1.21.0"
|
insta = "1.34.0"
|
||||||
test-case = "2.2.2"
|
test-case = "3.3.1"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
LICENSE
|
|
@ -0,0 +1,52 @@
|
||||||
|
//! The `cli-reference` subcommand, only available in debug mode.
|
||||||
|
|
||||||
|
use std::{fs::write, process::Command, str};
|
||||||
|
|
||||||
|
use {
|
||||||
|
color_eyre::Result,
|
||||||
|
hooked_config::Config,
|
||||||
|
tera::{Context, Tera},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::cli::CliReferenceArgs;
|
||||||
|
|
||||||
|
/// The CLI reference template.
|
||||||
|
const REFERENCE_TEMPLATE: &str = include_str!("../templates/cli-reference.txt");
|
||||||
|
|
||||||
|
/// The `cli-reference` subcommand.
|
||||||
|
pub fn hooked_cli_reference(
|
||||||
|
_config: Config,
|
||||||
|
args: CliReferenceArgs,
|
||||||
|
) -> Result<()> {
|
||||||
|
let out_path = if args.output.is_dir() {
|
||||||
|
args.output.join("cli-reference.txt")
|
||||||
|
} else {
|
||||||
|
args.output
|
||||||
|
};
|
||||||
|
|
||||||
|
let commands = {
|
||||||
|
let mut commands = vec![];
|
||||||
|
let commands_to_document = &["install", "run", "uninstall"];
|
||||||
|
|
||||||
|
for command_name in commands_to_document {
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.env("NO_COLOR", "1")
|
||||||
|
.args(["run", "-q", "--", command_name, "--help"])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let usage = str::from_utf8(&output.stdout).unwrap().trim().to_string();
|
||||||
|
commands.push((command_name.to_string(), usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
commands
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("commands", &commands);
|
||||||
|
write(
|
||||||
|
out_path,
|
||||||
|
Tera::one_off(REFERENCE_TEMPLATE, &context, false)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
//! The `install` subcommand.
|
||||||
|
use std::{
|
||||||
|
fs::{read_to_string, set_permissions, write, Permissions},
|
||||||
|
os::unix::fs::PermissionsExt,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use {
|
||||||
|
color_eyre::{eyre::eyre, Result},
|
||||||
|
hooked_config::Config,
|
||||||
|
tera::{Context, Tera},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{cli::InstallArgs, DEFAULT_TEMPLATE, HOOK_TYPES};
|
||||||
|
|
||||||
|
/// The `install` subcommand.
|
||||||
|
pub fn hooked_install(config: Config, args: InstallArgs) -> Result<()> {
|
||||||
|
let silent = args.silent;
|
||||||
|
let git_hooks_dir = PathBuf::from(".git/hooks/");
|
||||||
|
if !git_hooks_dir.exists() {
|
||||||
|
return Err(eyre!("The \".git/hooks/\" directory does not exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hooked_directory = config.general.directory;
|
||||||
|
for hook_type in HOOK_TYPES {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("config_path", &config.general.config);
|
||||||
|
context.insert("hook_type", hook_type);
|
||||||
|
|
||||||
|
let hook_path = git_hooks_dir.join(hook_type);
|
||||||
|
if hook_path.exists() && !args.overwrite {
|
||||||
|
if !silent {
|
||||||
|
println!(
|
||||||
|
"{:?} exists, use --overwrite to replace the existing file",
|
||||||
|
hook_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = match config.general.template.as_ref() {
|
||||||
|
Some(template_path) => {
|
||||||
|
read_to_string(hooked_directory.join(template_path))?
|
||||||
|
}
|
||||||
|
None => DEFAULT_TEMPLATE.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
write(&hook_path, Tera::one_off(&template, &context, false)?)?;
|
||||||
|
set_permissions(hook_path, Permissions::from_mode(0o775))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -2,11 +2,22 @@
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use {
|
||||||
|
clap::{Args as Arguments, Parser, Subcommand},
|
||||||
|
hooked_config::NoiseLevel,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
mod cli_reference;
|
||||||
|
mod install;
|
||||||
mod run;
|
mod run;
|
||||||
|
mod uninstall;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub use cli_reference::hooked_cli_reference;
|
||||||
|
pub use install::hooked_install;
|
||||||
pub use run::hooked_run;
|
pub use run::hooked_run;
|
||||||
|
pub use uninstall::hooked_uninstall;
|
||||||
|
|
||||||
/// CLI arguments struct using [`clap::Parser`].
|
/// CLI arguments struct using [`clap::Parser`].
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -26,23 +37,55 @@ pub struct Args {
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum MainSubcommands {
|
pub enum MainSubcommands {
|
||||||
/// Install Hooked into ".git/hooks".
|
/// Install Hooked into ".git/hooks".
|
||||||
Install {
|
Install(InstallArgs),
|
||||||
/// Overwrite existing files.
|
|
||||||
#[clap(long)]
|
|
||||||
overwrite: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Remove installed hooks.
|
/// Remove installed hooks.
|
||||||
Uninstall {
|
Uninstall(UninstallArgs),
|
||||||
/// Remove hooks not installed by Hooked.
|
|
||||||
#[clap(long)]
|
|
||||||
all: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Manually run hooks.
|
/// Manually run hooks.
|
||||||
Run {
|
Run(RunArgs),
|
||||||
/// The hook type to run.
|
|
||||||
#[clap(value_parser = crate::HOOK_TYPES)]
|
#[cfg(debug_assertions)]
|
||||||
hook_type: String,
|
/// Generate the CLI reference file for the mdBook.
|
||||||
},
|
CliReference(CliReferenceArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `install` subcommand arguments.
|
||||||
|
#[derive(Debug, Arguments)]
|
||||||
|
pub struct InstallArgs {
|
||||||
|
/// Overwrite existing files.
|
||||||
|
#[clap(long)]
|
||||||
|
pub overwrite: bool,
|
||||||
|
|
||||||
|
/// Don't output any information.
|
||||||
|
#[clap(long)]
|
||||||
|
pub silent: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `uninstall` subcommand arguments.
|
||||||
|
#[derive(Debug, Arguments)]
|
||||||
|
pub struct UninstallArgs {
|
||||||
|
/// Remove hooks not installed by Hooked.
|
||||||
|
#[clap(long)]
|
||||||
|
pub all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `run` subcommand arguments.
|
||||||
|
#[derive(Debug, Arguments)]
|
||||||
|
pub struct RunArgs {
|
||||||
|
/// The hook type to run.
|
||||||
|
#[clap(value_parser = crate::HOOK_TYPES)]
|
||||||
|
pub hook_type: String,
|
||||||
|
|
||||||
|
/// The noise level to override for all hooks.
|
||||||
|
#[clap(long)]
|
||||||
|
pub noise_level: Option<NoiseLevel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `cli-reference` subcommand arguments.
|
||||||
|
#[derive(Debug, Arguments)]
|
||||||
|
pub struct CliReferenceArgs {
|
||||||
|
/// Path where the CLI reference file should be generated.
|
||||||
|
#[clap(short, long, default_value = "hooked-book/source/")]
|
||||||
|
pub output: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,57 @@ use std::{io::Read, process::exit};
|
||||||
use {
|
use {
|
||||||
color_eyre::{eyre::eyre, Result},
|
color_eyre::{eyre::eyre, Result},
|
||||||
hooked_config::{Config, ExitAction},
|
hooked_config::{Config, ExitAction},
|
||||||
owo_colors::{OwoColorize, Style},
|
owo_colors::OwoColorize,
|
||||||
subprocess::{Exec, Redirection},
|
subprocess::{Exec, Redirection},
|
||||||
supports_color::Stream,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::utilities::plural;
|
use crate::{
|
||||||
|
cli::RunArgs,
|
||||||
|
printer::{print, PrintType, PRINT_STYLE},
|
||||||
|
utilities::{globset_from_strings, plural},
|
||||||
|
};
|
||||||
|
|
||||||
/// The `run` subcommand.
|
/// The `run` subcommand.
|
||||||
pub fn hooked_run(config: Config, hook_type: String) -> Result<()> {
|
pub fn hooked_run(config: Config, args: RunArgs) -> Result<()> {
|
||||||
let (success_style, warn_style, error_style) =
|
let cli_noise_level = args.noise_level.as_ref();
|
||||||
if let Some(_support) = supports_color::on(Stream::Stdout) {
|
let global_noise_level = &config.general.noise_level;
|
||||||
let shared_style = Style::new().bold();
|
|
||||||
(
|
|
||||||
shared_style.green(),
|
|
||||||
shared_style.yellow(),
|
|
||||||
shared_style.red(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(Style::new(), Style::new(), Style::new())
|
|
||||||
};
|
|
||||||
|
|
||||||
if hook_type == "pre-commit" {
|
if args.hook_type == "pre-commit" {
|
||||||
let hook_count = config.pre_commit.len();
|
let hook_count = config.pre_commit.len();
|
||||||
println!(
|
print(
|
||||||
"Hooked: Running {} pre-commit {}.",
|
format!(
|
||||||
hook_count,
|
"Hooked: Running {} pre-commit {}.",
|
||||||
plural(hook_count, "hook", None)
|
hook_count,
|
||||||
|
plural(hook_count, "hook", None)
|
||||||
|
),
|
||||||
|
cli_noise_level.unwrap_or(global_noise_level),
|
||||||
|
PrintType::Info,
|
||||||
);
|
);
|
||||||
|
|
||||||
for hook in config.pre_commit {
|
'hook_loop: for hook in config.pre_commit {
|
||||||
let hook_name = hook.name.unwrap_or_else(|| "Unnamed Hook".to_string());
|
let hook_name = hook.name.unwrap_or_else(|| "Unnamed Hook".to_string());
|
||||||
|
|
||||||
|
if !hook.staged.is_empty() {
|
||||||
|
let globs = globset_from_strings(&hook.staged)?;
|
||||||
|
|
||||||
|
let staged_files = Exec::cmd("git")
|
||||||
|
.args(&["diff", "--name-only", "--cached"])
|
||||||
|
.capture()?
|
||||||
|
.stdout_str();
|
||||||
|
if !staged_files.lines().any(|line| globs.is_match(line)) {
|
||||||
|
print(
|
||||||
|
format!(
|
||||||
|
"\t{} {}",
|
||||||
|
"≫".style(PRINT_STYLE.skipped),
|
||||||
|
hook_name.style(PRINT_STYLE.skipped)
|
||||||
|
),
|
||||||
|
cli_noise_level.unwrap_or(global_noise_level),
|
||||||
|
PrintType::Info,
|
||||||
|
);
|
||||||
|
continue 'hook_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let command = match (hook.task.command, hook.task.script) {
|
let command = match (hook.task.command, hook.task.script) {
|
||||||
(Some(command), _) => Ok(Exec::shell(command)),
|
(Some(command), _) => Ok(Exec::shell(command)),
|
||||||
|
|
||||||
|
@ -66,16 +85,32 @@ pub fn hooked_run(config: Config, hook_type: String) -> Result<()> {
|
||||||
output
|
output
|
||||||
};
|
};
|
||||||
|
|
||||||
let (stop, print_output, prefix, style) =
|
let (stop, print_output, prefix, style, print_type) =
|
||||||
match (exit_status.success(), hook.on_failure) {
|
match (exit_status.success(), hook.on_failure) {
|
||||||
(true, _) => (false, false, "✓", success_style),
|
(true, _) => {
|
||||||
(false, ExitAction::Continue) => (false, true, "⚠", warn_style),
|
(false, false, "✓", PRINT_STYLE.success, PrintType::Info)
|
||||||
(false, ExitAction::Stop) => (true, true, "✗", error_style),
|
}
|
||||||
|
(false, ExitAction::Continue) => {
|
||||||
|
(false, true, "⚠", PRINT_STYLE.warn, PrintType::Warn)
|
||||||
|
}
|
||||||
|
(false, ExitAction::Stop) => {
|
||||||
|
(true, true, "✗", PRINT_STYLE.error, PrintType::Error)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("\t{} {}", prefix.style(style), hook_name.style(style));
|
let hook_noise_level =
|
||||||
|
hook.noise_level.as_ref().unwrap_or(global_noise_level);
|
||||||
|
print(
|
||||||
|
format!("\t{} {}", prefix.style(style), hook_name.style(style)),
|
||||||
|
cli_noise_level.unwrap_or(hook_noise_level),
|
||||||
|
print_type,
|
||||||
|
);
|
||||||
if !output.is_empty() && print_output {
|
if !output.is_empty() && print_output {
|
||||||
println!("{}", output);
|
print(
|
||||||
|
output,
|
||||||
|
cli_noise_level.unwrap_or(hook_noise_level),
|
||||||
|
PrintType::Info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stop {
|
if stop {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
//! The `uninstall` subcommand.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::{read_to_string, remove_file},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use {
|
||||||
|
color_eyre::{eyre::eyre, Result},
|
||||||
|
hooked_config::Config,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{cli::UninstallArgs, HOOK_TYPES};
|
||||||
|
|
||||||
|
/// The `uninstall` subcommand.
|
||||||
|
pub fn hooked_uninstall(_config: Config, args: UninstallArgs) -> Result<()> {
|
||||||
|
let git_hooks_dir = PathBuf::from(".git/hooks/");
|
||||||
|
if !git_hooks_dir.exists() {
|
||||||
|
return Err(eyre!("The \".git/hooks/\" directory does not exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for hook_type in HOOK_TYPES {
|
||||||
|
let hook_path = git_hooks_dir.join(hook_type);
|
||||||
|
if !hook_path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hook_contents = read_to_string(&hook_path)?;
|
||||||
|
if args.all || hook_contents.contains("# Installed by Hooked.") {
|
||||||
|
remove_file(hook_path)?;
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{:?} wasn't installed by Hooked, use --all to remove it",
|
||||||
|
hook_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -2,20 +2,10 @@
|
||||||
//!
|
//!
|
||||||
//! > **Git hooks manager.**
|
//! > **Git hooks manager.**
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(missing_docs, clippy::missing_docs_in_private_items)]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
fs::{read_to_string, remove_file, set_permissions, write, Permissions},
|
|
||||||
os::unix::fs::PermissionsExt,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use {
|
use {
|
||||||
clap::Parser,
|
clap::Parser,
|
||||||
color_eyre::{eyre::eyre, install, Result},
|
color_eyre::{install, Result},
|
||||||
hooked_config::Config,
|
hooked_config::Config,
|
||||||
tera::{Context, Tera},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cli::{Args, MainSubcommands};
|
use crate::cli::{Args, MainSubcommands};
|
||||||
|
@ -27,6 +17,7 @@ pub const DEFAULT_TEMPLATE: &str = include_str!("templates/default.sh");
|
||||||
pub const HOOK_TYPES: [&str; 1] = ["pre-commit"];
|
pub const HOOK_TYPES: [&str; 1] = ["pre-commit"];
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod printer;
|
||||||
mod utilities;
|
mod utilities;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -35,61 +26,22 @@ fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let config = Config::from_toml_file(args.config)?;
|
let config = Config::from_toml_file(args.config)?;
|
||||||
|
|
||||||
let git_hooks_dir = PathBuf::from(".git/hooks/");
|
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
MainSubcommands::Install { overwrite } => {
|
MainSubcommands::Install(sub_args) => {
|
||||||
if !git_hooks_dir.exists() {
|
cli::hooked_install(config, sub_args)?;
|
||||||
return Err(eyre!("The \".git/hooks/\" directory does not exist"));
|
|
||||||
}
|
|
||||||
|
|
||||||
for hook_type in HOOK_TYPES {
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.insert("config_path", &config.general.config);
|
|
||||||
context.insert("hook_type", hook_type);
|
|
||||||
|
|
||||||
let hook_path = git_hooks_dir.join(hook_type);
|
|
||||||
if hook_path.exists() && !overwrite {
|
|
||||||
println!(
|
|
||||||
"{:?} exists, use --overwrite to replace the existing file",
|
|
||||||
hook_path
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
write(
|
|
||||||
&hook_path,
|
|
||||||
Tera::one_off(DEFAULT_TEMPLATE, &context, false)?,
|
|
||||||
)?;
|
|
||||||
set_permissions(hook_path, Permissions::from_mode(0o775))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainSubcommands::Uninstall { all } => {
|
MainSubcommands::Uninstall(sub_args) => {
|
||||||
if !git_hooks_dir.exists() {
|
cli::hooked_uninstall(config, sub_args)?;
|
||||||
return Err(eyre!("The \".git/hooks/\" directory does not exist"));
|
|
||||||
}
|
|
||||||
|
|
||||||
for hook_type in HOOK_TYPES {
|
|
||||||
let hook_path = git_hooks_dir.join(hook_type);
|
|
||||||
if !hook_path.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hook_contents = read_to_string(&hook_path)?;
|
|
||||||
if all || hook_contents.contains("# Installed by Hooked.") {
|
|
||||||
remove_file(hook_path)?;
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"{:?} wasn't installed by Hooked, use --all to remove it",
|
|
||||||
hook_path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainSubcommands::Run { hook_type } => {
|
MainSubcommands::Run(sub_args) => {
|
||||||
cli::hooked_run(config, hook_type)?;
|
cli::hooked_run(config, sub_args)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
MainSubcommands::CliReference(sub_args) => {
|
||||||
|
cli::hooked_cli_reference(config, sub_args)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Shared logic for printing output to the terminal.
|
||||||
|
|
||||||
|
use {
|
||||||
|
hooked_config::NoiseLevel, lazy_static::lazy_static, owo_colors::Style,
|
||||||
|
std::fmt::Display, supports_color::Stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The available types to print output as.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum PrintType {
|
||||||
|
/// Print the output as an error line.
|
||||||
|
Error,
|
||||||
|
/// Print the output as a warning line.
|
||||||
|
Warn,
|
||||||
|
/// Print the output as an information line.
|
||||||
|
Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The available print styles for colorized output.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PrintStyles {
|
||||||
|
/// The style for errored hooks output.
|
||||||
|
pub error: Style,
|
||||||
|
/// The style for skipped hooks output.
|
||||||
|
pub skipped: Style,
|
||||||
|
/// The style for successful hooks output.
|
||||||
|
pub success: Style,
|
||||||
|
/// The style for hooks with warnings.
|
||||||
|
pub warn: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref PRINT_STYLE: PrintStyles = {
|
||||||
|
let (success_style, warn_style, error_style, skipped_style) =
|
||||||
|
if let Some(_support) = supports_color::on(Stream::Stdout) {
|
||||||
|
let shared_style = Style::new().bold();
|
||||||
|
(
|
||||||
|
shared_style.green(),
|
||||||
|
shared_style.yellow(),
|
||||||
|
shared_style.red(),
|
||||||
|
shared_style.blue(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(Style::new(), Style::new(), Style::new(), Style::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintStyles {
|
||||||
|
error: error_style,
|
||||||
|
skipped: skipped_style,
|
||||||
|
success: success_style,
|
||||||
|
warn: warn_style,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print something to the terminal according to a given [`NoiseLevel`] and
|
||||||
|
/// [`PrintType`].
|
||||||
|
pub fn print<D: Display>(
|
||||||
|
something: D,
|
||||||
|
noise_level: &NoiseLevel,
|
||||||
|
print_type: PrintType,
|
||||||
|
) {
|
||||||
|
let should_print = match (noise_level, print_type) {
|
||||||
|
// Only output errors under the quiet noise level.
|
||||||
|
(NoiseLevel::Quiet, PrintType::Error) => true,
|
||||||
|
(NoiseLevel::Quiet, _) => false,
|
||||||
|
// Output everything under loud.
|
||||||
|
(NoiseLevel::Loud | NoiseLevel::Standard | NoiseLevel::Minimal, _) => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !should_print {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{something}")
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
This file is automatically generated using the cli-reference subcommand.
|
||||||
|
Use `cargo run -- cli-reference` to generate it.
|
||||||
|
|
||||||
|
{% for command in commands %}
|
||||||
|
{%- set name = command.0 -%}
|
||||||
|
{%- set usage = command.1 -%}
|
||||||
|
// ANCHOR: {{ name }}
|
||||||
|
$ hooked {{ name }} --help
|
||||||
|
{{ usage }}
|
||||||
|
// ANCHOR_END: {{ name }}
|
||||||
|
{% endfor %}
|
|
@ -1,14 +0,0 @@
|
||||||
//! Miscellaneous utilities.
|
|
||||||
|
|
||||||
/// Simple function to create a pluralized string.
|
|
||||||
pub fn plural(count: usize, singular: &str, plural: Option<&str>) -> String {
|
|
||||||
if count == 1 {
|
|
||||||
return singular.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(plural) = plural {
|
|
||||||
return plural.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("{singular}s")
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
//! Miscellaneous utilities.
|
||||||
|
|
||||||
|
use color_eyre::Result;
|
||||||
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
|
/// Simple function to create a pluralized string.
|
||||||
|
pub fn plural(count: usize, singular: &str, plural: Option<&str>) -> String {
|
||||||
|
if count == 1 {
|
||||||
|
return singular.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(plural) = plural {
|
||||||
|
return plural.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("{singular}s")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a [`GlobSet`] from a list of strings.
|
||||||
|
pub fn globset_from_strings(input: &[String]) -> Result<GlobSet> {
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
for glob in input {
|
||||||
|
builder.add(Glob::new(glob)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build().map_err(Into::into)
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hooked-config"
|
name = "hooked-config"
|
||||||
description = "Configuration for Hooked."
|
description = "Configuration for Hooked."
|
||||||
|
documentation = "https://docs.rs/hooked-config"
|
||||||
|
homepage = "https://hooked.holllo.org"
|
||||||
repository = "https://git.bauke.xyz/Holllo/hooked"
|
repository = "https://git.bauke.xyz/Holllo/hooked"
|
||||||
|
readme = "../README.md"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Holllo <helllo@holllo.org>"]
|
authors = ["Holllo <helllo@holllo.org>"]
|
||||||
|
@ -10,14 +13,17 @@ edition = "2021"
|
||||||
[lib]
|
[lib]
|
||||||
path = "source/lib.rs"
|
path = "source/lib.rs"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
toml = "0.5.9"
|
toml = "0.8.8"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
version = "1.0.147"
|
version = "1.0.195"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.21.0"
|
insta = "1.34.0"
|
||||||
test-case = "2.2.2"
|
test-case = "3.3.1"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
LICENSE
|
|
@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize};
|
||||||
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ExitAction {
|
pub enum ExitAction {
|
||||||
|
/// Regardless of the hook's exit code, allow Hooked to continue.
|
||||||
Continue,
|
Continue,
|
||||||
|
/// Stop on a non-zero hook exit code.
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::NoiseLevel;
|
||||||
|
|
||||||
/// General Hooked configuration.
|
/// General Hooked configuration.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
|
@ -13,6 +15,12 @@ pub struct General {
|
||||||
|
|
||||||
/// The directory to use for hooks.
|
/// The directory to use for hooks.
|
||||||
pub directory: PathBuf,
|
pub directory: PathBuf,
|
||||||
|
|
||||||
|
/// The noise level tasks should output logs with by default.
|
||||||
|
pub noise_level: NoiseLevel,
|
||||||
|
|
||||||
|
/// Path to a script template for use with the install subcommand.
|
||||||
|
pub template: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for General {
|
impl Default for General {
|
||||||
|
@ -20,6 +28,8 @@ impl Default for General {
|
||||||
Self {
|
Self {
|
||||||
config: PathBuf::from("Hooked.toml"),
|
config: PathBuf::from("Hooked.toml"),
|
||||||
directory: PathBuf::from("hooks"),
|
directory: PathBuf::from("hooks"),
|
||||||
|
noise_level: NoiseLevel::default(),
|
||||||
|
template: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,13 @@ use {
|
||||||
|
|
||||||
mod exit_action;
|
mod exit_action;
|
||||||
mod general;
|
mod general;
|
||||||
|
mod noise_level;
|
||||||
mod pre_commit;
|
mod pre_commit;
|
||||||
mod task;
|
mod task;
|
||||||
|
|
||||||
pub use exit_action::*;
|
pub use exit_action::*;
|
||||||
pub use general::*;
|
pub use general::*;
|
||||||
|
pub use noise_level::*;
|
||||||
pub use pre_commit::*;
|
pub use pre_commit::*;
|
||||||
pub use task::*;
|
pub use task::*;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//! The noise level Hooked should output logs with.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// The noise level Hooked should output logs with.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum NoiseLevel {
|
||||||
|
/// Output only errors.
|
||||||
|
Quiet,
|
||||||
|
/// Output everything.
|
||||||
|
Loud,
|
||||||
|
/// Print a list of tasks and output warnings and errors, this is the default.
|
||||||
|
Standard,
|
||||||
|
/// The same as [`NoiseLevel::Standard`] except don't output task names or
|
||||||
|
/// warnings.
|
||||||
|
Minimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NoiseLevel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement `From<String>` so we can use Clap's automatic parsing in the CLI.
|
||||||
|
impl From<String> for NoiseLevel {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"quiet" => Self::Quiet,
|
||||||
|
"loud" => Self::Loud,
|
||||||
|
"standard" => Self::Standard,
|
||||||
|
"minimal" => Self::Minimal,
|
||||||
|
_ => NoiseLevel::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{ExitAction, Task};
|
use crate::{ExitAction, NoiseLevel, Task};
|
||||||
|
|
||||||
/// A pre-commit hook.
|
/// A pre-commit hook.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
@ -11,10 +11,17 @@ pub struct PreCommit {
|
||||||
/// Display name for this hook.
|
/// Display name for this hook.
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// The noise level this task should output with.
|
||||||
|
pub noise_level: Option<NoiseLevel>,
|
||||||
|
|
||||||
/// What to do when the hook exits with a non-zero status code.
|
/// What to do when the hook exits with a non-zero status code.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub on_failure: ExitAction,
|
pub on_failure: ExitAction,
|
||||||
|
|
||||||
|
/// List of globs to check against staged files.
|
||||||
|
#[serde(default)]
|
||||||
|
pub staged: Vec<String>,
|
||||||
|
|
||||||
/// Task to perform when this hook is called.
|
/// Task to perform when this hook is called.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub task: Task,
|
pub task: Task,
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
[general]
|
[general]
|
||||||
directory = "hooked"
|
directory = "hooked"
|
||||||
|
noise_level = "minimal"
|
||||||
|
template = "test.sh"
|
||||||
|
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
name = "Pre Commit 1"
|
name = "Pre Commit 1"
|
||||||
command = "exit 0"
|
command = "exit 0"
|
||||||
|
staged = ["*.txt"]
|
||||||
on_failure = "continue"
|
on_failure = "continue"
|
||||||
|
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
name = "Pre Commit 2"
|
name = "Pre Commit 2"
|
||||||
|
noise_level = "loud"
|
||||||
script = "test.sh"
|
script = "test.sh"
|
||||||
on_failure = "stop"
|
on_failure = "stop"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use {
|
use {
|
||||||
hooked_config::{Config, ExitAction, PreCommit, Task},
|
hooked_config::{Config, ExitAction, NoiseLevel, PreCommit, Task},
|
||||||
toml::to_string_pretty,
|
toml::to_string_pretty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ use insta::assert_snapshot;
|
||||||
fn test_serialize() {
|
fn test_serialize() {
|
||||||
let pre_commit_command = PreCommit {
|
let pre_commit_command = PreCommit {
|
||||||
name: Some("Command Test".to_string()),
|
name: Some("Command Test".to_string()),
|
||||||
|
noise_level: None,
|
||||||
on_failure: ExitAction::Continue,
|
on_failure: ExitAction::Continue,
|
||||||
|
staged: vec!["*.txt".to_string()],
|
||||||
task: Task {
|
task: Task {
|
||||||
command: Some("exit 0".to_string()),
|
command: Some("exit 0".to_string()),
|
||||||
script: None,
|
script: None,
|
||||||
|
@ -18,7 +20,9 @@ fn test_serialize() {
|
||||||
|
|
||||||
let pre_commit_script = PreCommit {
|
let pre_commit_script = PreCommit {
|
||||||
name: Some("Script Test".to_string()),
|
name: Some("Script Test".to_string()),
|
||||||
|
noise_level: Some(NoiseLevel::Loud),
|
||||||
on_failure: ExitAction::Stop,
|
on_failure: ExitAction::Stop,
|
||||||
|
staged: vec![],
|
||||||
task: Task {
|
task: Task {
|
||||||
command: None,
|
command: None,
|
||||||
script: Some("test.sh".into()),
|
script: Some("test.sh".into()),
|
||||||
|
|
|
@ -6,11 +6,15 @@ Config {
|
||||||
general: General {
|
general: General {
|
||||||
config: "Hooked.toml",
|
config: "Hooked.toml",
|
||||||
directory: "hooks",
|
directory: "hooks",
|
||||||
|
noise_level: Standard,
|
||||||
|
template: None,
|
||||||
},
|
},
|
||||||
pre_commit: [
|
pre_commit: [
|
||||||
PreCommit {
|
PreCommit {
|
||||||
name: None,
|
name: None,
|
||||||
|
noise_level: None,
|
||||||
on_failure: Stop,
|
on_failure: Stop,
|
||||||
|
staged: [],
|
||||||
task: Task {
|
task: Task {
|
||||||
command: None,
|
command: None,
|
||||||
script: None,
|
script: None,
|
||||||
|
|
|
@ -6,13 +6,21 @@ Config {
|
||||||
general: General {
|
general: General {
|
||||||
config: "Hooked.toml",
|
config: "Hooked.toml",
|
||||||
directory: "hooked",
|
directory: "hooked",
|
||||||
|
noise_level: Minimal,
|
||||||
|
template: Some(
|
||||||
|
"test.sh",
|
||||||
|
),
|
||||||
},
|
},
|
||||||
pre_commit: [
|
pre_commit: [
|
||||||
PreCommit {
|
PreCommit {
|
||||||
name: Some(
|
name: Some(
|
||||||
"Pre Commit 1",
|
"Pre Commit 1",
|
||||||
),
|
),
|
||||||
|
noise_level: None,
|
||||||
on_failure: Continue,
|
on_failure: Continue,
|
||||||
|
staged: [
|
||||||
|
"*.txt",
|
||||||
|
],
|
||||||
task: Task {
|
task: Task {
|
||||||
command: Some(
|
command: Some(
|
||||||
"exit 0",
|
"exit 0",
|
||||||
|
@ -24,7 +32,11 @@ Config {
|
||||||
name: Some(
|
name: Some(
|
||||||
"Pre Commit 2",
|
"Pre Commit 2",
|
||||||
),
|
),
|
||||||
|
noise_level: Some(
|
||||||
|
Loud,
|
||||||
|
),
|
||||||
on_failure: Stop,
|
on_failure: Stop,
|
||||||
|
staged: [],
|
||||||
task: Task {
|
task: Task {
|
||||||
command: None,
|
command: None,
|
||||||
script: Some(
|
script: Some(
|
||||||
|
|
|
@ -3,16 +3,20 @@ source: hooked-config/tests/serialize.rs
|
||||||
expression: to_string_pretty(&config).unwrap()
|
expression: to_string_pretty(&config).unwrap()
|
||||||
---
|
---
|
||||||
[general]
|
[general]
|
||||||
config = 'Hooked.toml'
|
config = "Hooked.toml"
|
||||||
directory = 'hooks'
|
directory = "hooks"
|
||||||
|
noise_level = "standard"
|
||||||
|
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
name = 'Command Test'
|
name = "Command Test"
|
||||||
on_failure = 'continue'
|
on_failure = "continue"
|
||||||
command = 'exit 0'
|
staged = ["*.txt"]
|
||||||
|
command = "exit 0"
|
||||||
|
|
||||||
[[pre_commit]]
|
[[pre_commit]]
|
||||||
name = 'Script Test'
|
name = "Script Test"
|
||||||
on_failure = 'stop'
|
noise_level = "loud"
|
||||||
script = 'test.sh'
|
on_failure = "stop"
|
||||||
|
staged = []
|
||||||
|
script = "test.sh"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
components = ["cargo", "clippy", "rustfmt", "rust-src"]
|
|
@ -0,0 +1,33 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
with pkgs;
|
||||||
|
|
||||||
|
let
|
||||||
|
rustup-toolchain = rust-bin.fromRustupToolchainFile ./rustup-toolchain.toml;
|
||||||
|
in
|
||||||
|
mkShell rec {
|
||||||
|
packages = [
|
||||||
|
cargo-audit
|
||||||
|
cargo-edit
|
||||||
|
cargo-insta
|
||||||
|
cargo-make
|
||||||
|
cargo-outdated
|
||||||
|
cargo-tarpaulin
|
||||||
|
mdbook
|
||||||
|
mdbook-linkcheck
|
||||||
|
rustup-toolchain
|
||||||
|
typos
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
# Add the outputs directory to PATH where local tools will be installed.
|
||||||
|
PATH="$PATH:$out/bin"
|
||||||
|
|
||||||
|
# If Hooked isn't installed, use cargo to install the local version of it.
|
||||||
|
if ! [[ -x "$(command -v hooked)" ]]; then
|
||||||
|
cargo install --path hooked-cli --root $out
|
||||||
|
fi
|
||||||
|
|
||||||
|
hooked install --silent
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in New Issue