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 85 // Add the ModuleInfo to the corresponding Package map/slice entries. 86 pkg := pkgMap[mtInfo.PkgPath] 87 if pkg == nil { 88 var err error 89 pkg, err = r.Package(mtInfo.PkgPath) 90 if err != nil { 91 return nil, err 92 } 93 pkgMap[mtInfo.PkgPath] = pkg 94 pkgs = append(pkgs, pkg) 95 } 96 pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo) 97 } 98 99 // Sort ModuleTypes within each package. 100 for _, pkg := range pkgs { 101 sort.Slice(pkg.ModuleTypes, func(i, j int) bool { return pkg.ModuleTypes[i].Name < pkg.ModuleTypes[j].Name }) 102 } 103 // Sort packages. 104 sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path }) 105 106 return pkgs, nil 107} 108 109func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value, 110 propertyStructs []interface{}) (*ModuleType, error) { 111 112 mt, err := r.ModuleType(name, factory) 113 if err != nil { 114 return nil, err 115 } 116 117 // Reader.ModuleType only fills basic information such as name and package path. Collect more info 118 // from property struct data. 119 for _, s := range propertyStructs { 120 v := reflect.ValueOf(s).Elem() 121 t := v.Type() 122 123 ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v) 124 125 if err != nil { 126 return nil, err 127 } 128 ps.ExcludeByTag("blueprint", "mutated") 129 for _, nestedProperty := range nestedPropertyStructs(v) { 130 nestedName := nestedProperty.nestPoint 131 nestedValue := nestedProperty.value 132 nestedType := nestedValue.Type() 133 134 // Ignore property structs with unexported or unnamed types 135 if nestedType.PkgPath() == "" { 136 continue 137 } 138 nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) 139 if err != nil { 140 return nil, err 141 } 142 nested.ExcludeByTag("blueprint", "mutated") 143 if nestedName == "" { 144 ps.Nest(nested) 145 } else { 146 nestPoint := ps.GetByName(nestedName) 147 if nestPoint == nil { 148 return nil, fmt.Errorf("nesting point %q not found", nestedName) 149 } 150 nestPoint.Nest(nested) 151 } 152 153 if nestedProperty.anonymous { 154 if nestedName != "" { 155 nestedName += "." 156 } 157 nestedName += proptools.PropertyNameForField(nested.Name) 158 nestedProp := ps.GetByName(nestedName) 159 // Anonymous properties may have already been omitted, no need to ensure they are filtered later 160 if nestedProp != nil { 161 // Set property to anonymous to allow future filtering 162 nestedProp.SetAnonymous() 163 } 164 } 165 } 166 mt.PropertyStructs = append(mt.PropertyStructs, ps) 167 } 168 169 return mt, nil 170} 171 172type nestedProperty struct { 173 nestPoint string 174 value reflect.Value 175 anonymous bool 176} 177 178func nestedPropertyStructs(s reflect.Value) []nestedProperty { 179 ret := make([]nestedProperty, 0) 180 var walk func(structValue reflect.Value, prefix string) 181 walk = func(structValue reflect.Value, prefix string) { 182 var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string) 183 nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) { 184 nestPoint := prefix 185 if field.Anonymous { 186 nestPoint = strings.TrimSuffix(nestPoint, ".") 187 } else { 188 nestPoint = nestPoint + proptools.PropertyNameForField(fieldName) 189 } 190 ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous}) 191 if nestPoint != "" { 192 nestPoint += "." 193 } 194 walk(value, nestPoint) 195 } 196 197 typ := structValue.Type() 198 for i := 0; i < structValue.NumField(); i++ { 199 field := typ.Field(i) 200 if field.PkgPath != "" { 201 // The field is not exported so just skip it. 202 continue 203 } 204 if proptools.HasTag(field, "blueprint", "mutated") { 205 continue 206 } 207 if proptools.IsConfigurable(field.Type) { 208 // Don't recurse into configurable properties, they're structs but not property structs 209 continue 210 } 211 212 fieldValue := structValue.Field(i) 213 214 switch fieldValue.Kind() { 215 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 216 // Nothing 217 case reflect.Struct: 218 nestStruct(field, fieldValue, field.Name) 219 case reflect.Ptr, reflect.Interface: 220 221 if !fieldValue.IsNil() { 222 // We leave the pointer intact and zero out the struct that's 223 // pointed to. 224 elem := fieldValue.Elem() 225 if fieldValue.Kind() == reflect.Interface { 226 if elem.Kind() != reflect.Ptr { 227 panic(fmt.Errorf("can't get type of field %q: interface "+ 228 "refers to a non-pointer", field.Name)) 229 } 230 elem = elem.Elem() 231 } 232 if elem.Kind() == reflect.Struct { 233 nestStruct(field, elem, field.Name) 234 } 235 } 236 default: 237 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 238 field.Name, fieldValue.Kind())) 239 } 240 } 241 } 242 243 walk(s, "") 244 return ret 245} 246 247// Remove any property structs that have no exported fields 248func removeEmptyPropertyStructs(mt *ModuleType) { 249 for i := 0; i < len(mt.PropertyStructs); i++ { 250 if len(mt.PropertyStructs[i].Properties) == 0 { 251 mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...) 252 i-- 253 } 254 } 255} 256 257// Remove any property structs that are anonymous 258func removeAnonymousProperties(mt *ModuleType) { 259 var removeAnonymousProps func(props []Property) []Property 260 removeAnonymousProps = func(props []Property) []Property { 261 newProps := make([]Property, 0, len(props)) 262 for _, p := range props { 263 if p.Anonymous { 264 continue 265 } 266 if len(p.Properties) > 0 { 267 p.Properties = removeAnonymousProps(p.Properties) 268 } 269 newProps = append(newProps, p) 270 } 271 return newProps 272 } 273 for _, ps := range mt.PropertyStructs { 274 ps.Properties = removeAnonymousProps(ps.Properties) 275 } 276} 277 278// Squashes duplicates of the same property struct into single entries 279func collapseDuplicatePropertyStructs(mt *ModuleType) { 280 var collapsed []*PropertyStruct 281 282propertyStructLoop: 283 for _, from := range mt.PropertyStructs { 284 for _, to := range collapsed { 285 if from.Name == to.Name { 286 CollapseDuplicateProperties(&to.Properties, &from.Properties) 287 continue propertyStructLoop 288 } 289 } 290 collapsed = append(collapsed, from) 291 } 292 mt.PropertyStructs = collapsed 293} 294 295func CollapseDuplicateProperties(to, from *[]Property) { 296propertyLoop: 297 for _, f := range *from { 298 for i := range *to { 299 t := &(*to)[i] 300 if f.Name == t.Name { 301 CollapseDuplicateProperties(&t.Properties, &f.Properties) 302 continue propertyLoop 303 } 304 } 305 *to = append(*to, f) 306 } 307} 308 309// Find all property structs that only contain structs, and move their children up one with 310// a prefixed name 311func collapseNestedPropertyStructs(mt *ModuleType) { 312 for _, ps := range mt.PropertyStructs { 313 collapseNestedProperties(&ps.Properties) 314 } 315} 316 317func collapseNestedProperties(p *[]Property) { 318 var n []Property 319 320 for _, parent := range *p { 321 var containsProperty bool 322 for j := range parent.Properties { 323 child := &parent.Properties[j] 324 if len(child.Properties) > 0 { 325 collapseNestedProperties(&child.Properties) 326 } else { 327 containsProperty = true 328 } 329 } 330 if containsProperty || len(parent.Properties) == 0 { 331 n = append(n, parent) 332 } else { 333 for j := range parent.Properties { 334 child := parent.Properties[j] 335 child.Name = parent.Name + "." + child.Name 336 n = append(n, child) 337 } 338 } 339 } 340 *p = n 341} 342