1package bpdoc 2 3import ( 4 "fmt" 5 "html/template" 6 "reflect" 7 "sort" 8 9 "github.com/google/blueprint" 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} 63 64func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[string]reflect.Value, 65 moduleTypeNamePropertyStructs map[string][]interface{}) ([]*Package, error) { 66 // Read basic info from the files to construct a Reader instance. 67 r := NewReader(pkgFiles) 68 69 pkgMap := map[string]*Package{} 70 var pkgs []*Package 71 // Scan through per-module-type property structs map. 72 for mtName, propertyStructs := range moduleTypeNamePropertyStructs { 73 // Construct ModuleType with the given info. 74 mtInfo, err := assembleModuleTypeInfo(r, mtName, moduleTypeNameFactories[mtName], propertyStructs) 75 if err != nil { 76 return nil, err 77 } 78 // Some pruning work 79 removeEmptyPropertyStructs(mtInfo) 80 collapseDuplicatePropertyStructs(mtInfo) 81 collapseNestedPropertyStructs(mtInfo) 82 combineDuplicateProperties(mtInfo) 83 84 // Add the ModuleInfo to the corresponding Package map/slice entries. 85 pkg := pkgMap[mtInfo.PkgPath] 86 if pkg == nil { 87 var err error 88 pkg, err = r.Package(mtInfo.PkgPath) 89 if err != nil { 90 return nil, err 91 } 92 pkgMap[mtInfo.PkgPath] = pkg 93 pkgs = append(pkgs, pkg) 94 } 95 pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo) 96 } 97 98 // Sort ModuleTypes within each package. 99 for _, pkg := range pkgs { 100 sort.Slice(pkg.ModuleTypes, func(i, j int) bool { return pkg.ModuleTypes[i].Name < pkg.ModuleTypes[j].Name }) 101 } 102 // Sort packages. 103 sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path }) 104 105 return pkgs, nil 106} 107 108func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value, 109 propertyStructs []interface{}) (*ModuleType, error) { 110 111 mt, err := r.ModuleType(name, factory) 112 if err != nil { 113 return nil, err 114 } 115 116 // Reader.ModuleType only fills basic information such as name and package path. Collect more info 117 // from property struct data. 118 for _, s := range propertyStructs { 119 v := reflect.ValueOf(s).Elem() 120 t := v.Type() 121 122 // Ignore property structs with unexported or unnamed types 123 if t.PkgPath() == "" { 124 continue 125 } 126 ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v) 127 if err != nil { 128 return nil, err 129 } 130 ps.ExcludeByTag("blueprint", "mutated") 131 132 for nestedName, nestedValue := range nestedPropertyStructs(v) { 133 nestedType := nestedValue.Type() 134 135 // Ignore property structs with unexported or unnamed types 136 if nestedType.PkgPath() == "" { 137 continue 138 } 139 nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) 140 if err != nil { 141 return nil, err 142 } 143 nested.ExcludeByTag("blueprint", "mutated") 144 nestPoint := ps.GetByName(nestedName) 145 if nestPoint == nil { 146 return nil, fmt.Errorf("nesting point %q not found", nestedName) 147 } 148 149 key, value, err := blueprint.HasFilter(nestPoint.Tag) 150 if err != nil { 151 return nil, err 152 } 153 if key != "" { 154 nested.IncludeByTag(key, value) 155 } 156 157 nestPoint.Nest(nested) 158 } 159 mt.PropertyStructs = append(mt.PropertyStructs, ps) 160 } 161 162 return mt, nil 163} 164 165func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value { 166 ret := make(map[string]reflect.Value) 167 var walk func(structValue reflect.Value, prefix string) 168 walk = func(structValue reflect.Value, prefix string) { 169 typ := structValue.Type() 170 for i := 0; i < structValue.NumField(); i++ { 171 field := typ.Field(i) 172 if field.PkgPath != "" { 173 // The field is not exported so just skip it. 174 continue 175 } 176 177 fieldValue := structValue.Field(i) 178 179 switch fieldValue.Kind() { 180 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 181 // Nothing 182 case reflect.Struct: 183 walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".") 184 case reflect.Ptr, reflect.Interface: 185 if !fieldValue.IsNil() { 186 // We leave the pointer intact and zero out the struct that's 187 // pointed to. 188 elem := fieldValue.Elem() 189 if fieldValue.Kind() == reflect.Interface { 190 if elem.Kind() != reflect.Ptr { 191 panic(fmt.Errorf("can't get type of field %q: interface "+ 192 "refers to a non-pointer", field.Name)) 193 } 194 elem = elem.Elem() 195 } 196 if elem.Kind() == reflect.Struct { 197 nestPoint := prefix + proptools.PropertyNameForField(field.Name) 198 ret[nestPoint] = elem 199 walk(elem, nestPoint+".") 200 } 201 } 202 default: 203 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 204 field.Name, fieldValue.Kind())) 205 } 206 } 207 } 208 209 walk(s, "") 210 return ret 211} 212 213// Remove any property structs that have no exported fields 214func removeEmptyPropertyStructs(mt *ModuleType) { 215 for i := 0; i < len(mt.PropertyStructs); i++ { 216 if len(mt.PropertyStructs[i].Properties) == 0 { 217 mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...) 218 i-- 219 } 220 } 221} 222 223// Squashes duplicates of the same property struct into single entries 224func collapseDuplicatePropertyStructs(mt *ModuleType) { 225 var collapsed []*PropertyStruct 226 227propertyStructLoop: 228 for _, from := range mt.PropertyStructs { 229 for _, to := range collapsed { 230 if from.Name == to.Name { 231 CollapseDuplicateProperties(&to.Properties, &from.Properties) 232 continue propertyStructLoop 233 } 234 } 235 collapsed = append(collapsed, from) 236 } 237 mt.PropertyStructs = collapsed 238} 239 240func CollapseDuplicateProperties(to, from *[]Property) { 241propertyLoop: 242 for _, f := range *from { 243 for i := range *to { 244 t := &(*to)[i] 245 if f.Name == t.Name { 246 CollapseDuplicateProperties(&t.Properties, &f.Properties) 247 continue propertyLoop 248 } 249 } 250 *to = append(*to, f) 251 } 252} 253 254// Find all property structs that only contain structs, and move their children up one with 255// a prefixed name 256func collapseNestedPropertyStructs(mt *ModuleType) { 257 for _, ps := range mt.PropertyStructs { 258 collapseNestedProperties(&ps.Properties) 259 } 260} 261 262func collapseNestedProperties(p *[]Property) { 263 var n []Property 264 265 for _, parent := range *p { 266 var containsProperty bool 267 for j := range parent.Properties { 268 child := &parent.Properties[j] 269 if len(child.Properties) > 0 { 270 collapseNestedProperties(&child.Properties) 271 } else { 272 containsProperty = true 273 } 274 } 275 if containsProperty || len(parent.Properties) == 0 { 276 n = append(n, parent) 277 } else { 278 for j := range parent.Properties { 279 child := parent.Properties[j] 280 child.Name = parent.Name + "." + child.Name 281 n = append(n, child) 282 } 283 } 284 } 285 *p = n 286} 287 288func combineDuplicateProperties(mt *ModuleType) { 289 for _, ps := range mt.PropertyStructs { 290 combineDuplicateSubProperties(&ps.Properties) 291 } 292} 293 294func combineDuplicateSubProperties(p *[]Property) { 295 var n []Property 296propertyLoop: 297 for _, child := range *p { 298 if len(child.Properties) > 0 { 299 combineDuplicateSubProperties(&child.Properties) 300 for i := range n { 301 s := &n[i] 302 if s.SameSubProperties(child) { 303 s.OtherNames = append(s.OtherNames, child.Name) 304 s.OtherTexts = append(s.OtherTexts, child.Text) 305 continue propertyLoop 306 } 307 } 308 } 309 n = append(n, child) 310 } 311 *p = n 312} 313