github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/bootstrap/bpdoc/bpdoc.go (about) 1 package bpdoc 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/doc" 7 "go/parser" 8 "go/token" 9 "html/template" 10 "reflect" 11 "sort" 12 "strconv" 13 "strings" 14 "sync" 15 "unicode" 16 "unicode/utf8" 17 18 "github.com/google/blueprint" 19 "github.com/google/blueprint/proptools" 20 ) 21 22 type Context struct { 23 pkgFiles map[string][]string // Map of package name to source files, provided by constructor 24 25 mutex sync.Mutex 26 pkgs map[string]*doc.Package // Map of package name to parsed Go AST, protected by mutex 27 ps map[string]*PropertyStruct // Map of type name to property struct, protected by mutex 28 } 29 30 func NewContext(pkgFiles map[string][]string) *Context { 31 return &Context{ 32 pkgFiles: pkgFiles, 33 pkgs: make(map[string]*doc.Package), 34 ps: make(map[string]*PropertyStruct), 35 } 36 } 37 38 // Return the PropertyStruct associated with a property struct type. The type should be in the 39 // format <package path>.<type name> 40 func (c *Context) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) { 41 ps := c.getPropertyStruct(pkgPath, name) 42 43 if ps == nil { 44 pkg, err := c.pkg(pkgPath) 45 if err != nil { 46 return nil, err 47 } 48 49 for _, t := range pkg.Types { 50 if t.Name == name { 51 ps, err = newPropertyStruct(t) 52 if err != nil { 53 return nil, err 54 } 55 ps = c.putPropertyStruct(pkgPath, name, ps) 56 } 57 } 58 } 59 60 if ps == nil { 61 return nil, fmt.Errorf("package %q type %q not found", pkgPath, name) 62 } 63 64 ps = ps.Clone() 65 ps.SetDefaults(defaults) 66 67 return ps, nil 68 } 69 70 func (c *Context) getPropertyStruct(pkgPath, name string) *PropertyStruct { 71 c.mutex.Lock() 72 defer c.mutex.Unlock() 73 74 name = pkgPath + "." + name 75 76 return c.ps[name] 77 } 78 79 func (c *Context) putPropertyStruct(pkgPath, name string, ps *PropertyStruct) *PropertyStruct { 80 c.mutex.Lock() 81 defer c.mutex.Unlock() 82 83 name = pkgPath + "." + name 84 85 if c.ps[name] != nil { 86 return c.ps[name] 87 } else { 88 c.ps[name] = ps 89 return ps 90 } 91 } 92 93 type PropertyStruct struct { 94 Name string 95 Text string 96 Properties []Property 97 } 98 99 type Property struct { 100 Name string 101 OtherNames []string 102 Type string 103 Tag reflect.StructTag 104 Text template.HTML 105 OtherTexts []template.HTML 106 Properties []Property 107 Default string 108 } 109 110 func (ps *PropertyStruct) Clone() *PropertyStruct { 111 ret := *ps 112 ret.Properties = append([]Property(nil), ret.Properties...) 113 for i, prop := range ret.Properties { 114 ret.Properties[i] = prop.Clone() 115 } 116 117 return &ret 118 } 119 120 func (p *Property) Clone() Property { 121 ret := *p 122 ret.Properties = append([]Property(nil), ret.Properties...) 123 for i, prop := range ret.Properties { 124 ret.Properties[i] = prop.Clone() 125 } 126 127 return ret 128 } 129 130 func (p *Property) Equal(other Property) bool { 131 return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag && 132 p.Text == other.Text && p.Default == other.Default && 133 stringArrayEqual(p.OtherNames, other.OtherNames) && 134 htmlArrayEqual(p.OtherTexts, other.OtherTexts) && 135 p.SameSubProperties(other) 136 } 137 138 func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) { 139 setDefaults(ps.Properties, defaults) 140 } 141 142 func setDefaults(properties []Property, defaults reflect.Value) { 143 for i := range properties { 144 prop := &properties[i] 145 fieldName := proptools.FieldNameForProperty(prop.Name) 146 f := defaults.FieldByName(fieldName) 147 if (f == reflect.Value{}) { 148 panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type())) 149 } 150 151 if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) { 152 continue 153 } 154 155 if f.Kind() == reflect.Interface { 156 f = f.Elem() 157 } 158 159 if f.Kind() == reflect.Ptr { 160 if f.IsNil() { 161 continue 162 } 163 f = f.Elem() 164 } 165 166 if f.Kind() == reflect.Struct { 167 setDefaults(prop.Properties, f) 168 } else { 169 prop.Default = fmt.Sprintf("%v", f.Interface()) 170 } 171 } 172 } 173 174 func stringArrayEqual(a, b []string) bool { 175 if len(a) != len(b) { 176 return false 177 } 178 179 for i := range a { 180 if a[i] != b[i] { 181 return false 182 } 183 } 184 185 return true 186 } 187 188 func htmlArrayEqual(a, b []template.HTML) bool { 189 if len(a) != len(b) { 190 return false 191 } 192 193 for i := range a { 194 if a[i] != b[i] { 195 return false 196 } 197 } 198 199 return true 200 } 201 202 func (p *Property) SameSubProperties(other Property) bool { 203 if len(p.Properties) != len(other.Properties) { 204 return false 205 } 206 207 for i := range p.Properties { 208 if !p.Properties[i].Equal(other.Properties[i]) { 209 return false 210 } 211 } 212 213 return true 214 } 215 216 func (ps *PropertyStruct) GetByName(name string) *Property { 217 return getByName(name, "", &ps.Properties) 218 } 219 220 func getByName(name string, prefix string, props *[]Property) *Property { 221 for i := range *props { 222 if prefix+(*props)[i].Name == name { 223 return &(*props)[i] 224 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") { 225 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties) 226 } 227 } 228 return nil 229 } 230 231 func (p *Property) Nest(nested *PropertyStruct) { 232 //p.Name += "(" + nested.Name + ")" 233 //p.Text += "(" + nested.Text + ")" 234 p.Properties = append(p.Properties, nested.Properties...) 235 } 236 237 func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) { 238 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec) 239 ps := PropertyStruct{ 240 Name: t.Name, 241 Text: t.Doc, 242 } 243 244 structType, ok := typeSpec.Type.(*ast.StructType) 245 if !ok { 246 return nil, fmt.Errorf("type of %q is not a struct", t.Name) 247 } 248 249 var err error 250 ps.Properties, err = structProperties(structType) 251 if err != nil { 252 return nil, err 253 } 254 255 return &ps, nil 256 } 257 258 func structProperties(structType *ast.StructType) (props []Property, err error) { 259 for _, f := range structType.Fields.List { 260 names := f.Names 261 if names == nil { 262 // Anonymous fields have no name, use the type as the name 263 // TODO: hide the name and make the properties show up in the embedding struct 264 if t, ok := f.Type.(*ast.Ident); ok { 265 names = append(names, t) 266 } 267 } 268 for _, n := range names { 269 var name, typ, tag, text string 270 var innerProps []Property 271 if n != nil { 272 name = proptools.PropertyNameForField(n.Name) 273 } 274 if f.Doc != nil { 275 text = f.Doc.Text() 276 } 277 if f.Tag != nil { 278 tag, err = strconv.Unquote(f.Tag.Value) 279 if err != nil { 280 return nil, err 281 } 282 } 283 284 t := f.Type 285 if star, ok := t.(*ast.StarExpr); ok { 286 t = star.X 287 } 288 switch a := t.(type) { 289 case *ast.ArrayType: 290 typ = "list of strings" 291 case *ast.InterfaceType: 292 typ = "interface" 293 case *ast.Ident: 294 typ = a.Name 295 case *ast.StructType: 296 innerProps, err = structProperties(a) 297 if err != nil { 298 return nil, err 299 } 300 default: 301 typ = fmt.Sprintf("%T", f.Type) 302 } 303 304 var html template.HTML 305 306 lines := strings.Split(text, "\n") 307 preformatted := false 308 for _, line := range lines { 309 r, _ := utf8.DecodeRuneInString(line) 310 indent := unicode.IsSpace(r) 311 if indent && !preformatted { 312 html += "<pre>\n" 313 } else if !indent && preformatted { 314 html += "</pre>\n" 315 } 316 preformatted = indent 317 html += template.HTML(template.HTMLEscapeString(line)) + "\n" 318 } 319 if preformatted { 320 html += "</pre>\n" 321 } 322 323 props = append(props, Property{ 324 Name: name, 325 Type: typ, 326 Tag: reflect.StructTag(tag), 327 Text: html, 328 Properties: innerProps, 329 }) 330 } 331 } 332 333 return props, nil 334 } 335 336 func (ps *PropertyStruct) ExcludeByTag(key, value string) { 337 filterPropsByTag(&ps.Properties, key, value, true) 338 } 339 340 func (ps *PropertyStruct) IncludeByTag(key, value string) { 341 filterPropsByTag(&ps.Properties, key, value, false) 342 } 343 344 func filterPropsByTag(props *[]Property, key, value string, exclude bool) { 345 // Create a slice that shares the storage of props but has 0 length. Appending up to 346 // len(props) times to this slice will overwrite the original slice contents 347 filtered := (*props)[:0] 348 for _, x := range *props { 349 tag := x.Tag.Get(key) 350 for _, entry := range strings.Split(tag, ",") { 351 if (entry == value) == !exclude { 352 filtered = append(filtered, x) 353 } 354 } 355 } 356 357 *props = filtered 358 } 359 360 // Package AST generation and storage 361 func (c *Context) pkg(pkgPath string) (*doc.Package, error) { 362 pkg := c.getPackage(pkgPath) 363 if pkg == nil { 364 if files, ok := c.pkgFiles[pkgPath]; ok { 365 var err error 366 pkgAST, err := NewPackageAST(files) 367 if err != nil { 368 return nil, err 369 } 370 pkg = doc.New(pkgAST, pkgPath, doc.AllDecls) 371 pkg = c.putPackage(pkgPath, pkg) 372 } else { 373 return nil, fmt.Errorf("unknown package %q", pkgPath) 374 } 375 } 376 return pkg, nil 377 } 378 379 func (c *Context) getPackage(pkgPath string) *doc.Package { 380 c.mutex.Lock() 381 defer c.mutex.Unlock() 382 383 return c.pkgs[pkgPath] 384 } 385 386 func (c *Context) putPackage(pkgPath string, pkg *doc.Package) *doc.Package { 387 c.mutex.Lock() 388 defer c.mutex.Unlock() 389 390 if c.pkgs[pkgPath] != nil { 391 return c.pkgs[pkgPath] 392 } else { 393 c.pkgs[pkgPath] = pkg 394 return pkg 395 } 396 } 397 398 func NewPackageAST(files []string) (*ast.Package, error) { 399 asts := make(map[string]*ast.File) 400 401 fset := token.NewFileSet() 402 for _, file := range files { 403 ast, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 404 if err != nil { 405 return nil, err 406 } 407 asts[file] = ast 408 } 409 410 pkg, _ := ast.NewPackage(fset, asts, nil, nil) 411 return pkg, nil 412 } 413 414 func ModuleTypes(pkgFiles map[string][]string, moduleTypePropertyStructs map[string][]interface{}) ([]*ModuleType, error) { 415 c := NewContext(pkgFiles) 416 417 var moduleTypeList []*ModuleType 418 for moduleType, propertyStructs := range moduleTypePropertyStructs { 419 mt, err := getModuleType(c, moduleType, propertyStructs) 420 if err != nil { 421 return nil, err 422 } 423 removeEmptyPropertyStructs(mt) 424 collapseDuplicatePropertyStructs(mt) 425 collapseNestedPropertyStructs(mt) 426 combineDuplicateProperties(mt) 427 moduleTypeList = append(moduleTypeList, mt) 428 } 429 430 sort.Sort(moduleTypeByName(moduleTypeList)) 431 432 return moduleTypeList, nil 433 } 434 435 func getModuleType(c *Context, moduleTypeName string, 436 propertyStructs []interface{}) (*ModuleType, error) { 437 mt := &ModuleType{ 438 Name: moduleTypeName, 439 //Text: c.ModuleTypeDocs(moduleType), 440 } 441 442 for _, s := range propertyStructs { 443 v := reflect.ValueOf(s).Elem() 444 t := v.Type() 445 446 // Ignore property structs with unexported or unnamed types 447 if t.PkgPath() == "" { 448 continue 449 } 450 ps, err := c.PropertyStruct(t.PkgPath(), t.Name(), v) 451 if err != nil { 452 return nil, err 453 } 454 ps.ExcludeByTag("blueprint", "mutated") 455 456 for nestedName, nestedValue := range nestedPropertyStructs(v) { 457 nestedType := nestedValue.Type() 458 459 // Ignore property structs with unexported or unnamed types 460 if nestedType.PkgPath() == "" { 461 continue 462 } 463 nested, err := c.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) 464 if err != nil { 465 return nil, err 466 } 467 nested.ExcludeByTag("blueprint", "mutated") 468 nestPoint := ps.GetByName(nestedName) 469 if nestPoint == nil { 470 return nil, fmt.Errorf("nesting point %q not found", nestedName) 471 } 472 473 key, value, err := blueprint.HasFilter(nestPoint.Tag) 474 if err != nil { 475 return nil, err 476 } 477 if key != "" { 478 nested.IncludeByTag(key, value) 479 } 480 481 nestPoint.Nest(nested) 482 } 483 mt.PropertyStructs = append(mt.PropertyStructs, ps) 484 } 485 486 return mt, nil 487 } 488 489 func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value { 490 ret := make(map[string]reflect.Value) 491 var walk func(structValue reflect.Value, prefix string) 492 walk = func(structValue reflect.Value, prefix string) { 493 typ := structValue.Type() 494 for i := 0; i < structValue.NumField(); i++ { 495 field := typ.Field(i) 496 if field.PkgPath != "" { 497 // The field is not exported so just skip it. 498 continue 499 } 500 501 fieldValue := structValue.Field(i) 502 503 switch fieldValue.Kind() { 504 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 505 // Nothing 506 case reflect.Struct: 507 walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".") 508 case reflect.Ptr, reflect.Interface: 509 if !fieldValue.IsNil() { 510 // We leave the pointer intact and zero out the struct that's 511 // pointed to. 512 elem := fieldValue.Elem() 513 if fieldValue.Kind() == reflect.Interface { 514 if elem.Kind() != reflect.Ptr { 515 panic(fmt.Errorf("can't get type of field %q: interface "+ 516 "refers to a non-pointer", field.Name)) 517 } 518 elem = elem.Elem() 519 } 520 if elem.Kind() == reflect.Struct { 521 nestPoint := prefix + proptools.PropertyNameForField(field.Name) 522 ret[nestPoint] = elem 523 walk(elem, nestPoint+".") 524 } 525 } 526 default: 527 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 528 field.Name, fieldValue.Kind())) 529 } 530 } 531 532 } 533 534 walk(s, "") 535 return ret 536 } 537 538 // Remove any property structs that have no exported fields 539 func removeEmptyPropertyStructs(mt *ModuleType) { 540 for i := 0; i < len(mt.PropertyStructs); i++ { 541 if len(mt.PropertyStructs[i].Properties) == 0 { 542 mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...) 543 i-- 544 } 545 } 546 } 547 548 // Squashes duplicates of the same property struct into single entries 549 func collapseDuplicatePropertyStructs(mt *ModuleType) { 550 var collapsed []*PropertyStruct 551 552 propertyStructLoop: 553 for _, from := range mt.PropertyStructs { 554 for _, to := range collapsed { 555 if from.Name == to.Name { 556 collapseDuplicateProperties(&to.Properties, &from.Properties) 557 continue propertyStructLoop 558 } 559 } 560 collapsed = append(collapsed, from) 561 } 562 mt.PropertyStructs = collapsed 563 } 564 565 func collapseDuplicateProperties(to, from *[]Property) { 566 propertyLoop: 567 for _, f := range *from { 568 for i := range *to { 569 t := &(*to)[i] 570 if f.Name == t.Name { 571 collapseDuplicateProperties(&t.Properties, &f.Properties) 572 continue propertyLoop 573 } 574 } 575 *to = append(*to, f) 576 } 577 } 578 579 // Find all property structs that only contain structs, and move their children up one with 580 // a prefixed name 581 func collapseNestedPropertyStructs(mt *ModuleType) { 582 for _, ps := range mt.PropertyStructs { 583 collapseNestedProperties(&ps.Properties) 584 } 585 } 586 587 func collapseNestedProperties(p *[]Property) { 588 var n []Property 589 590 for _, parent := range *p { 591 var containsProperty bool 592 for j := range parent.Properties { 593 child := &parent.Properties[j] 594 if len(child.Properties) > 0 { 595 collapseNestedProperties(&child.Properties) 596 } else { 597 containsProperty = true 598 } 599 } 600 if containsProperty || len(parent.Properties) == 0 { 601 n = append(n, parent) 602 } else { 603 for j := range parent.Properties { 604 child := parent.Properties[j] 605 child.Name = parent.Name + "." + child.Name 606 n = append(n, child) 607 } 608 } 609 } 610 *p = n 611 } 612 613 func combineDuplicateProperties(mt *ModuleType) { 614 for _, ps := range mt.PropertyStructs { 615 combineDuplicateSubProperties(&ps.Properties) 616 } 617 } 618 619 func combineDuplicateSubProperties(p *[]Property) { 620 var n []Property 621 propertyLoop: 622 for _, child := range *p { 623 if len(child.Properties) > 0 { 624 combineDuplicateSubProperties(&child.Properties) 625 for i := range n { 626 s := &n[i] 627 if s.SameSubProperties(child) { 628 s.OtherNames = append(s.OtherNames, child.Name) 629 s.OtherTexts = append(s.OtherTexts, child.Text) 630 continue propertyLoop 631 } 632 } 633 } 634 n = append(n, child) 635 } 636 637 *p = n 638 } 639 640 type moduleTypeByName []*ModuleType 641 642 func (l moduleTypeByName) Len() int { return len(l) } 643 func (l moduleTypeByName) Less(i, j int) bool { return l[i].Name < l[j].Name } 644 func (l moduleTypeByName) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 645 646 // ModuleType contains the info about a module type that is relevant to generating documentation. 647 type ModuleType struct { 648 // Name is the string that will appear in Blueprints files when defining a new module of 649 // this type. 650 Name string 651 652 // Text is the contents of the comment documenting the module type 653 Text string 654 655 // PropertyStructs is a list of PropertyStruct objects that contain information about each 656 // property struct that is used by the module type, containing all properties that are valid 657 // for the module type. 658 PropertyStructs []*PropertyStruct 659 }