• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
208			fieldValue := structValue.Field(i)
209
210			switch fieldValue.Kind() {
211			case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
212				// Nothing
213			case reflect.Struct:
214				nestStruct(field, fieldValue, field.Name)
215			case reflect.Ptr, reflect.Interface:
216
217				if !fieldValue.IsNil() {
218					// We leave the pointer intact and zero out the struct that's
219					// pointed to.
220					elem := fieldValue.Elem()
221					if fieldValue.Kind() == reflect.Interface {
222						if elem.Kind() != reflect.Ptr {
223							panic(fmt.Errorf("can't get type of field %q: interface "+
224								"refers to a non-pointer", field.Name))
225						}
226						elem = elem.Elem()
227					}
228					if elem.Kind() == reflect.Struct {
229						nestStruct(field, elem, field.Name)
230					}
231				}
232			default:
233				panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
234					field.Name, fieldValue.Kind()))
235			}
236		}
237	}
238
239	walk(s, "")
240	return ret
241}
242
243// Remove any property structs that have no exported fields
244func removeEmptyPropertyStructs(mt *ModuleType) {
245	for i := 0; i < len(mt.PropertyStructs); i++ {
246		if len(mt.PropertyStructs[i].Properties) == 0 {
247			mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...)
248			i--
249		}
250	}
251}
252
253// Remove any property structs that are anonymous
254func removeAnonymousProperties(mt *ModuleType) {
255	var removeAnonymousProps func(props []Property) []Property
256	removeAnonymousProps = func(props []Property) []Property {
257		newProps := make([]Property, 0, len(props))
258		for _, p := range props {
259			if p.Anonymous {
260				continue
261			}
262			if len(p.Properties) > 0 {
263				p.Properties = removeAnonymousProps(p.Properties)
264			}
265			newProps = append(newProps, p)
266		}
267		return newProps
268	}
269	for _, ps := range mt.PropertyStructs {
270		ps.Properties = removeAnonymousProps(ps.Properties)
271	}
272}
273
274// Squashes duplicates of the same property struct into single entries
275func collapseDuplicatePropertyStructs(mt *ModuleType) {
276	var collapsed []*PropertyStruct
277
278propertyStructLoop:
279	for _, from := range mt.PropertyStructs {
280		for _, to := range collapsed {
281			if from.Name == to.Name {
282				CollapseDuplicateProperties(&to.Properties, &from.Properties)
283				continue propertyStructLoop
284			}
285		}
286		collapsed = append(collapsed, from)
287	}
288	mt.PropertyStructs = collapsed
289}
290
291func CollapseDuplicateProperties(to, from *[]Property) {
292propertyLoop:
293	for _, f := range *from {
294		for i := range *to {
295			t := &(*to)[i]
296			if f.Name == t.Name {
297				CollapseDuplicateProperties(&t.Properties, &f.Properties)
298				continue propertyLoop
299			}
300		}
301		*to = append(*to, f)
302	}
303}
304
305// Find all property structs that only contain structs, and move their children up one with
306// a prefixed name
307func collapseNestedPropertyStructs(mt *ModuleType) {
308	for _, ps := range mt.PropertyStructs {
309		collapseNestedProperties(&ps.Properties)
310	}
311}
312
313func collapseNestedProperties(p *[]Property) {
314	var n []Property
315
316	for _, parent := range *p {
317		var containsProperty bool
318		for j := range parent.Properties {
319			child := &parent.Properties[j]
320			if len(child.Properties) > 0 {
321				collapseNestedProperties(&child.Properties)
322			} else {
323				containsProperty = true
324			}
325		}
326		if containsProperty || len(parent.Properties) == 0 {
327			n = append(n, parent)
328		} else {
329			for j := range parent.Properties {
330				child := parent.Properties[j]
331				child.Name = parent.Name + "." + child.Name
332				n = append(n, child)
333			}
334		}
335	}
336	*p = n
337}
338