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
85		// Add the ModuleInfo to the corresponding Package map/slice entries.
86		pkg := pkgMap[mtInfo.PkgPath]
87		if pkg == nil {
88			var err error
89			pkg, err = r.Package(mtInfo.PkgPath)
90			if err != nil {
91				return nil, err
92			}
93			pkgMap[mtInfo.PkgPath] = pkg
94			pkgs = append(pkgs, pkg)
95		}
96		pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo)
97	}
98
99	// Sort ModuleTypes within each package.
100	for _, pkg := range pkgs {
101		sort.Slice(pkg.ModuleTypes, func(i, j int) bool { return pkg.ModuleTypes[i].Name < pkg.ModuleTypes[j].Name })
102	}
103	// Sort packages.
104	sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path })
105
106	return pkgs, nil
107}
108
109func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
110	propertyStructs []interface{}) (*ModuleType, error) {
111
112	mt, err := r.ModuleType(name, factory)
113	if err != nil {
114		return nil, err
115	}
116
117	// Reader.ModuleType only fills basic information such as name and package path. Collect more info
118	// from property struct data.
119	for _, s := range propertyStructs {
120		v := reflect.ValueOf(s).Elem()
121		t := v.Type()
122
123		ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
124
125		if err != nil {
126			return nil, err
127		}
128		ps.ExcludeByTag("blueprint", "mutated")
129		for _, nestedProperty := range nestedPropertyStructs(v) {
130			nestedName := nestedProperty.nestPoint
131			nestedValue := nestedProperty.value
132			nestedType := nestedValue.Type()
133
134			// Ignore property structs with unexported or unnamed types
135			if nestedType.PkgPath() == "" {
136				continue
137			}
138			nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
139			if err != nil {
140				return nil, err
141			}
142			nested.ExcludeByTag("blueprint", "mutated")
143			if nestedName == "" {
144				ps.Nest(nested)
145			} else {
146				nestPoint := ps.GetByName(nestedName)
147				if nestPoint == nil {
148					return nil, fmt.Errorf("nesting point %q not found", nestedName)
149				}
150				nestPoint.Nest(nested)
151			}
152
153			if nestedProperty.anonymous {
154				if nestedName != "" {
155					nestedName += "."
156				}
157				nestedName += proptools.PropertyNameForField(nested.Name)
158				nestedProp := ps.GetByName(nestedName)
159				// Anonymous properties may have already been omitted, no need to ensure they are filtered later
160				if nestedProp != nil {
161					// Set property to anonymous to allow future filtering
162					nestedProp.SetAnonymous()
163				}
164			}
165		}
166		mt.PropertyStructs = append(mt.PropertyStructs, ps)
167	}
168
169	return mt, nil
170}
171
172type nestedProperty struct {
173	nestPoint string
174	value     reflect.Value
175	anonymous bool
176}
177
178func nestedPropertyStructs(s reflect.Value) []nestedProperty {
179	ret := make([]nestedProperty, 0)
180	var walk func(structValue reflect.Value, prefix string)
181	walk = func(structValue reflect.Value, prefix string) {
182		var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string)
183		nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) {
184			nestPoint := prefix
185			if field.Anonymous {
186				nestPoint = strings.TrimSuffix(nestPoint, ".")
187			} else {
188				nestPoint = nestPoint + proptools.PropertyNameForField(fieldName)
189			}
190			ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous})
191			if nestPoint != "" {
192				nestPoint += "."
193			}
194			walk(value, nestPoint)
195		}
196
197		typ := structValue.Type()
198		for i := 0; i < structValue.NumField(); i++ {
199			field := typ.Field(i)
200			if field.PkgPath != "" {
201				// The field is not exported so just skip it.
202				continue
203			}
204			if proptools.HasTag(field, "blueprint", "mutated") {
205				continue
206			}
207			if proptools.IsConfigurable(field.Type) {
208				// Don't recurse into configurable properties, they're structs but not property structs
209				continue
210			}
211
212			fieldValue := structValue.Field(i)
213
214			switch fieldValue.Kind() {
215			case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
216				// Nothing
217			case reflect.Struct:
218				nestStruct(field, fieldValue, field.Name)
219			case reflect.Ptr, reflect.Interface:
220
221				if !fieldValue.IsNil() {
222					// We leave the pointer intact and zero out the struct that's
223					// pointed to.
224					elem := fieldValue.Elem()
225					if fieldValue.Kind() == reflect.Interface {
226						if elem.Kind() != reflect.Ptr {
227							panic(fmt.Errorf("can't get type of field %q: interface "+
228								"refers to a non-pointer", field.Name))
229						}
230						elem = elem.Elem()
231					}
232					if elem.Kind() == reflect.Struct {
233						nestStruct(field, elem, field.Name)
234					}
235				}
236			default:
237				panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
238					field.Name, fieldValue.Kind()))
239			}
240		}
241	}
242
243	walk(s, "")
244	return ret
245}
246
247// Remove any property structs that have no exported fields
248func removeEmptyPropertyStructs(mt *ModuleType) {
249	for i := 0; i < len(mt.PropertyStructs); i++ {
250		if len(mt.PropertyStructs[i].Properties) == 0 {
251			mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...)
252			i--
253		}
254	}
255}
256
257// Remove any property structs that are anonymous
258func removeAnonymousProperties(mt *ModuleType) {
259	var removeAnonymousProps func(props []Property) []Property
260	removeAnonymousProps = func(props []Property) []Property {
261		newProps := make([]Property, 0, len(props))
262		for _, p := range props {
263			if p.Anonymous {
264				continue
265			}
266			if len(p.Properties) > 0 {
267				p.Properties = removeAnonymousProps(p.Properties)
268			}
269			newProps = append(newProps, p)
270		}
271		return newProps
272	}
273	for _, ps := range mt.PropertyStructs {
274		ps.Properties = removeAnonymousProps(ps.Properties)
275	}
276}
277
278// Squashes duplicates of the same property struct into single entries
279func collapseDuplicatePropertyStructs(mt *ModuleType) {
280	var collapsed []*PropertyStruct
281
282propertyStructLoop:
283	for _, from := range mt.PropertyStructs {
284		for _, to := range collapsed {
285			if from.Name == to.Name {
286				CollapseDuplicateProperties(&to.Properties, &from.Properties)
287				continue propertyStructLoop
288			}
289		}
290		collapsed = append(collapsed, from)
291	}
292	mt.PropertyStructs = collapsed
293}
294
295func CollapseDuplicateProperties(to, from *[]Property) {
296propertyLoop:
297	for _, f := range *from {
298		for i := range *to {
299			t := &(*to)[i]
300			if f.Name == t.Name {
301				CollapseDuplicateProperties(&t.Properties, &f.Properties)
302				continue propertyLoop
303			}
304		}
305		*to = append(*to, f)
306	}
307}
308
309// Find all property structs that only contain structs, and move their children up one with
310// a prefixed name
311func collapseNestedPropertyStructs(mt *ModuleType) {
312	for _, ps := range mt.PropertyStructs {
313		collapseNestedProperties(&ps.Properties)
314	}
315}
316
317func collapseNestedProperties(p *[]Property) {
318	var n []Property
319
320	for _, parent := range *p {
321		var containsProperty bool
322		for j := range parent.Properties {
323			child := &parent.Properties[j]
324			if len(child.Properties) > 0 {
325				collapseNestedProperties(&child.Properties)
326			} else {
327				containsProperty = true
328			}
329		}
330		if containsProperty || len(parent.Properties) == 0 {
331			n = append(n, parent)
332		} else {
333			for j := range parent.Properties {
334				child := parent.Properties[j]
335				child.Name = parent.Name + "." + child.Name
336				n = append(n, child)
337			}
338		}
339	}
340	*p = n
341}
342