forked from cue-lang/cue
/
cmd.go
187 lines (153 loc) · 5.38 KB
/
cmd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// 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"
"os"
"github.com/wylswz/cue-se/cue/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// TODO: generate long description from documentation.
func newCmdCmd(c *Command) *cobra.Command {
cmd := &cobra.Command{
Use: "cmd <name> [inputs]",
Short: "run a user-defined shell command",
Long: `cmd executes 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 module root so that they apply
to all instances.
Each command consists of one or more tasks. A task may, for
example, 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 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.
Available tasks can be found in the package documentation at
https://pkg.go.dev/cuelang.org/go/pkg/tool?tab=subdirectories
Examples:
In this simple example, we define a command called "hello",
which declares a single task called "print" which uses
"tool/exec.Run" to execute a shell command that echos output to
the terminal:
$ cat <<EOF > hello_tool.cue
package foo
import "tool/exec"
city: "Amsterdam"
who: *"World" | string @tag(who)
// Say hello!
command: hello: {
print: exec.Run & {
cmd: "echo Hello \(who)! Welcome to \(city)."
}
}
EOF
We run the "hello" command like this:
$ cue cmd hello
Hello World! Welcome to Amsterdam.
$ cue cmd --inject who=Jan hello
Hello Jan! Welcome to Amsterdam.
In this example we declare the "prompted" command which has four
tasks. The first task prompts the user for a string input. The
second task depends on the first, and echos the response back to
the user with a friendly message. The third task pipes the output
from the second to a file. The fourth task pipes the output from
the second to standard output (i.e. it echos it again).
package foo
import (
"tool/cli"
"tool/exec"
"tool/file"
)
city: "Amsterdam"
// Say hello!
command: prompter: {
// save transcript to this file
var: file: *"out.txt" | string @tag(file)
ask: cli.Ask & {
prompt: "What is your name?"
response: string
}
// starts after ask
echo: exec.Run & {
cmd: ["echo", "Hello", ask.response + "!"]
stdout: string // capture stdout
}
// starts after echo
append: file.Append & {
filename: var.file
contents: echo.stdout
}
// also starts after echo
print: cli.Print & {
text: echo.stdout
}
}
Run "cue help commands" for more details on tasks and commands.
`,
RunE: mkRunE(c, func(cmd *Command, args []string) error {
// The behavior when there's no known subcommand is different
// depending on whether we ran via `cue cmd` or the `cue` shortcut.
isRootCmd := cmd.Command == cmd.root
// TODO(mvdan): test running `cue` and `cue cmd` via testscript as well
if len(args) == 0 {
// `cue` should print the top-level help like `cue -h`,
// but `cue cmd` should explain that a custom command is required.
if isRootCmd {
return pflag.ErrHelp
}
w := cmd.OutOrStderr()
fmt.Fprintln(w, "cmd must be run as one of its subcommands")
fmt.Fprintln(w, "Run 'cue help cmd' for known subcommands.")
return ErrPrintedError
}
tools, err := buildTools(cmd, args[1:])
if err != nil && !isRootCmd {
// `cue cmd` fails immediately if there is no CUE package,
// but `cue` does not in order to always show a useful error in `cue typo`.
return err
}
sub, err := customCommand(cmd, commandSection, args[0], tools)
if err != nil {
w := cmd.OutOrStderr()
cmdline := "cue"
if !isRootCmd {
cmdline += " cmd"
}
cwd, _ := os.Getwd()
fmt.Fprint(w, errors.Details(err, &errors.Config{Cwd: cwd}))
fmt.Fprintln(w, `Ensure custom commands are defined in a "_tool.cue" file.`)
fmt.Fprintln(w, "Run 'cue help cmd' to list available custom commands.")
if isRootCmd {
fmt.Fprintln(w, "Run 'cue help' to see the built-in 'cue' commands.")
}
return ErrPrintedError
}
// Presumably the *cobra.Command argument should be cmd.Command,
// as that is the one which will have the right settings applied.
return sub.RunE(cmd.Command, args[1:])
}),
}
cmd.Flags().SetInterspersed(false)
addInjectionFlags(cmd.Flags(), true, false)
return cmd
}