Skip to content

Instantly share code, notes, and snippets.

@lelandbatey
Last active August 19, 2022 19:27
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save lelandbatey/ee1d06c823127c620dd127f361d68a7b to your computer and use it in GitHub Desktop.
Print all functions in all Go source files under PATH
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"io/ioutil"
"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 := ioutil.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
}
// 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:
fmt.Printf("%s %s func ", fname, 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))
}
fmt.Println()
}
return true
})
}
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))
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