1package bpdoc
2
3import (
4	"fmt"
5	"html/template"
6	"reflect"
7	"sort"
8	"strings"
9
10	"github.com/google/blueprint/proptools"
11)
12
13// Package contains the information about a package relevant to generating documentation.
14type Package struct {
15	// Name is the name of the package.
16	Name string
17
18	// Path is the full package path of the package as used in the primary builder.
19	Path string
20
21	// Text is the contents of the package comment documenting the module types in the package.
22	Text string
23
24	// ModuleTypes is a list of ModuleType objects that contain information about each module type that is
25	// defined by the package.
26	ModuleTypes []*ModuleType
27}
28
29// ModuleType contains the information about a module type that is relevant to generating documentation.
30type ModuleType struct {
31	// Name is the string that will appear in Blueprints files when defining a new module of
32	// this type.
33	Name string
34
35	// PkgPath is the full package path of the package that contains the module type factory.
36	PkgPath string
37
38	// Text is the contents of the comment documenting the module type.
39	Text template.HTML
40
41	// PropertyStructs is a list of PropertyStruct objects that contain information about each
42	// property struct that is used by the module type, containing all properties that are valid
43	// for the module type.
44	PropertyStructs []*PropertyStruct
45}
46
47type PropertyStruct struct {
48	Name       string
49	Text       string
50	Properties []Property
51}
52
53type Property struct {
54	Name       string
55	OtherNames []string
56	Type       string
57	Tag        reflect.StructTag
58	Text       template.HTML
59	OtherTexts []template.HTML
60	Properties []Property
61	Default    string
62	Anonymous  bool
63}
64
65func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[string]reflect.Value,
66	moduleTypeNamePropertyStructs map[string][]interface{}) ([]*Package, error) {
67	// Read basic info from the files to construct a Reader instance.
68	r := NewReader(pkgFiles)
69
70	pkgMap := map[string]*Package{}
71	var pkgs []*Package
72	// Scan through per-module-type property structs map.
73	for mtName, propertyStructs := range moduleTypeNamePropertyStructs {
74		// Construct ModuleType with the given info.
75		mtInfo, err := assembleModuleTypeInfo(r, mtName, moduleTypeNameFactories[mtName], propertyStructs)
76		if err != nil {
77			return nil, err
78		}
79		// Some pruning work
80		removeAnonymousProperties(mtInfo)
81		removeEmptyPropertyStructs(mtInfo)
82		collapseDuplicatePropertyStructs(mtInfo)
83		collapseNestedPropertyStructs(mtInfo)
84		combineDuplicateProperties(mtInfo)
85
86		// Add the ModuleInfo to the corresponding Package map/slice entries.
87		pkg := pkgMap[mtInfo.PkgPath]
88		if pkg == nil {
89			var err error
90			pkg, err = r.Package(mtInfo.PkgPath)
91			if err != nil {
92				return nil, err
93			}
94			pkgMap[mtInfo.PkgPath] = pkg
95			pkgs = append(pkgs, pkg)
96		}
97		pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo)
98	}
99
100	// Sort ModuleTypes within each package.
101	for _, pkg := range pkgs {
102		sort.Slice(pkg.ModuleTypes, func(i, j int) bool { return pkg.ModuleTypes[i].Name < pkg.ModuleTypes[j].Name })
103	}
104	// Sort packages.
105	sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path })
106
107	return pkgs, nil
108}
109
110func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
111	propertyStructs []interface{}) (*ModuleType, error) {
112
113	mt, err := r.ModuleType(name, factory)
114	if err != nil {
115		return nil, err
116	}
117
118	// Reader.ModuleType only fills basic information such as name and package path. Collect more info
119	// from property struct data.
120	for _, s := range propertyStructs {
121		v := reflect.ValueOf(s).Elem()
122		t := v.Type()
123
124		// Ignore property structs with unexported or unnamed types
125		if t.PkgPath() == "" {
126			continue
127		}
128		ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
129		if err != nil {
130			return nil, err
131		}
132		ps.ExcludeByTag("blueprint", "mutated")
133
134		for _, nestedProperty := range nestedPropertyStructs(v) {
135			nestedName := nestedProperty.nestPoint
136			nestedValue := nestedProperty.value
137			nestedType := nestedValue.Type()
138
139			// Ignore property structs with unexported or unnamed types
140			if nestedType.PkgPath() == "" {
141				continue
142			}
143			nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
144			if err != nil {
145				return nil, err
146			}
147			nested.ExcludeByTag("blueprint", "mutated")
148			if nestedName == "" {
149				ps.Nest(nested)
150			} else {
151				nestPoint := ps.GetByName(nestedName)
152				if nestPoint == nil {
153					return nil, fmt.Errorf("nesting point %q not found", nestedName)
154				}
155				nestPoint.Nest(nested)
156			}
157
158			if nestedProperty.anonymous {
159				if nestedName != "" {
160					nestedName += "."
161				}
162				nestedName += proptools.PropertyNameForField(nested.Name)
163				nestedProp := ps.GetByName(nestedName)
164				// Anonymous properties may have already been omitted, no need to ensure they are filtered later
165				if nestedProp != nil {
166					// Set property to anonymous to allow future filtering
167					nestedProp.SetAnonymous()
168				}
169			}
170		}
171		mt.PropertyStructs = append(mt.PropertyStructs, ps)
172	}
173
174	return mt, nil
175}
176
177type nestedProperty struct {
178	nestPoint string
179	value     reflect.Value
180	anonymous bool
181}
182
183func nestedPropertyStructs(s reflect.Value) []nestedProperty {
184	ret := make([]nestedProperty, 0)
185	var walk func(structValue reflect.Value, prefix string)
186	walk = func(structValue reflect.Value, prefix string) {
187		var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string)
188		nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) {
189			nestPoint := prefix
190			if field.Anonymous {
191				nestPoint = strings.TrimSuffix(nestPoint, ".")
192			} else {
193				nestPoint = nestPoint + proptools.PropertyNameForField(fieldName)
194			}
195			ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous})
196			if nestPoint != "" {
197				nestPoint += "."
198			}
199			walk(value, nestPoint)
200		}
201
202		typ := structValue.Type()
203		for i := 0; i < structValue.NumField(); i++ {
204			field := typ.Field(i)
205			if field.PkgPath != "" {
206				// The field is not exported so just skip it.
207				continue
208			}
209			if proptools.HasTag(field, "blueprint", "mutated") {
210				continue
211			}
212
213			fieldValue := structValue.Field(i)
214
215			switch fieldValue.Kind() {
216			case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
217				// Nothing
218			case reflect.Struct:
219				nestStruct(field, fieldValue, field.Name)
220			case reflect.Ptr, reflect.Interface:
221
222				if !fieldValue.IsNil() {
223					// We leave the pointer intact and zero out the struct that's
224					// pointed to.
225					elem := fieldValue.Elem()
226					if fieldValue.Kind() == reflect.Interface {
227						if elem.Kind() != reflect.Ptr {
228							panic(fmt.Errorf("can't get type of field %q: interface "+
229								"refers to a non-pointer", field.Name))
230						}
231						elem = elem.Elem()
232					}
233					if elem.Kind() == reflect.Struct {
234						nestStruct(field, elem, field.Name)
235					}
236				}
237			default:
238				panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
239					field.Name, fieldValue.Kind()))
240			}
241		}
242	}
243
244	walk(s, "")
245	return ret
246}
247
248// Remove any property structs that have no exported fields
249func removeEmptyPropertyStructs(mt *ModuleType) {
250	for i := 0; i < len(mt.PropertyStructs); i++ {
251		if len(mt.PropertyStructs[i].Properties) == 0 {
252			mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...)
253			i--
254		}
255	}
256}
257
258// Remove any property structs that are anonymous
259func removeAnonymousProperties(mt *ModuleType) {
260	var removeAnonymousProps func(props []Property) []Property
261	removeAnonymousProps = func(props []Property) []Property {
262		newProps := make([]Property, 0, len(props))
263		for _, p := range props {
264			if p.Anonymous {
265				continue
266			}
267			if len(p.Properties) > 0 {
268				p.Properties = removeAnonymousProps(p.Properties)
269			}
270			newProps = append(newProps, p)
271		}
272		return newProps
273	}
274	for _, ps := range mt.PropertyStructs {
275		ps.Properties = removeAnonymousProps(ps.Properties)
276	}
277}
278
279// Squashes duplicates of the same property struct into single entries
280func collapseDuplicatePropertyStructs(mt *ModuleType) {
281	var collapsed []*PropertyStruct
282
283propertyStructLoop:
284	for _, from := range mt.PropertyStructs {
285		for _, to := range collapsed {
286			if from.Name == to.Name {
287				CollapseDuplicateProperties(&to.Properties, &from.Properties)
288				continue propertyStructLoop
289			}
290		}
291		collapsed = append(collapsed, from)
292	}
293	mt.PropertyStructs = collapsed
294}
295
296func CollapseDuplicateProperties(to, from *[]Property) {
297propertyLoop:
298	for _, f := range *from {
299		for i := range *to {
300			t := &(*to)[i]
301			if f.Name == t.Name {
302				CollapseDuplicateProperties(&t.Properties, &f.Properties)
303				continue propertyLoop
304			}
305		}
306		*to = append(*to, f)
307	}
308}
309
310// Find all property structs that only contain structs, and move their children up one with
311// a prefixed name
312func collapseNestedPropertyStructs(mt *ModuleType) {
313	for _, ps := range mt.PropertyStructs {
314		collapseNestedProperties(&ps.Properties)
315	}
316}
317
318func collapseNestedProperties(p *[]Property) {
319	var n []Property
320
321	for _, parent := range *p {
322		var containsProperty bool
323		for j := range parent.Properties {
324			child := &parent.Properties[j]
325			if len(child.Properties) > 0 {
326				collapseNestedProperties(&child.Properties)
327			} else {
328				containsProperty = true
329			}
330		}
331		if containsProperty || len(parent.Properties) == 0 {
332			n = append(n, parent)
333		} else {
334			for j := range parent.Properties {
335				child := parent.Properties[j]
336				child.Name = parent.Name + "." + child.Name
337				n = append(n, child)
338			}
339		}
340	}
341	*p = n
342}
343
344func combineDuplicateProperties(mt *ModuleType) {
345	for _, ps := range mt.PropertyStructs {
346		combineDuplicateSubProperties(&ps.Properties)
347	}
348}
349
350func combineDuplicateSubProperties(p *[]Property) {
351	var n []Property
352propertyLoop:
353	for _, child := range *p {
354		if len(child.Properties) > 0 {
355			combineDuplicateSubProperties(&child.Properties)
356			for i := range n {
357				s := &n[i]
358				if s.SameSubProperties(child) {
359					s.OtherNames = append(s.OtherNames, child.Name)
360					s.OtherTexts = append(s.OtherTexts, child.Text)
361					continue propertyLoop
362				}
363			}
364		}
365		n = append(n, child)
366	}
367	*p = n
368}
369