Skip to content

Commit

Permalink
Support for CUE
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Henderson <[email protected]>
  • Loading branch information
hairyhenderson committed Jul 3, 2023
1 parent 65f1816 commit 8bdce36
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 2 deletions.
63 changes: 63 additions & 0 deletions data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"io"
"strings"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/format"
"github.com/Shopify/ejson"
ejsonJson "github.com/Shopify/ejson/json"
"github.com/hairyhenderson/gomplate/v4/conv"
Expand Down Expand Up @@ -442,3 +445,63 @@ func ToTOML(in interface{}) (string, error) {
}
return buf.String(), nil
}

// CUE - Unmarshal a CUE expression into the appropriate type
func CUE(in string) (interface{}, error) {
cuectx := cuecontext.New()
val := cuectx.CompileString(in)

if val.Err() != nil {
return nil, fmt.Errorf("unable to process CUE: %w", val.Err())
}

switch val.Kind() {
case cue.StructKind:
out := map[string]interface{}{}
err := val.Decode(&out)
return out, err
case cue.ListKind:
out := []interface{}{}
err := val.Decode(&out)
return out, err
case cue.BytesKind:
out := []byte{}
err := val.Decode(&out)
return out, err
case cue.StringKind:
out := ""
err := val.Decode(&out)
return out, err
case cue.IntKind:
out := 0
err := val.Decode(&out)
return out, err
case cue.NumberKind, cue.FloatKind:
out := 0.0
err := val.Decode(&out)
return out, err
case cue.BoolKind:
out := false
err := val.Decode(&out)
return out, err
case cue.NullKind:
return nil, nil
default:
return nil, fmt.Errorf("unsupported CUE type %q", val.Kind())
}
}

func ToCUE(in interface{}) (string, error) {
cuectx := cuecontext.New()
v := cuectx.Encode(in)
if v.Err() != nil {
return "", v.Err()
}

bs, err := format.Node(v.Syntax())
if err != nil {
return "", err
}

return string(bs), nil
}
117 changes: 117 additions & 0 deletions data/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,3 +659,120 @@ func TestStringifyYAMLMapMapKeys(t *testing.T) {
assert.EqualValues(t, c.want, c.input)
}
}

func TestCUE(t *testing.T) {
in := `package foo
import "regexp"
matches: regexp.FindSubmatch(#"^([^:]*):(\d+)$"#, "localhost:443")
one: 1
two: 2
// A field using quotes.
"two-and-a-half": 2.5
list: [ 1, 2, 3 ]
`

expected := map[string]interface{}{
"matches": []interface{}{
"localhost:443",
"localhost",
"443",
},
"one": 1,
"two": 2,
"two-and-a-half": 2.5,
"list": []interface{}{1, 2, 3},
}

out, err := CUE(in)
require.NoError(t, err)
assert.EqualValues(t, expected, out)

out, err = CUE(`[1,2,3]`)
require.NoError(t, err)
assert.EqualValues(t, []interface{}{1, 2, 3}, out)

out, err = CUE(`"hello world"`)
require.NoError(t, err)
assert.EqualValues(t, "hello world", out)

out, err = CUE(`true`)
require.NoError(t, err)
assert.EqualValues(t, true, out)

out, err = CUE(`'\x00\x01\x02\x03\x04'`)
require.NoError(t, err)
assert.EqualValues(t, []byte{0, 1, 2, 3, 4}, out)

out, err = CUE(`42`)
require.NoError(t, err)
assert.EqualValues(t, 42, out)

out, err = CUE(`42.0`)
require.NoError(t, err)
assert.EqualValues(t, 42.0, out)

out, err = CUE(`null`)
require.NoError(t, err)
assert.EqualValues(t, nil, out)

_, err = CUE(`>=0 & <=7 & >=3 & <=10`)
require.Error(t, err)
}

func TestToCUE(t *testing.T) {
in := map[string]interface{}{
"matches": []interface{}{
"localhost:443",
"localhost",
"443",
},
"one": 1,
"two": 2,
"two-and-a-half": 2.5,
"list": []interface{}{1, 2, 3},
}

expected := `{
"two-and-a-half": 2.5
list: [1, 2, 3]
two: 2
one: 1
matches: ["localhost:443", "localhost", "443"]
}`

out, err := ToCUE(in)
require.NoError(t, err)
assert.EqualValues(t, expected, out)

out, err = ToCUE([]interface{}{1, 2, 3})
require.NoError(t, err)
assert.EqualValues(t, `[1, 2, 3]`, out)

out, err = ToCUE("hello world")
require.NoError(t, err)
assert.EqualValues(t, `"hello world"`, out)

out, err = ToCUE(true)
require.NoError(t, err)
assert.EqualValues(t, `true`, out)

out, err = ToCUE([]byte{0, 1, 2, 3, 4})
require.NoError(t, err)
assert.EqualValues(t, `'\x00\x01\x02\x03\x04'`, out)

out, err = ToCUE(42)
require.NoError(t, err)
assert.EqualValues(t, `42`, out)

out, err = ToCUE(42.0)
require.NoError(t, err)
assert.EqualValues(t, `42.0`, out)

out, err = ToCUE(nil)
require.NoError(t, err)
assert.EqualValues(t, `null`, out)

out, err = ToCUE(struct{}{})
require.NoError(t, err)
assert.EqualValues(t, `{}`, out)
}
3 changes: 3 additions & 0 deletions data/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func init() {
regExtension(".csv", csvMimetype)
regExtension(".toml", tomlMimetype)
regExtension(".env", envMimetype)
regExtension(".cue", cueMimetype)
}

// registerReaders registers the source-reader functions
Expand Down Expand Up @@ -348,6 +349,8 @@ func parseData(mimeType, s string) (out interface{}, err error) {
out, err = dotEnv(s)
case textMimetype:
out = s
case cueMimetype:
out, err = CUE(s)
default:
return nil, fmt.Errorf("datasources of type %s not yet supported", mimeType)
}
Expand Down
1 change: 1 addition & 0 deletions data/mimetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
tomlMimetype = "application/toml"
yamlMimetype = "application/yaml"
envMimetype = "application/x-env"
cueMimetype = "application/cue"
)

// mimeTypeAliases defines a mapping for non-canonical mime types that are
Expand Down
46 changes: 46 additions & 0 deletions docs-src/content/functions/data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,27 @@ funcs:
Go
COBOL
```
- name: data.CUE
alias: cue
description: |
Converts a [CUE](https://cuelang.org/) document into an object. Any type
of CUE document is supported. This can be used to access properties of CUE
documents.
Note that the `import` statement is not yet supported, and will result in
an error (except for importing builtin packages).
pipeline: true
arguments:
- name: input
required: true
description: the CUE document to parse
examples:
- |
$ gomplate -i '{{ $t := `data: {
hello: "world"
}` -}}
Hello {{ (cue $t).data.hello }}'
Hello world
- name: data.ToJSON
alias: toJSON
description: |
Expand Down Expand Up @@ -492,3 +513,28 @@ funcs:
1,2
3,4
```
- name: data.ToCUE
alias: toCUE
description: |
Converts an object to a [CUE](https://cuelang.org/) document in canonical
format. The input object can be of any type.
This is roughly equivalent to using the `cue export --out=cue <file>`
command to convert from other formats to CUE.
pipeline: true
arguments:
- name: input
required: true
description: the object to marshal as a CUE document
examples:
- |
$ gomplate -i '{{ `{"foo":"bar"}` | data.JSON | data.ToCUE }}'
{
foo: "bar"
}
- |
$ gomplate -i '{{ toCUE "hello world" }}'
"hello world"
- |
$ gomplate -i '{{ coll.Slice 1 "two" true | data.ToCUE }}'
[1, "two", true]
12 changes: 12 additions & 0 deletions funcs/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ func CreateDataFuncs(ctx context.Context,
f["csv"] = ns.CSV
f["csvByRow"] = ns.CSVByRow
f["csvByColumn"] = ns.CSVByColumn
f["cue"] = ns.CUE
f["toJSON"] = ns.ToJSON
f["toJSONPretty"] = ns.ToJSONPretty
f["toYAML"] = ns.ToYAML
f["toTOML"] = ns.ToTOML
f["toCSV"] = ns.ToCSV
f["toCUE"] = ns.ToCUE
return f
}

Expand Down Expand Up @@ -101,11 +103,21 @@ func (f *DataFuncs) CSVByColumn(args ...string) (cols map[string][]string, err e
return data.CSVByColumn(args...)
}

// CUE -
func (f *DataFuncs) CUE(in interface{}) (interface{}, error) {
return data.CUE(conv.ToString(in))
}

// ToCSV -
func (f *DataFuncs) ToCSV(args ...interface{}) (string, error) {
return data.ToCSV(args...)
}

// ToCUE -
func (f *DataFuncs) ToCUE(in interface{}) (string, error) {
return data.ToCUE(in)
}

// ToJSON -
func (f *DataFuncs) ToJSON(in interface{}) (string, error) {
return data.ToJSON(in)
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/hairyhenderson/gomplate/v4
go 1.20

require (
cuelang.org/go v0.6.0-alpha.2
github.com/Masterminds/goutils v1.1.1
github.com/Masterminds/semver v1.5.0
github.com/Shopify/ejson v1.4.0
Expand Down Expand Up @@ -73,6 +74,7 @@ require (
github.com/aws/smithy-go v1.13.5 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cockroachdb/apd/v3 v3.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
github.com/emirpasic/gods v1.18.1 // indirect
Expand Down Expand Up @@ -109,6 +111,7 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
Expand Down
Loading

0 comments on commit 8bdce36

Please sign in to comment.