1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
99
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
127
128
129
130
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
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
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
182
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
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