1package bpdoc 2 3import ( 4 "bytes" 5 "fmt" 6 "go/ast" 7 "go/doc" 8 "go/parser" 9 "go/token" 10 "io/ioutil" 11 "reflect" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "text/template" 17 18 "github.com/google/blueprint" 19 "github.com/google/blueprint/proptools" 20) 21 22type 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 30func 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> 40func (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 70func (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 79func (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 93type PropertyStruct struct { 94 Name string 95 Text string 96 Properties []Property 97} 98 99type Property struct { 100 Name string 101 OtherNames []string 102 Type string 103 Tag reflect.StructTag 104 Text string 105 OtherTexts []string 106 Properties []Property 107 Default string 108} 109 110func (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 120func (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 130func (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 stringArrayEqual(p.OtherTexts, other.OtherTexts) && 135 p.SameSubProperties(other) 136} 137 138func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) { 139 setDefaults(ps.Properties, defaults) 140} 141 142func 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 174func 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 188func (p *Property) SameSubProperties(other Property) bool { 189 if len(p.Properties) != len(other.Properties) { 190 return false 191 } 192 193 for i := range p.Properties { 194 if !p.Properties[i].Equal(other.Properties[i]) { 195 return false 196 } 197 } 198 199 return true 200} 201 202func (ps *PropertyStruct) GetByName(name string) *Property { 203 return getByName(name, "", &ps.Properties) 204} 205 206func getByName(name string, prefix string, props *[]Property) *Property { 207 for i := range *props { 208 if prefix+(*props)[i].Name == name { 209 return &(*props)[i] 210 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") { 211 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties) 212 } 213 } 214 return nil 215} 216 217func (p *Property) Nest(nested *PropertyStruct) { 218 //p.Name += "(" + nested.Name + ")" 219 //p.Text += "(" + nested.Text + ")" 220 p.Properties = append(p.Properties, nested.Properties...) 221} 222 223func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) { 224 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec) 225 ps := PropertyStruct{ 226 Name: t.Name, 227 Text: t.Doc, 228 } 229 230 structType, ok := typeSpec.Type.(*ast.StructType) 231 if !ok { 232 return nil, fmt.Errorf("type of %q is not a struct", t.Name) 233 } 234 235 var err error 236 ps.Properties, err = structProperties(structType) 237 if err != nil { 238 return nil, err 239 } 240 241 return &ps, nil 242} 243 244func structProperties(structType *ast.StructType) (props []Property, err error) { 245 for _, f := range structType.Fields.List { 246 names := f.Names 247 if names == nil { 248 // Anonymous fields have no name, use the type as the name 249 // TODO: hide the name and make the properties show up in the embedding struct 250 if t, ok := f.Type.(*ast.Ident); ok { 251 names = append(names, t) 252 } 253 } 254 for _, n := range names { 255 var name, typ, tag, text string 256 var innerProps []Property 257 if n != nil { 258 name = proptools.PropertyNameForField(n.Name) 259 } 260 if f.Doc != nil { 261 text = f.Doc.Text() 262 } 263 if f.Tag != nil { 264 tag, err = strconv.Unquote(f.Tag.Value) 265 if err != nil { 266 return nil, err 267 } 268 } 269 270 t := f.Type 271 if star, ok := t.(*ast.StarExpr); ok { 272 t = star.X 273 } 274 switch a := t.(type) { 275 case *ast.ArrayType: 276 typ = "list of strings" 277 case *ast.InterfaceType: 278 typ = "interface" 279 case *ast.Ident: 280 typ = a.Name 281 case *ast.StructType: 282 innerProps, err = structProperties(a) 283 if err != nil { 284 return nil, err 285 } 286 default: 287 typ = fmt.Sprintf("%T", f.Type) 288 } 289 290 props = append(props, Property{ 291 Name: name, 292 Type: typ, 293 Tag: reflect.StructTag(tag), 294 Text: text, 295 Properties: innerProps, 296 }) 297 } 298 } 299 300 return props, nil 301} 302 303func (ps *PropertyStruct) ExcludeByTag(key, value string) { 304 filterPropsByTag(&ps.Properties, key, value, true) 305} 306 307func (ps *PropertyStruct) IncludeByTag(key, value string) { 308 filterPropsByTag(&ps.Properties, key, value, false) 309} 310 311func filterPropsByTag(props *[]Property, key, value string, exclude bool) { 312 // Create a slice that shares the storage of props but has 0 length. Appending up to 313 // len(props) times to this slice will overwrite the original slice contents 314 filtered := (*props)[:0] 315 for _, x := range *props { 316 tag := x.Tag.Get(key) 317 for _, entry := range strings.Split(tag, ",") { 318 if (entry == value) == !exclude { 319 filtered = append(filtered, x) 320 } 321 } 322 } 323 324 *props = filtered 325} 326 327// Package AST generation and storage 328func (c *Context) pkg(pkgPath string) (*doc.Package, error) { 329 pkg := c.getPackage(pkgPath) 330 if pkg == nil { 331 if files, ok := c.pkgFiles[pkgPath]; ok { 332 var err error 333 pkgAST, err := NewPackageAST(files) 334 if err != nil { 335 return nil, err 336 } 337 pkg = doc.New(pkgAST, pkgPath, doc.AllDecls) 338 pkg = c.putPackage(pkgPath, pkg) 339 } else { 340 return nil, fmt.Errorf("unknown package %q", pkgPath) 341 } 342 } 343 return pkg, nil 344} 345 346func (c *Context) getPackage(pkgPath string) *doc.Package { 347 c.mutex.Lock() 348 defer c.mutex.Unlock() 349 350 return c.pkgs[pkgPath] 351} 352 353func (c *Context) putPackage(pkgPath string, pkg *doc.Package) *doc.Package { 354 c.mutex.Lock() 355 defer c.mutex.Unlock() 356 357 if c.pkgs[pkgPath] != nil { 358 return c.pkgs[pkgPath] 359 } else { 360 c.pkgs[pkgPath] = pkg 361 return pkg 362 } 363} 364 365func NewPackageAST(files []string) (*ast.Package, error) { 366 asts := make(map[string]*ast.File) 367 368 fset := token.NewFileSet() 369 for _, file := range files { 370 ast, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 371 if err != nil { 372 return nil, err 373 } 374 asts[file] = ast 375 } 376 377 pkg, _ := ast.NewPackage(fset, asts, nil, nil) 378 return pkg, nil 379} 380 381func Write(filename string, pkgFiles map[string][]string, 382 moduleTypePropertyStructs map[string][]interface{}) error { 383 384 c := NewContext(pkgFiles) 385 386 var moduleTypeList []*moduleType 387 for moduleType, propertyStructs := range moduleTypePropertyStructs { 388 mt, err := getModuleType(c, moduleType, propertyStructs) 389 if err != nil { 390 return err 391 } 392 removeEmptyPropertyStructs(mt) 393 collapseDuplicatePropertyStructs(mt) 394 collapseNestedPropertyStructs(mt) 395 combineDuplicateProperties(mt) 396 moduleTypeList = append(moduleTypeList, mt) 397 } 398 399 sort.Sort(moduleTypeByName(moduleTypeList)) 400 401 buf := &bytes.Buffer{} 402 403 unique := 0 404 405 tmpl, err := template.New("file").Funcs(map[string]interface{}{ 406 "unique": func() int { 407 unique++ 408 return unique 409 }}).Parse(fileTemplate) 410 if err != nil { 411 return err 412 } 413 414 err = tmpl.Execute(buf, moduleTypeList) 415 if err != nil { 416 return err 417 } 418 419 err = ioutil.WriteFile(filename, buf.Bytes(), 0666) 420 if err != nil { 421 return err 422 } 423 424 return nil 425} 426 427func getModuleType(c *Context, moduleTypeName string, 428 propertyStructs []interface{}) (*moduleType, error) { 429 mt := &moduleType{ 430 Name: moduleTypeName, 431 //Text: c.ModuleTypeDocs(moduleType), 432 } 433 434 for _, s := range propertyStructs { 435 v := reflect.ValueOf(s).Elem() 436 t := v.Type() 437 438 // Ignore property structs with unexported or unnamed types 439 if t.PkgPath() == "" { 440 continue 441 } 442 ps, err := c.PropertyStruct(t.PkgPath(), t.Name(), v) 443 if err != nil { 444 return nil, err 445 } 446 ps.ExcludeByTag("blueprint", "mutated") 447 448 for nestedName, nestedValue := range nestedPropertyStructs(v) { 449 nestedType := nestedValue.Type() 450 451 // Ignore property structs with unexported or unnamed types 452 if nestedType.PkgPath() == "" { 453 continue 454 } 455 nested, err := c.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue) 456 if err != nil { 457 return nil, err 458 } 459 nested.ExcludeByTag("blueprint", "mutated") 460 nestPoint := ps.GetByName(nestedName) 461 if nestPoint == nil { 462 return nil, fmt.Errorf("nesting point %q not found", nestedName) 463 } 464 465 key, value, err := blueprint.HasFilter(nestPoint.Tag) 466 if err != nil { 467 return nil, err 468 } 469 if key != "" { 470 nested.IncludeByTag(key, value) 471 } 472 473 nestPoint.Nest(nested) 474 } 475 mt.PropertyStructs = append(mt.PropertyStructs, ps) 476 } 477 478 return mt, nil 479} 480 481func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value { 482 ret := make(map[string]reflect.Value) 483 var walk func(structValue reflect.Value, prefix string) 484 walk = func(structValue reflect.Value, prefix string) { 485 typ := structValue.Type() 486 for i := 0; i < structValue.NumField(); i++ { 487 field := typ.Field(i) 488 if field.PkgPath != "" { 489 // The field is not exported so just skip it. 490 continue 491 } 492 493 fieldValue := structValue.Field(i) 494 495 switch fieldValue.Kind() { 496 case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: 497 // Nothing 498 case reflect.Struct: 499 walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".") 500 case reflect.Ptr, reflect.Interface: 501 if !fieldValue.IsNil() { 502 // We leave the pointer intact and zero out the struct that's 503 // pointed to. 504 elem := fieldValue.Elem() 505 if fieldValue.Kind() == reflect.Interface { 506 if elem.Kind() != reflect.Ptr { 507 panic(fmt.Errorf("can't get type of field %q: interface "+ 508 "refers to a non-pointer", field.Name)) 509 } 510 elem = elem.Elem() 511 } 512 if elem.Kind() == reflect.Struct { 513 nestPoint := prefix + proptools.PropertyNameForField(field.Name) 514 ret[nestPoint] = elem 515 walk(elem, nestPoint+".") 516 } 517 } 518 default: 519 panic(fmt.Errorf("unexpected kind for property struct field %q: %s", 520 field.Name, fieldValue.Kind())) 521 } 522 } 523 524 } 525 526 walk(s, "") 527 return ret 528} 529 530// Remove any property structs that have no exported fields 531func removeEmptyPropertyStructs(mt *moduleType) { 532 for i := 0; i < len(mt.PropertyStructs); i++ { 533 if len(mt.PropertyStructs[i].Properties) == 0 { 534 mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...) 535 i-- 536 } 537 } 538} 539 540// Squashes duplicates of the same property struct into single entries 541func collapseDuplicatePropertyStructs(mt *moduleType) { 542 var collapsed []*PropertyStruct 543 544propertyStructLoop: 545 for _, from := range mt.PropertyStructs { 546 for _, to := range collapsed { 547 if from.Name == to.Name { 548 collapseDuplicateProperties(&to.Properties, &from.Properties) 549 continue propertyStructLoop 550 } 551 } 552 collapsed = append(collapsed, from) 553 } 554 mt.PropertyStructs = collapsed 555} 556 557func collapseDuplicateProperties(to, from *[]Property) { 558propertyLoop: 559 for _, f := range *from { 560 for i := range *to { 561 t := &(*to)[i] 562 if f.Name == t.Name { 563 collapseDuplicateProperties(&t.Properties, &f.Properties) 564 continue propertyLoop 565 } 566 } 567 *to = append(*to, f) 568 } 569} 570 571// Find all property structs that only contain structs, and move their children up one with 572// a prefixed name 573func collapseNestedPropertyStructs(mt *moduleType) { 574 for _, ps := range mt.PropertyStructs { 575 collapseNestedProperties(&ps.Properties) 576 } 577} 578 579func collapseNestedProperties(p *[]Property) { 580 var n []Property 581 582 for _, parent := range *p { 583 var containsProperty bool 584 for j := range parent.Properties { 585 child := &parent.Properties[j] 586 if len(child.Properties) > 0 { 587 collapseNestedProperties(&child.Properties) 588 } else { 589 containsProperty = true 590 } 591 } 592 if containsProperty || len(parent.Properties) == 0 { 593 n = append(n, parent) 594 } else { 595 for j := range parent.Properties { 596 child := parent.Properties[j] 597 child.Name = parent.Name + "." + child.Name 598 n = append(n, child) 599 } 600 } 601 } 602 *p = n 603} 604 605func combineDuplicateProperties(mt *moduleType) { 606 for _, ps := range mt.PropertyStructs { 607 combineDuplicateSubProperties(&ps.Properties) 608 } 609} 610 611func combineDuplicateSubProperties(p *[]Property) { 612 var n []Property 613propertyLoop: 614 for _, child := range *p { 615 if len(child.Properties) > 0 { 616 combineDuplicateSubProperties(&child.Properties) 617 for i := range n { 618 s := &n[i] 619 if s.SameSubProperties(child) { 620 s.OtherNames = append(s.OtherNames, child.Name) 621 s.OtherTexts = append(s.OtherTexts, child.Text) 622 continue propertyLoop 623 } 624 } 625 } 626 n = append(n, child) 627 } 628 629 *p = n 630} 631 632type moduleTypeByName []*moduleType 633 634func (l moduleTypeByName) Len() int { return len(l) } 635func (l moduleTypeByName) Less(i, j int) bool { return l[i].Name < l[j].Name } 636func (l moduleTypeByName) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 637 638type moduleType struct { 639 Name string 640 Text string 641 PropertyStructs []*PropertyStruct 642} 643 644var ( 645 fileTemplate = ` 646<html> 647<head> 648<title>Build Docs</title> 649<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> 650<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> 651<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> 652</head> 653<body> 654<h1>Build Docs</h1> 655<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> 656 {{range .}} 657 {{ $collapseIndex := unique }} 658 <div class="panel panel-default"> 659 <div class="panel-heading" role="tab" id="heading{{$collapseIndex}}"> 660 <h2 class="panel-title"> 661 <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}"> 662 {{.Name}} 663 </a> 664 </h2> 665 </div> 666 </div> 667 <div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}"> 668 <div class="panel-body"> 669 <p>{{.Text}}</p> 670 {{range .PropertyStructs}} 671 <p>{{.Text}}</p> 672 {{template "properties" .Properties}} 673 {{end}} 674 </div> 675 </div> 676 {{end}} 677</div> 678</body> 679</html> 680 681{{define "properties"}} 682 <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> 683 {{range .}} 684 {{$collapseIndex := unique}} 685 {{if .Properties}} 686 <div class="panel panel-default"> 687 <div class="panel-heading" role="tab" id="heading{{$collapseIndex}}"> 688 <h4 class="panel-title"> 689 <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}"> 690 {{.Name}}{{range .OtherNames}}, {{.}}{{end}} 691 </a> 692 </h4> 693 </div> 694 </div> 695 <div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}"> 696 <div class="panel-body"> 697 <p>{{.Text}}</p> 698 {{range .OtherTexts}}<p>{{.}}</p>{{end}} 699 {{template "properties" .Properties}} 700 </div> 701 </div> 702 {{else}} 703 <div> 704 <h4>{{.Name}}{{range .OtherNames}}, {{.}}{{end}}</h4> 705 <p>{{.Text}}</p> 706 {{range .OtherTexts}}<p>{{.}}</p>{{end}} 707 <p><i>Type: {{.Type}}</i></p> 708 {{if .Default}}<p><i>Default: {{.Default}}</i></p>{{end}} 709 </div> 710 {{end}} 711 {{end}} 712 </div> 713{{end}} 714` 715) 716