Move genflags tool to cmd/ and pin to previous release
to alleviate problems caused by the circular dependency of using the same code as a library that is potentially being generated to adhere to a different API.
This commit is contained in:
@@ -94,7 +94,7 @@ func main() {
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "packages",
|
||||
Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"),
|
||||
Value: cli.NewStringSlice("cli", "altsrc", "internal/build"),
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -1,163 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/internal/genflags"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPackageName = "cli"
|
||||
)
|
||||
|
||||
func sh(ctx context.Context, exe string, args ...string) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, exe, args...)
|
||||
cmd.Stderr = os.Stderr
|
||||
outBytes, err := cmd.Output()
|
||||
return string(outBytes), err
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
top := "../../"
|
||||
if v, err := sh(ctx, "git", "rev-parse", "--show-toplevel"); err == nil {
|
||||
top = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "genflags",
|
||||
Usage: "Generate flag types for urfave/cli",
|
||||
Flags: []cli.Flag{
|
||||
&cli.PathFlag{
|
||||
Name: "flag-spec-yaml",
|
||||
Aliases: []string{"f"},
|
||||
Value: filepath.Join(top, "flag-spec.yaml"),
|
||||
},
|
||||
&cli.PathFlag{
|
||||
Name: "generated-output",
|
||||
Aliases: []string{"o"},
|
||||
Value: filepath.Join(top, "zz_generated.flags.go"),
|
||||
},
|
||||
&cli.PathFlag{
|
||||
Name: "generated-test-output",
|
||||
Aliases: []string{"t"},
|
||||
Value: filepath.Join(top, "zz_generated.flags_test.go"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "generated-package-name",
|
||||
Aliases: []string{"p"},
|
||||
Value: defaultPackageName,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "generated-test-package-name",
|
||||
Aliases: []string{"T"},
|
||||
Value: defaultPackageName + "_test",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "urfave-cli-namespace",
|
||||
Aliases: []string{"n"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "urfave-cli-test-namespace",
|
||||
Aliases: []string{"N"},
|
||||
Value: "cli.",
|
||||
},
|
||||
},
|
||||
Action: runGenFlags,
|
||||
}
|
||||
|
||||
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runGenFlags(cCtx *cli.Context) error {
|
||||
specBytes, err := os.ReadFile(cCtx.Path("flag-spec-yaml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := &genflags.Spec{}
|
||||
if err := yaml.Unmarshal(specBytes, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cCtx.IsSet("generated-package-name") {
|
||||
spec.PackageName = strings.TrimSpace(cCtx.String("generated-package-name"))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(spec.PackageName) == "" {
|
||||
spec.PackageName = defaultPackageName
|
||||
}
|
||||
|
||||
if cCtx.IsSet("generated-test-package-name") {
|
||||
spec.TestPackageName = strings.TrimSpace(cCtx.String("generated-test-package-name"))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(spec.TestPackageName) == "" {
|
||||
spec.TestPackageName = defaultPackageName + "_test"
|
||||
}
|
||||
|
||||
if cCtx.IsSet("urfave-cli-namespace") {
|
||||
spec.UrfaveCLINamespace = strings.TrimSpace(cCtx.String("urfave-cli-namespace"))
|
||||
}
|
||||
|
||||
if cCtx.IsSet("urfave-cli-test-namespace") {
|
||||
spec.UrfaveCLITestNamespace = strings.TrimSpace(cCtx.String("urfave-cli-test-namespace"))
|
||||
} else {
|
||||
spec.UrfaveCLITestNamespace = "cli."
|
||||
}
|
||||
|
||||
genTmpl, err := template.New("gen").Parse(genflags.TemplateString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genBuf := &bytes.Buffer{}
|
||||
if err := genTmpl.Execute(genBuf, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genTestBuf := &bytes.Buffer{}
|
||||
if err := genTestTmpl.Execute(genTestBuf, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(cCtx.Path("generated-output"), genBuf.Bytes(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(cCtx.Path("generated-test-output"), genTestBuf.Bytes(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
// WARNING: this file is generated. DO NOT EDIT
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
{{range .SortedFlagTypes}}
|
||||
// {{.TypeName}} is a flag with type {{if .ValuePointer}}*{{end}}{{.GoType}}
|
||||
type {{.TypeName}} struct {
|
||||
Name string
|
||||
|
||||
Category string
|
||||
DefaultText string
|
||||
FilePath string
|
||||
Usage string
|
||||
|
||||
Required bool
|
||||
Hidden bool
|
||||
HasBeenSet bool
|
||||
|
||||
Value {{if .ValuePointer}}*{{end}}{{.GoType}}
|
||||
Destination *{{.GoType}}
|
||||
|
||||
Aliases []string
|
||||
EnvVars []string
|
||||
|
||||
{{range .StructFields}}
|
||||
{{.Name}} {{.Type}}
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{if .GenerateFmtStringerInterface}}
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *{{.TypeName}}) String() string {
|
||||
return {{$.UrfaveCLINamespace}}FlagStringer(f)
|
||||
}
|
||||
{{end}}{{/* /if .GenerateFmtStringerInterface */}}
|
||||
|
||||
{{if .GenerateFlagInterface}}
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
func (f *{{.TypeName}}) IsSet() bool {
|
||||
return f.HasBeenSet
|
||||
}
|
||||
|
||||
// Names returns the names of the flag
|
||||
func (f *{{.TypeName}}) Names() []string {
|
||||
return {{$.UrfaveCLINamespace}}FlagNames(f.Name, f.Aliases)
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f *{{.TypeName}}) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *{{.TypeName}}) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (f *{{.TypeName}}) GetCategory() string {
|
||||
return f.Category
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f *{{.TypeName}}) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *{{.TypeName}}) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
func (f *{{.TypeName}}) TakesValue() bool {
|
||||
return "{{.TypeName }}" != "BoolFlag"
|
||||
}
|
||||
|
||||
{{if .GenerateDefaultText}}
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *{{.TypeName}}) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{end}}{{/* /if .GenerateFlagInterface */}}
|
||||
|
||||
{{end}}{{/* /range .SortedFlagTypes */}}
|
||||
|
||||
// vim{{/* 👻 */}}:ro
|
||||
{{/*
|
||||
vim:filetype=gotexttmpl
|
||||
*/}}
|
@@ -1,42 +0,0 @@
|
||||
// WARNING: this file is generated. DO NOT EDIT
|
||||
|
||||
package {{.TestPackageName}}
|
||||
|
||||
{{range .SortedFlagTypes}}
|
||||
{{if .GenerateFlagInterface}}
|
||||
func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) {
|
||||
var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||
|
||||
_ = f.IsSet()
|
||||
_ = f.Names()
|
||||
}
|
||||
|
||||
func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||
var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||
|
||||
_ = f.IsRequired()
|
||||
}
|
||||
|
||||
func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||
var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||
|
||||
_ = f.IsVisible()
|
||||
}
|
||||
|
||||
{{end}}
|
||||
|
||||
{{if .GenerateFmtStringerInterface}}
|
||||
func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||
var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||
|
||||
_ = f.String()
|
||||
}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{end}}
|
||||
|
||||
// vim{{/* 👻 */}}:ro
|
||||
{{/*
|
||||
vim:filetype=gotexttmpl
|
||||
*/}}
|
@@ -1,34 +0,0 @@
|
||||
package genflags
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed generated.gotmpl
|
||||
TemplateString string
|
||||
|
||||
//go:embed generated_test.gotmpl
|
||||
TestTemplateString string
|
||||
|
||||
titler = cases.Title(language.Und, cases.NoLower)
|
||||
)
|
||||
|
||||
func TypeName(goType string, fc *FlagTypeConfig) string {
|
||||
if fc != nil && strings.TrimSpace(fc.TypeName) != "" {
|
||||
return strings.TrimSpace(fc.TypeName)
|
||||
}
|
||||
|
||||
dotSplit := strings.Split(goType, ".")
|
||||
goType = dotSplit[len(dotSplit)-1]
|
||||
|
||||
if strings.HasPrefix(goType, "[]") {
|
||||
return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag"
|
||||
}
|
||||
|
||||
return titler.String(goType) + "Flag"
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
package genflags_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli/v2/internal/genflags"
|
||||
)
|
||||
|
||||
func TestTypeName(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
gt string
|
||||
fc *genflags.FlagTypeConfig
|
||||
expected string
|
||||
}{
|
||||
{gt: "int", fc: nil, expected: "IntFlag"},
|
||||
{gt: "int", fc: &genflags.FlagTypeConfig{}, expected: "IntFlag"},
|
||||
{gt: "int", fc: &genflags.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"},
|
||||
{gt: "[]bool", fc: nil, expected: "BoolSliceFlag"},
|
||||
{gt: "[]bool", fc: &genflags.FlagTypeConfig{}, expected: "BoolSliceFlag"},
|
||||
{gt: "[]bool", fc: &genflags.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"},
|
||||
{gt: "time.Rumination", fc: nil, expected: "RuminationFlag"},
|
||||
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{}, expected: "RuminationFlag"},
|
||||
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"},
|
||||
} {
|
||||
t.Run(
|
||||
fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string {
|
||||
if tc.fc != nil {
|
||||
return tc.fc.TypeName
|
||||
}
|
||||
return "nil"
|
||||
}()),
|
||||
func(ct *testing.T) {
|
||||
actual := genflags.TypeName(tc.gt, tc.fc)
|
||||
if tc.expected != actual {
|
||||
ct.Errorf("expected %q, got %q", tc.expected, actual)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,105 +0,0 @@
|
||||
package genflags
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"`
|
||||
PackageName string `yaml:"package_name"`
|
||||
TestPackageName string `yaml:"test_package_name"`
|
||||
UrfaveCLINamespace string `yaml:"urfave_cli_namespace"`
|
||||
UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"`
|
||||
}
|
||||
|
||||
func (gfs *Spec) SortedFlagTypes() []*FlagType {
|
||||
typeNames := []string{}
|
||||
|
||||
for name := range gfs.FlagTypes {
|
||||
if strings.HasPrefix(name, "[]") {
|
||||
name = strings.TrimPrefix(name, "[]") + "Slice"
|
||||
}
|
||||
|
||||
typeNames = append(typeNames, name)
|
||||
}
|
||||
|
||||
sort.Strings(typeNames)
|
||||
|
||||
ret := make([]*FlagType, len(typeNames))
|
||||
|
||||
for i, typeName := range typeNames {
|
||||
ret[i] = &FlagType{
|
||||
GoType: typeName,
|
||||
Config: gfs.FlagTypes[typeName],
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type FlagTypeConfig struct {
|
||||
SkipInterfaces []string `yaml:"skip_interfaces"`
|
||||
StructFields []*FlagStructField `yaml:"struct_fields"`
|
||||
TypeName string `yaml:"type_name"`
|
||||
ValuePointer bool `yaml:"value_pointer"`
|
||||
NoDefaultText bool `yaml:"no_default_text"`
|
||||
}
|
||||
|
||||
type FlagStructField struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
type FlagType struct {
|
||||
GoType string
|
||||
Config *FlagTypeConfig
|
||||
}
|
||||
|
||||
func (ft *FlagType) StructFields() []*FlagStructField {
|
||||
if ft.Config == nil || ft.Config.StructFields == nil {
|
||||
return []*FlagStructField{}
|
||||
}
|
||||
|
||||
return ft.Config.StructFields
|
||||
}
|
||||
|
||||
func (ft *FlagType) ValuePointer() bool {
|
||||
if ft.Config == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return ft.Config.ValuePointer
|
||||
}
|
||||
|
||||
func (ft *FlagType) TypeName() string {
|
||||
return TypeName(ft.GoType, ft.Config)
|
||||
}
|
||||
|
||||
func (ft *FlagType) GenerateFmtStringerInterface() bool {
|
||||
return ft.skipInterfaceNamed("fmt.Stringer")
|
||||
}
|
||||
|
||||
func (ft *FlagType) GenerateFlagInterface() bool {
|
||||
return ft.skipInterfaceNamed("Flag")
|
||||
}
|
||||
|
||||
func (ft *FlagType) GenerateDefaultText() bool {
|
||||
return !ft.Config.NoDefaultText
|
||||
}
|
||||
|
||||
func (ft *FlagType) skipInterfaceNamed(name string) bool {
|
||||
if ft.Config == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
lowName := strings.ToLower(name)
|
||||
|
||||
for _, interfaceName := range ft.Config.SkipInterfaces {
|
||||
if strings.ToLower(interfaceName) == lowName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
package genflags_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli/v2/internal/genflags"
|
||||
)
|
||||
|
||||
func TestSpec_SortedFlagTypes(t *testing.T) {
|
||||
spec := &genflags.Spec{
|
||||
FlagTypes: map[string]*genflags.FlagTypeConfig{
|
||||
"nerf": &genflags.FlagTypeConfig{},
|
||||
"gerf": nil,
|
||||
},
|
||||
}
|
||||
|
||||
actual := spec.SortedFlagTypes()
|
||||
expected := []*genflags.FlagType{
|
||||
{
|
||||
GoType: "gerf",
|
||||
Config: nil,
|
||||
},
|
||||
{
|
||||
GoType: "nerf",
|
||||
Config: &genflags.FlagTypeConfig{},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("expected %#v, got %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func genFlagType() *genflags.FlagType {
|
||||
return &genflags.FlagType{
|
||||
GoType: "blerf",
|
||||
Config: &genflags.FlagTypeConfig{
|
||||
SkipInterfaces: []string{"fmt.Stringer"},
|
||||
StructFields: []*genflags.FlagStructField{
|
||||
{
|
||||
Name: "Foibles",
|
||||
Type: "int",
|
||||
},
|
||||
{
|
||||
Name: "Hoopled",
|
||||
Type: "bool",
|
||||
},
|
||||
},
|
||||
TypeName: "YeOldeBlerfFlag",
|
||||
ValuePointer: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType_StructFields(t *testing.T) {
|
||||
ft := genFlagType()
|
||||
|
||||
sf := ft.StructFields()
|
||||
if 2 != len(sf) {
|
||||
t.Errorf("expected 2 struct fields, got %v", len(sf))
|
||||
return
|
||||
}
|
||||
|
||||
if "Foibles" != sf[0].Name {
|
||||
t.Errorf("expected struct field order to be retained")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType_ValuePointer(t *testing.T) {
|
||||
ft := genFlagType()
|
||||
|
||||
if !ft.ValuePointer() {
|
||||
t.Errorf("expected ValuePointer to be true")
|
||||
return
|
||||
}
|
||||
|
||||
ft.Config = nil
|
||||
|
||||
if ft.ValuePointer() {
|
||||
t.Errorf("expected ValuePointer to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType_GenerateFmtStringerInterface(t *testing.T) {
|
||||
ft := genFlagType()
|
||||
|
||||
if ft.GenerateFmtStringerInterface() {
|
||||
t.Errorf("expected GenerateFmtStringerInterface to be false")
|
||||
return
|
||||
}
|
||||
|
||||
ft.Config = nil
|
||||
|
||||
if !ft.GenerateFmtStringerInterface() {
|
||||
t.Errorf("expected GenerateFmtStringerInterface to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType_GenerateFlagInterface(t *testing.T) {
|
||||
ft := genFlagType()
|
||||
|
||||
if !ft.GenerateFlagInterface() {
|
||||
t.Errorf("expected GenerateFlagInterface to be true")
|
||||
return
|
||||
}
|
||||
|
||||
ft.Config = nil
|
||||
|
||||
if !ft.GenerateFlagInterface() {
|
||||
t.Errorf("expected GenerateFlagInterface to be true")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user