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 208 fieldValue := structValue.Field(i) 209 210 switch fieldValue.Kind() { 211 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 212 // Nothing 213 case reflect.Struct: 214 nestStruct(field, fieldValue, field.Name) 215 case reflect.Ptr, reflect.Interface: 216 217 if !fieldValue.IsNil() { 218 // We leave the pointer intact and zero out the struct that's 219 // pointed to. 220 elem := fieldValue.Elem() 221 if fieldValue.Kind() == reflect.Interface { 222 if elem.Kind() != reflect.Ptr { 223 panic(fmt.Errorf("can't get type of field %q: interface "+ 224 "refers to a non-pointer", field.Name)) 225 } 226 elem = elem.Elem() 227 } 228 if elem.Kind() == reflect.Struct { 229 nestStruct(field, elem, field.Name) 230 } 231 } 232 default: 233 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 234 field.Name, fieldValue.Kind())) 235 } 236 } 237 } 238 239 walk(s, "") 240 return ret 241} 242 243// Remove any property structs that have no exported fields 244func removeEmptyPropertyStructs(mt *ModuleType) { 245 for i := 0; i < len(mt.PropertyStructs); i++ { 246 if len(mt.PropertyStructs[i].Properties) == 0 { 247 mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...) 248 i-- 249 } 250 } 251} 252 253// Remove any property structs that are anonymous 254func removeAnonymousProperties(mt *ModuleType) { 255 var removeAnonymousProps func(props []Property) []Property 256 removeAnonymousProps = func(props []Property) []Property { 257 newProps := make([]Property, 0, len(props)) 258 for _, p := range props { 259 if p.Anonymous { 260 continue 261 } 262 if len(p.Properties) > 0 { 263 p.Properties = removeAnonymousProps(p.Properties) 264 } 265 newProps = append(newProps, p) 266 } 267 return newProps 268 } 269 for _, ps := range mt.PropertyStructs { 270 ps.Properties = removeAnonymousProps(ps.Properties) 271 } 272} 273 274// Squashes duplicates of the same property struct into single entries 275func collapseDuplicatePropertyStructs(mt *ModuleType) { 276 var collapsed []*PropertyStruct 277 278propertyStructLoop: 279 for _, from := range mt.PropertyStructs { 280 for _, to := range collapsed { 281 if from.Name == to.Name { 282 CollapseDuplicateProperties(&to.Properties, &from.Properties) 283 continue propertyStructLoop 284 } 285 } 286 collapsed = append(collapsed, from) 287 } 288 mt.PropertyStructs = collapsed 289} 290 291func CollapseDuplicateProperties(to, from *[]Property) { 292propertyLoop: 293 for _, f := range *from { 294 for i := range *to { 295 t := &(*to)[i] 296 if f.Name == t.Name { 297 CollapseDuplicateProperties(&t.Properties, &f.Properties) 298 continue propertyLoop 299 } 300 } 301 *to = append(*to, f) 302 } 303} 304 305// Find all property structs that only contain structs, and move their children up one with 306// a prefixed name 307func collapseNestedPropertyStructs(mt *ModuleType) { 308 for _, ps := range mt.PropertyStructs { 309 collapseNestedProperties(&ps.Properties) 310 } 311} 312 313func collapseNestedProperties(p *[]Property) { 314 var n []Property 315 316 for _, parent := range *p { 317 var containsProperty bool 318 for j := range parent.Properties { 319 child := &parent.Properties[j] 320 if len(child.Properties) > 0 { 321 collapseNestedProperties(&child.Properties) 322 } else { 323 containsProperty = true 324 } 325 } 326 if containsProperty || len(parent.Properties) == 0 { 327 n = append(n, parent) 328 } else { 329 for j := range parent.Properties { 330 child := parent.Properties[j] 331 child.Name = parent.Name + "." + child.Name 332 n = append(n, child) 333 } 334 } 335 } 336 *p = n 337} 338