diff --git a/crates/prek/src/cli/list_builtins.rs b/crates/prek/src/cli/list_builtins.rs new file mode 100644 index 00000000..44260af7 --- /dev/null +++ b/crates/prek/src/cli/list_builtins.rs @@ -0,0 +1,60 @@ +use std::fmt::Write; + +use owo_colors::OwoColorize; +use serde::Serialize; +use strum::IntoEnumIterator; + +use crate::cli::{ExitStatus, ListOutputFormat}; +use crate::config::BuiltinHook; +use crate::hooks::BuiltinHooks; +use crate::printer::Printer; + +#[derive(Serialize)] +struct SerializableBuiltinHook { + id: String, + name: String, + description: Option, +} + +/// List all builtin hooks. +pub(crate) fn list_builtins( + output_format: ListOutputFormat, + verbose: bool, + printer: Printer, +) -> anyhow::Result { + let hooks = BuiltinHooks::iter().map(|variant| { + let id = variant.as_ref(); + BuiltinHook::from_id(id).expect("All BuiltinHooks variants should be valid") + }); + + match output_format { + ListOutputFormat::Text => { + if verbose { + for hook in hooks { + writeln!(printer.stdout(), "{}", hook.id.bold())?; + if let Some(description) = &hook.options.description { + writeln!(printer.stdout(), " {description}")?; + } + writeln!(printer.stdout())?; + } + } else { + for hook in hooks { + writeln!(printer.stdout(), "{}", hook.id)?; + } + } + } + ListOutputFormat::Json => { + let serializable: Vec<_> = hooks + .map(|h| SerializableBuiltinHook { + id: h.id, + name: h.name, + description: h.options.description, + }) + .collect(); + let json_output = serde_json::to_string_pretty(&serializable)?; + writeln!(printer.stdout(), "{json_output}")?; + } + } + + Ok(ExitStatus::Success) +} diff --git a/crates/prek/src/cli/mod.rs b/crates/prek/src/cli/mod.rs index 87941a74..7c37930b 100644 --- a/crates/prek/src/cli/mod.rs +++ b/crates/prek/src/cli/mod.rs @@ -20,6 +20,7 @@ mod hook_impl; mod identify; mod install; mod list; +mod list_builtins; pub mod reporter; pub mod run; mod sample_config; @@ -38,6 +39,7 @@ pub(crate) use hook_impl::hook_impl; pub(crate) use identify::identify; pub(crate) use install::{init_template_dir, install, install_hooks, uninstall}; pub(crate) use list::list; +pub(crate) use list_builtins::list_builtins; pub(crate) use run::run; pub(crate) use sample_config::sample_config; #[cfg(feature = "self-update")] @@ -518,6 +520,13 @@ pub(crate) enum IdentifyOutputFormat { Json, } +#[derive(Debug, Clone, Default, Args)] +pub(crate) struct ListBuiltinsArgs { + /// The output format. + #[arg(long, value_enum, default_value_t = ListOutputFormat::Text)] + pub(crate) output_format: ListOutputFormat, +} + #[derive(Debug, Clone, Default, Args)] pub(crate) struct ListArgs { /// Include the specified hooks or projects. @@ -722,6 +731,8 @@ pub(crate) struct UtilNamespace { pub(crate) enum UtilCommand { /// Show file identification tags. Identify(IdentifyArgs), + /// List all built-in hooks bundled with prek. + ListBuiltins(ListBuiltinsArgs), /// Install hook script in a directory intended for use with `git config init.templateDir`. #[command(alias = "init-templatedir")] InitTemplateDir(InitTemplateDirArgs), diff --git a/crates/prek/src/hooks/builtin_hooks/mod.rs b/crates/prek/src/hooks/builtin_hooks/mod.rs index 92c0360a..9271d6d3 100644 --- a/crates/prek/src/hooks/builtin_hooks/mod.rs +++ b/crates/prek/src/hooks/builtin_hooks/mod.rs @@ -11,7 +11,17 @@ use crate::store::Store; mod check_json5; -#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::AsRefStr, strum::Display, strum::EnumString)] +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + strum::AsRefStr, + strum::Display, + strum::EnumIter, + strum::EnumString, +)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", schemars(rename_all = "kebab-case"))] #[strum(serialize_all = "kebab-case")] diff --git a/crates/prek/src/main.rs b/crates/prek/src/main.rs index 21a0d7b0..4b5b2d90 100644 --- a/crates/prek/src/main.rs +++ b/crates/prek/src/main.rs @@ -384,6 +384,11 @@ async fn run(cli: Cli) -> Result { cli::identify(&args.paths, args.output_format, printer) } + UtilCommand::ListBuiltins(args) => { + show_settings!(args); + + cli::list_builtins(args.output_format, cli.globals.verbose > 0, printer) + } UtilCommand::InitTemplateDir(args) => { show_settings!(args); diff --git a/crates/prek/tests/list_builtins.rs b/crates/prek/tests/list_builtins.rs new file mode 100644 index 00000000..323c2ed0 --- /dev/null +++ b/crates/prek/tests/list_builtins.rs @@ -0,0 +1,187 @@ +use crate::common::{TestContext, cmd_snapshot}; + +mod common; + +#[test] +fn list_builtins_basic() { + let context = TestContext::new(); + + cmd_snapshot!(context.filters(), context.command().arg("util").arg("list-builtins"), @r" + success: true + exit_code: 0 + ----- stdout ----- + check-added-large-files + check-case-conflict + check-executables-have-shebangs + check-json + check-json5 + check-merge-conflict + check-symlinks + check-toml + check-xml + check-yaml + detect-private-key + end-of-file-fixer + fix-byte-order-marker + mixed-line-ending + no-commit-to-branch + trailing-whitespace + + ----- stderr ----- + "); +} + +#[test] +fn list_builtins_verbose() { + let context = TestContext::new(); + + cmd_snapshot!(context.filters(), context.command().arg("util").arg("list-builtins").arg("--verbose"), @r" + success: true + exit_code: 0 + ----- stdout ----- + check-added-large-files + prevents giant files from being committed. + + check-case-conflict + checks for files that would conflict in case-insensitive filesystems + + check-executables-have-shebangs + ensures that (non-binary) executables have a shebang. + + check-json + checks json files for parseable syntax. + + check-json5 + checks json5 files for parseable syntax. + + check-merge-conflict + checks for files that contain merge conflict strings. + + check-symlinks + checks for symlinks which do not point to anything. + + check-toml + checks toml files for parseable syntax. + + check-xml + checks xml files for parseable syntax. + + check-yaml + checks yaml files for parseable syntax. + + detect-private-key + detects the presence of private keys. + + end-of-file-fixer + ensures that a file is either empty, or ends with one newline. + + fix-byte-order-marker + removes utf-8 byte order marker. + + mixed-line-ending + replaces or checks mixed line ending. + + no-commit-to-branch + + trailing-whitespace + trims trailing whitespace. + + + ----- stderr ----- + "); +} + +#[test] +fn list_builtins_json() { + let context = TestContext::new(); + + cmd_snapshot!(context.filters(), context.command().arg("util").arg("list-builtins").arg("--output-format=json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + [ + { + "id": "check-added-large-files", + "name": "check for added large files", + "description": "prevents giant files from being committed." + }, + { + "id": "check-case-conflict", + "name": "check for case conflicts", + "description": "checks for files that would conflict in case-insensitive filesystems" + }, + { + "id": "check-executables-have-shebangs", + "name": "check that executables have shebangs", + "description": "ensures that (non-binary) executables have a shebang." + }, + { + "id": "check-json", + "name": "check json", + "description": "checks json files for parseable syntax." + }, + { + "id": "check-json5", + "name": "check json5", + "description": "checks json5 files for parseable syntax." + }, + { + "id": "check-merge-conflict", + "name": "check for merge conflicts", + "description": "checks for files that contain merge conflict strings." + }, + { + "id": "check-symlinks", + "name": "check for broken symlinks", + "description": "checks for symlinks which do not point to anything." + }, + { + "id": "check-toml", + "name": "check toml", + "description": "checks toml files for parseable syntax." + }, + { + "id": "check-xml", + "name": "check xml", + "description": "checks xml files for parseable syntax." + }, + { + "id": "check-yaml", + "name": "check yaml", + "description": "checks yaml files for parseable syntax." + }, + { + "id": "detect-private-key", + "name": "detect private key", + "description": "detects the presence of private keys." + }, + { + "id": "end-of-file-fixer", + "name": "fix end of files", + "description": "ensures that a file is either empty, or ends with one newline." + }, + { + "id": "fix-byte-order-marker", + "name": "fix utf-8 byte order marker", + "description": "removes utf-8 byte order marker." + }, + { + "id": "mixed-line-ending", + "name": "mixed line ending", + "description": "replaces or checks mixed line ending." + }, + { + "id": "no-commit-to-branch", + "name": "don't commit to branch", + "description": null + }, + { + "id": "trailing-whitespace", + "name": "trim trailing whitespace", + "description": "trims trailing whitespace." + } + ] + + ----- stderr ----- + "#); +} diff --git a/docs/cli.md b/docs/cli.md index dc777c1f..f48b3626 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -810,6 +810,7 @@ prek util [OPTIONS]

Commands

prek util identify

Show file identification tags

+
prek util list-builtins

List all built-in hooks bundled with prek

prek util init-template-dir

Install hook script in a directory intended for use with git config init.templateDir

prek util yaml-to-toml

Convert a YAML configuration file to prek.toml

@@ -855,6 +856,42 @@ prek util identify [OPTIONS] [PATH]...
--version, -V

Display the prek version

+### prek util list-builtins + +List all built-in hooks bundled with prek + +

Usage

+ +``` +prek util list-builtins [OPTIONS] +``` + +

Options

+ +
--cd, -C dir

Change to directory before running

+
--color color

Whether to use color in output

+

May also be set with the PREK_COLOR environment variable.

[default: auto]

Possible values:

+
    +
  • auto: Enables colored output only when the output is going to a terminal or TTY with support
  • +
  • always: Enables colored output regardless of the detected environment
  • +
  • never: Disables colored output
  • +
--config, -c config

Path to alternate config file

+
--help, -h

Display the concise help for this command

+
--log-file log-file

Write trace logs to the specified file. If not specified, trace logs will be written to $PREK_HOME/prek.log

+
--no-progress

Hide all progress outputs.

+

For example, spinners or progress bars.

+
--output-format output-format

The output format

+

[default: text]

Possible values:

+
    +
  • text
  • +
  • json
  • +
--quiet, -q

Use quiet output.

+

Repeating this option, e.g., -qq, will enable a silent mode in which prek will write no output to stdout.

+

May also be set with the PREK_QUIET environment variable.

--refresh

Refresh all cached data

+
--verbose, -v

Use verbose output

+
--version, -V

Display the prek version

+
+ ### prek util init-template-dir Install hook script in a directory intended for use with `git config init.templateDir`