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