-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
200 lines (176 loc) · 4.98 KB
/
main.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package main
import (
"flag"
"fmt"
"go/types"
"io"
"os"
"runtime"
"text/template"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/tools/go/packages"
)
var (
// logger base configuration, used by unit tests as well.
logWriter = zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: true,
PartsOrder: []string{zerolog.LevelFieldName, zerolog.MessageFieldName},
}
logger = zerolog.New(logWriter)
)
// TemplateData holds the context for the template.
type TemplateData struct {
Record *ElmRecord
Nested []*ElmRecord
}
const help = `
Usage Example:
go-to-elm-json *.go -- main MyThingJSON:MyThing > MyThing.elm
<go files> syntax:
This list is passed to packages.Load() unmodified. It can be a literal list
of files, or a list of packages. Examples:
path/main.go - single file
path/*.go - shell glob
./path - local package path
github.com/jhillyerd/go-to-elm-json - full package path
See https://pkg.go.dev/golang.org/x/tools/go/packages for more.
`
func main() {
// Flags.
verbose := flag.Bool("v", false, "verbose (debug) output")
color := flag.Bool("color", runtime.GOOS != "windows", "colorize debug output")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [opts] <go files> -- <pkg name> \\\n"+
" <root go type:elm name> [<go type:elm name> ...]:\n\n", os.Args[0])
fmt.Fprint(flag.CommandLine.Output(), help)
flag.PrintDefaults()
}
flag.Parse()
// Logging.
if *verbose {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
logWriter.NoColor = !*color
logger = zerolog.New(logWriter)
// Split files and args at `--`.
var args []string
files := flag.Args()
for i, arg := range files {
if arg == "--" {
args = files[i+1:]
files = files[:i]
break
}
}
if len(args) < 2 {
fmt.Fprintf(flag.CommandLine.Output(),
"Wanted a package and a struct type to convert, got: %v\n\n", args)
flag.Usage()
os.Exit(1)
}
// Parse Go.
pkgs, err := loadPackages(files)
if err != nil {
logger.Fatal().Err(err).Msg("Couldn't load Go package")
}
packageName := args[0]
objectName, _ := splitTypeNamePair(args[1])
renames := make(TypeNamePairs)
for _, arg := range args[1:] {
renames.Add(arg)
}
// Output Elm.
err = generateElm(os.Stdout, pkgs, packageName, objectName, renames)
if err != nil {
logger.Fatal().Err(err).Msg("Generation failed")
}
}
// generateElm processes the provided program and outputs Elm code to the provider writer.
func generateElm(
w io.Writer,
pkgs []*packages.Package,
packageName string,
objectName string,
renames TypeNamePairs) error {
// Load output template.
tmpl, err := template.New("elm").Parse(elmTemplate)
if err != nil {
return errors.Wrap(err, "Couldn't parse template")
}
// Process definition.
structType, err := getStructDef(pkgs, packageName, objectName)
if err != nil {
return errors.Wrap(err, "Couldn't find struct")
}
resolver := NewResolver(renames)
record, err := recordFromStruct(resolver, structType, objectName)
if err != nil {
return errors.Wrap(err, "Couldn't convert struct")
}
// Render Elm.
data := &TemplateData{
Record: record,
Nested: resolver.CachedRecords(),
}
err = tmpl.Execute(w, data)
if err != nil {
return errors.Wrap(err, "Couldn't render template")
}
return nil
}
// loadPackages takes an x/tools/go/packages argument list and parses the specified Go files.
func loadPackages(args []string) (pkgs []*packages.Package, err error) {
// Configure package loader, load packages.
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedTypes,
}
pkgs, err = packages.Load(cfg, args...)
if err != nil {
return nil, err
}
if packages.PrintErrors(pkgs) > 0 {
logger.Warn().Msg("There were non-fatal errors loading package")
}
// Dump package type info for troubleshooting loading problems.
logger.Debug().Func(func(e *zerolog.Event) {
for _, p := range pkgs {
e.Str("ID", p.ID).Str("Name", p.Name).Str("PkgPath", p.PkgPath).Msg("Go package loaded")
for _, sn := range p.Types.Scope().Names() {
logger.Debug().
Str("Name", sn).
Str("PkgName", p.Name).
Msg("Type found")
}
}
})
return pkgs, nil
}
// getStructDef finds the requested object and confirms it's a struct type definition.
func getStructDef(pkgs []*packages.Package, packageName, typeName string) (*types.Struct, error) {
// Lookup package.
var pkg *packages.Package
for _, p := range pkgs {
if p.Name == packageName {
pkg = p
break
}
}
if pkg == nil {
return nil, errors.Errorf("Package %s not found", packageName)
}
// Lookup type definition.
obj := pkg.Types.Scope().Lookup(typeName)
if obj == nil {
return nil, errors.Errorf("Definition %s.%s not found", packageName, typeName)
}
objType := obj.Type().Underlying()
structType, ok := objType.(*types.Struct)
if !ok {
return nil, errors.Errorf("%s type is %T, want *types.Struct", obj.Id(), objType)
}
return structType, nil
}