1// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cmd
16
17import (
18	"fmt"
19	"os"
20
21	"github.com/spf13/cobra"
22)
23
24// TODO: generate long description from documentation.
25
26func newCmdCmd(c *Command) *cobra.Command {
27	cmd := &cobra.Command{
28		Use:   "cmd <name> [inputs]",
29		Short: "run a user-defined shell command",
30		Long: `cmd executes the named command for each of the named instances.
31
32Commands define actions on instances. For example, they may
33specify how to upload a configuration to Kubernetes. Commands are
34defined directly in tool files, which are regular CUE files
35within the same package with a filename ending in _tool.cue.
36These are typically defined at the module root so that they apply
37to all instances.
38
39Each command consists of one or more tasks. A task may, for
40example, load or write a file, consult a user on the command
41line, fetch a web page, and so on. Each task has inputs and
42outputs. Outputs are typically filled out by the task
43implementation as the task completes.
44
45Inputs of tasks my refer to outputs of other tasks. The cue tool
46does a static analysis of the configuration and only starts tasks
47that are fully specified. Upon completion of each task, cue
48rewrites the instance, filling in the completed task, and
49reevaluates which other tasks can now start, and so on until all
50tasks have completed.
51
52Available tasks can be found in the package documentation at
53
54	https://pkg.go.dev/cuelang.org/go/pkg/tool?tab=subdirectories
55
56Examples:
57
58In this simple example, we define a command called "hello",
59which declares a single task called "print" which uses
60"tool/exec.Run" to execute a shell command that echos output to
61the terminal:
62
63	$ cat <<EOF > hello_tool.cue
64	package foo
65
66	import "tool/exec"
67
68	city: "Amsterdam"
69	who: *"World" | string @tag(who)
70
71	// Say hello!
72	command: hello: {
73		print: exec.Run & {
74			cmd: "echo Hello \(who)! Welcome to \(city)."
75		}
76	}
77	EOF
78
79We run the "hello" command like this:
80
81	$ cue cmd hello
82	Hello World! Welcome to Amsterdam.
83
84	$ cue cmd --inject who=Jan hello
85	Hello Jan! Welcome to Amsterdam.
86
87
88In this example we declare the "prompted" command which has four
89tasks. The first task prompts the user for a string input. The
90second task depends on the first, and echos the response back to
91the user with a friendly message. The third task pipes the output
92from the second to a file. The fourth task pipes the output from
93the second to standard output (i.e. it echos it again).
94
95	package foo
96
97	import (
98		"tool/cli"
99		"tool/exec"
100		"tool/file"
101	)
102
103	city: "Amsterdam"
104
105	// Say hello!
106	command: prompter: {
107		// save transcript to this file
108		var: file: *"out.txt" | string @tag(file)
109
110		ask: cli.Ask & {
111			prompt:   "What is your name?"
112			response: string
113		}
114
115		// starts after ask
116		echo: exec.Run & {
117			cmd:    ["echo", "Hello", ask.response + "!"]
118			stdout: string // capture stdout
119		}
120
121		// starts after echo
122		append: file.Append & {
123			filename: var.file
124			contents: echo.stdout
125		}
126
127		// also starts after echo
128		print: cli.Print & {
129			text: echo.stdout
130		}
131	}
132
133Run "cue help commands" for more details on tasks and commands.
134`,
135		RunE: mkRunE(c, func(cmd *Command, args []string) error {
136			w := cmd.Stderr()
137			if len(args) == 0 {
138				fmt.Fprintln(w, "cmd must be run as one of its subcommands")
139			} else {
140				const msg = `cmd must be run as one of its subcommands: unknown subcommand %q
141Ensure commands are defined in a "_tool.cue" file.
142`
143				fmt.Fprintf(w, msg, args[0])
144			}
145			fmt.Fprintln(w, "Run 'cue help cmd' for known subcommands.")
146			os.Exit(1) // TODO: get rid of this
147			return nil
148		}),
149	}
150
151	cmd.Flags().SetInterspersed(false)
152
153	addInjectionFlags(cmd.Flags(), true)
154
155	return cmd
156}
157