-
Notifications
You must be signed in to change notification settings - Fork 40
/
generate.go
164 lines (153 loc) · 4.6 KB
/
generate.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
package run // import "gnorm.org/gnorm/run"
import (
"bytes"
"context"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/natefinch/atomic"
"github.com/pkg/errors"
"gnorm.org/gnorm/environ"
"gnorm.org/gnorm/run/data"
)
// Generate reads your database, gets the schema for it, and then generates
// files based on your templates and your configuration.
func Generate(env environ.Values, cfg *Config) error {
info, err := cfg.Driver.Parse(env.Log, cfg.ConnStr, cfg.Schemas, makeFilter(cfg.IncludeTables, cfg.ExcludeTables))
if err != nil {
return err
}
db, err := makeData(env.Log, info, cfg)
if err != nil {
return err
}
if len(cfg.SchemaPaths) == 0 {
env.Log.Println("No SchemaPaths specified, skipping schemas.")
} else {
if err := generateSchemas(env, cfg, db); err != nil {
return err
}
}
if len(cfg.EnumPaths) == 0 {
env.Log.Println("No EnumPath specified, skipping enums.")
} else {
if err := generateEnums(env, cfg, db); err != nil {
return err
}
}
if len(cfg.TablePaths) == 0 {
env.Log.Println("No table path specified, skipping tables.")
} else {
if err := generateTables(env, cfg, db); err != nil {
return err
}
}
return nil
}
func generateSchemas(env environ.Values, cfg *Config, db *data.DBData) error {
for _, schema := range db.Schemas {
fileData := struct{ Schema string }{Schema: schema.Name}
contents := data.SchemaData{
Schema: schema,
DB: db,
Config: cfg.ConfigData,
Params: cfg.Params,
}
for _, target := range cfg.SchemaPaths {
env.Log.Printf("Generating output for schema %v", schema.Name)
if err := genFile(env, fileData, contents, target, cfg.PostRun); err != nil {
return errors.WithMessage(err, "generating file for schema "+schema.Name)
}
}
}
return nil
}
func generateEnums(env environ.Values, cfg *Config, db *data.DBData) error {
for _, schema := range db.Schemas {
for _, enum := range schema.Enums {
fileData := struct{ Schema, Enum string }{Schema: schema.Name, Enum: enum.Name}
contents := data.EnumData{
Enum: enum,
DB: db,
Config: cfg.ConfigData,
Params: cfg.Params,
}
for _, target := range cfg.EnumPaths {
if err := genFile(env, fileData, contents, target, cfg.PostRun); err != nil {
env.Log.Printf("Generating output for enum %v", enum.Name)
return errors.WithMessage(err, "generating file for enum "+enum.Name)
}
}
}
}
return nil
}
func generateTables(env environ.Values, cfg *Config, db *data.DBData) error {
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
contents := data.TableData{
Table: table,
DB: db,
Config: cfg.ConfigData,
Params: cfg.Params,
}
fileData := struct{ Schema, Table string }{Schema: schema.Name, Table: table.Name}
for _, target := range cfg.TablePaths {
if err := genFile(env, fileData, contents, target, cfg.PostRun); err != nil {
env.Log.Printf("Generating output for table %v", table.Name)
return errors.WithMessage(err, "generating file for table "+table.Name)
}
}
}
}
return nil
}
func genFile(env environ.Values, filedata, contents interface{}, target OutputTarget, postrun []string) error {
buf := &bytes.Buffer{}
err := target.Filename.Execute(buf, filedata)
if err != nil {
return errors.WithMessage(err, "failed to run Filename template")
}
outputPath := buf.String()
if err := os.MkdirAll(filepath.Dir(outputPath), 0700); err != nil {
return errors.WithMessage(err, "error creating template output directory")
}
outbuf := &bytes.Buffer{}
if err := target.Contents.Execute(outbuf, contents); err != nil {
return errors.WithMessage(err, "failed to run contents template")
}
if err := atomic.WriteFile(outputPath, outbuf); err != nil {
return errors.Wrapf(err, "error writing generated file %q", outputPath)
}
if len(postrun) > 0 {
return doPostRun(env, outputPath, postrun)
}
return nil
}
func doPostRun(env environ.Values, file string, postrun []string) error {
newenv := make(map[string]string, len(env.Env)+1)
for k := range env.Env {
newenv[k] = env.Env[k]
}
run := make([]string, len(postrun))
newenv["GNORMFILE"] = file
conv := func(s string) string { return newenv[s] }
for x, s := range postrun {
run[x] = os.Expand(s, conv)
}
var cmd *exec.Cmd
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if len(run) > 1 {
cmd = exec.CommandContext(ctx, run[0], run[1:]...)
} else {
cmd = exec.CommandContext(ctx, run[0])
}
cmd.Stderr = env.Stderr
cmd.Stdout = env.Stdout
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "error running postrun command %q", run)
}
return nil
}