blob: 9256d8ebc52c71cef23f9914f47c77bccbcfbe5e [file] [log] [blame]
Jaewoong Jung781f6b22019-02-06 16:20:17 -08001// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bpdoc
16
17import (
18 "fmt"
19 "go/ast"
20 "go/doc"
21 "html/template"
22 "reflect"
23 "strconv"
24 "strings"
25 "unicode"
26 "unicode/utf8"
27
28 "github.com/google/blueprint/proptools"
29)
30
31//
32// Utility functions for PropertyStruct and Property
33//
34
35func (ps *PropertyStruct) Clone() *PropertyStruct {
36 ret := *ps
37 ret.Properties = append([]Property(nil), ret.Properties...)
38 for i, prop := range ret.Properties {
39 ret.Properties[i] = prop.Clone()
40 }
41
42 return &ret
43}
44
45func (p *Property) Clone() Property {
46 ret := *p
47 ret.Properties = append([]Property(nil), ret.Properties...)
48 for i, prop := range ret.Properties {
49 ret.Properties[i] = prop.Clone()
50 }
51
52 return ret
53}
54
55func (p *Property) Equal(other Property) bool {
56 return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag &&
57 p.Text == other.Text && p.Default == other.Default &&
58 stringArrayEqual(p.OtherNames, other.OtherNames) &&
59 htmlArrayEqual(p.OtherTexts, other.OtherTexts) &&
60 p.SameSubProperties(other)
61}
62
63func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) {
64 setDefaults(ps.Properties, defaults)
65}
66
67func setDefaults(properties []Property, defaults reflect.Value) {
68 for i := range properties {
69 prop := &properties[i]
70 fieldName := proptools.FieldNameForProperty(prop.Name)
71 f := defaults.FieldByName(fieldName)
72 if (f == reflect.Value{}) {
73 panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type()))
74 }
75
76 if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) {
77 continue
78 }
79
80 if f.Kind() == reflect.Interface {
81 f = f.Elem()
82 }
83
84 if f.Kind() == reflect.Ptr {
85 if f.IsNil() {
86 continue
87 }
88 f = f.Elem()
89 }
90
91 if f.Kind() == reflect.Struct {
92 setDefaults(prop.Properties, f)
93 } else {
94 prop.Default = fmt.Sprintf("%v", f.Interface())
95 }
96 }
97}
98
99func stringArrayEqual(a, b []string) bool {
100 if len(a) != len(b) {
101 return false
102 }
103
104 for i := range a {
105 if a[i] != b[i] {
106 return false
107 }
108 }
109
110 return true
111}
112
113func htmlArrayEqual(a, b []template.HTML) bool {
114 if len(a) != len(b) {
115 return false
116 }
117
118 for i := range a {
119 if a[i] != b[i] {
120 return false
121 }
122 }
123
124 return true
125}
126
127func (p *Property) SameSubProperties(other Property) bool {
128 if len(p.Properties) != len(other.Properties) {
129 return false
130 }
131
132 for i := range p.Properties {
133 if !p.Properties[i].Equal(other.Properties[i]) {
134 return false
135 }
136 }
137
138 return true
139}
140
141func (ps *PropertyStruct) GetByName(name string) *Property {
142 return getByName(name, "", &ps.Properties)
143}
144
145func getByName(name string, prefix string, props *[]Property) *Property {
146 for i := range *props {
147 if prefix+(*props)[i].Name == name {
148 return &(*props)[i]
149 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") {
150 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties)
151 }
152 }
153 return nil
154}
155
156func (p *Property) Nest(nested *PropertyStruct) {
157 p.Properties = append(p.Properties, nested.Properties...)
158}
159
160func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
161 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
162 ps := PropertyStruct{
163 Name: t.Name,
164 Text: t.Doc,
165 }
166
167 structType, ok := typeSpec.Type.(*ast.StructType)
168 if !ok {
169 return nil, fmt.Errorf("type of %q is not a struct", t.Name)
170 }
171
172 var err error
173 ps.Properties, err = structProperties(structType)
174 if err != nil {
175 return nil, err
176 }
177
178 return &ps, nil
179}
180
181func structProperties(structType *ast.StructType) (props []Property, err error) {
182 for _, f := range structType.Fields.List {
183 names := f.Names
184 if names == nil {
185 // Anonymous fields have no name, use the type as the name
186 // TODO: hide the name and make the properties show up in the embedding struct
187 if t, ok := f.Type.(*ast.Ident); ok {
188 names = append(names, t)
189 }
190 }
191 for _, n := range names {
192 var name, typ, tag, text string
193 var innerProps []Property
194 if n != nil {
195 name = proptools.PropertyNameForField(n.Name)
196 }
197 if f.Doc != nil {
198 text = f.Doc.Text()
199 }
200 if f.Tag != nil {
201 tag, err = strconv.Unquote(f.Tag.Value)
202 if err != nil {
203 return nil, err
204 }
205 }
206
207 t := f.Type
208 if star, ok := t.(*ast.StarExpr); ok {
209 t = star.X
210 }
211 switch a := t.(type) {
212 case *ast.ArrayType:
213 typ = "list of strings"
214 case *ast.InterfaceType:
215 typ = "interface"
216 case *ast.Ident:
217 typ = a.Name
218 case *ast.StructType:
219 innerProps, err = structProperties(a)
220 if err != nil {
221 return nil, err
222 }
223 default:
224 typ = fmt.Sprintf("%T", f.Type)
225 }
226
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800227 props = append(props, Property{
228 Name: name,
229 Type: typ,
230 Tag: reflect.StructTag(tag),
Jaewoong Jung8bc6bf12019-03-11 10:54:36 -0700231 Text: formatText(text),
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800232 Properties: innerProps,
233 })
234 }
235 }
236
237 return props, nil
238}
239
240func (ps *PropertyStruct) ExcludeByTag(key, value string) {
241 filterPropsByTag(&ps.Properties, key, value, true)
242}
243
244func (ps *PropertyStruct) IncludeByTag(key, value string) {
245 filterPropsByTag(&ps.Properties, key, value, false)
246}
247
248func filterPropsByTag(props *[]Property, key, value string, exclude bool) {
249 // Create a slice that shares the storage of props but has 0 length. Appending up to
250 // len(props) times to this slice will overwrite the original slice contents
251 filtered := (*props)[:0]
252 for _, x := range *props {
Jaewoong Jungbd0f6c32019-05-28 13:16:20 -0700253 if hasTag(x.Tag, key, value) == !exclude {
254 filtered = append(filtered, x)
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800255 }
256 }
257
258 *props = filtered
259}
Jaewoong Jung8bc6bf12019-03-11 10:54:36 -0700260
Jaewoong Jungbd0f6c32019-05-28 13:16:20 -0700261func hasTag(tag reflect.StructTag, key, value string) bool {
262 for _, entry := range strings.Split(tag.Get(key), ",") {
263 if entry == value {
264 return true
265 }
266 }
267 return false
268}
269
Jaewoong Jung8bc6bf12019-03-11 10:54:36 -0700270func formatText(text string) template.HTML {
271 var html template.HTML
272 lines := strings.Split(text, "\n")
273 preformatted := false
274 for _, line := range lines {
275 r, _ := utf8.DecodeRuneInString(line)
276 indent := unicode.IsSpace(r)
277 if indent && !preformatted {
278 html += "<pre>\n\n"
279 preformatted = true
280 } else if !indent && line != "" && preformatted {
281 html += "</pre>\n"
282 preformatted = false
283 }
284 html += template.HTML(template.HTMLEscapeString(line)) + "\n"
285 }
286 if preformatted {
287 html += "</pre>\n"
288 }
289 return html
290}