From 35f45b4e4cc1293376f9150a32dc2b16251e50bd Mon Sep 17 00:00:00 2001 From: Benjamin Bengfort Date: Sat, 4 Apr 2026 15:31:58 -0500 Subject: [PATCH] [FEAT] CLI for Generation --- cmd/enumify/main.go | 65 ++++++++++++++++++++++++++++++++++++++++++--- enumify.go | 29 ++++++++++++++++++++ enumify_test.go | 22 +++++++++++++++ go.mod | 5 +++- go.sum | 2 ++ version.go | 13 +++++++++ 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 version.go diff --git a/cmd/enumify/main.go b/cmd/enumify/main.go index c245260..98c364d 100644 --- a/cmd/enumify/main.go +++ b/cmd/enumify/main.go @@ -1,12 +1,71 @@ package main import ( + "flag" "fmt" "os" + + "go.rtnl.ai/enumify" ) func main() { - fname := os.Getenv("GOFILE") - pkg := os.Getenv("GOPACKAGE") - fmt.Println(fname, pkg) + // Set up the flags and options for go generate processing. + versionFlag := flag.Bool("version", false, "print the version and exit") + opts := enumify.Options{ + File: os.Getenv("GOFILE"), + Pkg: os.Getenv("GOPACKAGE"), + } + + // Bind flag variables directly to the opts struct + flag.StringVar(&opts.NameVar, "names", "", "variable name that contains the string reprs of the enum values") + flag.BoolVar(&opts.NoTests, "no-tests", false, "skip testing code generation") + flag.BoolVar(&opts.NoParser, "no-parser", false, "skip parser code generation") + flag.BoolVar(&opts.NoStringer, "no-stringer", false, "skip Stringer interface code generation") + flag.BoolVar(&opts.NoText, "no-text", false, "skip text interfaces code generation") + flag.BoolVar(&opts.NoBinary, "no-binary", false, "skip binary interfaces code generation") + flag.BoolVar(&opts.NoJSON, "no-json", false, "skip JSON interfaces code generation") + flag.BoolVar(&opts.NoYAML, "no-yaml", false, "skip YAML interfaces code generation") + flag.BoolVar(&opts.NoSQL, "no-sql", false, "skip SQL interfaces code generation") + + // Set the usage function and parse command line arguments + flag.Usage = usage + flag.Parse() + + // Print the version and exit if the version flag is set + if *versionFlag { + fmt.Printf("enumify %s\n", enumify.Version()) + os.Exit(0) + } + + // Perform code generation! + if err := enumify.Generate(opts); err != nil { + fmt.Fprintf(os.Stderr, "enumify: %v\n", err) + os.Exit(1) + } + + // Generate tests if not skipped + if !opts.NoTests { + if err := enumify.GenerateTests(opts); err != nil { + fmt.Fprintf(os.Stderr, "enumify: %v\n", err) + os.Exit(1) + } + } + + // Signal that we succeeded with the code generation + os.Exit(0) +} + +func usage() { + // Get the default output (usually stderr) + w := flag.CommandLine.Output() + + fmt.Fprintln(w, "Enumify is a code generation tool for easily creating and managing") + fmt.Fprintln(w, "typed enumerations with code generation and automated enum tests.") + fmt.Fprintln(w, "") + fmt.Fprintln(w, "Usage: add a go directive to a go file file for generation:") + fmt.Fprintln(w, "\n //go:generate enumify [-version] [-help] [opts]") + fmt.Fprintln(w, "\nThen run run the go tool code generation command:") + fmt.Fprintln(w, "\n go generate ./...") + fmt.Fprintln(w, "\nOptions:") + flag.PrintDefaults() } diff --git a/enumify.go b/enumify.go index bf0fedc..f5791f8 100644 --- a/enumify.go +++ b/enumify.go @@ -14,3 +14,32 @@ type Enum interface { sql.Scanner driver.Valuer } + +// Options defines how the code generation should be performed. These values are set by +// the CLI flags passed via the go generate directive. +type Options struct { + File string // The file that kicked off the generation (from $GOFILE) + Pkg string // The package that the enum is in (from $GOPACKAGE) + NameVar string // The variable name that contains the names of the enum values + NoTests bool // Whether to skip the testing code generation (defaults to false) + NoParser bool // Whether to skip the parser code generation (defaults to false) + NoStringer bool // Whether to skip the Stringer interface code generation (defaults to false) + NoText bool // Whether to skip the text interfaces code generation (defaults to false) + NoBinary bool // Whether to skip the binary interfaces code generation (defaults to false) + NoJSON bool // Whether to skip the JSON interfaces code generation (defaults to false) + NoYAML bool // Whether to skip the YAML interfaces code generation (defaults to false) + NoSQL bool // Whether to skip the SQL interfaces code generation (defaults to false) +} + +func GenerateComment() string { + return fmt.Sprintf("Code generated by enumify v%s. DO NOT EDIT.", Version()) +} + +func Generate(opts Options) error { + fmt.Printf("%+v\n", opts) + return nil +} + +func GenerateTests(opts Options) error { + return nil +} diff --git a/enumify_test.go b/enumify_test.go index 1f8e4f6..718484d 100644 --- a/enumify_test.go +++ b/enumify_test.go @@ -3,10 +3,32 @@ package enumify_test import ( "database/sql/driver" "encoding/json" + "regexp" + "testing" + "github.com/stretchr/testify/require" "go.rtnl.ai/enumify" ) +//============================================================================ +// Testing Code Generation Utilities +//============================================================================ + +// To convey to humans and machine tools that code is generated, +// generated source should have a line that matches the following +// regular expression (in Go syntax): + +// ^// Code generated .* DO NOT EDIT\.$ + +// This line must appear before the first non-comment, non-blank +// text in the file. +// +// From: https://pkg.go.dev/cmd/go/internal/generate#pkg-variables +func TestGenerateComment(t *testing.T) { + regex := regexp.MustCompile(`^Code generated .* DO NOT EDIT\.$`) + require.True(t, regex.MatchString(enumify.GenerateComment())) +} + //============================================================================ // Enum type for testing the library //============================================================================ diff --git a/go.mod b/go.mod index efddd5c..c9c2501 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module go.rtnl.ai/enumify go 1.26.1 -require github.com/stretchr/testify v1.11.1 +require ( + github.com/stretchr/testify v1.11.1 + go.rtnl.ai/x v1.15.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index c4c1710..ec9d4c3 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.rtnl.ai/x v1.15.0 h1:tzMqlAXrwZ4CHNscAawlBbMjDvEwZxSu9AMxJB4CPOs= +go.rtnl.ai/x v1.15.0/go.mod h1:ciQ9PaXDtZDznzBrGDBV2yTElKX3aJgtQfi6V8613bo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/version.go b/version.go new file mode 100644 index 0000000..86fdaef --- /dev/null +++ b/version.go @@ -0,0 +1,13 @@ +package enumify + +import "go.rtnl.ai/x/semver" + +var version = semver.Version{ + Major: 1, + Minor: 0, + Patch: 0, +} + +func Version() string { + return version.String() +}