...

Source file src/cuelang.org/go/cue/marshal.go

Documentation: cuelang.org/go/cue

     1  // Copyright 2019 CUE Authors
     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  
    15  package cue
    16  
    17  import (
    18  	"bytes"
    19  	"compress/gzip"
    20  	"encoding/gob"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"cuelang.org/go/cue/ast"
    25  	"cuelang.org/go/cue/ast/astutil"
    26  	"cuelang.org/go/cue/build"
    27  	"cuelang.org/go/cue/errors"
    28  	"cuelang.org/go/cue/format"
    29  	"cuelang.org/go/cue/token"
    30  	"cuelang.org/go/internal"
    31  	"cuelang.org/go/internal/core/export"
    32  )
    33  
    34  // root.
    35  type instanceData struct {
    36  	Root  bool
    37  	Path  string
    38  	Files []fileData
    39  }
    40  
    41  type fileData struct {
    42  	Name string
    43  	Data []byte
    44  }
    45  
    46  const version = 1
    47  
    48  type unmarshaller struct {
    49  	ctxt    *build.Context
    50  	imports map[string]*instanceData
    51  }
    52  
    53  func (b *unmarshaller) load(pos token.Pos, path string) *build.Instance {
    54  	bi := b.imports[path]
    55  	if bi == nil {
    56  		return nil
    57  	}
    58  	return b.build(bi)
    59  }
    60  
    61  func (b *unmarshaller) build(bi *instanceData) *build.Instance {
    62  	p := b.ctxt.NewInstance(bi.Path, b.load)
    63  	p.ImportPath = bi.Path
    64  	for _, f := range bi.Files {
    65  		_ = p.AddFile(f.Name, f.Data)
    66  	}
    67  	p.Complete()
    68  	return p
    69  }
    70  
    71  func compileInstances(r *Runtime, data []*instanceData) (instances []*Instance, err error) {
    72  	b := unmarshaller{
    73  		ctxt:    build.NewContext(),
    74  		imports: map[string]*instanceData{},
    75  	}
    76  	for _, i := range data {
    77  		if i.Path == "" {
    78  			if !i.Root {
    79  				return nil, errors.Newf(token.NoPos,
    80  					"data contains non-root package without import path")
    81  			}
    82  			continue
    83  		}
    84  		b.imports[i.Path] = i
    85  	}
    86  
    87  	builds := []*build.Instance{}
    88  	for _, i := range data {
    89  		if !i.Root {
    90  			continue
    91  		}
    92  		builds = append(builds, b.build(i))
    93  	}
    94  
    95  	return r.build(builds)
    96  }
    97  
    98  // Unmarshal returns a slice of instances from bytes generated by
    99  // Runtime.Marshal.
   100  func (r *Runtime) Unmarshal(b []byte) ([]*Instance, error) {
   101  	if len(b) == 0 {
   102  		return nil, errors.Newf(token.NoPos, "unmarshal failed: empty buffer")
   103  	}
   104  
   105  	switch b[0] {
   106  	case version:
   107  	default:
   108  		return nil, errors.Newf(token.NoPos,
   109  			"unmarshal failed: unsupported version %d, regenerate data", b[0])
   110  	}
   111  
   112  	reader, err := gzip.NewReader(bytes.NewReader(b[1:]))
   113  	if err != nil {
   114  		return nil, errors.Newf(token.NoPos, "unmarshal failed: %v", err)
   115  	}
   116  
   117  	data := []*instanceData{}
   118  	err = gob.NewDecoder(reader).Decode(&data)
   119  	if err != nil {
   120  		return nil, errors.Newf(token.NoPos, "unmarshal failed: %v", err)
   121  	}
   122  
   123  	return compileInstances(r, data)
   124  }
   125  
   126  // Marshal creates bytes from a group of instances. Imported instances will
   127  // be included in the emission.
   128  //
   129  // The stored instances are functionally the same, but preserving of file
   130  // information is only done on a best-effort basis.
   131  func (r *Runtime) Marshal(values ...InstanceOrValue) (b []byte, err error) {
   132  	staged := []instanceData{}
   133  	done := map[string]int{}
   134  
   135  	var errs errors.Error
   136  
   137  	var stageInstance func(i Value) (pos int)
   138  	stageInstance = func(i Value) (pos int) {
   139  		inst := i.BuildInstance()
   140  		if p, ok := done[inst.ImportPath]; ok {
   141  			return p
   142  		}
   143  		// TODO: support exporting instance
   144  		file, _ := export.Def(r.runtime(), inst.ID(), i.instance().root)
   145  		imports := []string{}
   146  		file.VisitImports(func(i *ast.ImportDecl) {
   147  			for _, spec := range i.Specs {
   148  				info, _ := astutil.ParseImportSpec(spec)
   149  				imports = append(imports, info.ID)
   150  			}
   151  		})
   152  
   153  		if inst.PkgName != "" {
   154  			pi := internal.GetPackageInfo(file)
   155  			if pi.Package == nil {
   156  				pkg := &ast.Package{Name: ast.NewIdent(inst.PkgName)}
   157  				file.Decls = append([]ast.Decl{pkg}, file.Decls...)
   158  			} else if pi.Name != inst.PkgName {
   159  				// pi is guaranteed to be generated by Def, so it is "safe" to modify.
   160  				pi.Package.Name = ast.NewIdent(inst.PkgName)
   161  			}
   162  		}
   163  
   164  		b, err := format.Node(file)
   165  		errs = errors.Append(errs, errors.Promote(err, "marshal"))
   166  
   167  		filename := "unmarshal"
   168  		if len(inst.Files) == 1 {
   169  			filename = inst.Files[0].Filename
   170  
   171  			dir := inst.Dir
   172  			if inst.Root != "" {
   173  				dir = inst.Root
   174  			}
   175  			if dir != "" {
   176  				filename = filepath.FromSlash(filename)
   177  				filename, _ = filepath.Rel(dir, filename)
   178  				filename = filepath.ToSlash(filename)
   179  			}
   180  		}
   181  		// TODO: this should probably be changed upstream, but as the path
   182  		// is for reference purposes only, this is safe.
   183  		importPath := filepath.ToSlash(i.instance().ImportPath)
   184  
   185  		staged = append(staged, instanceData{
   186  			Path:  importPath,
   187  			Files: []fileData{{filename, b}},
   188  		})
   189  
   190  		p := len(staged) - 1
   191  
   192  		for _, imp := range imports {
   193  			i := getImportFromPath(r.runtime(), imp)
   194  			if i == nil || !strings.Contains(imp, ".") {
   195  				continue // a builtin package.
   196  			}
   197  			stageInstance(i.Value())
   198  		}
   199  
   200  		return p
   201  	}
   202  
   203  	for _, val := range values {
   204  		staged[stageInstance(val.Value())].Root = true
   205  	}
   206  
   207  	buf := &bytes.Buffer{}
   208  	buf.WriteByte(version)
   209  
   210  	zw := gzip.NewWriter(buf)
   211  	if err := gob.NewEncoder(zw).Encode(staged); err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	if err := zw.Close(); err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	return buf.Bytes(), nil
   220  
   221  }
   222  

View as plain text