pkg/tool/os: add tasks for environment variables
These are implemented as tasks as they are state
altering and may yield different results on repeated
calls.
Also:
- Remove usage of package errors for pkgs.
- Updated go-cmp
- Fix bug in dependency analysis where default
values could cause a dependency to be concrete
prematurely.
Issue #159
Change-Id: I517eb6892cbeff538c806a822510ffce5dcb31b0
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4461
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index 39684db..af08fd4 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -37,6 +37,7 @@
_ "cuelang.org/go/pkg/tool/exec"
_ "cuelang.org/go/pkg/tool/file"
_ "cuelang.org/go/pkg/tool/http"
+ _ "cuelang.org/go/pkg/tool/os"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
@@ -249,8 +250,11 @@
}
for _, r := range appendReferences(nil, cr.root, v) {
if dep := cr.findTask(r); dep != nil && t != dep {
+ // TODO(string): consider adding dependencies
+ // unconditionally here.
+ // Something like IsFinal would be the right semantics here.
v := cr.root.Lookup(r...)
- if v.IsIncomplete() && v.Kind() != cue.StructKind {
+ if !v.IsConcrete() && v.Kind() != cue.StructKind {
t.dep[dep] = true
}
}
@@ -385,8 +389,9 @@
if err != nil {
// Lookup kind for backwards compatibility.
// TODO: consider at some point whether kind can be removed.
- kind, err = v.Lookup("kind").String()
- if err != nil {
+ var err1 error
+ kind, err1 = v.Lookup("kind").String()
+ if err1 != nil {
return nil, err
}
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_env.txt b/cmd/cue/cmd/testdata/script/cmd_env.txt
new file mode 100644
index 0000000..bfda9d4
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_env.txt
@@ -0,0 +1,39 @@
+cue cmd env
+cmp stdout cmd_env.out
+-- cmd_env.out --
+Hello World!
+Hello someone else!
+-- task_tool.cue --
+package home
+
+import (
+ "tool/os"
+ "tool/cli"
+)
+
+command: env: {
+ setenv1: os.Setenv & {
+ TESTNUM: 10
+ MYTEXT: "World"
+ }
+ env1: os.Getenv & {
+ $after: setenv1
+
+ TESTNUM: *<10 | string
+ MYTEXT: string
+ }
+ print1: cli.Print & {
+ text: "Hello \(env1.MYTEXT)!"
+ }
+ setenv2: os.Clearenv & {
+ $after: print1
+ }
+ env2: os.Environ & {
+ $after: setenv2
+
+ MYTEXT: string | *"someone else"
+ }
+ print2: cli.Print & {
+ text: "Hello \(env2.MYTEXT)!"
+ }
+}
diff --git a/cue/builtins.go b/cue/builtins.go
index e9eca40..9668515 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -12,7 +12,6 @@
"encoding/csv"
"encoding/hex"
"encoding/json"
- "errors"
"fmt"
"html"
"io"
@@ -31,6 +30,7 @@
"unicode"
"unicode/utf8"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/literal"
"cuelang.org/go/cue/parser"
"cuelang.org/go/internal"
@@ -146,6 +146,21 @@
return true, nil
}
+var boolValues = map[string]bool{
+ "1": true,
+ "0": false,
+ "t": true,
+ "f": false,
+ "T": true,
+ "F": false,
+ "true": true,
+ "false": false,
+ "TRUE": true,
+ "FALSE": false,
+ "True": true,
+ "False": false,
+}
+
var builtinPackages = map[string]*builtinPkg{
"": &builtinPkg{
native: []*builtin{{}},
@@ -3584,4 +3599,26 @@
}
}`,
},
+ "tool/os": &builtinPkg{
+ native: []*builtin{{}},
+ cue: `{
+ Value :: bool | number | *string | null
+ Name :: !="" & !~"^[$]"
+ Setenv: {
+ [Name]: Value
+ $id: "tool/os.Setenv"
+ }
+ Getenv: {
+ [Name]: Value
+ $id: "tool/os.Getenv"
+ }
+ Environ: {
+ [Name]: Value
+ $id: "tool/os.Environ"
+ }
+ Clearenv: {
+ $id: "tool/os.Clearenv"
+ }
+}`,
+ },
}
diff --git a/cue/types.go b/cue/types.go
index 02db68c..292aefc 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -923,6 +923,8 @@
return pos
}
+// TODO: IsFinal: this value can never be changed.
+
// IsConcrete reports whether the current value is a concrete scalar value
// (not relying on default values), a terminal error, a list, or a struct.
// It does not verify that values of lists or structs are concrete themselves.
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
index a3353e9..edec7d8 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue
@@ -2134,7 +2134,7 @@
containerName?: string @go(ContainerName) @protobuf(1,bytes,opt)
// Required: resource to select
- resource: string @go(Resource) @protobuf(2,bytes,opt)
+ "resource": string @go(Resource) @protobuf(2,bytes,opt)
// Specifies the output format of the exposed resources, defaults to "1"
// +optional
@@ -5040,7 +5040,7 @@
ResourceAttachableVolumesPrefix :: "attachable-volumes-"
// ResourceList is a set of (resource name, quantity) pairs.
-ResourceList :: {[string]: resource.Quantity}
+ResourceList :: [string]: resource.Quantity
// Node is a worker node in Kubernetes.
// Each node will have a unique identifier in the cache (i.e. in etcd).
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
index 8a401fa..cf0dbdd 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/codec_go_gen.cue
@@ -5,11 +5,7 @@
package runtime
// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding.
-NoopEncoder :: {
- Decoder: Decoder
-}
+NoopEncoder :: "Decoder": Decoder
// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding.
-NoopDecoder :: {
- Encoder: Encoder
-}
+NoopDecoder :: "Encoder": Encoder
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
index 445f642..9ffaf82 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/helper_go_gen.cue
@@ -9,12 +9,10 @@
// WithVersionEncoder serializes an object and ensures the GVK is set.
WithVersionEncoder :: {
- Version: GroupVersioner
- Encoder: Encoder
- ObjectTyper: ObjectTyper
+ Version: GroupVersioner
+ "Encoder": Encoder
+ "ObjectTyper": ObjectTyper
}
// WithoutVersionDecoder clears the group version kind of a deserialized object.
-WithoutVersionDecoder :: {
- Decoder: Decoder
-}
+WithoutVersionDecoder :: "Decoder": Decoder
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
index 2467f67..a673d37 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/runtime/interfaces_go_gen.cue
@@ -58,7 +58,7 @@
EncodesAsText: bool
// Serializer is the individual object serializer for this media type.
- Serializer: Serializer
+ "Serializer": Serializer
// PrettySerializer, if set, can serialize this object in a form biased towards
// readability.
@@ -75,10 +75,10 @@
EncodesAsText: bool
// Serializer is the top level object serializer for this type when streaming
- Serializer: Serializer
+ "Serializer": Serializer
// Framer is the factory for retrieving streams that separate objects on the wire
- Framer: Framer
+ "Framer": Framer
}
// NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
index e20ae6d..2cf6b29 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/filter_go_gen.cue
@@ -5,6 +5,4 @@
package watch
// Recorder records all events that are sent from the watch until it is closed.
-Recorder :: {
- Interface: Interface
-}
+Recorder :: "Interface": Interface
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
index 34a7ecd..ec357b7 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
@@ -44,11 +44,7 @@
}
// FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
-FakeWatcher :: {
- Stopped: bool
-}
+FakeWatcher :: Stopped: bool
// RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
-RaceFreeFakeWatcher :: {
- Stopped: bool
-}
+RaceFreeFakeWatcher :: Stopped: bool
diff --git a/doc/tutorial/kubernetes/testdata/quick.out b/doc/tutorial/kubernetes/testdata/quick.out
index 95bdcd4..6d4fa10 100644
--- a/doc/tutorial/kubernetes/testdata/quick.out
+++ b/doc/tutorial/kubernetes/testdata/quick.out
@@ -111,8 +111,8 @@
}
deployment: {
bartender: {
- kind: "Deployment"
Name :: "bartender"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "bartender"
@@ -217,8 +217,8 @@
}
deployment: {
breaddispatcher: {
- kind: "Deployment"
Name :: "breaddispatcher"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "breaddispatcher"
@@ -323,8 +323,8 @@
}
deployment: {
host: {
- kind: "Deployment"
Name :: "host"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "host"
@@ -429,8 +429,8 @@
}
deployment: {
maitred: {
- kind: "Deployment"
Name :: "maitred"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "maitred"
@@ -535,8 +535,8 @@
}
deployment: {
valeter: {
- kind: "Deployment"
Name :: "valeter"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "valeter"
@@ -641,8 +641,8 @@
}
deployment: {
waiter: {
- kind: "Deployment"
Name :: "waiter"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "waiter"
@@ -746,8 +746,8 @@
}
deployment: {
waterdispatcher: {
- kind: "Deployment"
Name :: "waterdispatcher"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "waterdispatcher"
@@ -893,8 +893,8 @@
}
deployment: {
download: {
- kind: "Deployment"
Name :: "download"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "download"
@@ -1004,8 +1004,8 @@
}
statefulSet: {
etcd: {
- kind: "StatefulSet"
Name :: "etcd"
+ kind: "StatefulSet"
apiVersion: "apps/v1"
metadata: {
name: "etcd"
@@ -1176,8 +1176,8 @@
}
deployment: {
events: {
- kind: "Deployment"
Name :: "events"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "events"
@@ -1311,8 +1311,8 @@
}
deployment: {
tasks: {
- kind: "Deployment"
Name :: "tasks"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "tasks"
@@ -1429,8 +1429,8 @@
}
deployment: {
updater: {
- kind: "Deployment"
Name :: "updater"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "updater"
@@ -1543,8 +1543,8 @@
}
deployment: {
watcher: {
- kind: "Deployment"
Name :: "watcher"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "watcher"
@@ -1698,8 +1698,8 @@
}
deployment: {
caller: {
- kind: "Deployment"
Name :: "caller"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "caller"
@@ -1841,8 +1841,8 @@
}
deployment: {
dishwasher: {
- kind: "Deployment"
Name :: "dishwasher"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "dishwasher"
@@ -1984,8 +1984,8 @@
}
deployment: {
expiditer: {
- kind: "Deployment"
Name :: "expiditer"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "expiditer"
@@ -2118,8 +2118,8 @@
}
deployment: {
headchef: {
- kind: "Deployment"
Name :: "headchef"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "headchef"
@@ -2252,8 +2252,8 @@
}
deployment: {
linecook: {
- kind: "Deployment"
Name :: "linecook"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "linecook"
@@ -2386,8 +2386,8 @@
}
deployment: {
pastrychef: {
- kind: "Deployment"
Name :: "pastrychef"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "pastrychef"
@@ -2520,8 +2520,8 @@
}
deployment: {
souschef: {
- kind: "Deployment"
Name :: "souschef"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "souschef"
@@ -2708,8 +2708,8 @@
}
deployment: {
alertmanager: {
- kind: "Deployment"
Name :: "alertmanager"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "alertmanager"
@@ -2832,8 +2832,8 @@
}
deployment: {
grafana: {
- kind: "Deployment"
Name :: "grafana"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "grafana"
@@ -2972,8 +2972,8 @@
}
daemonSet: {
"node-exporter": {
- kind: "DaemonSet"
Name :: "node-exporter"
+ kind: "DaemonSet"
apiVersion: "apps/v1"
metadata: {
name: "node-exporter"
@@ -3352,8 +3352,8 @@
}
deployment: {
prometheus: {
- kind: "Deployment"
Name :: "prometheus"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "prometheus"
@@ -3585,8 +3585,8 @@
}
deployment: {
authproxy: {
- kind: "Deployment"
Name :: "authproxy"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "authproxy"
@@ -3699,8 +3699,8 @@
}
deployment: {
goget: {
- kind: "Deployment"
Name :: "goget"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "goget"
@@ -3983,8 +3983,8 @@
}
deployment: {
nginx: {
- kind: "Deployment"
Name :: "nginx"
+ kind: "Deployment"
apiVersion: "apps/v1"
metadata: {
name: "nginx"
diff --git a/go.mod b/go.mod
index 68b2e98..dafaba4 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/emicklei/proto v1.6.15
github.com/ghodss/yaml v1.0.0
- github.com/google/go-cmp v0.2.0
+ github.com/google/go-cmp v0.3.1
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pretty v0.1.0
github.com/kylelemons/godebug v1.1.0
diff --git a/go.sum b/go.sum
index 6cc3c30..41e0c47 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
diff --git a/pkg/regexp/manual.go b/pkg/regexp/manual.go
index 1a14476..c9201d1 100644
--- a/pkg/regexp/manual.go
+++ b/pkg/regexp/manual.go
@@ -15,8 +15,9 @@
package regexp
import (
- "errors"
"regexp"
+
+ "cuelang.org/go/cue/errors"
)
var errNoMatch = errors.New("no match")
diff --git a/pkg/tool/exec/exec.go b/pkg/tool/exec/exec.go
index 1bdca9a..6146e53 100644
--- a/pkg/tool/exec/exec.go
+++ b/pkg/tool/exec/exec.go
@@ -17,12 +17,12 @@
//go:generate go run gen.go
import (
- "errors"
"fmt"
"os/exec"
"strings"
"cuelang.org/go/cue"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/internal/task"
"golang.org/x/xerrors"
)
diff --git a/pkg/tool/os/doc.go b/pkg/tool/os/doc.go
new file mode 100644
index 0000000..b32321f
--- /dev/null
+++ b/pkg/tool/os/doc.go
@@ -0,0 +1,48 @@
+// Code generated by cue get go. DO NOT EDIT.
+
+// Package os defines tasks for retrieving os-related information.
+//
+// CUE definitions:
+//
+// // A Value are all possible values allowed in flags.
+// // A null value unsets an environment variable.
+// Value :: bool | number | *string | null
+//
+// // Name indicates a valid flag name.
+// Name :: !="" & !~"^[$]"
+//
+// // Setenv defines a set of command line flags, the values of which will be set
+// // at run time. The doc comment of the flag is presented to the user in help.
+// //
+// // To define a shorthand, define the shorthand as a new flag referring to
+// // the flag of which it is a shorthand.
+// Setenv: {
+// $id: "tool/os.Setenv"
+//
+// [Name]: Value
+// }
+//
+// // Getenv gets and parses the specific command line variables.
+// Getenv: {
+// $id: "tool/os.Getenv"
+//
+// [Name]: Value
+// }
+//
+// // Environ populates a struct with all environment variables.
+// Environ: {
+// $id: "tool/os.Environ"
+//
+// // A map of all populated values.
+// // Individual entries may be specified ahead of time to enable
+// // validation and parsing. Values that are marked as required
+// // will fail the task if they are not found.
+// [Name]: Value
+// }
+//
+// // Clearenv clears all environment variables.
+// Clearenv: {
+// $id: "tool/os.Clearenv"
+// }
+//
+package os
diff --git a/pkg/tool/os/env.go b/pkg/tool/os/env.go
new file mode 100644
index 0000000..487d397
--- /dev/null
+++ b/pkg/tool/os/env.go
@@ -0,0 +1,274 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package os
+
+//go:generate go run gen.go
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal/task"
+)
+
+func init() {
+ task.Register("tool/os.Setenv", newSetenvCmd)
+ task.Register("tool/os.Getenv", newGetenvCmd)
+ task.Register("tool/os.Environ", newEnvironCmd)
+ task.Register("tool/os.Clearenv", newClearenvCmd)
+
+ // TODO:
+ // Tasks:
+ // - Exit?
+ // - Getwd/ Setwd (or in tool/file?)
+
+ // Functions:
+ // - Hostname
+ // - UserCache/Home/Config (or in os/user?)
+}
+
+type clearenvCmd struct{}
+
+func newClearenvCmd(v cue.Value) (task.Runner, error) {
+ return &clearenvCmd{}, nil
+}
+
+func (c *clearenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+ os.Clearenv()
+ return map[string]interface{}{}, nil
+}
+
+type setenvCmd struct{}
+
+func newSetenvCmd(v cue.Value) (task.Runner, error) {
+ return &setenvCmd{}, nil
+}
+
+func (c *setenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+ iter, err := v.Fields()
+ if err != nil {
+ return nil, err
+ }
+
+ for iter.Next() {
+ name := iter.Label()
+ if strings.HasPrefix(name, "$") {
+ continue
+ }
+
+ v, _ := iter.Value().Default()
+
+ if !v.IsConcrete() {
+ return nil, errors.Newf(v.Pos(),
+ "non-concrete environment variable %s", name)
+ }
+ switch k := v.IncompleteKind(); k {
+ case cue.ListKind, cue.StructKind:
+ return nil, errors.Newf(v.Pos(),
+ "unsupported type %s for environment variable %s", k, name)
+
+ case cue.NullKind:
+ err = os.Unsetenv(name)
+
+ case cue.BoolKind:
+ if b, _ := v.Bool(); b {
+ err = os.Setenv(name, "1")
+ } else {
+ err = os.Setenv(name, "0")
+ }
+
+ case cue.StringKind:
+ s, _ := v.String()
+ err = os.Setenv(name, s)
+
+ default:
+ err = os.Setenv(name, fmt.Sprint(v))
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return map[string]interface{}{}, err
+}
+
+type getenvCmd struct{}
+
+func newGetenvCmd(v cue.Value) (task.Runner, error) {
+ return &getenvCmd{}, nil
+}
+
+func (c *getenvCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+ iter, err := v.Fields()
+ if err != nil {
+ return nil, err
+ }
+
+ update := map[string]interface{}{}
+
+ for iter.Next() {
+ name := iter.Label()
+ if strings.HasPrefix(name, "$") {
+ continue
+ }
+ v := iter.Value()
+
+ if err := validateEntry(name, v); err != nil {
+ return nil, err
+ }
+
+ str, ok := os.LookupEnv(name)
+ if !ok {
+ update[name] = nil
+ continue
+ }
+ x, err := fromString(name, str, v)
+ if err != nil {
+ return nil, err
+ }
+ update[name] = x
+ }
+
+ return update, nil
+}
+
+type environCmd struct{}
+
+func newEnvironCmd(v cue.Value) (task.Runner, error) {
+ return &environCmd{}, nil
+}
+
+func (c *environCmd) Run(ctx *task.Context, v cue.Value) (res interface{}, err error) {
+ iter, err := v.Fields()
+ if err != nil {
+ return nil, err
+ }
+
+ update := map[string]interface{}{}
+
+ for _, kv := range os.Environ() {
+ a := strings.SplitN(kv, "=", 2)
+
+ name := a[0]
+ str := a[1]
+
+ if v := v.Lookup(name); v.Exists() {
+ update[name], err = fromString(name, str, v)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ update[name] = str
+ }
+ }
+
+ for iter.Next() {
+ name := iter.Label()
+ if strings.HasPrefix(name, "$") {
+ continue
+ }
+ if err := validateEntry(name, iter.Value()); err != nil {
+ return nil, err
+ }
+ if _, ok := update[name]; !ok {
+ update[name] = nil
+ }
+ }
+
+ return update, nil
+}
+
+func validateEntry(name string, v cue.Value) error {
+ if k := v.IncompleteKind(); k&^(cue.NumberKind|cue.NullKind|cue.BoolKind|cue.StringKind) != 0 {
+ return errors.Newf(v.Pos(),
+ "invalid type %s for environment variable %s", k, name)
+ }
+ return nil
+}
+
+func fromString(name, str string, v cue.Value) (x interface{}, err error) {
+ k := v.IncompleteKind()
+
+ var expr ast.Expr
+ var errs errors.Error
+
+ if k&cue.NumberKind != 0 {
+ expr, err = parser.ParseExpr(name, str)
+ if err != nil {
+ errs = errors.Wrapf(err, v.Pos(),
+ "invalid number for environment variable %s", name)
+ }
+ }
+
+ if k&cue.BoolKind != 0 {
+ str = strings.TrimSpace(str)
+ b, ok := boolValues[str]
+ if !ok {
+ errors.Append(errs, errors.Newf(v.Pos(),
+ "invalid boolean value %q for environment variable %s", str, name))
+ } else if expr != nil || k&cue.StringKind != 0 {
+ // Convert into an expression
+ bl := ast.NewBool(b)
+ if expr != nil {
+ expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: bl}
+ } else {
+ expr = bl
+ }
+ } else {
+ x = b
+ }
+ }
+
+ if k&cue.StringKind != 0 {
+ if expr != nil {
+ expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: ast.NewString(str)}
+ } else {
+ x = str
+ }
+ }
+
+ switch {
+ case expr != nil:
+ return expr, nil
+ case x != nil:
+ return x, nil
+ case errs == nil:
+ return nil, errors.Newf(v.Pos(),
+ "invalid type for environment variable %s", name)
+ }
+ return nil, errs
+}
+
+var boolValues = map[string]bool{
+ "1": true,
+ "0": false,
+ "t": true,
+ "f": false,
+ "T": true,
+ "F": false,
+ "true": true,
+ "false": false,
+ "TRUE": true,
+ "FALSE": false,
+ "True": true,
+ "False": false,
+}
diff --git a/pkg/tool/os/env_test.go b/pkg/tool/os/env_test.go
new file mode 100644
index 0000000..5b426b7
--- /dev/null
+++ b/pkg/tool/os/env_test.go
@@ -0,0 +1,182 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package os
+
+import (
+ "os"
+ "testing"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/parser"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+ "cuelang.org/go/internal/task"
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func TestSetenv(t *testing.T) {
+ os.Setenv("CUEOSTESTUNSET", "SET")
+ v := parse(t, "tool/os.Setenv", `{
+ CUEOSTESTMOOD: "yippie"
+ CUEOSTESTTRUE: true
+ CUEOSTESTFALSE: false
+ CUEOSTESTNUM: 34K
+ CUEOSTESTUNSET: null
+ }`)
+ _, err := (*setenvCmd).Run(nil, nil, v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, p := range [][2]string{
+ {"CUEOSTESTMOOD", "yippie"},
+ {"CUEOSTESTTRUE", "1"},
+ {"CUEOSTESTFALSE", "0"},
+ {"CUEOSTESTNUM", "34000"},
+ } {
+ got := os.Getenv(p[0])
+ if got != p[1] {
+ t.Errorf("got %v; want %v", got, p[1])
+ }
+ }
+
+ if _, ok := os.LookupEnv("CUEOSTESTUNSET"); ok {
+ t.Error("CUEOSTESTUNSET should have been unset")
+ }
+
+ v = parse(t, "tool/os.Setenv", `{
+ CUEOSTESTMOOD: string
+ }`)
+ _, err = (*setenvCmd).Run(nil, nil, v)
+ if err == nil {
+ t.Fatal("expected incomplete error")
+ }
+ // XXX: ensure error is not concrete.
+}
+
+func TestGetenv(t *testing.T) {
+
+ for _, p := range [][2]string{
+ {"CUEOSTESTMOOD", "yippie"},
+ {"CUEOSTESTTRUE", "True"},
+ {"CUEOSTESTFALSE", "0"},
+ {"CUEOSTESTBI", "1"},
+ {"CUEOSTESTNUM", "34K"},
+ {"CUEOSTESTNUMD", "not a num"},
+ {"CUEOSTESTMULTI", "10"},
+ } {
+ os.Setenv(p[0], p[1])
+ }
+
+ config := `{
+ CUEOSTESTMOOD: string
+ CUEOSTESTTRUE: bool
+ CUEOSTESTFALSE: bool | string
+ CUEOSTESTBI: *bool | int,
+ CUEOSTESTNUM: int
+ CUEOSTESTNUMD: *int | *bool | string
+ CUEOSTESTMULTI: *<10 | string
+ CUEOSTESTNULL: int | null
+ }`
+
+ want := map[string]interface{}{
+ "CUEOSTESTMOOD": "yippie",
+ "CUEOSTESTTRUE": true,
+ "CUEOSTESTFALSE": &ast.BinaryExpr{
+ Op: token.OR,
+ X: ast.NewBool(false),
+ Y: ast.NewString("0"),
+ },
+ "CUEOSTESTBI": &ast.BinaryExpr{
+ Op: token.OR,
+ X: &ast.BasicLit{Kind: token.INT, Value: "1"},
+ Y: ast.NewBool(true),
+ },
+ "CUEOSTESTNUM": &ast.BasicLit{Kind: token.INT, Value: "34K"},
+ "CUEOSTESTNUMD": "not a num",
+ "CUEOSTESTMULTI": &ast.BinaryExpr{
+ Op: token.OR,
+ X: &ast.BasicLit{Kind: token.INT, Value: "10"},
+ Y: ast.NewString("10"),
+ },
+ "CUEOSTESTNULL": nil,
+ }
+
+ for _, tc := range []struct {
+ pkg string
+ runner task.Runner
+ }{
+ {"tool/os.Getenv", &getenvCmd{}},
+ {"tool/os.Environ", &environCmd{}},
+ } {
+ v := parse(t, tc.pkg, config)
+ got, err := tc.runner.Run(nil, v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var opts = []cmp.Option{
+ cmpopts.IgnoreFields(ast.BinaryExpr{}, "OpPos"),
+ cmpopts.IgnoreFields(ast.BasicLit{}, "ValuePos"),
+ cmpopts.IgnoreUnexported(ast.BasicLit{}, ast.BinaryExpr{}),
+ // For ignoring addinonal entries from os.Environ:
+ cmpopts.IgnoreMapEntries(func(s string, x interface{}) bool {
+ _, ok := want[s]
+ return !ok
+ }),
+ }
+
+ if !cmp.Equal(got, want, opts...) {
+ t.Error(cmp.Diff(got, want, opts...))
+ }
+
+ // Errors:
+ for _, etc := range []struct{ config, err string }{{
+ config: `{ CUEOSTESTNULL: [...string] }`,
+ err: "expected unsupported type error",
+ }, {
+ config: `{ CUEOSTESTNUMD: int }`,
+ err: "expected invalid number error",
+ }, {
+ config: `{ CUEOSTESTNUMD: null }`,
+ err: "expected invalid type",
+ }} {
+ t.Run(etc.err, func(t *testing.T) {
+ v = parse(t, tc.pkg, etc.config)
+ if _, err = tc.runner.Run(nil, v); err == nil {
+ t.Error(etc.err)
+ }
+ })
+ }
+ }
+}
+
+func parse(t *testing.T, kind, expr string) cue.Value {
+ t.Helper()
+
+ x, err := parser.ParseExpr("test", expr)
+ if err != nil {
+ errors.Print(os.Stderr, err, nil)
+ t.Fatal(err)
+ }
+ var r cue.Runtime
+ i, err := r.CompileExpr(x)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return internal.UnifyBuiltin(i.Value(), kind).(cue.Value)
+}
diff --git a/pkg/tool/os/gen.go b/pkg/tool/os/gen.go
new file mode 100644
index 0000000..b6df11a
--- /dev/null
+++ b/pkg/tool/os/gen.go
@@ -0,0 +1,47 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+package main
+
+// TODO: remove when we have a cuedoc server. Until then,
+// piggyback on godoc.org.
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+const msg = `// Code generated by cue get go. DO NOT EDIT.
+
+// Package os defines tasks for retrieving os-related information.
+//
+// CUE definitions:
+// %s
+package os
+`
+
+func main() {
+ f, _ := os.Create("doc.go")
+ defer f.Close()
+ b, _ := ioutil.ReadFile("os.cue")
+ i := bytes.Index(b, []byte("package os"))
+ b = b[i+len("package os")+1:]
+ b = bytes.ReplaceAll(b, []byte("\n"), []byte("\n// "))
+ b = bytes.ReplaceAll(b, []byte("\t"), []byte(" "))
+ fmt.Fprintf(f, msg, string(b))
+}
diff --git a/pkg/tool/os/os.cue b/pkg/tool/os/os.cue
new file mode 100644
index 0000000..d7e4322
--- /dev/null
+++ b/pkg/tool/os/os.cue
@@ -0,0 +1,56 @@
+// Copyright 2019 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package os
+
+// A Value are all possible values allowed in flags.
+// A null value unsets an environment variable.
+Value :: bool | number | *string | null
+
+// Name indicates a valid flag name.
+Name :: !="" & !~"^[$]"
+
+// Setenv defines a set of command line flags, the values of which will be set
+// at run time. The doc comment of the flag is presented to the user in help.
+//
+// To define a shorthand, define the shorthand as a new flag referring to
+// the flag of which it is a shorthand.
+Setenv: {
+ $id: "tool/os.Setenv"
+
+ [Name]: Value
+}
+
+// Getenv gets and parses the specific command line variables.
+Getenv: {
+ $id: "tool/os.Getenv"
+
+ [Name]: Value
+}
+
+// Environ populates a struct with all environment variables.
+Environ: {
+ $id: "tool/os.Environ"
+
+ // A map of all populated values.
+ // Individual entries may be specified ahead of time to enable
+ // validation and parsing. Values that are marked as required
+ // will fail the task if they are not found.
+ [Name]: Value
+}
+
+// Clearenv clears all environment variables.
+Clearenv: {
+ $id: "tool/os.Clearenv"
+}