Skip to content

Commit

Permalink
Add generated code marker (#12)
Browse files Browse the repository at this point in the history
"// Code generated by dingo; DO NOT EDIT" is the first line of dingo.go.

Fixes #1
  • Loading branch information
elliotchance committed Jun 29, 2019
1 parent 711d751 commit a683aa0
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 122 deletions.
5 changes: 3 additions & 2 deletions dingotest/dingo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ func (e Expression) Dependencies() (deps []string) {
return pie.Strings(deps).Unique()
}

func (e Expression) performSubstitutions(services Services, fromArgs bool) string {
func (e Expression) performSubstitutions(file *File, services Services, fromArgs bool) string {
stmt := string(e)

// Replace environment variables.
stmt = replaceAllStringSubmatchFunc(
regexp.MustCompile(`\${(.*?)}`), stmt, func(i []string) string {
astutil.AddImport(fset, file, "os")
astutil.AddImport(file.fset, file.file, "os")

return fmt.Sprintf("os.Getenv(\"%s\")", i[1])
})
Expand Down
123 changes: 123 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,128 @@
package main

import (
"fmt"
"github.com/go-yaml/yaml"
"go/ast"
"go/parser"
"go/token"
"golang.org/x/tools/go/ast/astutil"
"io/ioutil"
"path/filepath"
"strings"
)

type File struct {
Services Services
fset *token.FileSet
file *ast.File
}

func ParseYAMLFile(filepath, outputFile string) (*File, error) {
f, err := ioutil.ReadFile(filepath)
if err != nil {
return nil, err
}

var all *File
err = yaml.Unmarshal(f, &all)
if err != nil {
return nil, err
}

all.fset = token.NewFileSet()
packageLine := fmt.Sprintf("// Code generated by dingo; DO NOT EDIT\npackage %s", all.getPackageName(filepath))
all.file, err = parser.ParseFile(all.fset, outputFile, packageLine, parser.ParseComments)
if err != nil {
return nil, err
}

all.file.Decls = append(all.file.Decls,
all.Services.astContainerStruct(),
all.Services.astDefaultContainer(),
all.astNewContainerFunc())

for _, serviceName := range all.Services.ServiceNames() {
definition := all.Services[serviceName]

// Add imports for type, interface and explicit imports.
for packageName, shortName := range definition.Imports() {
astutil.AddNamedImport(all.fset, all.file, shortName, packageName)
}

all.file.Decls = append(all.file.Decls, &ast.FuncDecl{
Name: newIdent("Get" + serviceName),
Recv: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
newIdent("container"),
},
Type: newIdent("*Container"),
},
},
},
Type: &ast.FuncType{
Params: definition.astArguments(),
Results: newFieldList(definition.InterfaceOrLocalEntityType(all.Services, false)),
},
Body: definition.astFunctionBody(all, all.Services, serviceName, serviceName),
})
}

ast.SortImports(all.fset, all.file)

return all, nil
}

func (file *File) getPackageName(dingoYMLPath string) string {
abs, err := filepath.Abs(dingoYMLPath)
if err != nil {
panic(err)
}

// The directory name is not enough because it may contain a command
// (package main). Find the first non-test file to get the real package
// name.
dir := filepath.Dir(abs)
files, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}

for _, fileInfo := range files {
if strings.HasSuffix(fileInfo.Name(), ".go") &&
!strings.HasSuffix(fileInfo.Name(), "_test.go") {
f, err := ioutil.ReadFile(dir + "/" + fileInfo.Name())
if err != nil {
panic(err)
}

parsedFile, err := parser.ParseFile(file.fset, fileInfo.Name(), f, 0)
if err != nil {
panic(err)
}

return parsedFile.Name.String()
}
}

// Couldn't find the package name. Assume command.
return "main"
}

func (file *File) astNewContainerFunc() *ast.FuncDecl {
fields := make(map[string]ast.Expr)

for _, serviceName := range file.Services.ServicesWithScope(ScopePrototype).ServiceNames() {
service := file.Services[serviceName]
fields[serviceName] = &ast.FuncLit{
Type: service.astFunctionPrototype(file.Services),
Body: service.astFunctionBody(file, file.Services, "", serviceName),
}
}

return newFunc("NewContainer", nil, []string{"*Container"}, newBlock(
newReturn(newCompositeLit("&Container", fields)),
))
}
102 changes: 3 additions & 99 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
package main

import (
"fmt"
"github.com/go-yaml/yaml"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"golang.org/x/tools/go/ast/astutil"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)

var fset *token.FileSet
var file *ast.File

func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
Expand All @@ -40,102 +28,18 @@ func main() {
dingoYMLPath := "dingo.yml"
outputFile := "dingo.go"

f, err := ioutil.ReadFile(dingoYMLPath)
if err != nil {
log.Fatalln("reading file:", err)
}

all := File{}
err = yaml.Unmarshal(f, &all)
if err != nil {
log.Fatalln("yaml:", err)
}

fset = token.NewFileSet()
packageLine := fmt.Sprintf("package %s", getPackageName(dingoYMLPath))
file, err = parser.ParseFile(fset, outputFile, packageLine, 0)
file, err := ParseYAMLFile(dingoYMLPath, outputFile)
if err != nil {
log.Fatalln("parser:", err)
log.Fatalln(err)
}

file.Decls = append(file.Decls,
all.Services.astContainerStruct(),
all.Services.astDefaultContainer(),
all.Services.astNewContainerFunc())

for _, serviceName := range all.Services.ServiceNames() {
definition := all.Services[serviceName]

// Add imports for type, interface and explicit imports.
for packageName, shortName := range definition.Imports() {
astutil.AddNamedImport(fset, file, shortName, packageName)
}

file.Decls = append(file.Decls, &ast.FuncDecl{
Name: newIdent("Get" + serviceName),
Recv: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
newIdent("container"),
},
Type: newIdent("*Container"),
},
},
},
Type: &ast.FuncType{
Params: definition.astArguments(),
Results: newFieldList(definition.InterfaceOrLocalEntityType(all.Services, false)),
},
Body: definition.astFunctionBody(all.Services, serviceName, serviceName),
})
}

ast.SortImports(fset, file)

outFile, err := os.Create(outputFile)
if err != nil {
log.Fatalln("open file:", err)
}

err = printer.Fprint(outFile, fset, file)
err = printer.Fprint(outFile, file.fset, file.file)
if err != nil {
log.Fatalln("writer:", err)
}
}

func getPackageName(dingoYMLPath string) string {
abs, err := filepath.Abs(dingoYMLPath)
if err != nil {
panic(err)
}

// The directory name is not enough because it may contain a command
// (package main). Find the first non-test file to get the real package
// name.
dir := filepath.Dir(abs)
files, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}

for _, fileInfo := range files {
if strings.HasSuffix(fileInfo.Name(), ".go") &&
!strings.HasSuffix(fileInfo.Name(), "_test.go") {
f, err := ioutil.ReadFile(dir + "/" + fileInfo.Name())
if err != nil {
panic(err)
}

file, err = parser.ParseFile(fset, fileInfo.Name(), f, 0)
if err != nil {
panic(err)
}

return file.Name.String()
}
}

// Couldn't find the package name. Assume command.
return "main"
}
6 changes: 3 additions & 3 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (service *Service) astFunctionPrototype(services Services) *ast.FuncType {
}
}

func (service *Service) astFunctionBody(services Services, name, serviceName string) *ast.BlockStmt {
func (service *Service) astFunctionBody(file *File, services Services, name, serviceName string) *ast.BlockStmt {
if name != "" && service.Scope == ScopePrototype {
var arguments []string
for _, dep := range service.Returns.Dependencies() {
Expand Down Expand Up @@ -220,7 +220,7 @@ func (service *Service) astFunctionBody(services Services, name, serviceName str
Tok: token.DEFINE,
Lhs: lhs,
Rhs: []ast.Expr{
newIdent(service.Returns.performSubstitutions(services, name == "")),
newIdent(service.Returns.performSubstitutions(file, services, name == "")),
},
},
}
Expand All @@ -244,7 +244,7 @@ func (service *Service) astFunctionBody(services Services, name, serviceName str
instantiation = append(instantiation, &ast.AssignStmt{
Tok: token.ASSIGN,
Lhs: []ast.Expr{&ast.Ident{Name: serviceTempVariable + "." + property.Name}},
Rhs: []ast.Expr{&ast.Ident{Name: property.Value.performSubstitutions(services, name == "")}},
Rhs: []ast.Expr{&ast.Ident{Name: property.Value.performSubstitutions(file, services, name == "")}},
})
}

Expand Down
16 changes: 0 additions & 16 deletions services.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,6 @@ func (services Services) astContainerStruct() *ast.GenDecl {
}
}

func (services Services) astNewContainerFunc() *ast.FuncDecl {
fields := make(map[string]ast.Expr)

for _, serviceName := range services.ServicesWithScope(ScopePrototype).ServiceNames() {
service := services[serviceName]
fields[serviceName] = &ast.FuncLit{
Type: service.astFunctionPrototype(services),
Body: service.astFunctionBody(services, "", serviceName),
}
}

return newFunc("NewContainer", nil, []string{"*Container"}, newBlock(
newReturn(newCompositeLit("&Container", fields)),
))
}

func (services Services) astDefaultContainer() *ast.GenDecl {
return &ast.GenDecl{
Tok: token.VAR,
Expand Down
3 changes: 3 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"go/ast"
"sort"
)

func newIdent(name string) *ast.Ident {
Expand Down Expand Up @@ -61,6 +62,8 @@ func newCompositeLit(ty string, m map[string]ast.Expr) *ast.CompositeLit {
keys = append(keys, k)
}

sort.Strings(keys)

for _, k := range keys {
exprs = append(exprs, &ast.KeyValueExpr{
Key: newIdent(k),
Expand Down

0 comments on commit a683aa0

Please sign in to comment.