/
parser.go
110 lines (100 loc) · 2.85 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package cmd
import (
"fmt"
"go/ast"
"go/build"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"path/filepath"
"strings"
)
// getPkgAndType parses src (directories/files) and return the *types.Package,
// *types.Struct for the passed in structName and the directory for the sources
func getPkgAndType(structName string, src ...string) (*types.Package, *types.Struct, string, error) {
pkg, dir, err := parsePkg(src...)
if err != nil {
return nil, nil, "", fmt.Errorf("parsing package from provided sources: %s", err)
}
// Check that struct exists in package
o := pkg.Scope().Lookup(structName)
if o == nil {
return nil, nil, "", fmt.Errorf("the struct %s doesn't seem to exists in package %s", structName, pkg.Name())
}
// Check that it really is of type struct
t, ok := o.Type().Underlying().(*types.Struct)
if !ok {
return nil, nil, "", fmt.Errorf("the type %s is not a struct", structName)
}
return pkg, t, dir, nil
}
// parsePkg parses the directory or files for Go code and
// do type-checks on it. It will return the types.Package and
// directory location if successful
func parsePkg(src ...string) (*types.Package, string, error) {
var (
dir string
fileNames []string
)
if len(src) == 1 && isDirectory(src[0]) {
dir = src[0]
pkg, err := build.Default.ImportDir(dir, 0)
if err != nil {
return nil, dir, fmt.Errorf("cannot process directory %s: %s", dir, err)
}
fileNames = append(fileNames, pkg.GoFiles...)
fileNames = append(fileNames, pkg.CgoFiles...)
fileNames = append(fileNames, pkg.SFiles...)
fileNames = prefixDirectory(dir, fileNames)
} else {
dir = filepath.Dir(src[0])
fileNames = src
}
var (
astFiles []*ast.File
pkgName string
)
fset := token.NewFileSet()
for _, fileName := range fileNames {
if !strings.HasSuffix(fileName, ".go") {
continue
}
parsedFile, err := parser.ParseFile(fset, fileName, nil, 0)
if err != nil {
return nil, dir, fmt.Errorf("parsing package: %s: %s", fileName, err)
}
astFiles = append(astFiles, parsedFile)
}
if len(astFiles) == 0 {
return nil, dir, fmt.Errorf("%s: no buildable Go files", dir)
}
pkgName = astFiles[0].Name.Name
conf := types.Config{Importer: importer.Default()}
pkg, err := conf.Check(pkgName, fset, astFiles, nil)
if err != nil {
return nil, dir, fmt.Errorf("type-checking package %s: %s", pkgName, err)
}
return pkg, dir, nil
}
// isDirectory reports whether the named file is a directory.
func isDirectory(name string) bool {
info, err := os.Stat(name)
if err != nil {
log.Fatal(err)
}
return info.IsDir()
}
// prefixDirectory joins the directoy with each filename
func prefixDirectory(directory string, fileNames []string) []string {
if directory == "." {
return fileNames
}
ret := make([]string, len(fileNames))
for i, fileName := range fileNames {
ret[i] = filepath.Join(directory, fileName)
}
return ret
}