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