Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 1 | package bpdoc |
| 2 | |
| 3 | import ( |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 4 | "fmt" |
Colin Cross | 4ef23af | 2017-10-17 17:40:42 -0700 | [diff] [blame] | 5 | "html/template" |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 6 | "reflect" |
| 7 | "sort" |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 8 | |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 9 | "github.com/google/blueprint/proptools" |
| 10 | ) |
| 11 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 12 | // Package contains the information about a package relevant to generating documentation. |
| 13 | type Package struct { |
| 14 | // Name is the name of the package. |
| 15 | Name string |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 16 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 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 |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 26 | } |
| 27 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 28 | // ModuleType contains the information about a module type that is relevant to generating documentation. |
| 29 | type 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 Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 33 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 34 | // PkgPath is the full package path of the package that contains the module type factory. |
| 35 | PkgPath string |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 36 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 37 | // Text is the contents of the comment documenting the module type. |
Jaewoong Jung | 8bc6bf1 | 2019-03-11 10:54:36 -0700 | [diff] [blame] | 38 | Text template.HTML |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 39 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 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 |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 44 | } |
| 45 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 46 | type PropertyStruct struct { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 47 | Name string |
| 48 | Text string |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 49 | Properties []Property |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 50 | } |
| 51 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 52 | type Property struct { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 53 | Name string |
| 54 | OtherNames []string |
| 55 | Type string |
| 56 | Tag reflect.StructTag |
Colin Cross | 7311337 | 2017-10-17 18:01:04 -0700 | [diff] [blame] | 57 | Text template.HTML |
| 58 | OtherTexts []template.HTML |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 59 | Properties []Property |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 60 | Default string |
| 61 | } |
| 62 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 63 | func 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 Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 67 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 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 |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 76 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 77 | // Some pruning work |
| 78 | removeEmptyPropertyStructs(mtInfo) |
| 79 | collapseDuplicatePropertyStructs(mtInfo) |
| 80 | collapseNestedPropertyStructs(mtInfo) |
| 81 | combineDuplicateProperties(mtInfo) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 82 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 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 |
Colin Cross | c3d7312 | 2016-08-05 17:19:36 -0700 | [diff] [blame] | 90 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 91 | pkgMap[mtInfo.PkgPath] = pkg |
| 92 | pkgs = append(pkgs, pkg) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 93 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 94 | pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 95 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 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 |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 105 | } |
| 106 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 107 | func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value, |
| 108 | propertyStructs []interface{}) (*ModuleType, error) { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 109 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 110 | mt, err := r.ModuleType(name, factory) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 111 | if err != nil { |
| 112 | return nil, err |
| 113 | } |
| 114 | |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 115 | // Reader.ModuleType only fills basic information such as name and package path. Collect more info |
| 116 | // from property struct data. |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 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 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 125 | ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 126 | if err != nil { |
| 127 | return nil, err |
| 128 | } |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 129 | ps.ExcludeByTag("blueprint", "mutated") |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 130 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 131 | for nestedName, nestedValue := range nestedPropertyStructs(v) { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 132 | nestedType := nestedValue.Type() |
| 133 | |
| 134 | // Ignore property structs with unexported or unnamed types |
| 135 | if nestedType.PkgPath() == "" { |
| 136 | continue |
| 137 | } |
Jaewoong Jung | 781f6b2 | 2019-02-06 16:20:17 -0800 | [diff] [blame] | 138 | nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 139 | if err != nil { |
| 140 | return nil, err |
| 141 | } |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 142 | nested.ExcludeByTag("blueprint", "mutated") |
| 143 | nestPoint := ps.GetByName(nestedName) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 144 | if nestPoint == nil { |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 145 | return nil, fmt.Errorf("nesting point %q not found", nestedName) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 146 | } |
| 147 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 148 | nestPoint.Nest(nested) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 149 | } |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 150 | mt.PropertyStructs = append(mt.PropertyStructs, ps) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 151 | } |
| 152 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 153 | return mt, nil |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 154 | } |
| 155 | |
| 156 | func 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 Jung | bd0f6c3 | 2019-05-28 13:16:20 -0700 | [diff] [blame] | 167 | if proptools.HasTag(field, "blueprint", "mutated") { |
| 168 | continue |
| 169 | } |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 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 | } |
Dan Willemsen | 9c4e050 | 2016-01-13 13:56:24 -0800 | [diff] [blame] | 190 | if elem.Kind() == reflect.Struct { |
| 191 | nestPoint := prefix + proptools.PropertyNameForField(field.Name) |
| 192 | ret[nestPoint] = elem |
| 193 | walk(elem, nestPoint+".") |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 194 | } |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 195 | } |
| 196 | default: |
| 197 | panic(fmt.Errorf("unexpected kind for property struct field %q: %s", |
| 198 | field.Name, fieldValue.Kind())) |
| 199 | } |
| 200 | } |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | walk(s, "") |
| 204 | return ret |
| 205 | } |
| 206 | |
| 207 | // Remove any property structs that have no exported fields |
Colin Cross | 92639f3 | 2017-12-11 14:39:41 -0800 | [diff] [blame] | 208 | func removeEmptyPropertyStructs(mt *ModuleType) { |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 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:]...) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 212 | i-- |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // Squashes duplicates of the same property struct into single entries |
Colin Cross | 92639f3 | 2017-12-11 14:39:41 -0800 | [diff] [blame] | 218 | func collapseDuplicatePropertyStructs(mt *ModuleType) { |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 219 | var collapsed []*PropertyStruct |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 220 | |
| 221 | propertyStructLoop: |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 222 | for _, from := range mt.PropertyStructs { |
| 223 | for _, to := range collapsed { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 224 | if from.Name == to.Name { |
Sasha Smundak | 797563b | 2019-02-14 10:58:48 -0800 | [diff] [blame] | 225 | CollapseDuplicateProperties(&to.Properties, &from.Properties) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 226 | continue propertyStructLoop |
| 227 | } |
| 228 | } |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 229 | collapsed = append(collapsed, from) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 230 | } |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 231 | mt.PropertyStructs = collapsed |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 232 | } |
| 233 | |
Sasha Smundak | 797563b | 2019-02-14 10:58:48 -0800 | [diff] [blame] | 234 | func CollapseDuplicateProperties(to, from *[]Property) { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 235 | propertyLoop: |
| 236 | for _, f := range *from { |
| 237 | for i := range *to { |
| 238 | t := &(*to)[i] |
| 239 | if f.Name == t.Name { |
Sasha Smundak | 797563b | 2019-02-14 10:58:48 -0800 | [diff] [blame] | 240 | CollapseDuplicateProperties(&t.Properties, &f.Properties) |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 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 |
Colin Cross | 92639f3 | 2017-12-11 14:39:41 -0800 | [diff] [blame] | 250 | func collapseNestedPropertyStructs(mt *ModuleType) { |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 251 | for _, ps := range mt.PropertyStructs { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 252 | collapseNestedProperties(&ps.Properties) |
| 253 | } |
| 254 | } |
| 255 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 256 | func collapseNestedProperties(p *[]Property) { |
| 257 | var n []Property |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 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 | |
Colin Cross | 92639f3 | 2017-12-11 14:39:41 -0800 | [diff] [blame] | 282 | func combineDuplicateProperties(mt *ModuleType) { |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 283 | for _, ps := range mt.PropertyStructs { |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 284 | combineDuplicateSubProperties(&ps.Properties) |
| 285 | } |
| 286 | } |
| 287 | |
Colin Cross | d9f6fd5 | 2016-05-31 16:16:00 -0700 | [diff] [blame] | 288 | func combineDuplicateSubProperties(p *[]Property) { |
| 289 | var n []Property |
Colin Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 290 | propertyLoop: |
| 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 Cross | 4572edd | 2015-05-13 14:36:24 -0700 | [diff] [blame] | 305 | *p = n |
| 306 | } |