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  }