cuelang.org/go@v0.10.1/cue/marshal.go (about) 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 }