1// Copyright 2019 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bpdoc 16 17import ( 18 "fmt" 19 "go/ast" 20 "go/doc" 21 "html/template" 22 "reflect" 23 "strconv" 24 "strings" 25 "unicode" 26 "unicode/utf8" 27 28 "github.com/google/blueprint/proptools" 29) 30 31// 32// Utility functions for PropertyStruct and Property 33// 34 35func (ps *PropertyStruct) Clone() *PropertyStruct { 36 ret := *ps 37 ret.Properties = append([]Property(nil), ret.Properties...) 38 for i, prop := range ret.Properties { 39 ret.Properties[i] = prop.Clone() 40 } 41 42 return &ret 43} 44 45func (p *Property) Clone() Property { 46 ret := *p 47 ret.Properties = append([]Property(nil), ret.Properties...) 48 for i, prop := range ret.Properties { 49 ret.Properties[i] = prop.Clone() 50 } 51 52 return ret 53} 54 55func (p *Property) Equal(other Property) bool { 56 return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag && 57 p.Text == other.Text && p.Default == other.Default && 58 stringArrayEqual(p.OtherNames, other.OtherNames) && 59 htmlArrayEqual(p.OtherTexts, other.OtherTexts) && 60 p.SameSubProperties(other) 61} 62 63func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) { 64 setDefaults(ps.Properties, defaults) 65} 66 67func setDefaults(properties []Property, defaults reflect.Value) { 68 for i := range properties { 69 prop := &properties[i] 70 fieldName := proptools.FieldNameForProperty(prop.Name) 71 f := defaults.FieldByName(fieldName) 72 if (f == reflect.Value{}) { 73 panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type())) 74 } 75 76 if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) { 77 continue 78 } 79 80 if f.Kind() == reflect.Interface { 81 f = f.Elem() 82 } 83 84 if f.Kind() == reflect.Ptr { 85 if f.IsNil() { 86 continue 87 } 88 f = f.Elem() 89 } 90 91 if f.Kind() == reflect.Struct { 92 setDefaults(prop.Properties, f) 93 } else { 94 prop.Default = fmt.Sprintf("%v", f.Interface()) 95 } 96 } 97} 98 99func stringArrayEqual(a, b []string) bool { 100 if len(a) != len(b) { 101 return false 102 } 103 104 for i := range a { 105 if a[i] != b[i] { 106 return false 107 } 108 } 109 110 return true 111} 112 113func htmlArrayEqual(a, b []template.HTML) bool { 114 if len(a) != len(b) { 115 return false 116 } 117 118 for i := range a { 119 if a[i] != b[i] { 120 return false 121 } 122 } 123 124 return true 125} 126 127func (p *Property) SameSubProperties(other Property) bool { 128 if len(p.Properties) != len(other.Properties) { 129 return false 130 } 131 132 for i := range p.Properties { 133 if !p.Properties[i].Equal(other.Properties[i]) { 134 return false 135 } 136 } 137 138 return true 139} 140 141func (ps *PropertyStruct) GetByName(name string) *Property { 142 return getByName(name, "", &ps.Properties) 143} 144 145func (ps *PropertyStruct) Nest(nested *PropertyStruct) { 146 ps.Properties = append(ps.Properties, nested.Properties...) 147} 148 149func getByName(name string, prefix string, props *[]Property) *Property { 150 for i := range *props { 151 if prefix+(*props)[i].Name == name { 152 return &(*props)[i] 153 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") { 154 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties) 155 } 156 } 157 return nil 158} 159 160func (p *Property) Nest(nested *PropertyStruct) { 161 p.Properties = append(p.Properties, nested.Properties...) 162} 163 164func (p *Property) SetAnonymous() { 165 p.Anonymous = true 166} 167 168func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) { 169 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec) 170 ps := PropertyStruct{ 171 Name: t.Name, 172 Text: t.Doc, 173 } 174 175 structType, ok := typeSpec.Type.(*ast.StructType) 176 if !ok { 177 return nil, fmt.Errorf("type of %q is not a struct", t.Name) 178 } 179 180 var err error 181 ps.Properties, err = structProperties(structType) 182 if err != nil { 183 return nil, err 184 } 185 186 return &ps, nil 187} 188 189func structProperties(structType *ast.StructType) (props []Property, err error) { 190 for _, f := range structType.Fields.List { 191 names := f.Names 192 if names == nil { 193 // Anonymous fields have no name, use the type as the name 194 // TODO: hide the name and make the properties show up in the embedding struct 195 if t, ok := f.Type.(*ast.Ident); ok { 196 names = append(names, t) 197 } 198 } 199 for _, n := range names { 200 var name, tag, text string 201 if n != nil { 202 name = proptools.PropertyNameForField(n.Name) 203 } 204 if f.Doc != nil { 205 text = f.Doc.Text() 206 } 207 if f.Tag != nil { 208 tag, err = strconv.Unquote(f.Tag.Value) 209 if err != nil { 210 return nil, err 211 } 212 } 213 typ, innerProps, err := getType(f.Type) 214 if err != nil { 215 return nil, err 216 } 217 218 props = append(props, Property{ 219 Name: name, 220 Type: typ, 221 Tag: reflect.StructTag(tag), 222 Text: formatText(text), 223 Properties: innerProps, 224 }) 225 } 226 } 227 228 return props, nil 229} 230 231func getType(expr ast.Expr) (typ string, innerProps []Property, err error) { 232 var t ast.Expr 233 if star, ok := expr.(*ast.StarExpr); ok { 234 t = star.X 235 } else { 236 t = expr 237 } 238 switch a := t.(type) { 239 case *ast.ArrayType: 240 var elt string 241 elt, innerProps, err = getType(a.Elt) 242 if err != nil { 243 return "", nil, err 244 } 245 typ = "list of " + elt 246 case *ast.InterfaceType: 247 typ = "interface" 248 case *ast.Ident: 249 typ = a.Name 250 case *ast.StructType: 251 innerProps, err = structProperties(a) 252 if err != nil { 253 return "", nil, err 254 } 255 default: 256 typ = fmt.Sprintf("%T", expr) 257 } 258 259 return typ, innerProps, nil 260} 261 262func (ps *PropertyStruct) ExcludeByTag(key, value string) { 263 filterPropsByTag(&ps.Properties, key, value, true) 264} 265 266func (ps *PropertyStruct) IncludeByTag(key, value string) { 267 filterPropsByTag(&ps.Properties, key, value, false) 268} 269 270func filterPropsByTag(props *[]Property, key, value string, exclude bool) { 271 // Create a slice that shares the storage of props but has 0 length. Appending up to 272 // len(props) times to this slice will overwrite the original slice contents 273 filtered := (*props)[:0] 274 for _, x := range *props { 275 if hasTag(x.Tag, key, value) == !exclude { 276 filterPropsByTag(&x.Properties, key, value, exclude) 277 filtered = append(filtered, x) 278 } 279 } 280 281 *props = filtered 282} 283 284func hasTag(tag reflect.StructTag, key, value string) bool { 285 for _, entry := range strings.Split(tag.Get(key), ",") { 286 if entry == value { 287 return true 288 } 289 } 290 return false 291} 292 293func formatText(text string) template.HTML { 294 var html template.HTML 295 lines := strings.Split(text, "\n") 296 preformatted := false 297 for _, line := range lines { 298 r, _ := utf8.DecodeRuneInString(line) 299 indent := unicode.IsSpace(r) 300 if indent && !preformatted { 301 html += "<pre>\n\n" 302 preformatted = true 303 } else if !indent && line != "" && preformatted { 304 html += "</pre>\n" 305 preformatted = false 306 } 307 html += template.HTML(template.HTMLEscapeString(line)) + "\n" 308 } 309 if preformatted { 310 html += "</pre>\n" 311 } 312 return html 313} 314