diff --git a/.golangci.yml b/.golangci.yml index 2d239c6..f916ca1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -69,8 +69,6 @@ linters: linters: - revive text: 'dot-imports' - # errcheck: We need to temporarily disable checking of auto generated - # code, until the new generator has been created - path: '-auto\.go' linters: - errcheck diff --git a/.vscode/settings.json b/.vscode/settings.json index e96e14b..68791d1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,6 +71,7 @@ "stylecheck", "subosito", "thelper", + "tmpl", "toplevel", "tparallel", "typecheck", diff --git a/Taskfile.yml b/Taskfile.yml index decd0ff..0e3f481 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -146,6 +146,12 @@ tasks: - go tool cover -html=./coverage.out -o {{.COVER_HTML_PATH}} - open {{.COVER_HTML_PATH}} + # === ps-gen (param set generator) ========================= + + ps-gen: + cmds: + - go run ./cmd/ps-gen/main.go + # === i18n ================================================= clear: diff --git a/assist/param-set-auto.go b/assist/param-set-auto.go index e286df5..2c83200 100644 --- a/assist/param-set-auto.go +++ b/assist/param-set-auto.go @@ -1,3 +1,5 @@ +// Code generated by gen-param-set; DO NOT EDIT. + package assist import ( @@ -5,8 +7,6 @@ import ( "time" ) -// ----> auto generated(Build-ParamSet/gen-ps) - // BindBool binds bool slice flag with a shorthand if // 'info.Short' has been set otherwise binds without a short name. func (params *ParamSet[N]) BindBool(info *FlagInfo, to *bool) *ParamSet[N] { @@ -61,18 +61,6 @@ func (params *ParamSet[N]) BindDurationSlice(info *FlagInfo, to *[]time.Duration // BindEnum binds enum slice flag with a shorthand if // 'info.Short' has been set otherwise binds without a short name. -// -// Note that normally the client would bind to a member of the native parameter -// set. However, since there is a discrepancy between the type of the native int -// based pseudo enum member and the equivalent acceptable string value typed by -// the user on the command line (idiomatically stored on the enum info), the -// client needs to extract the enum value from the enum info, something like this: -// -// paramSet.Native.Format = OutputFormatEnumInfo.Value() -// -// The best place to put this would be inside the PreRun/PreRunE function, assuming the -// param set and the enum info are both in scope. Actually, every int based enum -// flag, would need to have this assignment performed. func (params *ParamSet[N]) BindEnum(info *FlagInfo, to *string) *ParamSet[N] { flagSet := params.ResolveFlagSet(info) if info.Short == "" { @@ -369,5 +357,3 @@ func (params *ParamSet[N]) BindUintSlice(info *FlagInfo, to *[]uint) *ParamSet[N return params } - -// <---- end of auto generated diff --git a/cmd/ps-gen/main.go b/cmd/ps-gen/main.go new file mode 100644 index 0000000..71342d4 --- /dev/null +++ b/cmd/ps-gen/main.go @@ -0,0 +1,245 @@ +// ps-gen generates the file param-set-auto.go which contains all the +// Bind* methods on ParamSet[N]. To regenerate, run: +// +// go run ./cmd/ps-gen/main.go +// +// The output is written to: assist/param-set-auto.go +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "text/template" +) + +// outputPath is the destination file relative to the working directory from +// which you invoke `go run`. Adjust this to match your repository layout. +const outputPath = "assist/param-set-auto.go" + +// typeSpec describes a single Bind* method (or Bind*Slice pair). +type typeSpec struct { + // GoType is the Go type name used in the method signature, e.g. "bool", + // "int32", "net.IPMask". + GoType string + + // FlagType is the pflag method suffix, e.g. "Bool", "Int32", "IPMask". + // For BindEnum this differs from the method name. + FlagType string + + // MethodName overrides the generated Bind name when it differs + // from FlagType (only needed for BindEnum). + MethodName string + + // HasSlice indicates whether a Bind*Slice variant should also be generated. + HasSlice bool + + // SliceGoType overrides the slice element type when it differs from GoType + // (not currently needed, but kept for extensibility). + SliceGoType string + + // Comment is the doc-comment body (the part after "binds "). + Comment string + + // SliceComment is the doc-comment body for the slice variant. + SliceComment string +} + +func (s typeSpec) Resolve() typeSpec { + if s.MethodName == "" { + s.MethodName = s.FlagType + } + if s.SliceGoType == "" { + s.SliceGoType = s.GoType + } + return s +} + +var specs = []typeSpec{ + { + GoType: "bool", + FlagType: "Bool", + HasSlice: true, + Comment: "bool slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]bool slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "time.Duration", + FlagType: "Duration", + HasSlice: true, + Comment: "time.Duration slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]time.Duration slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "string", + FlagType: "String", + MethodName: "Enum", + HasSlice: false, + Comment: "enum slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "float32", + FlagType: "Float32", + HasSlice: true, + Comment: "float32 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]float32 slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "float64", + FlagType: "Float64", + HasSlice: true, + Comment: "float64 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]float64 slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "int", + FlagType: "Int", + HasSlice: true, + Comment: "int slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]int slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "int16", + FlagType: "Int16", + HasSlice: false, + Comment: "int16 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "int32", + FlagType: "Int32", + HasSlice: true, + Comment: "int32 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]int32 slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "int64", + FlagType: "Int64", + HasSlice: true, + Comment: "int64 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]int64 slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "int8", + FlagType: "Int8", + HasSlice: false, + Comment: "int8 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "net.IPMask", + FlagType: "IPMask", + HasSlice: false, + Comment: "net.IPMask slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "net.IPNet", + FlagType: "IPNet", + HasSlice: false, + Comment: "net.IPNet slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "string", + FlagType: "String", + HasSlice: true, + Comment: "string slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]string slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, + { + GoType: "uint16", + FlagType: "Uint16", + HasSlice: false, + Comment: "uint16 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "uint32", + FlagType: "Uint32", + HasSlice: false, + Comment: "uint32 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "uint64", + FlagType: "Uint64", + HasSlice: false, + Comment: "uint64 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "uint8", + FlagType: "Uint8", + HasSlice: false, + Comment: "uint8 slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + }, + { + GoType: "uint", + FlagType: "Uint", + HasSlice: true, + Comment: "uint slice flag with a shorthand if\n// 'info.Short' has been set otherwise binds without a short name.", + SliceComment: "[]uint slice flag with a shorthand if 'info.Short' has been set\n// otherwise binds without a short name.", + }, +} + +const tmplSrc = `// Code generated by gen-param-set; DO NOT EDIT. + +package assist + +import ( + "net" + "time" +) +{{range .}}{{$s := .}} +// Bind{{$s.MethodName}} binds {{$s.Comment}} +func (params *ParamSet[N]) Bind{{$s.MethodName}}(info *FlagInfo, to *{{$s.GoType}}) *ParamSet[N] { + flagSet := params.ResolveFlagSet(info) + if info.Short == "" { + flagSet.{{$s.FlagType}}Var(to, info.FlagName(), info.Default.({{$s.GoType}}), info.Usage) + } else { + flagSet.{{$s.FlagType}}VarP(to, info.FlagName(), info.Short, info.Default.({{$s.GoType}}), info.Usage) + } + + return params +} +{{if $s.HasSlice}} +// Bind{{$s.MethodName}}Slice binds {{$s.SliceComment}} +func (params *ParamSet[N]) Bind{{$s.MethodName}}Slice(info *FlagInfo, to *[]{{$s.SliceGoType}}) *ParamSet[N] { + flagSet := params.ResolveFlagSet(info) + if info.Short == "" { + flagSet.{{$s.FlagType}}SliceVar(to, info.FlagName(), info.Default.([]{{$s.SliceGoType}}), info.Usage) + } else { + flagSet.{{$s.FlagType}}SliceVarP(to, info.FlagName(), info.Short, info.Default.([]{{$s.SliceGoType}}), info.Usage) + } + + return params +} +{{end}}{{end}}` + +func main() { + // Resolve all specs (fill in derived fields). + resolved := make([]typeSpec, len(specs)) + for i, s := range specs { + resolved[i] = s.Resolve() + } + + tmpl, err := template.New("param-set-auto").Parse(tmplSrc) + if err != nil { + log.Fatalf("parse template: %v", err) + } + + var buf bytes.Buffer + if err = tmpl.Execute(&buf, resolved); err != nil { + log.Fatalf("execute template: %v", err) + } + + // Run through gofmt so the output is canonical. + formatted, err := format.Source(buf.Bytes()) + if err != nil { + // Emit the raw output to make debugging easier. + fmt.Fprintf(os.Stderr, "--- raw output ---\n%s\n--- end ---\n", buf.String()) + log.Fatalf("gofmt: %v", err) + } + + if err := os.WriteFile(outputPath, formatted, 0o644); err != nil { //nolint:gosec // ok + log.Fatalf("write %s: %v", outputPath, err) + } + + fmt.Printf("wrote %s\n", outputPath) +}