• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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 (ps *PropertyStruct) Nest(nested *PropertyStruct) {
146	ps.Properties = nestUnique(ps.Properties, nested.Properties)
147}
148
149// Adds a target element to src if it does not exist in src
150func nestUnique(src []Property, target []Property) []Property {
151	var ret []Property
152	ret = append(ret, src...)
153	for _, elem := range target {
154		isUnique := true
155		for _, retElement := range ret {
156			if elem.Equal(retElement) {
157				isUnique = false
158				break
159			}
160		}
161		if isUnique {
162			ret = append(ret, elem)
163		}
164	}
165	return ret
166}
167
168func getByName(name string, prefix string, props *[]Property) *Property {
169	for i := range *props {
170		if prefix+(*props)[i].Name == name {
171			return &(*props)[i]
172		} else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") {
173			return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties)
174		}
175	}
176	return nil
177}
178
179func (p *Property) Nest(nested *PropertyStruct) {
180	p.Properties = nestUnique(p.Properties, nested.Properties)
181}
182
183func (p *Property) SetAnonymous() {
184	p.Anonymous = true
185}
186
187func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
188	typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
189	ps := PropertyStruct{
190		Name: t.Name,
191		Text: t.Doc,
192	}
193
194	structType, ok := typeSpec.Type.(*ast.StructType)
195	if !ok {
196		return nil, fmt.Errorf("type of %q is not a struct", t.Name)
197	}
198
199	var err error
200	ps.Properties, err = structProperties(structType)
201	if err != nil {
202		return nil, err
203	}
204
205	return &ps, nil
206}
207
208func structProperties(structType *ast.StructType) (props []Property, err error) {
209	for _, f := range structType.Fields.List {
210		names := f.Names
211		if names == nil {
212			// Anonymous fields have no name, use the type as the name
213			// TODO: hide the name and make the properties show up in the embedding struct
214			if t, ok := f.Type.(*ast.Ident); ok {
215				names = append(names, t)
216			}
217		}
218		for _, n := range names {
219			var name, tag, text string
220			if n != nil {
221				name = proptools.PropertyNameForField(n.Name)
222			}
223			if f.Doc != nil {
224				text = f.Doc.Text()
225			}
226			if f.Tag != nil {
227				tag, err = strconv.Unquote(f.Tag.Value)
228				if err != nil {
229					return nil, err
230				}
231			}
232			typ, innerProps, err := getType(f.Type)
233			if err != nil {
234				return nil, err
235			}
236
237			props = append(props, Property{
238				Name:       name,
239				Type:       typ,
240				Tag:        reflect.StructTag(tag),
241				Text:       formatText(text),
242				Properties: innerProps,
243			})
244		}
245	}
246
247	return props, nil
248}
249
250func getType(expr ast.Expr) (typ string, innerProps []Property, err error) {
251	var t ast.Expr
252	if star, ok := expr.(*ast.StarExpr); ok {
253		t = star.X
254	} else {
255		t = expr
256	}
257	switch a := t.(type) {
258	case *ast.ArrayType:
259		var elt string
260		elt, innerProps, err = getType(a.Elt)
261		if err != nil {
262			return "", nil, err
263		}
264		typ = "list of " + elt
265	case *ast.InterfaceType:
266		typ = "interface"
267	case *ast.Ident:
268		typ = a.Name
269	case *ast.StructType:
270		innerProps, err = structProperties(a)
271		if err != nil {
272			return "", nil, err
273		}
274	default:
275		typ = fmt.Sprintf("%T", expr)
276	}
277
278	return typ, innerProps, nil
279}
280
281func (ps *PropertyStruct) ExcludeByTag(key, value string) {
282	filterPropsByTag(&ps.Properties, key, value, true)
283}
284
285func (ps *PropertyStruct) IncludeByTag(key, value string) {
286	filterPropsByTag(&ps.Properties, key, value, false)
287}
288
289func filterPropsByTag(props *[]Property, key, value string, exclude bool) {
290	// Create a slice that shares the storage of props but has 0 length.  Appending up to
291	// len(props) times to this slice will overwrite the original slice contents
292	filtered := (*props)[:0]
293	for _, x := range *props {
294		if hasTag(x.Tag, key, value) == !exclude {
295			filterPropsByTag(&x.Properties, key, value, exclude)
296			filtered = append(filtered, x)
297		}
298	}
299
300	*props = filtered
301}
302
303func hasTag(tag reflect.StructTag, key, value string) bool {
304	for _, entry := range strings.Split(tag.Get(key), ",") {
305		if entry == value {
306			return true
307		}
308	}
309	return false
310}
311
312func formatText(text string) template.HTML {
313	var html template.HTML
314	lines := strings.Split(text, "\n")
315	preformatted := false
316	for _, line := range lines {
317		r, _ := utf8.DecodeRuneInString(line)
318		indent := unicode.IsSpace(r)
319		if indent && !preformatted {
320			html += "<pre>\n\n"
321			preformatted = true
322		} else if !indent && line != "" && preformatted {
323			html += "</pre>\n"
324			preformatted = false
325		}
326		html += template.HTML(template.HTMLEscapeString(line)) + "\n"
327	}
328	if preformatted {
329		html += "</pre>\n"
330	}
331	return html
332}
333