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  }