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