package bpdoc
import (
"fmt"
"html/template"
"reflect"
"sort"
"strings"
"github.com/google/blueprint/proptools"
)
// Package contains the information about a package relevant to generating documentation.
type Package struct {
// Name is the name of the package.
Name string
// Path is the full package path of the package as used in the primary builder.
Path string
// Text is the contents of the package comment documenting the module types in the package.
Text string
// ModuleTypes is a list of ModuleType objects that contain information about each module type that is
// defined by the package.
ModuleTypes []*ModuleType
}
// ModuleType contains the information about a module type that is relevant to generating documentation.
type ModuleType struct {
// Name is the string that will appear in Blueprints files when defining a new module of
// this type.
Name string
// PkgPath is the full package path of the package that contains the module type factory.
PkgPath string
// Text is the contents of the comment documenting the module type.
Text template.HTML
// PropertyStructs is a list of PropertyStruct objects that contain information about each
// property struct that is used by the module type, containing all properties that are valid
// for the module type.
PropertyStructs []*PropertyStruct
}
type PropertyStruct struct {
Name string
Text string
Properties []Property
}
type Property struct {
Name string
OtherNames []string
Type string
Tag reflect.StructTag
Text template.HTML
OtherTexts []template.HTML
Properties []Property
Default string
Anonymous bool
}
func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[string]reflect.Value,
moduleTypeNamePropertyStructs map[string][]interface{}) ([]*Package, error) {
// Read basic info from the files to construct a Reader instance.
r := NewReader(pkgFiles)
pkgMap := map[string]*Package{}
var pkgs []*Package
// Scan through per-module-type property structs map.
for mtName, propertyStructs := range moduleTypeNamePropertyStructs {
// Construct ModuleType with the given info.
mtInfo, err := assembleModuleTypeInfo(r, mtName, moduleTypeNameFactories[mtName], propertyStructs)
if err != nil {
return nil, err
}
// Some pruning work
removeAnonymousProperties(mtInfo)
removeEmptyPropertyStructs(mtInfo)
collapseDuplicatePropertyStructs(mtInfo)
collapseNestedPropertyStructs(mtInfo)
combineDuplicateProperties(mtInfo)
// Add the ModuleInfo to the corresponding Package map/slice entries.
pkg := pkgMap[mtInfo.PkgPath]
if pkg == nil {
var err error
pkg, err = r.Package(mtInfo.PkgPath)
if err != nil {
return nil, err
}
pkgMap[mtInfo.PkgPath] = pkg
pkgs = append(pkgs, pkg)
}
pkg.ModuleTypes = append(pkg.ModuleTypes, mtInfo)
}
// Sort ModuleTypes within each package.
for _, pkg := range pkgs {
sort.Slice(pkg.ModuleTypes, func(i, j int) bool { return pkg.ModuleTypes[i].Name < pkg.ModuleTypes[j].Name })
}
// Sort packages.
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path })
return pkgs, nil
}
func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
propertyStructs []interface{}) (*ModuleType, error) {
mt, err := r.ModuleType(name, factory)
if err != nil {
return nil, err
}
// Reader.ModuleType only fills basic information such as name and package path. Collect more info
// from property struct data.
for _, s := range propertyStructs {
v := reflect.ValueOf(s).Elem()
t := v.Type()
// Ignore property structs with unexported or unnamed types
if t.PkgPath() == "" {
continue
}
ps, err := r.PropertyStruct(t.PkgPath(), t.Name(), v)
if err != nil {
return nil, err
}
ps.ExcludeByTag("blueprint", "mutated")
for _, nestedProperty := range nestedPropertyStructs(v) {
nestedName := nestedProperty.nestPoint
nestedValue := nestedProperty.value
nestedType := nestedValue.Type()
// Ignore property structs with unexported or unnamed types
if nestedType.PkgPath() == "" {
continue
}
nested, err := r.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
if err != nil {
return nil, err
}
nested.ExcludeByTag("blueprint", "mutated")
if nestedName == "" {
ps.Nest(nested)
} else {
nestPoint := ps.GetByName(nestedName)
if nestPoint == nil {
return nil, fmt.Errorf("nesting point %q not found", nestedName)
}
nestPoint.Nest(nested)
}
if nestedProperty.anonymous {
if nestedName != "" {
nestedName += "."
}
nestedName += proptools.PropertyNameForField(nested.Name)
nestedProp := ps.GetByName(nestedName)
// Anonymous properties may have already been omitted, no need to ensure they are filtered later
if nestedProp != nil {
// Set property to anonymous to allow future filtering
nestedProp.SetAnonymous()
}
}
}
mt.PropertyStructs = append(mt.PropertyStructs, ps)
}
return mt, nil
}
type nestedProperty struct {
nestPoint string
value reflect.Value
anonymous bool
}
func nestedPropertyStructs(s reflect.Value) []nestedProperty {
ret := make([]nestedProperty, 0)
var walk func(structValue reflect.Value, prefix string)
walk = func(structValue reflect.Value, prefix string) {
var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string)
nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) {
nestPoint := prefix
if field.Anonymous {
nestPoint = strings.TrimSuffix(nestPoint, ".")
} else {
nestPoint = nestPoint + proptools.PropertyNameForField(fieldName)
}
ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous})
if nestPoint != "" {
nestPoint += "."
}
walk(value, nestPoint)
}
typ := structValue.Type()
for i := 0; i < structValue.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
// The field is not exported so just skip it.
continue
}
if proptools.HasTag(field, "blueprint", "mutated") {
continue
}
fieldValue := structValue.Field(i)
switch fieldValue.Kind() {
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
// Nothing
case reflect.Struct:
nestStruct(field, fieldValue, field.Name)
case reflect.Ptr, reflect.Interface:
if !fieldValue.IsNil() {
// We leave the pointer intact and zero out the struct that's
// pointed to.
elem := fieldValue.Elem()
if fieldValue.Kind() == reflect.Interface {
if elem.Kind() != reflect.Ptr {
panic(fmt.Errorf("can't get type of field %q: interface "+
"refers to a non-pointer", field.Name))
}
elem = elem.Elem()
}
if elem.Kind() == reflect.Struct {
nestStruct(field, elem, field.Name)
}
}
default:
panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
field.Name, fieldValue.Kind()))
}
}
}
walk(s, "")
return ret
}
// Remove any property structs that have no exported fields
func removeEmptyPropertyStructs(mt *ModuleType) {
for i := 0; i < len(mt.PropertyStructs); i++ {
if len(mt.PropertyStructs[i].Properties) == 0 {
mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...)
i--
}
}
}
// Remove any property structs that are anonymous
func removeAnonymousProperties(mt *ModuleType) {
var removeAnonymousProps func(props []Property) []Property
removeAnonymousProps = func(props []Property) []Property {
newProps := make([]Property, 0, len(props))
for _, p := range props {
if p.Anonymous {
continue
}
if len(p.Properties) > 0 {
p.Properties = removeAnonymousProps(p.Properties)
}
newProps = append(newProps, p)
}
return newProps
}
for _, ps := range mt.PropertyStructs {
ps.Properties = removeAnonymousProps(ps.Properties)
}
}
// Squashes duplicates of the same property struct into single entries
func collapseDuplicatePropertyStructs(mt *ModuleType) {
var collapsed []*PropertyStruct
propertyStructLoop:
for _, from := range mt.PropertyStructs {
for _, to := range collapsed {
if from.Name == to.Name {
CollapseDuplicateProperties(&to.Properties, &from.Properties)
continue propertyStructLoop
}
}
collapsed = append(collapsed, from)
}
mt.PropertyStructs = collapsed
}
func CollapseDuplicateProperties(to, from *[]Property) {
propertyLoop:
for _, f := range *from {
for i := range *to {
t := &(*to)[i]
if f.Name == t.Name {
CollapseDuplicateProperties(&t.Properties, &f.Properties)
continue propertyLoop
}
}
*to = append(*to, f)
}
}
// Find all property structs that only contain structs, and move their children up one with
// a prefixed name
func collapseNestedPropertyStructs(mt *ModuleType) {
for _, ps := range mt.PropertyStructs {
collapseNestedProperties(&ps.Properties)
}
}
func collapseNestedProperties(p *[]Property) {
var n []Property
for _, parent := range *p {
var containsProperty bool
for j := range parent.Properties {
child := &parent.Properties[j]
if len(child.Properties) > 0 {
collapseNestedProperties(&child.Properties)
} else {
containsProperty = true
}
}
if containsProperty || len(parent.Properties) == 0 {
n = append(n, parent)
} else {
for j := range parent.Properties {
child := parent.Properties[j]
child.Name = parent.Name + "." + child.Name
n = append(n, child)
}
}
}
*p = n
}
func combineDuplicateProperties(mt *ModuleType) {
for _, ps := range mt.PropertyStructs {
combineDuplicateSubProperties(&ps.Properties)
}
}
func combineDuplicateSubProperties(p *[]Property) {
var n []Property
propertyLoop:
for _, child := range *p {
if len(child.Properties) > 0 {
combineDuplicateSubProperties(&child.Properties)
for i := range n {
s := &n[i]
if s.SameSubProperties(child) {
s.OtherNames = append(s.OtherNames, child.Name)
s.OtherTexts = append(s.OtherTexts, child.Text)
continue propertyLoop
}
}
}
n = append(n, child)
}
*p = n
}