Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ on:
- main

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v7
with:
version: v2.12.2

unit:
runs-on: ubuntu-latest
steps:
Expand Down
87 changes: 87 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
version: "2"

linters:
default: all

disable:
# Deprecated, replaced by wsl_v5 / gomodguard_v2 below.
- wsl
- gomodguard

# Forces every struct literal (including third-party ones) to set every
# field explicitly. Too invasive for general application code.
- exhaustruct

# Requires every error to be a package-level sentinel wrapped with %w.
# Fights idiomatic fmt.Errorf("...: %w", err) for one-off dynamic errors.
- err113

# Disallows `if err := f(); err != nil {}`, which is standard idiomatic
# Go (recommended by Effective Go) and used throughout this codebase.
- noinlineerr

# Flags common, readable short names (i, ok, t, tt, f, s) as too short;
# too aggressive for this codebase's style.
- varnamelen

# main.BuildVersion/BuildCommit/BuildDate/Repository are populated via
# -ldflags at build time and must be package-level vars.
- gochecknoglobals

# TODO comments here track real, intentional follow-up work; failing CI
# on their presence isn't useful for this project.
- godox

settings:
depguard:
rules:
main:
files:
- $all
deny:
- pkg: io/ioutil
desc: "io/ioutil is deprecated, use io or os instead"

exhaustive:
# Switches over third-party enums (e.g. yaml.Kind) commonly only care
# about a handful of cases and treat the rest as an error via a
# default case; requiring every variant to be listed explicitly adds
# no value there.
default-signifies-exhaustive: true

forbidigo:
# Only flag the bare print/println builtins (debug leftovers); this is
# a CLI tool where fmt.Print* is legitimate, intentional user output.
forbid:
- pattern: "^(print|println)$"
msg: "use fmt.Print* or a logger instead of the print/println builtins"

lll:
line-length: 140

funlen:
ignore-comments: true

exclusions:
rules:
# Table-driven tests are naturally long; that's not a complexity smell.
- path: _test\.go
linters:
- funlen

# These structs mirror external, fixed wire formats (the GitHub
# releases API and the pre-commit hook manifest schema), so their
# struct tags must stay snake_case to match those formats exactly.
- path: cmd/helm-values/internal/(github|precommit)\.go
linters:
- tagliatelle

formatters:
enable:
- gofmt
- goimports

settings:
goimports:
local-prefixes:
- helmvalues
24 changes: 24 additions & 0 deletions cmd/helm-values/internal/config/base.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
// Package config binds cobra command flags and environment variables to
// viper, and translates the bound values into the config types each
// helm-values subcommand's underlying package expects.
package config

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// logLevelFlag is the flag/env name shared by every subcommand's log-level
// setting.
const logLevelFlag = "log-level"

func standardViper() *viper.Viper {
cfg := viper.New()
cfg.AllowEmptyEnv(true)
cfg.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

return cfg
}

// bindFlag binds the cobra flag named name (already registered on cmd) to
// v, along with its environment-variable equivalent.
func bindFlag(v *viper.Viper, cmd *cobra.Command, name string) error {
if err := v.BindPFlag(name, cmd.Flags().Lookup(name)); err != nil {
return fmt.Errorf("binding %s flag: %w", name, err)
}

if err := v.BindEnv(name); err != nil {
return fmt.Errorf("binding %s env: %w", name, err)
}

return nil
}
102 changes: 59 additions & 43 deletions cmd/helm-values/internal/config/docs.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,53 @@
package config

import (
"fmt"
"path/filepath"

"helmvalues/pkg/docs"
"helmvalues/pkg/docs/templates"
"path/filepath"

"github.com/samber/mo"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// DocsConfig holds the flag/env-bound configuration for the docs command.
type DocsConfig struct {
*viper.Viper
}

// NewDocsConfig creates a DocsConfig backed by a fresh viper instance.
func NewDocsConfig() *DocsConfig {
cfg := standardViper()

return &DocsConfig{cfg}
}

type DocsConfig struct {
*viper.Viper
}

// ValuesOrder returns the configured order in which values rows are
// rendered.
func (c *DocsConfig) ValuesOrder() (docs.ValuesOrder, error) {
return docs.NewValuesOrder(c.GetString("order"))
order, err := docs.NewValuesOrder(c.GetString("order"))
if err != nil {
return order, fmt.Errorf("parsing values order: %w", err)
}

return order, nil
}

// LogLevel returns the configured log level.
func (c *DocsConfig) LogLevel() (logrus.Level, error) {
return logrus.ParseLevel(c.GetString("log-level"))
level, err := logrus.ParseLevel(c.GetString(logLevelFlag))
if err != nil {
return level, fmt.Errorf("parsing log level: %w", err)
}

return level, nil
}

// ExtraTemplates resolves the configured extra-templates glob into a list
// of matching file paths.
func (c *DocsConfig) ExtraTemplates() ([]string, error) {
et := c.GetString("extra-templates")
if et == "" {
Expand All @@ -37,93 +56,89 @@ func (c *DocsConfig) ExtraTemplates() ([]string, error) {

path, err := filepath.Abs(et)
if err != nil {
return nil, err
return nil, fmt.Errorf("resolving extra-templates path: %w", err)
}

matches, err := filepath.Glob(path)
if err != nil {
return nil, fmt.Errorf("globbing extra-templates path: %w", err)
}

return filepath.Glob(path)
return matches, nil
}

// Markup returns the configured output markup, if one was set.
func (c *DocsConfig) Markup() (mo.Option[templates.Markup], error) {
if !c.IsSet("markup") {
return mo.None[templates.Markup](), nil
}

markup, err := templates.MarkupFromString(c.GetString("markup"))
if err != nil {
return mo.None[templates.Markup](), err
}

return mo.Some(markup), nil
}

// UseDefault returns the configured use-default flag, if it was set.
func (c *DocsConfig) UseDefault() mo.Option[bool] {
if !c.IsSet("use-default") {
return mo.None[bool]()
}

return mo.Some(c.GetBool("use-default"))
}

// Output returns the configured output path, if one was set.
func (c *DocsConfig) Output() mo.Option[string] {
if !c.IsSet("output") {
return mo.None[string]()
}

return mo.Some(c.GetString("output"))
}

// UpdateLogger sets logger's level to the configured log level.
func (c *DocsConfig) UpdateLogger(logger *logrus.Logger) error {
level, err := c.LogLevel()
if err != nil {
return err
}

logger.SetLevel(level)

return nil
}

func (c *DocsConfig) BindFlags(cmd *cobra.Command) {
// BindFlags registers the docs command's flags on cmd and binds them (and
// their environment-variable equivalents) to this config.
func (c *DocsConfig) BindFlags(cmd *cobra.Command) error {
cmd.Flags().Bool("stdout", false, "write to stdout")
c.BindPFlag("stdout", cmd.Flags().Lookup("stdout"))
c.BindEnv("stdout")

cmd.Flags().Bool("git-add", false, "stage changes with git add (useful for pre-commit hooks)")
c.BindPFlag("git-add", cmd.Flags().Lookup("git-add"))
c.BindEnv("git-add")

cmd.Flags().Bool("strict", false, "fail on doc comment parsing errors")
c.BindPFlag("strict", cmd.Flags().Lookup("strict"))
c.BindEnv("strict")

cmd.Flags().Bool("dry-run", false, "don't write changes to disk")
c.BindPFlag("dry-run", cmd.Flags().Lookup("dry-run"))
c.BindEnv("dry-run")

cmd.Flags().String("log-level", "warn", "log level (debug, info, warn, error, fatal, panic)")
c.BindPFlag("log-level", cmd.Flags().Lookup("log-level"))
c.BindEnv("log-level")

cmd.Flags().String(logLevelFlag, "warn", "log level (debug, info, warn, error, fatal, panic)")
cmd.Flags().String("markup", "", "markup language (md, markdown, rst, restructuredtext)")
c.BindPFlag("markup", cmd.Flags().Lookup("markup"))
c.BindEnv("markup")

cmd.Flags().String("order", "preserve", "order of values (preserve, alphabetical)")
c.BindPFlag("order", cmd.Flags().Lookup("order"))
c.BindEnv("order")

cmd.Flags().Bool("use-default", true, "uses default template unless a custom template is present")
c.BindPFlag("use-default", cmd.Flags().Lookup("use-default"))
c.BindEnv("use-default")

cmd.Flags().String("output", "", "path to output (defaults to README.md or README.rst based on markup)")
c.BindPFlag("output", cmd.Flags().Lookup("output"))
c.BindEnv("output")

cmd.Flags().String("template", "", "path to template (defaults to README.md.tmpl or README.rst.tmpl based on markup)")
c.BindPFlag("template", cmd.Flags().Lookup("template"))
c.BindEnv("template")

cmd.Flags().String("extra-templates", "", "glob path to extra templates")
c.BindPFlag("extra-templates", cmd.Flags().Lookup("extra-templates"))
c.BindEnv("extra-templates")

for _, name := range []string{
"stdout", "git-add", "strict", "dry-run", logLevelFlag, "markup",
"order", "use-default", "output", "template", "extra-templates",
} {
if err := bindFlag(c.Viper, cmd, name); err != nil {
return err
}
}

return nil
}

// ToPackageConfig builds the docs.Config this configuration describes.
func (c *DocsConfig) ToPackageConfig() (*docs.Config, error) {
logLevel, err := c.LogLevel()
if err != nil {
Expand Down Expand Up @@ -158,5 +173,6 @@ func (c *DocsConfig) ToPackageConfig() (*docs.Config, error) {
Markup: markup,
Order: valuesOrder,
}

return config, nil
}
Loading
Loading