blob: 4abf2e764cdd6f4d2d7c8e8c9ddda4bd22c8e62c [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"
Colin Cross4572edd2015-05-13 14:36:24 -07008
Colin Cross4572edd2015-05-13 14:36:24 -07009 "github.com/google/blueprint/proptools"
10)
11
Jaewoong Jung781f6b22019-02-06 16:20:17 -080012// 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
Colin Cross4572edd2015-05-13 14:36:24 -070016
Jaewoong Jung781f6b22019-02-06 16:20:17 -080017 // 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
Colin Cross4572edd2015-05-13 14:36:24 -070026}
27
Jaewoong Jung781f6b22019-02-06 16:20:17 -080028// 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
Colin Cross4572edd2015-05-13 14:36:24 -070033
Jaewoong Jung781f6b22019-02-06 16:20:17 -080034 // PkgPath is the full package path of the package that contains the module type factory.
35 PkgPath string
Colin Cross4572edd2015-05-13 14:36:24 -070036
Jaewoong Jung781f6b22019-02-06 16:20:17 -080037 // Text is the contents of the comment documenting the module type.
Jaewoong Jung8bc6bf12019-03-11 10:54:36 -070038 Text template.HTML
Colin Cross4572edd2015-05-13 14:36:24 -070039
Jaewoong Jung781f6b22019-02-06 16:20:17 -080040 // 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
Colin Cross4572edd2015-05-13 14:36:24 -070044}
45
Colin Crossd9f6fd52016-05-31 16:16:00 -070046type PropertyStruct struct {
Colin Cross4572edd2015-05-13 14:36:24 -070047 Name string
48 Text string
Colin Crossd9f6fd52016-05-31 16:16:00 -070049 Properties []Property
Colin Cross4572edd2015-05-13 14:36:24 -070050}
51
Colin Crossd9f6fd52016-05-31 16:16:00 -070052type Property struct {
Colin Cross4572edd2015-05-13 14:36:24 -070053 Name string
54 OtherNames []string
55 Type string
56 Tag reflect.StructTag
Colin Cross73113372017-10-17 18:01:04 -070057 Text template.HTML
58 OtherTexts []template.HTML
Colin Crossd9f6fd52016-05-31 16:16:00 -070059 Properties []Property
Colin Cross4572edd2015-05-13 14:36:24 -070060 Default string
61}
62
Jaewoong Jung781f6b22019-02-06 16:20:17 -080063func 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)
Colin Cross4572edd2015-05-13 14:36:24 -070067
Jaewoong Jung781f6b22019-02-06 16:20:17 -080068 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
Colin Cross4572edd2015-05-13 14:36:24 -070076 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080077 // Some pruning work
78 removeEmptyPropertyStructs(mtInfo)
79 collapseDuplicatePropertyStructs(mtInfo)
80 collapseNestedPropertyStructs(mtInfo)
81 combineDuplicateProperties(mtInfo)
Colin Cross4572edd2015-05-13 14:36:24 -070082
Jaewoong Jung781f6b22019-02-06 16:20:17 -080083 // 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
Colin Crossc3d73122016-08-05 17:19:36 -070090 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080091 pkgMap[mtInfo.PkgPath] = pkg
92 pkgs = append(pkgs, pkg)
Colin Cross4572edd2015-05-13 14:36:24 -070093 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080094 pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo)
Colin Cross4572edd2015-05-13 14:36:24 -070095 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -080096
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
Colin Cross4572edd2015-05-13 14:36:24 -0700105}
106
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800107func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
108 propertyStructs []interface{}) (*ModuleType, error) {
Colin Cross4572edd2015-05-13 14:36:24 -0700109
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800110 mt, err := r.ModuleType(name, factory)
Colin Cross4572edd2015-05-13 14:36:24 -0700111 if err != nil {
112 return nil, err
113 }
114
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800115 // Reader.ModuleType only fills basic information such as name and package path. Collect more info
116 // from property struct data.
Colin Cross4572edd2015-05-13 14:36:24 -0700117 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 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800125 ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
Colin Cross4572edd2015-05-13 14:36:24 -0700126 if err != nil {
127 return nil, err
128 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700129 ps.ExcludeByTag("blueprint", "mutated")
Colin Cross4572edd2015-05-13 14:36:24 -0700130
Colin Crossd9f6fd52016-05-31 16:16:00 -0700131 for nestedName, nestedValue := range nestedPropertyStructs(v) {
Colin Cross4572edd2015-05-13 14:36:24 -0700132 nestedType := nestedValue.Type()
133
134 // Ignore property structs with unexported or unnamed types
135 if nestedType.PkgPath() == "" {
136 continue
137 }
Jaewoong Jung781f6b22019-02-06 16:20:17 -0800138 nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
Colin Cross4572edd2015-05-13 14:36:24 -0700139 if err != nil {
140 return nil, err
141 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700142 nested.ExcludeByTag("blueprint", "mutated")
143 nestPoint := ps.GetByName(nestedName)
Colin Cross4572edd2015-05-13 14:36:24 -0700144 if nestPoint == nil {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700145 return nil, fmt.Errorf("nesting point %q not found", nestedName)
Colin Cross4572edd2015-05-13 14:36:24 -0700146 }
147
Colin Crossd9f6fd52016-05-31 16:16:00 -0700148 nestPoint.Nest(nested)
Colin Cross4572edd2015-05-13 14:36:24 -0700149 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700150 mt.PropertyStructs = append(mt.PropertyStructs, ps)
Colin Cross4572edd2015-05-13 14:36:24 -0700151 }
152
Colin Crossd9f6fd52016-05-31 16:16:00 -0700153 return mt, nil
Colin Cross4572edd2015-05-13 14:36:24 -0700154}
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 }
Jaewoong Jungbd0f6c32019-05-28 13:16:20 -0700167 if proptools.HasTag(field, "blueprint", "mutated") {
168 continue
169 }
Colin Cross4572edd2015-05-13 14:36:24 -0700170
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 }
Dan Willemsen9c4e0502016-01-13 13:56:24 -0800190 if elem.Kind() == reflect.Struct {
191 nestPoint := prefix + proptools.PropertyNameForField(field.Name)
192 ret[nestPoint] = elem
193 walk(elem, nestPoint+".")
Colin Cross4572edd2015-05-13 14:36:24 -0700194 }
Colin Cross4572edd2015-05-13 14:36:24 -0700195 }
196 default:
197 panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
198 field.Name, fieldValue.Kind()))
199 }
200 }
Colin Cross4572edd2015-05-13 14:36:24 -0700201 }
202
203 walk(s, "")
204 return ret
205}
206
207// Remove any property structs that have no exported fields
Colin Cross92639f32017-12-11 14:39:41 -0800208func removeEmptyPropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700209 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:]...)
Colin Cross4572edd2015-05-13 14:36:24 -0700212 i--
213 }
214 }
215}
216
217// Squashes duplicates of the same property struct into single entries
Colin Cross92639f32017-12-11 14:39:41 -0800218func collapseDuplicatePropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700219 var collapsed []*PropertyStruct
Colin Cross4572edd2015-05-13 14:36:24 -0700220
221propertyStructLoop:
Colin Crossd9f6fd52016-05-31 16:16:00 -0700222 for _, from := range mt.PropertyStructs {
223 for _, to := range collapsed {
Colin Cross4572edd2015-05-13 14:36:24 -0700224 if from.Name == to.Name {
Sasha Smundak797563b2019-02-14 10:58:48 -0800225 CollapseDuplicateProperties(&to.Properties, &from.Properties)
Colin Cross4572edd2015-05-13 14:36:24 -0700226 continue propertyStructLoop
227 }
228 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700229 collapsed = append(collapsed, from)
Colin Cross4572edd2015-05-13 14:36:24 -0700230 }
Colin Crossd9f6fd52016-05-31 16:16:00 -0700231 mt.PropertyStructs = collapsed
Colin Cross4572edd2015-05-13 14:36:24 -0700232}
233
Sasha Smundak797563b2019-02-14 10:58:48 -0800234func CollapseDuplicateProperties(to, from *[]Property) {
Colin Cross4572edd2015-05-13 14:36:24 -0700235propertyLoop:
236 for _, f := range *from {
237 for i := range *to {
238 t := &(*to)[i]
239 if f.Name == t.Name {
Sasha Smundak797563b2019-02-14 10:58:48 -0800240 CollapseDuplicateProperties(&t.Properties, &f.Properties)
Colin Cross4572edd2015-05-13 14:36:24 -0700241 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
Colin Cross92639f32017-12-11 14:39:41 -0800250func collapseNestedPropertyStructs(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700251 for _, ps := range mt.PropertyStructs {
Colin Cross4572edd2015-05-13 14:36:24 -0700252 collapseNestedProperties(&ps.Properties)
253 }
254}
255
Colin Crossd9f6fd52016-05-31 16:16:00 -0700256func collapseNestedProperties(p *[]Property) {
257 var n []Property
Colin Cross4572edd2015-05-13 14:36:24 -0700258
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
Colin Cross92639f32017-12-11 14:39:41 -0800282func combineDuplicateProperties(mt *ModuleType) {
Colin Crossd9f6fd52016-05-31 16:16:00 -0700283 for _, ps := range mt.PropertyStructs {
Colin Cross4572edd2015-05-13 14:36:24 -0700284 combineDuplicateSubProperties(&ps.Properties)
285 }
286}
287
Colin Crossd9f6fd52016-05-31 16:16:00 -0700288func combineDuplicateSubProperties(p *[]Property) {
289 var n []Property
Colin Cross4572edd2015-05-13 14:36:24 -0700290propertyLoop:
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 }
Colin Cross4572edd2015-05-13 14:36:24 -0700305 *p = n
306}