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 = nestUnique(ps.Properties, nested.Properties) 147} 148 149// Adds a target element to src if it does not exist in src 150func nestUnique(src []Property, target []Property) []Property { 151 var ret []Property 152 ret = append(ret, src...) 153 for _, elem := range target { 154 isUnique := true 155 for _, retElement := range ret { 156 if elem.Equal(retElement) { 157 isUnique = false 158 break 159 } 160 } 161 if isUnique { 162 ret = append(ret, elem) 163 } 164 } 165 return ret 166} 167 168func getByName(name string, prefix string, props *[]Property) *Property { 169 for i := range *props { 170 if prefix+(*props)[i].Name == name { 171 return &(*props)[i] 172 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") { 173 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties) 174 } 175 } 176 return nil 177} 178 179func (p *Property) Nest(nested *PropertyStruct) { 180 p.Properties = nestUnique(p.Properties, nested.Properties) 181} 182 183func (p *Property) SetAnonymous() { 184 p.Anonymous = true 185} 186 187func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) { 188 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec) 189 ps := PropertyStruct{ 190 Name: t.Name, 191 Text: t.Doc, 192 } 193 194 structType, ok := typeSpec.Type.(*ast.StructType) 195 if !ok { 196 return nil, fmt.Errorf("type of %q is not a struct", t.Name) 197 } 198 199 var err error 200 ps.Properties, err = structProperties(structType) 201 if err != nil { 202 return nil, err 203 } 204 205 return &ps, nil 206} 207 208func structProperties(structType *ast.StructType) (props []Property, err error) { 209 for _, f := range structType.Fields.List { 210 names := f.Names 211 if names == nil { 212 // Anonymous fields have no name, use the type as the name 213 // TODO: hide the name and make the properties show up in the embedding struct 214 if t, ok := f.Type.(*ast.Ident); ok { 215 names = append(names, t) 216 } 217 } 218 for _, n := range names { 219 var name, tag, text string 220 if n != nil { 221 name = proptools.PropertyNameForField(n.Name) 222 } 223 if f.Doc != nil { 224 text = f.Doc.Text() 225 } 226 if f.Tag != nil { 227 tag, err = strconv.Unquote(f.Tag.Value) 228 if err != nil { 229 return nil, err 230 } 231 } 232 typ, innerProps, err := getType(f.Type) 233 if err != nil { 234 return nil, err 235 } 236 237 props = append(props, Property{ 238 Name: name, 239 Type: typ, 240 Tag: reflect.StructTag(tag), 241 Text: formatText(text), 242 Properties: innerProps, 243 }) 244 } 245 } 246 247 return props, nil 248} 249 250func getType(expr ast.Expr) (typ string, innerProps []Property, err error) { 251 var t ast.Expr 252 if star, ok := expr.(*ast.StarExpr); ok { 253 t = star.X 254 } else { 255 t = expr 256 } 257 switch a := t.(type) { 258 case *ast.ArrayType: 259 var elt string 260 elt, innerProps, err = getType(a.Elt) 261 if err != nil { 262 return "", nil, err 263 } 264 typ = "list of " + elt 265 case *ast.InterfaceType: 266 typ = "interface" 267 case *ast.Ident: 268 typ = a.Name 269 case *ast.StructType: 270 innerProps, err = structProperties(a) 271 if err != nil { 272 return "", nil, err 273 } 274 default: 275 typ = fmt.Sprintf("%T", expr) 276 } 277 278 return typ, innerProps, nil 279} 280 281func (ps *PropertyStruct) ExcludeByTag(key, value string) { 282 filterPropsByTag(&ps.Properties, key, value, true) 283} 284 285func (ps *PropertyStruct) IncludeByTag(key, value string) { 286 filterPropsByTag(&ps.Properties, key, value, false) 287} 288 289func filterPropsByTag(props *[]Property, key, value string, exclude bool) { 290 // Create a slice that shares the storage of props but has 0 length. Appending up to 291 // len(props) times to this slice will overwrite the original slice contents 292 filtered := (*props)[:0] 293 for _, x := range *props { 294 if hasTag(x.Tag, key, value) == !exclude { 295 filterPropsByTag(&x.Properties, key, value, exclude) 296 filtered = append(filtered, x) 297 } 298 } 299 300 *props = filtered 301} 302 303func hasTag(tag reflect.StructTag, key, value string) bool { 304 for _, entry := range strings.Split(tag.Get(key), ",") { 305 if entry == value { 306 return true 307 } 308 } 309 return false 310} 311 312func formatText(text string) template.HTML { 313 var html template.HTML 314 lines := strings.Split(text, "\n") 315 preformatted := false 316 for _, line := range lines { 317 r, _ := utf8.DecodeRuneInString(line) 318 indent := unicode.IsSpace(r) 319 if indent && !preformatted { 320 html += "<pre>\n\n" 321 preformatted = true 322 } else if !indent && line != "" && preformatted { 323 html += "</pre>\n" 324 preformatted = false 325 } 326 html += template.HTML(template.HTMLEscapeString(line)) + "\n" 327 } 328 if preformatted { 329 html += "</pre>\n" 330 } 331 return html 332} 333