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