-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathknife.go
More file actions
152 lines (128 loc) · 3.03 KB
/
knife.go
File metadata and controls
152 lines (128 loc) · 3.03 KB
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package knife
import (
_ "embed"
"fmt"
"go/ast"
"go/token"
"io"
"strings"
"github.com/gostaticanalysis/astquery"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/packages"
)
//go:embed version.txt
var version string
// Version returns the version of knife.
func Version() string {
return strings.TrimSpace(version)
}
type Knife struct {
fset *token.FileSet
pkgs []*packages.Package
ins map[*packages.Package]*inspector.Inspector
}
func New(opt *KnifeOption, patterns ...string) (*Knife, error) {
if opt == nil {
opt = &KnifeOption{Tests: true}
}
mode := packages.NeedFiles | packages.NeedSyntax |
packages.NeedTypes | packages.NeedDeps | packages.NeedTypesInfo
cfg := &packages.Config{
Fset: token.NewFileSet(),
Mode: mode,
Tests: opt.Tests,
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
return nil, fmt.Errorf("load: %w", err)
}
ins := make(map[*packages.Package]*inspector.Inspector, len(pkgs))
for _, pkg := range pkgs {
ins[pkg] = inspector.New(pkg.Syntax)
}
return &Knife{
fset: cfg.Fset,
pkgs: pkgs,
ins: ins,
}, nil
}
// Packages returns packages.
func (k *Knife) Packages() []*packages.Package {
return k.pkgs
}
// Position returns position of v.
func (k *Knife) Position(v any) token.Position {
n, ok := v.(interface{ Pos() token.Pos })
if ok && k.fset != nil {
return k.fset.Position(n.Pos())
}
return token.Position{}
}
// KnifeOption is an option for New.
type KnifeOption struct {
Tests bool
}
// ExecuteOption is an option for Execute.
type ExecuteOption struct {
XPath string
ExtraData map[string]any
}
// Execute outputs the pkg with the format.
func (k *Knife) Execute(w io.Writer, pkg *packages.Package, tmpl any, opt *ExecuteOption) error {
var tmplStr string
switch tmpl := tmpl.(type) {
case string:
tmplStr = tmpl
case []byte:
tmplStr = string(tmpl)
case io.Reader:
b, err := io.ReadAll(tmpl)
if err != nil {
return fmt.Errorf("cannnot read template: %w", err)
}
tmplStr = string(b)
default:
return fmt.Errorf("template must be string, []byte or io.Reader: %T", tmpl)
}
td := &TempalteData{
Fset: pkg.Fset,
Files: pkg.Syntax,
TypesInfo: pkg.TypesInfo,
Pkg: pkg.Types,
Extra: opt.ExtraData,
}
t, err := NewTemplate(td).Parse(tmplStr)
if err != nil {
return fmt.Errorf("template parse: %w", err)
}
var data any
switch {
case opt != nil && opt.XPath != "":
data, err = k.evalXPath(pkg, opt.XPath)
if err != nil {
return err
}
default:
data = NewPackage(pkg.Types)
}
if err := t.Execute(w, data); err != nil {
return fmt.Errorf("template execute: %w", err)
}
return nil
}
func (k *Knife) evalXPath(pkg *packages.Package, xpath string) (any, error) {
e := astquery.New(pkg.Fset, pkg.Syntax, k.ins[pkg])
v, err := e.Eval(xpath)
if err != nil {
return nil, fmt.Errorf("XPath parse error: %w", err)
}
switch v := v.(type) {
case []ast.Node:
ns := make([]*ASTNode, len(v))
for i := range ns {
ns[i] = NewASTNode(pkg.TypesInfo, v[i])
}
return ns, nil
}
return v, nil
}