• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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