Last active
April 25, 2025 18:10
-
-
Save lelandbatey/ee1d06c823127c620dd127f361d68a7b to your computer and use it in GitHub Desktop.
Print all functions in all Go source files under PATH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"go/ast" | |
"go/parser" | |
"go/token" | |
"io" | |
"io/fs" | |
"log" | |
"os" | |
"path/filepath" | |
"strings" | |
) | |
func pathExists(pth string) bool { | |
_, err := os.Stat(pth) | |
return !os.IsNotExist(err) | |
} | |
func makeWalkDirFunc(gofiles *[]string) fs.WalkDirFunc { | |
// searches for Go files | |
return func(path string, info fs.DirEntry, err error) error { | |
if info.IsDir() { | |
return nil | |
} | |
if !strings.HasSuffix(path, ".go") { | |
return nil | |
} | |
*gofiles = append(*gofiles, path) | |
return nil | |
} | |
} | |
// Comes from here: https://stackoverflow.com/a/49579501 | |
func main() { | |
if len(os.Args) < 2 { | |
fmt.Printf("Usage: %s PATH [SEPARATOR]\n\tPrints all functions in all Go source files under PATH\n", os.Args[0]) | |
return | |
} | |
separator := ";;" | |
if len(os.Args) > 2 { | |
separator = os.Args[2] | |
} | |
gofiles := []string{} | |
wdf := makeWalkDirFunc(&gofiles) | |
err := filepath.WalkDir(os.Args[1], wdf) | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to search path %q due to error: %q", os.Args[1], err) | |
return | |
} | |
for _, f := range gofiles { | |
printFunctions(f, separator) | |
} | |
} | |
func printFunctions(fname, separator string) { | |
// read file | |
file, err := os.Open(fname) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
defer file.Close() | |
// read the whole file in | |
srcbuf, err := io.ReadAll(file) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
src := string(srcbuf) | |
// file set | |
fset := token.NewFileSet() | |
f, err := parser.ParseFile(fset, fname, src, 0) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
priorLine := 0 | |
// main inspection | |
ast.Inspect(f, func(n ast.Node) bool { | |
switch fn := n.(type) { | |
// catching all function declarations | |
// other intersting things to catch FuncLit and FuncType | |
case *ast.FuncDecl: | |
position := fset.Position(fn.Pos()) | |
if priorLine != 0 { | |
fmt.Printf(" linelen:%d\n", position.Line-priorLine) | |
} else { | |
fmt.Printf("") | |
} | |
priorLine = position.Line | |
fmt.Printf("%s:%d %s func ", fname, position.Line, separator) | |
// if a method, explore and print receiver | |
if fn.Recv != nil { | |
fmt.Printf("(%s) ", fields(*fn.Recv)) | |
} | |
// print actual function name | |
fmt.Printf("%v", fn.Name) | |
// print function parameters | |
if fn.Type.Params != nil { | |
fmt.Printf("(%s)", fields(*fn.Type.Params)) | |
} | |
// print return params | |
if fn.Type.Results != nil { | |
fmt.Printf("(%s)", fields(*fn.Type.Results)) | |
} | |
} | |
return true | |
}) | |
fmt.Printf(" linelen:%d\n", strings.Count(string(srcbuf), "\n")+1-priorLine) | |
} | |
func expr(e ast.Expr) string { | |
ret := "" | |
switch x := e.(type) { | |
case *ast.StarExpr: | |
subexpr := expr(x.X) | |
return fmt.Sprintf("*%v", subexpr) | |
case *ast.Ident: | |
return fmt.Sprintf("%v", x.Name) | |
case *ast.ArrayType: | |
if x.Len != nil { | |
// Case should never be hit, should match to ellipses type... | |
log.Println("This isn't supposed to be encountered, this is supposed to match to ellipses :|") | |
res := expr(x.Elt) | |
return fmt.Sprintf("...%s", res) | |
} | |
res := expr(x.Elt) | |
return fmt.Sprintf("[]%v", res) | |
case *ast.MapType: | |
return fmt.Sprintf("map[%s]%s", expr(x.Key), expr(x.Value)) | |
case *ast.SelectorExpr: | |
return fmt.Sprintf("%s.%s", expr(x.X), expr(x.Sel)) | |
case *ast.Ellipsis: | |
res := expr(x.Elt) | |
return fmt.Sprintf("...%s", res) | |
case *ast.FuncType: | |
params := "" | |
results := "" | |
if x.Params != nil { | |
params = fields(*x.Params) | |
} | |
if x.Results != nil { | |
results = fmt.Sprintf(" (%s)", fields(*x.Results)) | |
} | |
return fmt.Sprintf("func(%s)%s", params, results) | |
case *ast.InterfaceType: | |
return fmt.Sprintf("interface{%s}", fields(*x.Methods)) | |
case *ast.IndexExpr: | |
return fmt.Sprintf("%s[%s]", expr(x.X), expr(x.Index)) | |
default: | |
fmt.Printf("\nTODO HOMEWORK: %#v\n", x) | |
} | |
return ret | |
} | |
func fields(fl ast.FieldList) (ret string) { | |
pcomma := "" | |
for i, f := range fl.List { | |
// get all the names if present | |
var names string | |
ncomma := "" | |
for j, n := range f.Names { | |
if j > 0 { | |
ncomma = ", " | |
} | |
names = fmt.Sprintf("%s%s%s ", names, ncomma, n) | |
} | |
if i > 0 { | |
pcomma = ", " | |
} | |
ret = fmt.Sprintf("%s%s%s%s", ret, pcomma, names, expr(f.Type)) | |
} | |
return ret | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment