github.com/octohelm/cuemod@v0.9.4/pkg/cuex/bundle.go (about)

     1  package cuex
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	cueast "cuelang.org/go/cue/ast"
    11  	"cuelang.org/go/cue/build"
    12  	"cuelang.org/go/cue/format"
    13  	"github.com/octohelm/cuemod/pkg/cuemod/builtin"
    14  )
    15  
    16  // BundleToRaw bundle instance to single cue file
    17  func BundleToRaw(inst *build.Instance) ([]byte, error) {
    18  	sf := &bundler{
    19  		stds:    map[string]*cueast.ImportSpec{},
    20  		imports: map[string]*cueast.Field{},
    21  	}
    22  
    23  	f, err := sf.Export(inst)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	return format.Node(f, format.Simplify())
    29  }
    30  
    31  type bundler struct {
    32  	stds map[string]*cueast.ImportSpec
    33  
    34  	imports      map[string]*cueast.Field
    35  	importOrders []string
    36  }
    37  
    38  func (sf *bundler) importPkg(importPath string, f *cueast.Field) {
    39  	if _, ok := sf.imports[importPath]; !ok {
    40  		sf.imports[importPath] = f
    41  
    42  		cueast.AddComment(f, &cueast.CommentGroup{
    43  			Doc: true,
    44  			List: []*cueast.Comment{{
    45  				Text: "// " + importPath,
    46  			}},
    47  		})
    48  
    49  		sf.importOrders = append([]string{importPath}, sf.importOrders...)
    50  	}
    51  }
    52  
    53  func (sf *bundler) importDecl() *cueast.ImportDecl {
    54  	stds := make([]string, 0)
    55  
    56  	for i := range sf.stds {
    57  		stds = append(stds, i)
    58  	}
    59  
    60  	if len(stds) == 0 {
    61  		return nil
    62  	}
    63  
    64  	sort.Strings(stds)
    65  
    66  	d := &cueast.ImportDecl{}
    67  
    68  	d.Specs = make([]*cueast.ImportSpec, len(stds))
    69  
    70  	for i, importPath := range stds {
    71  		d.Specs[i] = sf.stds[importPath]
    72  	}
    73  
    74  	return d
    75  }
    76  
    77  func (sf *bundler) importAliases() []cueast.Decl {
    78  	decls := make([]cueast.Decl, 0)
    79  
    80  	for _, importPath := range sf.importOrders {
    81  		decls = append(decls, sf.imports[importPath])
    82  	}
    83  
    84  	return decls
    85  }
    86  
    87  func (sf *bundler) Export(inst *build.Instance) (*cueast.File, error) {
    88  	f, err := sf.Walk(inst)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	decls := f.Decls
    94  
    95  	f.Decls = make([]cueast.Decl, 0)
    96  
    97  	if importDecl := sf.importDecl(); importDecl != nil {
    98  		f.Decls = append(f.Decls, importDecl)
    99  	}
   100  
   101  	f.Decls = append(f.Decls, &cueast.StructLit{
   102  		Elts: decls,
   103  	})
   104  
   105  	f.Decls = append(f.Decls, sf.importAliases()...)
   106  
   107  	return f, nil
   108  }
   109  
   110  func (sf *bundler) Walk(inst *build.Instance) (*cueast.File, error) {
   111  	f := &cueast.File{
   112  		Filename: fmt.Sprintf("%s/%s.cue", inst.ImportPath, inst.PkgName),
   113  	}
   114  
   115  	stmts := make([]cueast.Decl, 0)
   116  	importAliases := map[string]string{}
   117  
   118  	for _, file := range inst.Files {
   119  		for _, d := range file.Decls {
   120  			switch decl := d.(type) {
   121  			case *cueast.Package:
   122  				continue
   123  			case *cueast.ImportDecl:
   124  				for i := range decl.Specs {
   125  					spec := decl.Specs[i]
   126  
   127  					importPath, _ := strconv.Unquote(spec.Path.Value)
   128  
   129  					if builtin.IsBuiltIn(importPath) {
   130  						id := spec.Name
   131  						if spec.Name != nil {
   132  							id = cueast.NewIdent(spec.Name.Name)
   133  						}
   134  						sf.stds[importPath] = cueast.NewImport(id, importPath)
   135  					} else {
   136  						for _, dep := range inst.Imports {
   137  							if dep.ImportPath == importPath {
   138  								f, err := sf.Walk(dep)
   139  								if err != nil {
   140  									return nil, err
   141  								}
   142  
   143  								id := cueast.NewIdent(toSafeID(importPath))
   144  
   145  								sf.importPkg(importPath, &cueast.Field{
   146  									Label: id,
   147  									Value: &cueast.StructLit{
   148  										Elts: f.Decls,
   149  									},
   150  								})
   151  
   152  								n := cueast.NewIdent(dep.PkgName)
   153  
   154  								if p := strings.Split(importPath, ":"); len(p) == 2 {
   155  									n = cueast.NewIdent(p[1])
   156  								}
   157  
   158  								if spec.Name != nil {
   159  									n = cueast.NewIdent(spec.Name.Name)
   160  								}
   161  
   162  								importAliases[n.Name] = id.Name
   163  							}
   164  						}
   165  					}
   166  				}
   167  
   168  			default:
   169  				stmts = append(stmts, decl)
   170  			}
   171  		}
   172  	}
   173  
   174  	for _, stmt := range stmts {
   175  		cueast.Walk(
   176  			stmt,
   177  			func(node cueast.Node) bool {
   178  				if id, ok := node.(*cueast.Ident); ok && id.Node != nil {
   179  					if _, ok := id.Node.(*cueast.ImportSpec); ok {
   180  						for n, uniqPkgName := range importAliases {
   181  							if n == id.Name {
   182  								id.Name = uniqPkgName
   183  							}
   184  						}
   185  					}
   186  				}
   187  				return true
   188  			},
   189  			nil,
   190  		)
   191  
   192  		f.Decls = append(f.Decls, stmt)
   193  	}
   194  
   195  	return f, nil
   196  }
   197  
   198  var re = regexp.MustCompile(`[^0-9A-Za-z_]`)
   199  
   200  func toSafeID(importPath string) string {
   201  	return "_" + re.ReplaceAllString(importPath, "_")
   202  }