From 23e1a7518f4b05d94c83e86d37270ad9a3194fca Mon Sep 17 00:00:00 2001 From: Paul Jolly Date: Fri, 25 Oct 2024 09:08:50 +0100 Subject: [PATCH] docs/concept: working with incomplete CUE This adds a concept guide explaining the basics of working with incomplete CUE using both the cue command and the Go API. Fixes cue-lang/docs-and-content#180. Preview-Path: /docs/concept/working-with-incomplete-cue/ Signed-off-by: Paul Jolly Change-Id: If2fdd49e42c96cd098a19464038827ad2000e951 Dispatch-Trailer: {"type":"trybot","CL":1203092,"patchset":9,"ref":"refs/changes/92/1203092/9","targetBranch":"master"} --- .../concept/working-with-incomplete-cue/en.md | 150 ++++++++++++++++ .../working-with-incomplete-cue/gen_cache.cue | 131 ++++++++++++++ .../working-with-incomplete-cue/page.cue | 3 + .../working-with-incomplete-cue/index.md | 169 ++++++++++++++++++ 4 files changed, 453 insertions(+) create mode 100644 content/docs/concept/working-with-incomplete-cue/en.md create mode 100644 content/docs/concept/working-with-incomplete-cue/gen_cache.cue create mode 100644 content/docs/concept/working-with-incomplete-cue/page.cue create mode 100644 hugo/content/en/docs/concept/working-with-incomplete-cue/index.md diff --git a/content/docs/concept/working-with-incomplete-cue/en.md b/content/docs/concept/working-with-incomplete-cue/en.md new file mode 100644 index 000000000..26f5bba7c --- /dev/null +++ b/content/docs/concept/working-with-incomplete-cue/en.md @@ -0,0 +1,150 @@ +--- +title: "Working with incomplete CUE" +toc_hide: true +authors: [myitcv, jpluscplusm] +tags: [go api] +--- + +{{{with _script_ "en" "set caches to speed up re-running"}}} +export GOMODCACHE=/caches/gomodcache +export GOCACHE=/caches/gobuild +{{{end}}} + +In general, CUE can handle references to fields that don't yet exist, or where +a value can't be calculated because there's insufficient information. +Otherwise valid CUE that contains such references or values is referred to as +*incomplete* CUE. + +Here's an example of incomplete CUE. In `example.cue`, field `A.x` doesn't +exist, which means that the value of field `B` can't be calculated given the +information in this file alone: + +{{{with upload "en" "example.cue"}}} +-- example.cue -- +package example + +A: b: 2 +B: A.x + 10 +{{{end}}} + +Because CUE permits data to be added into an evaluation +(through {{}}), +the value of field `B` might be able to be calculated in the future - but only +if the right information is introduced. +Until then, this CUE is still *valid*, but is referred to as "incomplete" +because there isn't sufficient information to perform a complete evaluation. + +Because incomplete CUE is valid CUE it *can* be evaluated ... + +{{{with script "en" "cue eval"}}} +cue eval example.cue +{{{end}}} + +... but it *can't* be exported: + +{{{with script "en" "cue export"}}} +! cue export example.cue +{{{end}}} + +A configuration that includes incomplete CUE can be made *complete* by unifying +it with the right data. This means that every field that contributes to the +configuration being emitted must be able to be calculated. + +Here's some some YAML data that will do this for our example, by "filling in +the gaps" in our incomplete CUE: + +{{{with upload "en" "data.yaml"}}} +-- data.yaml -- +A: + x: 5 +{{{end}}} + +Unifying our incomplete CUE with this data makes the configuration complete, +and allows us to export the result: + +{{{with script "en" "export example.cue data.yaml"}}} +cue export example.cue data.yaml +{{{end}}} + +### Using the Go API + +The Go API is also able to handle incomplete CUE. + +To demonstrate the Go API in action we start by initializing a Go module: + +{{{with script "en" "go mod init"}}} +#ellipsis 0 +go mod init go.example +{{{end}}} + +We place this example Go code in `main.go`. +Its comments explain what is being done at each step. + +{{{with upload "en" "main.go"}}} +-- main.go -- +package main + +import ( + "fmt" + "log" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/load" + "cuelang.org/go/encoding/yaml" +) + +func main() { + ctx := cuecontext.New() + + // Step #1: load the CUE package in the current directory. + // It contains a single file - "example.cue", as shown above. + bis := load.Instances([]string{"."}, nil) + step1 := ctx.BuildInstance(bis[0]) + fmt.Printf("step1: %v\n", step1) + + // Step #2: load the "data.yaml" file shown above. + step2File, err := yaml.Extract("data.yaml", nil) + if err != nil { + log.Fatal(err) + } + step2 := ctx.BuildFile(step2File) + fmt.Printf("step2: %v\n", step2) + + // Ensure that the result of unifying the two steps is both + // valid and concrete - and thus could be exported as data: + result := step1.Unify(step2) + if err := result.Validate(cue.Concrete(true)); err != nil { + log.Fatal(err) + } + + // Display the resulting CUE: + fmt.Printf("result: %v\n", result) +} +{{{end}}} + +We fetch the latest version of CUE, and tidy our Go module: + +{{{with script "en" "go get etc"}}} +#ellipsis 0 +go get cuelang.org/go@${CUELANG_CUE_LATEST} +#ellipsis 0 +go mod tidy +{{{end}}} + +When we run our Go code, it behaves the same as the `cue export` command above +\- except that it *also* displays the interim `step1` and `step2` values: + +{{{with script "en" "go run"}}} +go run . +{{{end}}} + +{{{with _script_ "en" "https://github.com/cue-lang/cue/issues/3496"}}} +go vet ./... +#ellipsis 0 +go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... +{{{end}}} + +## Related content + +- Tag: {{}} -- Guides exploring CUE's Go API diff --git a/content/docs/concept/working-with-incomplete-cue/gen_cache.cue b/content/docs/concept/working-with-incomplete-cue/gen_cache.cue new file mode 100644 index 000000000..62bedfc03 --- /dev/null +++ b/content/docs/concept/working-with-incomplete-cue/gen_cache.cue @@ -0,0 +1,131 @@ +package site +{ + content: { + docs: { + concept: { + "working-with-incomplete-cue": { + page: { + cache: { + upload: { + "example.cue": "lSc4mQOVt13ucRCN8PW9lvC2rWsKYxeLUDqYiH5Fyb0=" + "data.yaml": "Sm4bavm+gyoOANH83xv7b1yizv/QDycwh+P/ljI6mw8=" + "main.go": "2Vp1JchkoEcbNvo3MzItekAwLTG8qQt5mj+0kUsqtDc=" + } + multi_step: { + hash: "73LHVA359064DFLC4833GSVLSMPGITSI73FLH27N0RGUMM2UN3CG====" + scriptHash: "GDKIRP8KI7N6QV8BV02JOD5OJ9HSQ80NMNK77QJGH742TLTIFJAG====" + steps: [{ + doc: "" + cmd: "export GOMODCACHE=/caches/gomodcache" + exitCode: 0 + output: "" + }, { + doc: "" + cmd: "export GOCACHE=/caches/gobuild" + exitCode: 0 + output: "" + }, { + doc: "" + cmd: "cue eval example.cue" + exitCode: 0 + output: """ + A: { + b: 2 + } + B: A.x + 10 + + """ + }, { + doc: "" + cmd: "cue export example.cue" + exitCode: 1 + output: """ + B: undefined field: x: + ./example.cue:4:6 + + """ + }, { + doc: "" + cmd: "cue export example.cue data.yaml" + exitCode: 0 + output: """ + { + "A": { + "x": 5, + "b": 2 + }, + "B": 15 + } + + """ + }, { + doc: "#ellipsis 0" + cmd: "go mod init go.example" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "#ellipsis 0" + cmd: "go get cuelang.org/go@v0.11.0" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "#ellipsis 0" + cmd: "go mod tidy" + exitCode: 0 + output: """ + ... + + """ + }, { + doc: "" + cmd: "go run ." + exitCode: 0 + output: """ + step1: { + \tA: { + \t\tb: 2 + \t} + \tB: A.x + 10 + } + step2: { + \tA: { + \t\tx: 5 + \t} + } + result: { + \tA: { + \t\tb: 2 + \t\tx: 5 + \t} + \tB: 15 + } + + """ + }, { + doc: "" + cmd: "go vet ./..." + exitCode: 0 + output: "" + }, { + doc: "#ellipsis 0" + cmd: "go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./..." + exitCode: 0 + output: """ + ... + + """ + }] + } + } + } + } + } + } + } +} diff --git a/content/docs/concept/working-with-incomplete-cue/page.cue b/content/docs/concept/working-with-incomplete-cue/page.cue new file mode 100644 index 000000000..6e48e2125 --- /dev/null +++ b/content/docs/concept/working-with-incomplete-cue/page.cue @@ -0,0 +1,3 @@ +package site + +content: docs: concept: "working-with-incomplete-cue": page: _ diff --git a/hugo/content/en/docs/concept/working-with-incomplete-cue/index.md b/hugo/content/en/docs/concept/working-with-incomplete-cue/index.md new file mode 100644 index 000000000..2dbe173ce --- /dev/null +++ b/hugo/content/en/docs/concept/working-with-incomplete-cue/index.md @@ -0,0 +1,169 @@ +--- +title: "Working with incomplete CUE" +toc_hide: true +authors: [myitcv, jpluscplusm] +tags: [go api] +--- + +In general, CUE can handle references to fields that don't yet exist, or where +a value can't be calculated because there's insufficient information. +Otherwise valid CUE that contains such references or values is referred to as +*incomplete* CUE. + +Here's an example of incomplete CUE. In `example.cue`, field `A.x` doesn't +exist, which means that the value of field `B` can't be calculated given the +information in this file alone: + +{{< code-tabs >}} +{{< code-tab name="example.cue" language="cue" area="top-left" >}} +package example + +A: b: 2 +B: A.x + 10 +{{< /code-tab >}}{{< /code-tabs >}} + +Because CUE permits data to be added into an evaluation +(through {{}}), +the value of field `B` might be able to be calculated in the future - but only +if the right information is introduced. +Until then, this CUE is still *valid*, but is referred to as "incomplete" +because there isn't sufficient information to perform a complete evaluation. + +Because incomplete CUE is valid CUE it *can* be evaluated ... + +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIGV2YWwgZXhhbXBsZS5jdWU=" } +$ cue eval example.cue +A: { + b: 2 +} +B: A.x + 10 +``` + +... but it *can't* be exported: + +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIGV4cG9ydCBleGFtcGxlLmN1ZQ==" } +$ cue export example.cue +B: undefined field: x: + ./example.cue:4:6 +``` + +A configuration that includes incomplete CUE can be made *complete* by unifying +it with the right data. This means that every field that contributes to the +configuration being emitted must be able to be calculated. + +Here's some some YAML data that will do this for our example, by "filling in +the gaps" in our incomplete CUE: + +{{< code-tabs >}} +{{< code-tab name="data.yaml" language="yaml" area="top-left" >}} +A: + x: 5 +{{< /code-tab >}}{{< /code-tabs >}} + +Unifying our incomplete CUE with this data makes the configuration complete, +and allows us to export the result: + +```text { title="TERMINAL" type="terminal" codeToCopy="Y3VlIGV4cG9ydCBleGFtcGxlLmN1ZSBkYXRhLnlhbWw=" } +$ cue export example.cue data.yaml +{ + "A": { + "x": 5, + "b": 2 + }, + "B": 15 +} +``` + +### Using the Go API + +The Go API is also able to handle incomplete CUE. + +To demonstrate the Go API in action we start by initializing a Go module: + +```text { title="TERMINAL" type="terminal" codeToCopy="Z28gbW9kIGluaXQgZ28uZXhhbXBsZQ==" } +$ go mod init go.example +... +``` + +We place this example Go code in `main.go`. +Its comments explain what is being done at each step. + +{{< code-tabs >}} +{{< code-tab name="main.go" language="go" area="top-left" >}} +package main + +import ( + "fmt" + "log" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/load" + "cuelang.org/go/encoding/yaml" +) + +func main() { + ctx := cuecontext.New() + + // Step #1: load the CUE package in the current directory. + // It contains a single file - "example.cue", as shown above. + bis := load.Instances([]string{"."}, nil) + step1 := ctx.BuildInstance(bis[0]) + fmt.Printf("step1: %v\n", step1) + + // Step #2: load the "data.yaml" file shown above. + step2File, err := yaml.Extract("data.yaml", nil) + if err != nil { + log.Fatal(err) + } + step2 := ctx.BuildFile(step2File) + fmt.Printf("step2: %v\n", step2) + + // Ensure that the result of unifying the two steps is both + // valid and concrete - and thus could be exported as data: + result := step1.Unify(step2) + if err := result.Validate(cue.Concrete(true)); err != nil { + log.Fatal(err) + } + + // Display the resulting CUE: + fmt.Printf("result: %v\n", result) +} +{{< /code-tab >}}{{< /code-tabs >}} + +We fetch the latest version of CUE, and tidy our Go module: + +```text { title="TERMINAL" type="terminal" codeToCopy="Z28gZ2V0IGN1ZWxhbmcub3JnL2dvQHYwLjExLjAKZ28gbW9kIHRpZHk=" } +$ go get cuelang.org/go@v0.11.0 +... +$ go mod tidy +... +``` + +When we run our Go code, it behaves the same as the `cue export` command above +\- except that it *also* displays the interim `step1` and `step2` values: + +```text { title="TERMINAL" type="terminal" codeToCopy="Z28gcnVuIC4=" } +$ go run . +step1: { + A: { + b: 2 + } + B: A.x + 10 +} +step2: { + A: { + x: 5 + } +} +result: { + A: { + b: 2 + x: 5 + } + B: 15 +} +``` +## Related content + +- Tag: {{}} -- Guides exploring CUE's Go API