-
-
Notifications
You must be signed in to change notification settings - Fork 564
/
Copy pathfile.go
185 lines (169 loc) Β· 4.59 KB
/
file.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
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package codegen
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/scanner"
"go/token"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/imports"
)
// Gendir is the name of the subdirectory of the output directory that contains
// the generated files. This directory is wiped and re-written each time goa is
// run.
const Gendir = "gen"
type (
// A File contains the logic to generate a complete file.
File struct {
// SectionTemplates is the list of file section templates in
// order of rendering.
SectionTemplates []*SectionTemplate
// Path returns the file path relative to the output directory.
Path string
// SkipExist indicates whether the file should be skipped if one
// already exists at the given path.
SkipExist bool
// FinalizeFunc is called after the file has been generated. It
// is given the absolute path to the file as argument.
FinalizeFunc func(string) error
}
// A SectionTemplate is a template and accompanying render data. The
// template format is described in the (stdlib) text/template package.
SectionTemplate struct {
// Name is the name reported when parsing the source fails.
Name string
// Source is used to create the text/template.Template that
// renders the section text.
Source string
// FuncMap lists the functions used to render the templates.
FuncMap map[string]interface{}
// Data used as input of template.
Data interface{}
}
)
// Section returns the section templates with the given name or nil if not found.
func (f *File) Section(name string) []*SectionTemplate {
var sts []*SectionTemplate
for _, s := range f.SectionTemplates {
if s.Name == name {
sts = append(sts, s)
}
}
return sts
}
// Render executes the file section templates and writes the resulting bytes to
// an output file. The path of the output file is computed by appending the file
// path to dir. If a file already exists with the computed path then Render
// happens the smallest integer value greater than 1 to make it unique. Renders
// returns the computed path.
func (f *File) Render(dir string) (string, error) {
base, err := filepath.Abs(dir)
if err != nil {
return "", err
}
path := filepath.Join(base, f.Path)
if f.SkipExist {
if _, err = os.Stat(path); err == nil {
return "", nil
}
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return "", err
}
file, err := os.OpenFile(
path,
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
0644,
)
if err != nil {
return "", err
}
for _, s := range f.SectionTemplates {
if err := s.Write(file); err != nil {
return "", err
}
}
if err := file.Close(); err != nil {
return "", err
}
// Format Go source files
if filepath.Ext(path) == ".go" {
if err := finalizeGoSource(path); err != nil {
return "", err
}
}
// Run finalizer if any
if f.FinalizeFunc != nil {
if err := f.FinalizeFunc(path); err != nil {
return "", err
}
}
return path, nil
}
// Write writes the section to the given writer.
func (s *SectionTemplate) Write(w io.Writer) error {
funcs := TemplateFuncs()
for k, v := range s.FuncMap {
funcs[k] = v
}
tmpl := template.Must(template.New(s.Name).Funcs(funcs).Parse(s.Source))
return tmpl.Execute(w, s.Data)
}
// finalizeGoSource removes unneeded imports from the given Go source file and
// runs go fmt on it.
func finalizeGoSource(path string) error {
// Make sure file parses and print content if it does not.
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
content, _ := ioutil.ReadFile(path)
var buf bytes.Buffer
scanner.PrintError(&buf, err)
return fmt.Errorf("%s\n========\nContent:\n%s", buf.String(), content)
}
// Clean unused imports
imps := astutil.Imports(fset, file)
for _, group := range imps {
for _, imp := range group {
path := strings.Trim(imp.Path.Value, `"`)
if !astutil.UsesImport(file, path) {
if imp.Name != nil {
astutil.DeleteNamedImport(fset, file, imp.Name.Name, path)
} else {
astutil.DeleteImport(fset, file, path)
}
}
}
}
ast.SortImports(fset, file)
w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return err
}
if err := format.Node(w, fset, file); err != nil {
return err
}
w.Close()
// Format code using goimport standard
bs, err := ioutil.ReadFile(path)
if err != nil {
return err
}
opt := imports.Options{
Comments: true,
FormatOnly: true,
}
bs, err = imports.Process(path, bs, &opt)
if err != nil {
return err
}
return ioutil.WriteFile(path, bs, os.ModePerm)
}