Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial basic support for CUE #1781

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -660,3 +660,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.21

require (
cuelang.org/go v0.6.0
github.com/Masterminds/goutils v1.1.1
github.com/Masterminds/semver/v3 v3.2.1
github.com/Shopify/ejson v1.4.1
Expand Down Expand Up @@ -74,6 +75,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/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
Expand Down Expand Up @@ -110,6 +112,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.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
Expand Down
Loading
Loading