From 72ca01eb03c9eb4f18ff5054ddbd95eea5902ff5 Mon Sep 17 00:00:00 2001 From: closer-claudio Date: Sat, 17 Oct 2026 11:19:18 -0700 Subject: [PATCH] cmd/git-validation: kong instead of the flag package The command line moves to kong, the house parser, with upstream's flag names: --range, --list-rules, --run, --no-github, --no-travis and --travis-pr-only (now also --no-travis-pr-only), and -v, -D, -q, -d as short flags with --verbose, --debug, --quiet, --dir beside them. kong takes long flags with two dashes only: `-run X`, which the flag package read, now fails as an unknown flag; `--run X` works with both, so a caller can switch first. Usage errors exit 1, as the flag package's did (kong's 80 folded in), --help exits 0. primordium's shutdown runs the command: a run with commits to fix prints its count and exits 1 silently, any other failure is logged with its step and exits 1. The README's usage block is kong's --help; its examples use --run and --list-rules. depguard allows kong and primordium's shutdown beside its logger; main.go's backlog entry is re-measured (the flag globals and their findings are gone). Co-Authored-By: Claude Opus 5.5 Signed-off-by: closer-claudio --- .lint-go.yaml | 11 ++-- README.md | 40 +++++++++----- cmd/git-validation/main.go | 109 ++++++++++++++++++++++--------------- go.mod | 5 +- go.sum | 8 +++ 5 files changed, 107 insertions(+), 66 deletions(-) diff --git a/.lint-go.yaml b/.lint-go.yaml index e4d6547..690fe5b 100644 --- a/.lint-go.yaml +++ b/.lint-go.yaml @@ -67,10 +67,12 @@ golangci: depguard: rules: main: - # The house logging setup (slog through tint on a terminal, JSON - # otherwise), which the command installs in main. + # The house command line: kong for the flags, primordium's logger + # (slog, tint on a terminal) and shutdown (exit status). allow: + - github.com/alecthomas/kong - github.com/mycophonic/primordium/app/logger + - github.com/mycophonic/primordium/app/shutdown exclusions: rules: # --------------------------------------------------------------------------- @@ -83,11 +85,10 @@ golangci: # Counts are the measurement at enrolment, per file and linter: 239 in 10 files. # --------------------------------------------------------------------------- - path: ^cmd/git-validation/main\.go$ - linters: # 16 findings - - gochecknoglobals # 10 + linters: # 5 findings - forbidigo # 3 - - revive # 2 - gocognit # 1 + - revive # 1 - path: ^git/commits\.go$ linters: # 37 findings - wrapcheck # 11 diff --git a/README.md b/README.md index c900fb4..d71cb07 100644 --- a/README.md +++ b/README.md @@ -13,24 +13,34 @@ go install github.com/forkcloser/git-validation/cmd/git-validation@latest The flags ```shell -vbatts@valse ~/src/vb/git-validation (master *) $ git-validation -h -Usage of git-validation: - -D debug output - -d string - git directory to validate from (default ".") - -list-rules - list the rules registered - -range string - use this commit range instead - -run string - comma delimited list of rules to run. Defaults to all. - -v verbose +$ git-validation --help +Usage: git-validation [flags] + +A way to do validation on git commits. + +Flags: + -h, --help Show context-sensitive help. + --range=STRING use this commit range instead (implies + --no-travis) + --list-rules list the rules registered + --run=STRING comma delimited list of rules to run. Defaults to + all. + -v, --verbose verbose + -D, --debug debug output + -q, --quiet less output + -d, --dir="." git directory to validate from + --no-github disables Github Actions environment checks (when + env GITHUB_ACTIONS=true is set) + --no-travis disables travis environment checks (when env + TRAVIS=true is set) + --[no-]travis-pr-only when on travis, only run validations if the + CI-Build is checking pull-request build ``` The entire default rule set is run by default: ```shell -vbatts@valse ~/src/vb/git-validation (master) $ git-validation -list-rules +vbatts@valse ~/src/vb/git-validation (master) $ git-validation --list-rules "dangling-whitespace" -- checking the presence of dangling whitespaces on line endings "DCO" -- makes sure the commits are signed "message_regexp" -- checks the commit message for a user provided regular expression @@ -40,7 +50,7 @@ vbatts@valse ~/src/vb/git-validation (master) $ git-validation -list-rules Or, specify comma-delimited rules to run: ```shell -vbatts@valse ~/src/vb/git-validation (master) $ git-validation -run DCO,short-subject +vbatts@valse ~/src/vb/git-validation (master) $ git-validation --run DCO,short-subject * b243ca4 "README: adding install and usage" ... PASS * d614ccf "*: run tests in a runner" ... PASS * b9413c6 "shortsubject: add a subject length check" ... PASS @@ -94,7 +104,7 @@ vbatts@valse ~/src/vb/git-validation (master) $ echo $? Excluding paths that are out of the scope of your project: ```shell -vbatts@valse ~/src/vb/git-validation (master) $ GIT_CHECK_EXCLUDE="./vendor:./git/testdata" git-validation -q -run dangling-whitespace +vbatts@valse ~/src/vb/git-validation (master) $ GIT_CHECK_EXCLUDE="./vendor:./git/testdata" git-validation -q --run dangling-whitespace ... ``` diff --git a/cmd/git-validation/main.go b/cmd/git-validation/main.go index 12fa730..80fa43c 100644 --- a/cmd/git-validation/main.go +++ b/cmd/git-validation/main.go @@ -2,13 +2,14 @@ package main import ( "context" - "flag" "fmt" "log/slog" "os" "strings" + "github.com/alecthomas/kong" "github.com/mycophonic/primordium/app/logger" + "github.com/mycophonic/primordium/app/shutdown" _ "github.com/forkcloser/git-validation/rules/danglingwhitespace" _ "github.com/forkcloser/git-validation/rules/dco" @@ -17,49 +18,70 @@ import ( "github.com/forkcloser/git-validation/validate" ) -var ( - flCommitRange = flag.String("range", "", "use this commit range instead (implies -no-travis)") - flListRules = flag.Bool("list-rules", false, "list the rules registered") - flRun = flag.String("run", "", "comma delimited list of rules to run. Defaults to all.") - flVerbose = flag.Bool("v", false, "verbose") - flDebug = flag.Bool("D", false, "debug output") - flQuiet = flag.Bool("q", false, "less output") - flDir = flag.String("d", ".", "git directory to validate from") - flNoGithub = flag.Bool( - "no-github", - false, - "disables Github Actions environment checks (when env GITHUB_ACTIONS=true is set)", - ) - flNoTravis = flag.Bool("no-travis", false, "disables travis environment checks (when env TRAVIS=true is set)") - flTravisPROnly = flag.Bool( - "travis-pr-only", - true, - "when on travis, only run validations if the CI-Build is checking pull-request build", - ) -) +// cli is the command line. The flag names are upstream's; kong spells them +// with two dashes, and keeps the one-letter ones as short flags. +type cli struct { + Range string `help:"use this commit range instead (implies --no-travis)"` + ListRules bool `help:"list the rules registered"` + Run string `help:"comma delimited list of rules to run. Defaults to all."` + Verbose bool `help:"verbose" short:"v"` + Debug bool `help:"debug output" short:"D"` + Quiet bool `help:"less output" short:"q"` + Dir string `default:"." help:"git directory to validate from" short:"d"` + NoGithub bool `help:"disables Github Actions environment checks (when env GITHUB_ACTIONS=true is set)" name:"no-github"` + NoTravis bool `help:"disables travis environment checks (when env TRAVIS=true is set)"` + TravisPROnly bool `default:"true" help:"when on travis, only run validations if the CI-Build is checking pull-request build" name:"travis-pr-only" negatable:""` +} + +// errCommitsToFix is a run that found failing commits; they are printed +// already, so the exit carries no message. +var errCommitsToFix = shutdown.ExitError(1) func main() { - flag.Parse() + var flags cli + + // Parse and usage errors are kong's, printed with the usage; every failure + // exits 1, as the flag package did, and --help exits 0. + kong.Parse(&flags, + kong.Name("git-validation"), + kong.Description("A way to do validation on git commits."), + kong.UsageOnError(), + kong.Exit(func(code int) { + if code != 0 { + code = 1 + } + + os.Exit(code) + }), + ) + + configureLogger(flags.Debug, flags.Quiet) - configureLogger(*flDebug, *flQuiet) + // shutdown runs the command and exits with the status an ExitError asks + // for, silently, and 1 for any other error, which it logs. + shutdown.Run(shutdown.SetDefaults(context.Background()), func(context.Context) error { + return run(&flags) + }) +} - if *flQuiet { +func run(flags *cli) error { + if flags.Quiet { if err := os.Setenv("QUIET", "1"); err != nil { - exitOn("quiet output", err) + return fmt.Errorf("quiet output: %w", err) } } - if *flListRules { + if flags.ListRules { for _, r := range validate.RegisteredRules { fmt.Printf("%q -- %s\n", r.Name, r.Description) } - return + return nil } - if *flTravisPROnly && strings.ToLower(os.Getenv("TRAVIS_PULL_REQUEST")) == "false" { - fmt.Printf("only to check travis PR builds and this not a PR build. yielding.\n") - return + if flags.TravisPROnly && strings.ToLower(os.Getenv("TRAVIS_PULL_REQUEST")) == "false" { + fmt.Println("only to check travis PR builds and this not a PR build. yielding.") + return nil } // rules to be used @@ -72,15 +94,15 @@ func main() { } } // or reduce the set being run to what the user provided - if *flRun != "" { - rules = validate.FilterRules(validate.RegisteredRules, validate.SanitizeFilters(*flRun)) + if flags.Run != "" { + rules = validate.FilterRules(validate.RegisteredRules, validate.SanitizeFilters(flags.Run)) } slog.Debug("rules to run", "rules", ruleNames(rules)) - commitRange := *flCommitRange + commitRange := flags.Range if commitRange == "" { - if strings.ToLower(os.Getenv("TRAVIS")) == "true" && !*flNoTravis { + if strings.ToLower(os.Getenv("TRAVIS")) == "true" && !flags.NoTravis { if os.Getenv("TRAVIS_COMMIT_RANGE") != "" { commitRange = strings.Replace(os.Getenv("TRAVIS_COMMIT_RANGE"), "...", "..", 1) } else if os.Getenv("TRAVIS_COMMIT") != "" { @@ -88,27 +110,30 @@ func main() { } } // https://docs.github.com/en/actions/reference/environment-variables - if strings.ToLower(os.Getenv("GITHUB_ACTIONS")) == "true" && !*flNoGithub { + if strings.ToLower(os.Getenv("GITHUB_ACTIONS")) == "true" && !flags.NoGithub { commitRange = fmt.Sprintf("%s..%s", os.Getenv("GITHUB_SHA"), "HEAD") } } slog.Info("using commit range", "range", commitRange) - runner, err := validate.NewRunner(*flDir, rules, commitRange, *flVerbose) + runner, err := validate.NewRunner(flags.Dir, rules, commitRange, flags.Verbose) if err != nil { - exitOn("preparing the run", err) + return fmt.Errorf("preparing the run: %w", err) } if err := runner.Run(); err != nil { - exitOn("validating commits", err) + return fmt.Errorf("validating commits: %w", err) } _, fail := runner.Results.PassFail() if fail > 0 { fmt.Printf("%d commits to fix\n", fail) - os.Exit(1) + + return errCommitsToFix } + + return nil } // configureLogger installs the process-wide slog handler through primordium: @@ -127,12 +152,6 @@ func configureLogger(debug, quiet bool) { logger.SetDefaultsForLogger(context.Background(), level) } -// exitOn logs err with the step that failed and exits 1. -func exitOn(step string, err error) { - slog.Error("git-validation failed", "step", step, "err", err) - os.Exit(1) -} - func ruleNames(rules []validate.Rule) []string { names := make([]string, 0, len(rules)) for _, rule := range rules { diff --git a/go.mod b/go.mod index 7180a44..2386086 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/forkcloser/git-validation go 1.26.0 -require github.com/mycophonic/primordium v0.15.0 +require ( + github.com/alecthomas/kong v1.16.1 + github.com/mycophonic/primordium v0.15.0 +) require ( github.com/lmittmann/tint v1.2.1 // indirect diff --git a/go.sum b/go.sum index 0b1402c..08707e5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.16.1 h1:ixhCt93XkJ98kGposQ54+bl0IK6XwqB40AsMynU7Z8E= +github.com/alecthomas/kong v1.16.1/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/lmittmann/tint v1.2.1 h1:zOadah+f+nSm3jZM044jOsqh0+vSfgG/d9Ota9a/TLE= github.com/lmittmann/tint v1.2.1/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=