github.com/kubevela/workflow@v0.6.0/pkg/cue/model/sets/utils.go (about) 1 /* 2 Copyright 2022 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package sets 18 19 import ( 20 "bytes" 21 "fmt" 22 "path/filepath" 23 "strconv" 24 "strings" 25 26 "cuelang.org/go/cue" 27 "cuelang.org/go/cue/ast" 28 "cuelang.org/go/cue/format" 29 "cuelang.org/go/cue/literal" 30 "cuelang.org/go/cue/token" 31 "github.com/pkg/errors" 32 ) 33 34 func lookUp(node ast.Node, paths ...string) (ast.Node, error) { 35 if len(paths) == 0 { 36 return peelCloseExpr(node), nil 37 } 38 key := paths[0] 39 switch x := node.(type) { 40 case *ast.File: 41 for _, decl := range x.Decls { 42 nnode := lookField(decl, key) 43 if nnode != nil { 44 return lookUp(nnode, paths[1:]...) 45 } 46 } 47 case *ast.ListLit: 48 for index, elt := range x.Elts { 49 if strconv.Itoa(index) == key { 50 return lookUp(elt, paths[1:]...) 51 } 52 } 53 case *ast.StructLit: 54 for _, elt := range x.Elts { 55 nnode := lookField(elt, key) 56 if nnode != nil { 57 return lookUp(nnode, paths[1:]...) 58 } 59 } 60 case *ast.CallExpr: 61 if it, ok := x.Fun.(*ast.Ident); ok && it.Name == "close" && len(x.Args) == 1 { 62 return lookUp(x.Args[0], paths...) 63 } 64 for index, arg := range x.Args { 65 if strconv.Itoa(index) == key { 66 return lookUp(arg, paths[1:]...) 67 } 68 } 69 } 70 return nil, notFoundErr 71 } 72 73 // LookUpAll look up all the nodes by paths 74 func LookUpAll(node ast.Node, paths ...string) []ast.Node { 75 if len(paths) == 0 { 76 return []ast.Node{node} 77 } 78 key := paths[0] 79 var nodes []ast.Node 80 switch x := node.(type) { 81 case *ast.File: 82 for _, decl := range x.Decls { 83 nnode := lookField(decl, key) 84 if nnode != nil { 85 nodes = append(nodes, LookUpAll(nnode, paths[1:]...)...) 86 } 87 } 88 89 case *ast.StructLit: 90 for _, elt := range x.Elts { 91 nnode := lookField(elt, key) 92 if nnode != nil { 93 nodes = append(nodes, LookUpAll(nnode, paths[1:]...)...) 94 } 95 } 96 case *ast.ListLit: 97 for index, elt := range x.Elts { 98 if strconv.Itoa(index) == key { 99 return LookUpAll(elt, paths[1:]...) 100 } 101 } 102 } 103 return nodes 104 } 105 106 // PreprocessBuiltinFunc preprocess builtin function in cue file. 107 func PreprocessBuiltinFunc(root ast.Node, name string, process func(values []ast.Node) (ast.Expr, error)) error { 108 var gerr error 109 ast.Walk(root, func(node ast.Node) bool { 110 switch v := node.(type) { 111 case *ast.EmbedDecl: 112 if fname, args := extractFuncName(v.Expr); fname == name && len(args) > 0 { 113 expr, err := doBuiltinFunc(root, args[0], process) 114 if err != nil { 115 gerr = err 116 return false 117 } 118 v.Expr = expr 119 } 120 case *ast.Field: 121 if fname, args := extractFuncName(v.Value); fname == name && len(args) > 0 { 122 expr, err := doBuiltinFunc(root, args[0], process) 123 if err != nil { 124 gerr = err 125 return false 126 } 127 v.Value = expr 128 } 129 } 130 return true 131 }, nil) 132 return gerr 133 } 134 135 func doBuiltinFunc(root ast.Node, pathSel ast.Expr, do func(values []ast.Node) (ast.Expr, error)) (ast.Expr, error) { 136 paths := getPaths(pathSel) 137 if len(paths) == 0 { 138 return nil, errors.New("path resolve error") 139 } 140 values := LookUpAll(root, paths...) 141 return do(values) 142 } 143 144 func extractFuncName(expr ast.Expr) (string, []ast.Expr) { 145 if call, ok := expr.(*ast.CallExpr); ok && len(call.Args) > 0 { 146 if ident, ok := call.Fun.(*ast.Ident); ok { 147 return ident.Name, call.Args 148 } 149 } 150 return "", nil 151 } 152 153 func getPaths(node ast.Expr) []string { 154 switch v := node.(type) { 155 case *ast.SelectorExpr: 156 var sel string 157 if l, ok := v.Sel.(*ast.Ident); ok { 158 sel = l.Name 159 } else { 160 sel = fmt.Sprint(v.Sel) 161 } 162 return append(getPaths(v.X), sel) 163 case *ast.Ident: 164 return []string{v.Name} 165 case *ast.BasicLit: 166 s, err := literal.Unquote(v.Value) 167 if err != nil { 168 return nil 169 } 170 return []string{s} 171 case *ast.IndexExpr: 172 return append(getPaths(v.X), getPaths(v.Index)...) 173 } 174 return nil 175 } 176 177 func peelCloseExpr(node ast.Node) ast.Node { 178 x, ok := node.(*ast.CallExpr) 179 if !ok { 180 return node 181 } 182 if it, ok := x.Fun.(*ast.Ident); ok && it.Name == "close" && len(x.Args) == 1 { 183 return x.Args[0] 184 } 185 return node 186 } 187 188 func lookField(node ast.Node, key string) ast.Node { 189 if field, ok := node.(*ast.Field); ok { 190 // Note: the trim here has side effect: "\(v)" will be trimmed to \(v), only used for comparing fields 191 if strings.Trim(LabelStr(field.Label), `"`) == strings.Trim(key, `"`) { 192 return field.Value 193 } 194 } 195 return nil 196 } 197 198 // LabelStr get the string label 199 func LabelStr(label ast.Label) string { 200 switch v := label.(type) { 201 case *ast.Ident: 202 return v.Name 203 case *ast.BasicLit: 204 return v.Value 205 } 206 return "" 207 } 208 209 // nolint:staticcheck 210 func toString(v cue.Value, opts ...func(node ast.Node) ast.Node) (string, error) { 211 syopts := []cue.Option{cue.All(), cue.ResolveReferences(true), cue.DisallowCycles(true), cue.Docs(true), cue.Attributes(true)} 212 213 var w bytes.Buffer 214 useSep := false 215 format := func(name string, n ast.Node) error { 216 if name != "" { 217 fmt.Fprintf(&w, "// %s\n", filepath.Base(name)) 218 } else if useSep { 219 fmt.Fprintf(&w, "// ---") 220 } 221 useSep = true 222 223 f, err := toFile(n) 224 if err != nil { 225 return err 226 } 227 var node ast.Node = f 228 for _, opt := range opts { 229 node = opt(node) 230 } 231 b, err := format.Node(node) 232 if err != nil { 233 return err 234 } 235 _, err = w.Write(b) 236 return err 237 } 238 239 if err := format("", v.Syntax(syopts...)); err != nil { 240 return "", err 241 } 242 instStr := w.String() 243 return instStr, nil 244 } 245 246 // ToString convert cue.Value to string 247 func ToString(v cue.Value, opts ...func(node ast.Node) ast.Node) (string, error) { 248 return toString(v, opts...) 249 } 250 251 // ToFile convert ast.Node to ast.File 252 func ToFile(n ast.Node) (*ast.File, error) { 253 return toFile(n) 254 } 255 256 func toFile(n ast.Node) (*ast.File, error) { 257 switch x := n.(type) { 258 case nil: 259 return nil, nil 260 case *ast.StructLit: 261 decls := []ast.Decl{} 262 for _, elt := range x.Elts { 263 if _, ok := elt.(*ast.Ellipsis); ok { 264 continue 265 } 266 decls = append(decls, elt) 267 } 268 return &ast.File{Decls: decls}, nil 269 case ast.Expr: 270 ast.SetRelPos(x, token.NoSpace) 271 return &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: x}}}, nil 272 case *ast.File: 273 return x, nil 274 default: 275 return nil, errors.Errorf("Unsupported node type %T", x) 276 } 277 } 278 279 // OptBytesToString convert cue bytes to string. 280 func OptBytesToString(node ast.Node) ast.Node { 281 ast.Walk(node, nil, func(node ast.Node) { 282 basic, ok := node.(*ast.BasicLit) 283 if ok { 284 if basic.Kind == token.STRING { 285 s := basic.Value 286 if strings.HasPrefix(s, "'") { 287 info, nStart, _, err := literal.ParseQuotes(s, s) 288 if err != nil { 289 return 290 } 291 if !info.IsDouble() { 292 s = s[nStart:] 293 s, err := info.Unquote(s) 294 if err == nil { 295 basic.Value = fmt.Sprintf(`"%s"`, s) 296 } 297 } 298 } 299 } 300 } 301 }) 302 return node 303 } 304 305 // OpenBaiscLit make that the basicLit can be modified. 306 // nolint:staticcheck 307 func OpenBaiscLit(val cue.Value) (*ast.File, error) { 308 f, err := ToFile(val.Syntax(cue.Docs(true), cue.ResolveReferences(true))) 309 if err != nil { 310 return nil, err 311 } 312 openBaiscLit(f) 313 return f, err 314 } 315 316 // OpenListLit make that the listLit can be modified. 317 // nolint:staticcheck 318 func OpenListLit(val cue.Value) (*ast.File, error) { 319 f, err := ToFile(val.Syntax(cue.Docs(true), cue.ResolveReferences(true))) 320 if err != nil { 321 return nil, err 322 } 323 ast.Walk(f, func(node ast.Node) bool { 324 field, ok := node.(*ast.Field) 325 if ok { 326 v := field.Value 327 switch lit := v.(type) { 328 case *ast.ListLit: 329 if len(lit.Elts) > 0 { 330 if _, ok := lit.Elts[len(lit.Elts)-1].(*ast.Ellipsis); ok { 331 break 332 } 333 } 334 newList := lit.Elts 335 newList = append(newList, &ast.Ellipsis{}) 336 field.Value = ast.NewList(newList...) 337 } 338 } 339 return true 340 }, nil) 341 return f, nil 342 } 343 344 func openBaiscLit(root ast.Node) { 345 ast.Walk(root, func(node ast.Node) bool { 346 field, ok := node.(*ast.Field) 347 if ok { 348 v := field.Value 349 switch lit := v.(type) { 350 case *ast.BasicLit: 351 field.Value = ast.NewBinExpr(token.OR, &ast.UnaryExpr{X: lit, Op: token.MUL}, ast.NewIdent("_")) 352 case *ast.ListLit: 353 field.Value = ast.NewBinExpr(token.OR, &ast.UnaryExpr{X: lit, Op: token.MUL}, ast.NewList(&ast.Ellipsis{})) 354 } 355 } 356 return true 357 }, nil) 358 } 359 360 // ListOpen enable the cue list can add elements. 361 func ListOpen(expr ast.Node) ast.Node { 362 listOpen(expr) 363 return expr 364 } 365 366 func listOpen(expr ast.Node) { 367 switch v := expr.(type) { 368 case *ast.File: 369 for _, decl := range v.Decls { 370 listOpen(decl) 371 } 372 case *ast.Field: 373 listOpen(v.Value) 374 case *ast.StructLit: 375 for _, elt := range v.Elts { 376 listOpen(elt) 377 } 378 case *ast.BinaryExpr: 379 listOpen(v.X) 380 listOpen(v.Y) 381 case *ast.EmbedDecl: 382 listOpen(v.Expr) 383 case *ast.Comprehension: 384 listOpen(v.Value) 385 case *ast.ListLit: 386 for _, elt := range v.Elts { 387 listOpen(elt) 388 } 389 if len(v.Elts) > 0 { 390 if _, ok := v.Elts[len(v.Elts)-1].(*ast.Ellipsis); !ok { 391 v.Elts = append(v.Elts, &ast.Ellipsis{}) 392 } 393 } 394 } 395 } 396 397 func removeTmpVar(expr ast.Node) ast.Node { 398 switch v := expr.(type) { 399 case *ast.File: 400 for _, decl := range v.Decls { 401 removeTmpVar(decl) 402 } 403 case *ast.Field: 404 removeTmpVar(v.Value) 405 case *ast.StructLit: 406 var elts []ast.Decl 407 for _, elt := range v.Elts { 408 if field, isField := elt.(*ast.Field); isField { 409 if ident, isIdent := field.Label.(*ast.Ident); isIdent && strings.HasPrefix(ident.Name, "_") { 410 continue 411 } 412 } 413 removeTmpVar(elt) 414 elts = append(elts, elt) 415 } 416 v.Elts = elts 417 case *ast.BinaryExpr: 418 removeTmpVar(v.X) 419 removeTmpVar(v.Y) 420 case *ast.EmbedDecl: 421 removeTmpVar(v.Expr) 422 case *ast.Comprehension: 423 removeTmpVar(v.Value) 424 case *ast.ListLit: 425 for _, elt := range v.Elts { 426 removeTmpVar(elt) 427 } 428 } 429 return expr 430 }