2019-02-07 01:20:17 +01:00
|
|
|
// Copyright 2019 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package bpdoc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/doc"
|
|
|
|
"html/template"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/google/blueprint/proptools"
|
|
|
|
)
|
|
|
|
|
|
|
|
//
|
|
|
|
// Utility functions for PropertyStruct and Property
|
|
|
|
//
|
|
|
|
|
|
|
|
func (ps *PropertyStruct) Clone() *PropertyStruct {
|
|
|
|
ret := *ps
|
|
|
|
ret.Properties = append([]Property(nil), ret.Properties...)
|
|
|
|
for i, prop := range ret.Properties {
|
|
|
|
ret.Properties[i] = prop.Clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Property) Clone() Property {
|
|
|
|
ret := *p
|
|
|
|
ret.Properties = append([]Property(nil), ret.Properties...)
|
|
|
|
for i, prop := range ret.Properties {
|
|
|
|
ret.Properties[i] = prop.Clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Property) Equal(other Property) bool {
|
|
|
|
return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag &&
|
|
|
|
p.Text == other.Text && p.Default == other.Default &&
|
|
|
|
stringArrayEqual(p.OtherNames, other.OtherNames) &&
|
|
|
|
htmlArrayEqual(p.OtherTexts, other.OtherTexts) &&
|
|
|
|
p.SameSubProperties(other)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) {
|
|
|
|
setDefaults(ps.Properties, defaults)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setDefaults(properties []Property, defaults reflect.Value) {
|
|
|
|
for i := range properties {
|
|
|
|
prop := &properties[i]
|
|
|
|
fieldName := proptools.FieldNameForProperty(prop.Name)
|
|
|
|
f := defaults.FieldByName(fieldName)
|
|
|
|
if (f == reflect.Value{}) {
|
|
|
|
panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type()))
|
|
|
|
}
|
|
|
|
|
|
|
|
if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Kind() == reflect.Interface {
|
|
|
|
f = f.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Kind() == reflect.Ptr {
|
|
|
|
if f.IsNil() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
f = f.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Kind() == reflect.Struct {
|
|
|
|
setDefaults(prop.Properties, f)
|
|
|
|
} else {
|
|
|
|
prop.Default = fmt.Sprintf("%v", f.Interface())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringArrayEqual(a, b []string) bool {
|
|
|
|
if len(a) != len(b) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range a {
|
|
|
|
if a[i] != b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func htmlArrayEqual(a, b []template.HTML) bool {
|
|
|
|
if len(a) != len(b) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range a {
|
|
|
|
if a[i] != b[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Property) SameSubProperties(other Property) bool {
|
|
|
|
if len(p.Properties) != len(other.Properties) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range p.Properties {
|
|
|
|
if !p.Properties[i].Equal(other.Properties[i]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ps *PropertyStruct) GetByName(name string) *Property {
|
|
|
|
return getByName(name, "", &ps.Properties)
|
|
|
|
}
|
|
|
|
|
2020-09-25 21:49:21 +02:00
|
|
|
func (ps *PropertyStruct) Nest(nested *PropertyStruct) {
|
2021-08-31 02:07:30 +02:00
|
|
|
ps.Properties = nestUnique(ps.Properties, nested.Properties)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adds a target element to src if it does not exist in src
|
|
|
|
func nestUnique(src []Property, target []Property) []Property {
|
|
|
|
var ret []Property
|
|
|
|
ret = append(ret, src...)
|
|
|
|
for _, elem := range target {
|
|
|
|
isUnique := true
|
|
|
|
for _, retElement := range ret {
|
|
|
|
if elem.Equal(retElement) {
|
|
|
|
isUnique = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if isUnique {
|
|
|
|
ret = append(ret, elem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
2020-09-25 21:49:21 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 01:20:17 +01:00
|
|
|
func getByName(name string, prefix string, props *[]Property) *Property {
|
|
|
|
for i := range *props {
|
|
|
|
if prefix+(*props)[i].Name == name {
|
|
|
|
return &(*props)[i]
|
|
|
|
} else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") {
|
|
|
|
return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Property) Nest(nested *PropertyStruct) {
|
2021-08-31 02:07:30 +02:00
|
|
|
p.Properties = nestUnique(p.Properties, nested.Properties)
|
2019-02-07 01:20:17 +01:00
|
|
|
}
|
|
|
|
|
2020-09-25 21:49:21 +02:00
|
|
|
func (p *Property) SetAnonymous() {
|
|
|
|
p.Anonymous = true
|
|
|
|
}
|
|
|
|
|
2019-02-07 01:20:17 +01:00
|
|
|
func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
|
|
|
|
typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
|
|
|
|
ps := PropertyStruct{
|
|
|
|
Name: t.Name,
|
|
|
|
Text: t.Doc,
|
|
|
|
}
|
|
|
|
|
|
|
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("type of %q is not a struct", t.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
ps.Properties, err = structProperties(structType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ps, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func structProperties(structType *ast.StructType) (props []Property, err error) {
|
|
|
|
for _, f := range structType.Fields.List {
|
|
|
|
names := f.Names
|
|
|
|
if names == nil {
|
|
|
|
// Anonymous fields have no name, use the type as the name
|
|
|
|
// TODO: hide the name and make the properties show up in the embedding struct
|
|
|
|
if t, ok := f.Type.(*ast.Ident); ok {
|
|
|
|
names = append(names, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, n := range names {
|
2020-11-05 02:20:11 +01:00
|
|
|
var name, tag, text string
|
2019-02-07 01:20:17 +01:00
|
|
|
if n != nil {
|
|
|
|
name = proptools.PropertyNameForField(n.Name)
|
|
|
|
}
|
|
|
|
if f.Doc != nil {
|
|
|
|
text = f.Doc.Text()
|
|
|
|
}
|
|
|
|
if f.Tag != nil {
|
|
|
|
tag, err = strconv.Unquote(f.Tag.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2020-11-05 02:20:11 +01:00
|
|
|
typ, innerProps, err := getType(f.Type)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-02-07 01:20:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
props = append(props, Property{
|
|
|
|
Name: name,
|
|
|
|
Type: typ,
|
|
|
|
Tag: reflect.StructTag(tag),
|
2019-03-11 18:54:36 +01:00
|
|
|
Text: formatText(text),
|
2019-02-07 01:20:17 +01:00
|
|
|
Properties: innerProps,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return props, nil
|
|
|
|
}
|
|
|
|
|
2020-11-05 02:20:11 +01:00
|
|
|
func getType(expr ast.Expr) (typ string, innerProps []Property, err error) {
|
|
|
|
var t ast.Expr
|
|
|
|
if star, ok := expr.(*ast.StarExpr); ok {
|
|
|
|
t = star.X
|
|
|
|
} else {
|
|
|
|
t = expr
|
|
|
|
}
|
|
|
|
switch a := t.(type) {
|
|
|
|
case *ast.ArrayType:
|
|
|
|
var elt string
|
|
|
|
elt, innerProps, err = getType(a.Elt)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
typ = "list of " + elt
|
|
|
|
case *ast.InterfaceType:
|
|
|
|
typ = "interface"
|
|
|
|
case *ast.Ident:
|
|
|
|
typ = a.Name
|
|
|
|
case *ast.StructType:
|
|
|
|
innerProps, err = structProperties(a)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
typ = fmt.Sprintf("%T", expr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return typ, innerProps, nil
|
|
|
|
}
|
|
|
|
|
2019-02-07 01:20:17 +01:00
|
|
|
func (ps *PropertyStruct) ExcludeByTag(key, value string) {
|
|
|
|
filterPropsByTag(&ps.Properties, key, value, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ps *PropertyStruct) IncludeByTag(key, value string) {
|
|
|
|
filterPropsByTag(&ps.Properties, key, value, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func filterPropsByTag(props *[]Property, key, value string, exclude bool) {
|
|
|
|
// Create a slice that shares the storage of props but has 0 length. Appending up to
|
|
|
|
// len(props) times to this slice will overwrite the original slice contents
|
|
|
|
filtered := (*props)[:0]
|
|
|
|
for _, x := range *props {
|
2019-05-28 22:16:20 +02:00
|
|
|
if hasTag(x.Tag, key, value) == !exclude {
|
2020-07-28 18:30:46 +02:00
|
|
|
filterPropsByTag(&x.Properties, key, value, exclude)
|
2019-05-28 22:16:20 +02:00
|
|
|
filtered = append(filtered, x)
|
2019-02-07 01:20:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*props = filtered
|
|
|
|
}
|
2019-03-11 18:54:36 +01:00
|
|
|
|
2019-05-28 22:16:20 +02:00
|
|
|
func hasTag(tag reflect.StructTag, key, value string) bool {
|
|
|
|
for _, entry := range strings.Split(tag.Get(key), ",") {
|
|
|
|
if entry == value {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-03-11 18:54:36 +01:00
|
|
|
func formatText(text string) template.HTML {
|
|
|
|
var html template.HTML
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
preformatted := false
|
|
|
|
for _, line := range lines {
|
|
|
|
r, _ := utf8.DecodeRuneInString(line)
|
|
|
|
indent := unicode.IsSpace(r)
|
|
|
|
if indent && !preformatted {
|
|
|
|
html += "<pre>\n\n"
|
|
|
|
preformatted = true
|
|
|
|
} else if !indent && line != "" && preformatted {
|
|
|
|
html += "</pre>\n"
|
|
|
|
preformatted = false
|
|
|
|
}
|
|
|
|
html += template.HTML(template.HTMLEscapeString(line)) + "\n"
|
|
|
|
}
|
|
|
|
if preformatted {
|
|
|
|
html += "</pre>\n"
|
|
|
|
}
|
|
|
|
return html
|
|
|
|
}
|