-
Notifications
You must be signed in to change notification settings - Fork 5
/
converter.go
139 lines (126 loc) · 3.63 KB
/
converter.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
package main
import (
"fmt"
"os"
"strings"
"github.com/g4s8/envdoc/ast"
"github.com/g4s8/envdoc/debug"
"github.com/g4s8/envdoc/types"
)
type Resolver interface {
Resolve(ref *ast.FieldTypeRef) *ast.TypeSpec
}
type ConverterOpts struct {
EnvPrefix string
TagName string
TagDefault string
RequiredIfNoDef bool
UseFieldNames bool
}
type Converter struct {
opts ConverterOpts
}
func NewConverter(opts ConverterOpts) *Converter {
return &Converter{
opts: opts,
}
}
func (c *Converter) ScopesFromFiles(res Resolver, files []*ast.FileSpec) []*types.EnvScope {
var scopes []*types.EnvScope
for _, f := range files {
if !f.Export {
debug.Logf("# CONV: skip file %q\n", f.Name)
continue
}
for _, t := range f.Types {
if !t.Export {
debug.Logf("# CONV: skip type %q\n", t.Name)
continue
}
scopes = append(scopes, c.ScopeFromType(res, t))
}
}
return scopes
}
func (c *Converter) ScopeFromType(res Resolver, t *ast.TypeSpec) *types.EnvScope {
scope := &types.EnvScope{
Name: t.Name,
Doc: t.Doc,
}
scope.Vars = c.DocItemsFromFields(res, c.opts.EnvPrefix, t.Fields)
debug.Logf("# CONV: found scope %q\n", scope.Name)
return scope
}
func (c *Converter) DocItemsFromFields(res Resolver, prefix string, fields []*ast.FieldSpec) []*types.EnvDocItem {
var items []*types.EnvDocItem
for _, f := range fields {
debug.Logf("\t# CONV: field [%s] type=%s flen=%d\n",
strings.Join(f.Names, ","), f.TypeRef, len(f.Fields))
if len(f.Names) == 0 {
// embedded field
if len(f.Fields) == 0 {
// resolve embedded types
tpe := res.Resolve(&f.TypeRef)
if tpe != nil {
f.Fields = tpe.Fields
}
}
items = append(items, c.DocItemsFromFields(res, prefix, f.Fields)...)
continue
}
items = append(items, c.DocItemsFromField(res, prefix, f)...)
}
return items
}
func (c *Converter) DocItemsFromField(resolver Resolver, prefix string, f *ast.FieldSpec) []*types.EnvDocItem {
dec := ast.NewFieldSpecDecoder(prefix, c.opts.TagName, c.opts.TagDefault, c.opts.RequiredIfNoDef, c.opts.UseFieldNames)
info, newPrefix := dec.Decode(f)
if newPrefix != "" {
prefix = newPrefix
}
var children []*types.EnvDocItem
switch f.TypeRef.Kind {
case ast.FieldTypeStruct:
children = c.DocItemsFromFields(resolver, prefix, f.Fields)
debug.Logf("\t# CONV: struct %q (%d childrens)\n", f.TypeRef.String(), len(children))
case ast.FieldTypeSelector, ast.FieldTypeIdent, ast.FieldTypeArray, ast.FieldTypePtr:
if f.TypeRef.IsBuiltIn() {
break
}
tpe := resolver.Resolve(&f.TypeRef)
debug.Logf("\t# CONV: resolve %q -> %v\n", f.TypeRef.String(), tpe)
if tpe == nil {
if newPrefix != "" {
// Target type is env-prefixed, it means it's a reference
// to another struct type. We can't process it here, because
// we can't resolve the target type and its fields.
fmt.Fprintf(os.Stderr, "WARNING: failed to resolve type %q\n", f.TypeRef.String())
}
break
}
children = c.DocItemsFromFields(resolver, prefix, tpe.Fields)
debug.Logf("\t# CONV: selector %q (%d childrens)\n", f.TypeRef.String(), len(children))
}
res := make([]*types.EnvDocItem, len(info.Names), len(info.Names)+1)
opts := types.EnvVarOptions{
Required: info.Required,
Expand: info.Expand,
NonEmpty: info.NonEmpty,
FromFile: info.FromFile,
Default: info.Default,
Separator: info.Separator,
}
for i, name := range info.Names {
res[i] = &types.EnvDocItem{
Name: name,
Doc: f.Doc,
Opts: opts,
Children: children,
}
debug.Logf("\t# CONV: docItem %q (%d childrens)\n", name, len(children))
}
if len(info.Names) == 0 && len(children) > 0 {
return children
}
return res
}