Skip to content

Commit

Permalink
bake: refactor into iterable
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGamba committed Jun 14, 2024
1 parent e043210 commit 4382c9a
Showing 1 changed file with 137 additions and 104 deletions.
241 changes: 137 additions & 104 deletions bake/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"go/parser"
"go/printer"
"go/token"
"iter"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -173,124 +174,57 @@ func PrintAst(dir string) error {
// opt.String("hola", "mundo")
// return func(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
func LoadAst(ctx context.Context, opt *getoptions.GetOpt, dir string) error {
// Regex for description: fn-name - description
re := regexp.MustCompile(`^\w\S+ -`)
ot := NewOptTree(opt)

for p, err := range parsedFiles(dir) {
for getOptFn, err := range AstGetoptionFns(ctx, opt, dir) {
if err != nil {
return err
}

// Iterate through every node in the file
ast.Inspect(p.f, func(n ast.Node) bool {
switch x := n.(type) {
// Check function declarations for exported functions
case *ast.FuncDecl:
if x.Name.IsExported() {
fnName := x.Name.Name
name := ""
description := strings.TrimSpace(x.Doc.Text())
// var buf bytes.Buffer
// printer.Fprint(&buf, p.fset, x.Type)
// Logger.Printf("file: %s\n", p.file)
// Logger.Printf("type: %s, name: %s, desc: %s\n", buf.String(), name, description)

// Expect function of type:
// func Name(opt *getoptions.GetOpt) getoptions.CommandFn
cmd := ot.AddCommand(getOptFn.FnName, getOptFn.Name, getOptFn.Description)

// Check Params
// Expect opt *getoptions.GetOpt
if len(x.Type.Params.List) != 1 {
// Check for Expressions of opt type
ast.Inspect(getOptFn.Node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.BlockStmt:
for _, stmt := range x.List {
var buf bytes.Buffer
printer.Fprint(&buf, getOptFn.parsedFile.fset, stmt)
// We are expecting the expression before the return function
_, ok := stmt.(*ast.ReturnStmt)
if ok {
return false
}
var optFieldName string
for _, param := range x.Type.Params.List {
name := param.Names[0].Name
var buf bytes.Buffer
printer.Fprint(&buf, p.fset, param.Type)
// Logger.Printf("name: %s, %s\n", name, buf.String())
if buf.String() != "*getoptions.GetOpt" {
return false
}
optFieldName = name
// Logger.Printf("stmt: %s\n", buf.String())
exprStmt, ok := stmt.(*ast.ExprStmt)
if !ok {
continue
}
// spew.Dump(exprStmt)

// Check Results
// Expect getoptions.CommandFn
if len(x.Type.Results.List) != 1 {
return false
}
for _, result := range x.Type.Results.List {
var buf bytes.Buffer
printer.Fprint(&buf, p.fset, result.Type)
// Logger.Printf("result: %s\n", buf.String())
if buf.String() != "getoptions.CommandFn" {
return false
}
}

// TODO: The yield probably goes here
// Add function to OptTree
if description != "" {
// Logger.Printf("description '%s'\n", description)
if re.MatchString(description) {
// Get first word from string
name = strings.Split(description, " ")[0]
description = strings.TrimPrefix(description, fnName+" -")
description = strings.TrimSpace(description)
}
} else {
name = camelToKebab(fnName)
}
cmd := ot.AddCommand(fnName, name, description)

// Check for Expressions of opt type
ast.Inspect(n, func(n ast.Node) bool {
// Check for CallExpr
ast.Inspect(exprStmt, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.BlockStmt:
for _, stmt := range x.List {
var buf bytes.Buffer
printer.Fprint(&buf, p.fset, stmt)
// We are expecting the expression before the return function
_, ok := stmt.(*ast.ReturnStmt)
if ok {
return false
}
// Logger.Printf("stmt: %s\n", buf.String())
exprStmt, ok := stmt.(*ast.ExprStmt)
if !ok {
continue
}
// spew.Dump(exprStmt)

// Check for CallExpr
ast.Inspect(exprStmt, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.CallExpr:
fun, ok := x.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
xIdent, ok := fun.X.(*ast.Ident)
if !ok {
return false
}
if xIdent.Name != optFieldName {
return false
}
// Logger.Printf("handling %s.%s\n", xIdent.Name, fun.Sel.Name)

switch fun.Sel.Name {
case "String":
handleString(cmd, optFieldName, n)
}
case *ast.CallExpr:
fun, ok := x.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
xIdent, ok := fun.X.(*ast.Ident)
if !ok {
return false
}
if xIdent.Name != getOptFn.OptFieldName {
return false
}
// Logger.Printf("handling %s.%s\n", xIdent.Name, fun.Sel.Name)

return false
}
return true
})
switch fun.Sel.Name {
case "String":
handleString(cmd, getOptFn.OptFieldName, n)
}

return false
}
return true
})
Expand Down Expand Up @@ -323,6 +257,105 @@ func LoadAst(ctx context.Context, opt *getoptions.GetOpt, dir string) error {
return nil
}

type GetOptFn struct {
FnName string
Name string
Description string
OptFieldName string

Node ast.Node
parsedFile parsedFile
}

// The goal is to be able to find the getoptions.CommandFn calls.
// Also, we need to inspect the function and get the opt.<Type> calls to know what options are being used.
//
// func Asciidoc(opt *getoptions.GetOpt) getoptions.CommandFn {
// opt.String("lang", "en", opt.ValidValues("en", "es"))
// opt.String("hello", "world")
// opt.String("hola", "mundo")
// return func(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
func AstGetoptionFns(ctx context.Context, opt *getoptions.GetOpt, dir string) iter.Seq2[GetOptFn, error] {
return func(yield func(GetOptFn, error) bool) {
// Regex for description: fn-name - description
re := regexp.MustCompile(`^\w\S+ -`)

for p, err := range parsedFiles(dir) {
if err != nil {
yield(GetOptFn{}, err)
return
}
getOptFn := GetOptFn{parsedFile: p}

// Iterate through every node in the file
ast.Inspect(p.f, func(n ast.Node) bool {
getOptFn.Node = n
switch x := n.(type) {
// Check function declarations for exported functions
case *ast.FuncDecl:
if x.Name.IsExported() {
getOptFn.FnName = x.Name.Name
getOptFn.Description = strings.TrimSpace(x.Doc.Text())
// var buf bytes.Buffer
// printer.Fprint(&buf, p.fset, x.Type)
// Logger.Printf("file: %s\n", p.file)
// Logger.Printf("type: %s, name: %s, desc: %s\n", buf.String(), name, description)

// Expect function of type:
// func Name(opt *getoptions.GetOpt) getoptions.CommandFn

// Check Params
// Expect opt *getoptions.GetOpt
if len(x.Type.Params.List) != 1 {
return false
}
for _, param := range x.Type.Params.List {
name := param.Names[0].Name
var buf bytes.Buffer
printer.Fprint(&buf, p.fset, param.Type)
// Logger.Printf("name: %s, %s\n", name, buf.String())
if buf.String() != "*getoptions.GetOpt" {
return false
}
getOptFn.OptFieldName = name
}

// Check Results
// Expect getoptions.CommandFn
if len(x.Type.Results.List) != 1 {
return false
}
for _, result := range x.Type.Results.List {
var buf bytes.Buffer
printer.Fprint(&buf, p.fset, result.Type)
// Logger.Printf("result: %s\n", buf.String())
if buf.String() != "getoptions.CommandFn" {
return false
}
}

// TODO: The yield probably goes here
// Add function to OptTree
if getOptFn.Description != "" {
// Logger.Printf("description '%s'\n", description)
if re.MatchString(getOptFn.Description) {
// Get first word from string
getOptFn.Name = strings.Split(getOptFn.Description, " ")[0]
getOptFn.Description = strings.TrimPrefix(getOptFn.Description, getOptFn.FnName+" -")
getOptFn.Description = strings.TrimSpace(getOptFn.Description)
}
} else {
getOptFn.Name = camelToKebab(getOptFn.FnName)
}
yield(getOptFn, nil)
}
}
return true
})
}
}
}

func handleString(cmd *getoptions.GetOpt, optFieldName string, n ast.Node) error {
x := n.(*ast.CallExpr)
name := ""
Expand Down

0 comments on commit 4382c9a

Please sign in to comment.