blob: 8ed02c2317c63637670d4951fcf8b3519f054dbc [file] [log] [blame]
Colin Cross4572edd2015-05-13 14:36:24 -07001package bpdoc
2
3import (
Colin Cross4572edd2015-05-13 14:36:24 -07004 "fmt"
Colin Cross4ef23af2017-10-17 17:40:42 -07005 "html/template"
Colin Cross4572edd2015-05-13 14:36:24 -07006 "reflect"
7 "sort"
Liz Kammer5c9fe382020-09-25 12:49:21 -07008 "strings"
Colin Cross4572edd2015-05-13 14:36:24 -07009
Colin Cross4572edd2015-05-13 14:36:24 -070010 "github.com/google/blueprint/proptools"
11)
12
Jaewoong Jung781f6b22019-02-06 16:20:17 -080013// 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
Colin Cross4572edd2015-05-13 14:36:24 -070017
Jaewoong Jung781f6b22019-02-06 16:20:17 -080018 // 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
Colin Cross4572edd2015-05-13 14:36:24 -070027}
28
Jaewoong Jung781f6b22019-02-06 16:20:17 -080029// 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
Colin Cross4572edd2015-05-13 14:36:24 -070034
Jaewoong Jung781f6b22019-02-06 16:20:17 -080035 // PkgPath is the full package path of the package that contains the module type factory.
36 PkgPath string
Colin Cross4572edd2015-05-13 14:36:24 -070037
Jaewoong Jung781f6b22019-02-06 16:20:17 -080038 // Text is the contents of the comment documenting the module type.
Jaewoong Jung8bc6bf12019-03-11 10:54:36 -070039 Text template.HTML
Colin Cross4572edd2015-05-13 14:36:24 -070040
Jaewoong Jung781f6b22019-02-06 16:20:17 -080041 // 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
Colin Cross4572edd2015-05-13 14:36:24 -070045}
46
Colin Crossd9f6fd52016-05-31 16:16:00 -070047type PropertyStruct struct {
Colin Cross4572edd2015-05-13 14:36:24 -070048 Name string
49 Text string
Colin Crossd9f6fd52016-05-31 16:16:00 -070050 Properties []Property
Colin Cross4572edd2015-05-13 14:36:24 -070051}
52
Colin Crossd9f6fd52016-05-31 16:16:00 -070053type Property struct {
Colin Cross4572edd2015-05-13 14:36:24 -070054 Name string
55 OtherNames []string
56 Type string
57 Tag reflect.StructTag
Colin Cross73113372017-10-17 18:01:04 -070058 Text template.HTML
59 OtherTexts []template.HTML
Colin Crossd9f6fd52016-05-31 16:16:00 -070060 Properties []Property
Colin Cross4572edd2015-05-13 14:36:24 -070061 Default string
Liz Kammer5c9fe382020-09-25 12:49:21 -070062 Anonymous bool
Colin Cross4572edd2015-05-13 14:36:24 -070063}
64
Jaewoong Jung781f6b22019-02-06 16:20:17 -080065func 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)
Colin Cross4572edd2015-05-13 14:36:24 -070069
Jaewoong Jung781f6b22019-02-06 16:20:17 -080070 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
Colin Cross4572edd2015-05-13 14:36:24 -070078 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080079 // Some pruning work
Liz Kammer5c9fe382020-09-25 12:49:21 -070080 removeAnonymousProperties(mtInfo)
Jaewoong Jung781f6b22019-02-06 16:20:17 -080081 removeEmptyPropertyStructs(mtInfo)
82 collapseDuplicatePropertyStructs(mtInfo)
83 collapseNestedPropertyStructs(mtInfo)
84 combineDuplicateProperties(mtInfo)
Colin Cross4572edd2015-05-13 14:36:24 -070085
Jaewoong Jung781f6b22019-02-06 16:20:17 -080086 // 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
Colin Crossc3d73122016-08-05 17:19:36 -070093 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080094 pkgMap[mtInfo.PkgPath] = pkg
95 pkgs = append(pkgs, pkg)
Colin Cross4572edd2015-05-13 14:36:24 -070096 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080097 pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo)
Colin Cross4572edd2015-05-13 14:36:24 -070098 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080099
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
Colin Cross4572edd2015-05-13 14:36:24 -0700108}
109
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800110func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
111 propertyStructs []interface{}) (*ModuleType, error) {
Colin Cross4572edd2015-05-13 14:36:24 -0700112
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800113 mt, err := r.ModuleType(name, factory)
Colin Cross4572edd2015-05-13 14:36:24 -0700114 if err != nil {
115 return nil, err
116 }
117
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800118 // Reader.ModuleType only fills basic information such as name and package path. Collect more info
119 // from property struct data.
Colin Cross4572edd2015-05-13 14:36:24 -0700120 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 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800128 ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
Colin Cross4572edd2015-05-13 14:36:24 -0700129 if err != nil {
130 return nil, err
131 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700132 ps.ExcludeByTag("blueprint", "mutated")
Colin Cross4572edd2015-05-13 14:36:24 -0700133
Liz Kammer5c9fe382020-09-25 12:49:21 -0700134 for _, nestedProperty := range nestedPropertyStructs(v) {
135 nestedName := nestedProperty.nestPoint
136 nestedValue := nestedProperty.value
Colin Cross4572edd2015-05-13 14:36:24 -0700137 nestedType := nestedValue.Type()
138
139 // Ignore property structs with unexported or unnamed types
140 if nestedType.PkgPath() == "" {
141 continue
142 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800143 nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
Colin Cross4572edd2015-05-13 14:36:24 -0700144 if err != nil {
145 return nil, err
146 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700147 nested.ExcludeByTag("blueprint", "mutated")
Liz Kammer5c9fe382020-09-25 12:49:21 -0700148 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)
Colin Cross4572edd2015-05-13 14:36:24 -0700156 }
157
Liz Kammer5c9fe382020-09-25 12:49:21 -0700158 if nestedProperty.anonymous {
159 if nestedName != "" {
160 nestedName += "."
161 }
162 nestedName += proptools.PropertyNameForField(nested.Name)
163 nestedProp := ps.GetByName(nestedName)
Liz Kammer2068e082020-10-05 14:47:47 -0700164 // 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()
Liz Kammer5c9fe382020-09-25 12:49:21 -0700168 }
Liz Kammer5c9fe382020-09-25 12:49:21 -0700169 }
Colin Cross4572edd2015-05-13 14:36:24 -0700170 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700171 mt.PropertyStructs = append(mt.PropertyStructs, ps)
Colin Cross4572edd2015-05-13 14:36:24 -0700172 }
173
Colin Crossd9f6fd52016-05-31 16:16:00 -0700174 return mt, nil
Colin Cross4572edd2015-05-13 14:36:24 -0700175}
176
Liz Kammer5c9fe382020-09-25 12:49:21 -0700177type 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)
Colin Cross4572edd2015-05-13 14:36:24 -0700185 var walk func(structValue reflect.Value, prefix string)
186 walk = func(structValue reflect.Value, prefix string) {
Liz Kammer5c9fe382020-09-25 12:49:21 -0700187 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
Colin Cross4572edd2015-05-13 14:36:24 -0700202 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 }
Jaewoong Jungbd0f6c32019-05-28 13:16:20 -0700209 if proptools.HasTag(field, "blueprint", "mutated") {
210 continue
211 }
Colin Cross4572edd2015-05-13 14:36:24 -0700212
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:
Liz Kammer5c9fe382020-09-25 12:49:21 -0700219 nestStruct(field, fieldValue, field.Name)
Colin Cross4572edd2015-05-13 14:36:24 -0700220 case reflect.Ptr, reflect.Interface:
Liz Kammer5c9fe382020-09-25 12:49:21 -0700221
Colin Cross4572edd2015-05-13 14:36:24 -0700222 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 }
Dan Willemsen9c4e0502016-01-13 13:56:24 -0800233 if elem.Kind() == reflect.Struct {
Liz Kammer5c9fe382020-09-25 12:49:21 -0700234 nestStruct(field, elem, field.Name)
Colin Cross4572edd2015-05-13 14:36:24 -0700235 }
Colin Cross4572edd2015-05-13 14:36:24 -0700236 }
237 default:
238 panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
239 field.Name, fieldValue.Kind()))
240 }
241 }
Colin Cross4572edd2015-05-13 14:36:24 -0700242 }
243
244 walk(s, "")
245 return ret
246}
247
248// Remove any property structs that have no exported fields
Colin Cross92639f32017-12-11 14:39:41 -0800249func removeEmptyPropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700250 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:]...)
Colin Cross4572edd2015-05-13 14:36:24 -0700253 i--
254 }
255 }
256}
257
Liz Kammer5c9fe382020-09-25 12:49:21 -0700258// 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
Colin Cross4572edd2015-05-13 14:36:24 -0700279// Squashes duplicates of the same property struct into single entries
Colin Cross92639f32017-12-11 14:39:41 -0800280func collapseDuplicatePropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700281 var collapsed []*PropertyStruct
Colin Cross4572edd2015-05-13 14:36:24 -0700282
283propertyStructLoop:
Colin Crossd9f6fd52016-05-31 16:16:00 -0700284 for _, from := range mt.PropertyStructs {
285 for _, to := range collapsed {
Colin Cross4572edd2015-05-13 14:36:24 -0700286 if from.Name == to.Name {
Sasha Smundak797563b2019-02-14 10:58:48 -0800287 CollapseDuplicateProperties(&to.Properties, &from.Properties)
Colin Cross4572edd2015-05-13 14:36:24 -0700288 continue propertyStructLoop
289 }
290 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700291 collapsed = append(collapsed, from)
Colin Cross4572edd2015-05-13 14:36:24 -0700292 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700293 mt.PropertyStructs = collapsed
Colin Cross4572edd2015-05-13 14:36:24 -0700294}
295
Sasha Smundak797563b2019-02-14 10:58:48 -0800296func CollapseDuplicateProperties(to, from *[]Property) {
Colin Cross4572edd2015-05-13 14:36:24 -0700297propertyLoop:
298 for _, f := range *from {
299 for i := range *to {
300 t := &(*to)[i]
301 if f.Name == t.Name {
Sasha Smundak797563b2019-02-14 10:58:48 -0800302 CollapseDuplicateProperties(&t.Properties, &f.Properties)
Colin Cross4572edd2015-05-13 14:36:24 -0700303 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
Colin Cross92639f32017-12-11 14:39:41 -0800312func collapseNestedPropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700313 for _, ps := range mt.PropertyStructs {
Colin Cross4572edd2015-05-13 14:36:24 -0700314 collapseNestedProperties(&ps.Properties)
315 }
316}
317
Colin Crossd9f6fd52016-05-31 16:16:00 -0700318func collapseNestedProperties(p *[]Property) {
319 var n []Property
Colin Cross4572edd2015-05-13 14:36:24 -0700320
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
Colin Cross92639f32017-12-11 14:39:41 -0800344func combineDuplicateProperties(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700345 for _, ps := range mt.PropertyStructs {
Colin Cross4572edd2015-05-13 14:36:24 -0700346 combineDuplicateSubProperties(&ps.Properties)
347 }
348}
349
Colin Crossd9f6fd52016-05-31 16:16:00 -0700350func combineDuplicateSubProperties(p *[]Property) {
351 var n []Property
Colin Cross4572edd2015-05-13 14:36:24 -0700352propertyLoop:
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 }
Colin Cross4572edd2015-05-13 14:36:24 -0700367 *p = n
368}