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 combineDuplicateProperties(mtInfo) 85 86 // 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 93 } 94 pkgMap[mtInfo.PkgPath] = pkg 95 pkgs = append(pkgs, pkg) 96 } 97 pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo) 98 } 99 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 108} 109 110func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value, 111 propertyStructs []interface{}) (*ModuleType, error) { 112 113 mt, err := r.ModuleType(name, factory) 114 if err != nil { 115 return nil, err 116 } 117 118 // Reader.ModuleType only fills basic information such as name and package path. Collect more info 119 // from property struct data. 120 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 } 128 ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v) 129 if err != nil { 130 return nil, err 131 } 132 ps.ExcludeByTag("blueprint", "mutated") 133 134 for _, nestedProperty := range nestedPropertyStructs(v) { 135 nestedName := nestedProperty.nestPoint 136 nestedValue := nestedProperty.value 137 nestedType := nestedValue.Type() 138 139 // Ignore property structs with unexported or unnamed types 140 if nestedType.PkgPath() == "" { 141 continue 142 } 143 nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) 144 if err != nil { 145 return nil, err 146 } 147 nested.ExcludeByTag("blueprint", "mutated") 148 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) 156 } 157 158 if nestedProperty.anonymous { 159 if nestedName != "" { 160 nestedName += "." 161 } 162 nestedName += proptools.PropertyNameForField(nested.Name) 163 nestedProp := ps.GetByName(nestedName) 164 // 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() 168 } 169 } 170 } 171 mt.PropertyStructs = append(mt.PropertyStructs, ps) 172 } 173 174 return mt, nil 175} 176 177type 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) 185 var walk func(structValue reflect.Value, prefix string) 186 walk = func(structValue reflect.Value, prefix string) { 187 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 202 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 } 209 if proptools.HasTag(field, "blueprint", "mutated") { 210 continue 211 } 212 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: 219 nestStruct(field, fieldValue, field.Name) 220 case reflect.Ptr, reflect.Interface: 221 222 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 } 233 if elem.Kind() == reflect.Struct { 234 nestStruct(field, elem, field.Name) 235 } 236 } 237 default: 238 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 239 field.Name, fieldValue.Kind())) 240 } 241 } 242 } 243 244 walk(s, "") 245 return ret 246} 247 248// Remove any property structs that have no exported fields 249func removeEmptyPropertyStructs(mt *ModuleType) { 250 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:]...) 253 i-- 254 } 255 } 256} 257 258// 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 279// Squashes duplicates of the same property struct into single entries 280func collapseDuplicatePropertyStructs(mt *ModuleType) { 281 var collapsed []*PropertyStruct 282 283propertyStructLoop: 284 for _, from := range mt.PropertyStructs { 285 for _, to := range collapsed { 286 if from.Name == to.Name { 287 CollapseDuplicateProperties(&to.Properties, &from.Properties) 288 continue propertyStructLoop 289 } 290 } 291 collapsed = append(collapsed, from) 292 } 293 mt.PropertyStructs = collapsed 294} 295 296func CollapseDuplicateProperties(to, from *[]Property) { 297propertyLoop: 298 for _, f := range *from { 299 for i := range *to { 300 t := &(*to)[i] 301 if f.Name == t.Name { 302 CollapseDuplicateProperties(&t.Properties, &f.Properties) 303 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 312func collapseNestedPropertyStructs(mt *ModuleType) { 313 for _, ps := range mt.PropertyStructs { 314 collapseNestedProperties(&ps.Properties) 315 } 316} 317 318func collapseNestedProperties(p *[]Property) { 319 var n []Property 320 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 344func combineDuplicateProperties(mt *ModuleType) { 345 for _, ps := range mt.PropertyStructs { 346 combineDuplicateSubProperties(&ps.Properties) 347 } 348} 349 350func combineDuplicateSubProperties(p *[]Property) { 351 var n []Property 352propertyLoop: 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 } 367 *p = n 368} 369