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