forked from cue-lang/cue
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/cue: first implementation of cue tool
Change-Id: I9f6882d60c1f0dc6e4184fb8883f86ed0cacfa6e
- Loading branch information
Showing
40 changed files
with
3,360 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// Copyright 2018 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 cmd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
// TODO: generate long description from documentation. | ||
|
||
// cmdCmd represents the cmd command | ||
var cmdCmd = &cobra.Command{ | ||
Use: "cmd <name> [-x] [instances]", | ||
Short: "run a user-defined shell command", | ||
Long: `cmd executes defined the named command for each of the named instances. | ||
Commands define actions on instances. For example, they may specify | ||
how to upload a configuration to Kubernetes. Commands are defined | ||
directly in tool files, which are regular CUE files within the same | ||
package with a filename ending in _tool.cue. These are typically | ||
defined at the top of the module root so that they apply to all | ||
instances. | ||
Each command consists of one or more tasks. A task may load or write | ||
a file, consult a user on the command line, fetch a web page, and | ||
so on. Each task has inputs and outputs. Outputs are typically are | ||
filled out by the task implementation as the task completes. | ||
Inputs of tasks my refer to outputs of other tasks. The cue tool does | ||
a static analysis of the configuration and only starts tasks that are | ||
fully specified. Upon completion of each task, cue rewrites the instance, | ||
filling in the completed task, and reevaluates which other tasks can | ||
now start, and so on until all tasks have completed. | ||
Commands are defined at the top-level of the configuration: | ||
command <Name>: { // from tool.Command | ||
// usage gives a short usage pattern of the command. | ||
// Example: | ||
// fmt [-n] [-x] [packages] | ||
usage: Name | string | ||
// short gives a brief on-line description of the command. | ||
// Example: | ||
// reformat package sources | ||
short: "" | string | ||
// long gives a detailed description of the command, including a | ||
// description of flags usage and examples. | ||
long: "" | string | ||
// A task defines a single action to be run as part of this command. | ||
// Each task can have inputs and outputs, depending on the type | ||
// task. The outputs are initially unspecified, but are filled out | ||
// by the tooling | ||
task <Name>: { // from "tool".Task | ||
// supported fields depend on type | ||
} | ||
VarValue = string | bool | int | float | [...string|int|float] | ||
// var declares values that can be set by command line flags or | ||
// environment variables. | ||
// | ||
// Example: | ||
// // environment to run in | ||
// var env: "test" | "prod" | ||
// The tool would print documentation of this flag as: | ||
// Flags: | ||
// --env string environment to run in: test(default) or prod | ||
var <Name>: VarValue | ||
// flag defines a command line flag. | ||
// | ||
// Example: | ||
// var env: "test" | "prod" | ||
// | ||
// // augment the flag information for var | ||
// flag env: { | ||
// shortFlag: "e" | ||
// description: "environment to run in" | ||
// } | ||
// | ||
// The tool would print documentation of this flag as: | ||
// Flags: | ||
// -e, --env string environment to run in: test(default), staging, or prod | ||
// | ||
flag <Name>: { // from "tool".Flag | ||
// value defines the possible values for this flag. | ||
// The default is string. Users can define default values by | ||
// using disjunctions. | ||
value: env[Name].value | VarValue | ||
// name, if set, allows var to be set with the command-line flag | ||
// of the given name. null disables the command line flag. | ||
name: Name | null | string | ||
// short defines an abbreviated version of the flag. | ||
// Disabled by default. | ||
short: null | string | ||
} | ||
// populate flag with the default values for | ||
flag: { "\(k)": { value: v } | null for k, v in var } | ||
// env defines environment variables. It is populated with values | ||
// for var. | ||
// | ||
// To specify a var without an equivalent environment variable, | ||
// either specify it as a flag directly or disable the equally | ||
// named env entry explicitly: | ||
// | ||
// var foo: string | ||
// env foo: null // don't use environment variables for foo | ||
// | ||
env <Name>: { | ||
// name defines the environment variable that sets this flag. | ||
name: "CUE_VAR_" + strings.Upper(Name) | string | null | ||
// The value retrieved from the environment variable or null | ||
// if not set. | ||
value: null | string | bytes | ||
} | ||
env: { "\(k)": { value: v } | null for k, v in var } | ||
} | ||
Available tasks can be found in the package documentation at | ||
cuelang.org/pkg/tool. | ||
More on tasks can be found in the tasks topic. | ||
Examples: | ||
A simple file using command line execution: | ||
$ cat <<EOF > hello_tool.cue | ||
package foo | ||
import "tool/exec" | ||
city: "Amsterdam" | ||
// Say hello! | ||
command hello: { | ||
// whom to say hello to | ||
var who: "World" | string | ||
task print: exec.Run({ | ||
cmd: "echo Hello \(var.who)! Welcome to \(city)." | ||
}) | ||
} | ||
EOF | ||
$ cue cmd echo | ||
Hello World! Welcome to Amsterdam. | ||
$ cue cmd echo -who you | ||
Hello you! Welcome to Amsterdam. | ||
An example using pipes: | ||
package foo | ||
import "tool/exec" | ||
city: "Amsterdam" | ||
// Say hello! | ||
command hello: { | ||
var file: "out.txt" | string // save transcript to this file | ||
task ask: cli.Ask({ | ||
prompt: "What is your name?" | ||
response: string | ||
}) | ||
// starts after ask | ||
task echo: exec.Run({ | ||
cmd: ["echo", "Hello", task.ask.response + "!"] | ||
stdout: string // capture stdout | ||
}) | ||
// starts after echo | ||
task write: file.Append({ | ||
filename: var.file | ||
contents: task.echo.stdout | ||
}) | ||
// also starts after echo | ||
task print: cli.Print({ | ||
contents: task.echo.stdout | ||
}) | ||
} | ||
`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
fmt.Println("cmd run but shouldn't") | ||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
RootCmd.AddCommand(cmdCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright 2018 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 cmd | ||
|
||
import ( | ||
"testing" | ||
|
||
"cuelang.org/go/cue/errors" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func TestCmd(t *testing.T) { | ||
testCases := []string{ | ||
"echo", | ||
"run", | ||
"run_list", | ||
"baddisplay", | ||
"http", | ||
} | ||
for _, name := range testCases { | ||
run := func(cmd *cobra.Command, args []string) error { | ||
testOut = cmd.OutOrStdout() | ||
defer func() { testOut = nil }() | ||
|
||
tools := buildTools(RootCmd, args) | ||
cmd, err := addCustom(RootCmd, "command", name, tools) | ||
if err != nil { | ||
return err | ||
} | ||
err = executeTasks("command", name, tools) | ||
if err != nil { | ||
errors.Print(testOut, err) | ||
} | ||
return nil | ||
} | ||
runCommand(t, run, "cmd_"+name) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2018 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 cmd | ||
|
||
import ( | ||
"cuelang.org/go/cue" | ||
"cuelang.org/go/cue/build" | ||
"cuelang.org/go/cue/errors" | ||
"cuelang.org/go/cue/load" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func exitIfErr(cmd *cobra.Command, err error) { | ||
if err != nil { | ||
errors.Print(cmd.OutOrStderr(), err) | ||
exit() | ||
} | ||
} | ||
|
||
func buildFromArgs(cmd *cobra.Command, args []string) []*cue.Instance { | ||
binst := loadFromArgs(cmd, args) | ||
if binst == nil { | ||
return nil | ||
} | ||
return buildInstances(cmd, binst) | ||
} | ||
|
||
func loadFromArgs(cmd *cobra.Command, args []string) []*build.Instance { | ||
log.SetOutput(cmd.OutOrStderr()) | ||
binst := load.Instances(args, nil) | ||
if len(binst) == 0 { | ||
return nil | ||
} | ||
return binst | ||
} | ||
|
||
func buildInstances(cmd *cobra.Command, binst []*build.Instance) []*cue.Instance { | ||
instances := cue.Build(binst) | ||
for _, inst := range instances { | ||
// TODO: consider merging errors of multiple files, but ensure | ||
// duplicates are removed. | ||
exitIfErr(cmd, inst.Err) | ||
} | ||
|
||
for _, inst := range instances { | ||
// TODO: consider merging errors of multiple files, but ensure | ||
// duplicates are removed. | ||
exitIfErr(cmd, inst.Value().Validate()) | ||
} | ||
return instances | ||
} | ||
|
||
func buildTools(cmd *cobra.Command, args []string) *cue.Instance { | ||
binst := loadFromArgs(cmd, args) | ||
if len(binst) == 0 { | ||
return nil | ||
} | ||
|
||
included := map[string]bool{} | ||
|
||
ti := binst[0].Context().NewInstance(binst[0].Root, nil) | ||
for _, inst := range binst { | ||
for _, f := range inst.ToolCUEFiles { | ||
if file := inst.Abs(f); !included[file] { | ||
ti.AddFile(file, nil) | ||
included[file] = true | ||
} | ||
} | ||
} | ||
|
||
inst := cue.Merge(buildInstances(cmd, binst)...).Build(ti) | ||
exitIfErr(cmd, inst.Err) | ||
return inst | ||
} |
Oops, something went wrong.