-
Notifications
You must be signed in to change notification settings - Fork 3
/
cueutils.go
137 lines (120 loc) · 3.71 KB
/
cueutils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// This file is part of cueutils.
//
// Copyright (C) 2023 David Gamba Rios
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*
Package cueutils provides helpers to work with Cue
*/
package cueutils
import (
"fmt"
"io"
"log"
"path/filepath"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
cueErrors "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/interpreter/embed"
"cuelang.org/go/cue/load"
"cuelang.org/go/encoding/gocode/gocodec"
)
var Logger = log.New(io.Discard, "", log.LstdFlags)
type CueConfigFile struct {
Data io.Reader
Name string
}
func NewValue() *cue.Value {
return &cue.Value{}
}
// Given a set of cue files, it will aggregate them into a single cue config and then Unmarshal it unto the given data structure.
// If dir == "" it will default to the current directory.
// packageName can be set to _ to load files without a package.
// Because CUE doesn't support hidden files, hidden files need to be passed as configs.
// value is a pointer receiver to a cue.Value and can be used on the caller side to print the cue values.
func Unmarshal(configs []CueConfigFile, dir, packageName string, value *cue.Value, target any) error {
embedding := cuecontext.Interpreter(embed.New())
ctxOpts := []cuecontext.Option{embedding}
c := cuecontext.New(ctxOpts...)
packagePaths := []string{"."}
insts := []*build.Instance{}
var err error
dirAbs, err := filepath.Abs(dir)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
Logger.Printf("dir abs: %s\n", dirAbs)
overlay := map[string]load.Source{}
for i, cf := range configs {
Logger.Printf("config: n: %d, name: %s\n", i, cf.Name)
d, err := io.ReadAll(cf.Data)
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
abs, err := filepath.Abs(cf.Name)
if err != nil {
return fmt.Errorf("failed to get absolute path: %w", err)
}
fdir := filepath.Dir(abs)
Logger.Printf("abs: %s, dir: %s\n", abs, fdir)
overlayPath := filepath.Join(dirAbs, filepath.Base(cf.Name))
overlay[overlayPath] = load.FromBytes(d)
Logger.Printf("overlay: %s\n", overlayPath)
if strings.HasPrefix(filepath.Base(cf.Name), ".") {
packagePaths = append(packagePaths, overlayPath)
}
}
if dir == "" {
dir = dirAbs
}
// Load the CUE package in the dir directory
lc := &load.Config{
Package: packageName,
ModuleRoot: ".",
AcceptLegacyModules: true,
Dir: dir,
Overlay: overlay,
}
Logger.Printf("dir: %s\nModuleRoot: %s\npackagePaths: %v\n", dir, lc.ModuleRoot, packagePaths)
ii := load.Instances(packagePaths, lc)
logInstancesFiles(dir, ii)
insts = append(insts, ii...)
logInstancesFiles("building", insts)
vv, err := c.BuildInstances(insts)
if err != nil {
return fmt.Errorf("failed to build instances: %w", err)
}
for _, v := range vv {
*value = (*value).Unify(v)
}
if value.Err() != nil {
return fmt.Errorf("failed to compile: %s", cueErrors.Details(value.Err(), nil))
}
err = value.Validate(
cue.Final(),
cue.Concrete(true),
cue.Definitions(true),
cue.Hidden(true),
cue.Optional(true),
)
if err != nil {
return fmt.Errorf("failed config validation: %v", cueErrors.Details(err, nil))
}
g := gocodec.New(c, nil)
err = g.Encode(*value, &target)
if err != nil {
return fmt.Errorf("failed to encode cue values: %w", err)
}
return nil
}
func logInstancesFiles(kind string, insts []*build.Instance) {
for _, inst := range insts {
for i, f := range inst.BuildFiles {
Logger.Printf("%s: , n: %d, name: %s, file %s\n", kind, i, inst.ID(), f.Filename)
}
}
}