diff --git a/bake/ast.go b/bake/ast.go index a10acd5..ff088fc 100644 --- a/bake/ast.go +++ b/bake/ast.go @@ -8,6 +8,7 @@ import ( "go/parser" "go/printer" "go/token" + "iter" "os" "path/filepath" "regexp" @@ -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 }) @@ -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. 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 := ""