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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ option. This method fulfills the `fmt.Stringer` interface, allowing more detail
- `-imports=[<path>|<alias>=<path>],...` add imports to generated file
- `-option <string>` sets name of the interface to use for options (default "Option")
- `-output <string>` sets the name of the output file (default is <type>_options.go)
- `-input <string>` sets the name of the input file. When set uses "go/build" and "go/parser" directly, which can result in performance improvements
- `-prefix <string>` sets prefix to be used for options (defaults to the value of `option`)
- `-quote-default-strings=false` disables default quoting of default values for string
- `-stringer=false` controls whether we generate an `String()` method that exposes option names and values. Useful for debugging tests. (default true)
Expand Down
47 changes: 47 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/printer"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/fatih/structtag"
Expand All @@ -20,6 +23,7 @@ import (
var typeName string
var optionInterfaceName string
var outputName string
var inputFileName string
var applyFunctionName string
var applyOptionFunctionType string
var createNewFunc bool
Expand All @@ -46,6 +50,7 @@ func initFlags() {
flag.BoolVar(&createNewFunc, "new", true, "whether to create a function to return a new config")
flag.StringVar(&optionInterfaceName, "option", "Option", "name of the interface to use for options")
flag.StringVar(&imports, "imports", "", "a comma-separated list of packages with optional alias (e.g. time,url=net/url) ")
flag.StringVar(&inputFileName, "input", "", "name of input file")
flag.StringVar(&outputName, "output", "", "name of output file (default is <type>_options.go)")
flag.StringVar(&applyFunctionName, "func", "", `name of function created to apply options to <type> (default is "apply<Type>Options")`)
flag.StringVar(&applyOptionFunctionType, "option_func", "",
Expand Down Expand Up @@ -105,6 +110,14 @@ func main() {
types = append(types, typeName)
}

if inputFileName != "" {
err := runWithInputFile(inputFileName, types)
if err != nil {
log.Fatal(err)
}
return
}

cfg := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedName,
Tests: false,
Expand Down Expand Up @@ -135,6 +148,40 @@ func main() {
}
}

// runWithInputFile is an alternative to packages.Load because packages.Load requires a full go driver
// runWithInputFile uses "go/build" and "go/parser" directly, but requires a file name to be passed.
// This limits the number of required dependencies, and speeds up generation times
func runWithInputFile(src string, typeNames []string) error {
fset := token.NewFileSet()

if ok, err := build.Default.MatchFile(filepath.Dir(src), filepath.Base(src)); err != nil {
return fmt.Errorf("error checking if file matches constraint %w", err)
} else if !ok || filepath.Ext(src) == ".s" {
return nil
}
f, err := parser.ParseFile(fset, src, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("error parsing %q %w", src, err)
}
if f.Name == nil || f.Name.Name == "" {
return fmt.Errorf("error parsing %q: no name in file", src)
}
inferedPackage := f.Name.Name
success := false
ast.Inspect(f, func(node ast.Node) bool {
found := writeOptionsFile(typeNames, inferedPackage, node, fset)
if found {
success = true
}
return !found
})
if !success {
return fmt.Errorf(`unable to find type "%s"`, typeNames)
}

return nil
Comment on lines +169 to +182
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is semi-duplicated with the thing that main is already doing - should we create a common function? Alternatively can we write a function that constructs the pkgs array differently based on the input provided?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking closer - they're not that similar but there are similar pieces of data with similar types being looked at. There's probably something that could be done, but it's a question of whether or not that effort is worth it for this gen script.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is semi-duplicated with the thing that main is already doing - should we create a common function? Alternatively can we write a function that constructs the pkgs array differently based on the input provided?

The main duplication here is the call to ast.Inspect but a lot of the params are sourced differently.

  1. the method in main loops over all of the files vs single file when using the new mode
  2. the new method has to infer the package name from the input file

}

func writeOptionsFile(types []string, packageName string, node ast.Node, fset *token.FileSet) (found bool) {
decl, ok := node.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
Expand Down
Loading