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
20	"github.com/spf13/cobra"
21
22	"cuelang.org/go/cue"
23	"cuelang.org/go/cue/build"
24	"cuelang.org/go/cue/format"
25	"cuelang.org/go/internal"
26	"cuelang.org/go/internal/encoding"
27	"cuelang.org/go/internal/filetypes"
28)
29
30// newEvalCmd creates a new eval command
31func newEvalCmd(c *Command) *cobra.Command {
32	cmd := &cobra.Command{
33		Use:   "eval",
34		Short: "evaluate and print a configuration",
35		Long: `eval evaluates, validates, and prints a configuration.
36
37Printing is skipped if validation fails.
38
39The --expression flag is used to evaluate an expression within the
40configuration file, instead of the entire configuration file itself.
41
42Examples:
43
44  $ cat <<EOF > foo.cue
45  a: [ "a", "b", "c" ]
46  EOF
47
48  $ cue eval foo.cue -e a[0] -e a[2]
49  "a"
50  "c"
51`,
52		RunE: mkRunE(c, runEval),
53	}
54
55	addOutFlags(cmd.Flags(), true)
56	addOrphanFlags(cmd.Flags())
57	addInjectionFlags(cmd.Flags(), false)
58
59	cmd.Flags().StringArrayP(string(flagExpression), "e", nil, "evaluate this expression only")
60
61	cmd.Flags().BoolP(string(flagConcrete), "c", false,
62		"require the evaluation to be concrete")
63
64	cmd.Flags().BoolP(string(flagHidden), "H", false,
65		"display hidden fields")
66
67	cmd.Flags().BoolP(string(flagOptional), "O", false,
68		"display optional fields")
69
70	cmd.Flags().BoolP(string(flagAttributes), "A", false,
71		"display field attributes")
72
73	cmd.Flags().BoolP(string(flagAll), "a", false,
74		"show optional and hidden fields")
75
76	// TODO: Option to include comments in output.
77	return cmd
78}
79
80const (
81	flagConcrete   flagName = "concrete"
82	flagHidden     flagName = "show-hidden"
83	flagOptional   flagName = "show-optional"
84	flagAttributes flagName = "show-attributes"
85)
86
87func runEval(cmd *Command, args []string) error {
88	b, err := parseArgs(cmd, args, &config{outMode: filetypes.Eval})
89	exitOnErr(cmd, err, true)
90
91	syn := []cue.Option{
92		cue.Final(), // for backwards compatibility
93		cue.Definitions(true),
94		cue.Attributes(flagAttributes.Bool(cmd)),
95		cue.Optional(flagAll.Bool(cmd) || flagOptional.Bool(cmd)),
96	}
97
98	// Keep for legacy reasons. Note that `cue eval` is to be deprecated by
99	// `cue` eventually.
100	opts := []format.Option{
101		format.UseSpaces(4),
102		format.TabIndent(false),
103	}
104	if flagSimplify.Bool(cmd) {
105		opts = append(opts, format.Simplify())
106	}
107	b.encConfig.Format = opts
108
109	e, err := encoding.NewEncoder(b.outFile, b.encConfig)
110	exitOnErr(cmd, err, true)
111
112	iter := b.instances()
113	defer iter.close()
114	for i := 0; iter.scan(); i++ {
115		id := ""
116		if len(b.insts) > 1 {
117			id = iter.id()
118		}
119		v := iter.value()
120
121		errHeader := func() {
122			if id != "" {
123				fmt.Fprintf(cmd.OutOrStderr(), "// %s\n", id)
124			}
125		}
126		if b.outFile.Encoding != build.CUE {
127			err := e.Encode(v)
128			if err != nil {
129				errHeader()
130				exitOnErr(cmd, err, false)
131			}
132			continue
133		}
134
135		if flagConcrete.Bool(cmd) {
136			syn = append(syn, cue.Concrete(true))
137		}
138		if flagHidden.Bool(cmd) || flagAll.Bool(cmd) {
139			syn = append(syn, cue.Hidden(true))
140		}
141
142		if len(b.expressions) > 1 {
143			b, _ := format.Node(b.expressions[i%len(b.expressions)])
144			id = string(b)
145		}
146		if err := v.Err(); err != nil {
147			errHeader()
148			return err
149		}
150
151		// TODO(#553): this can be removed once v.Syntax() below retains line
152		// information.
153		if (e.IsConcrete() || flagConcrete.Bool(cmd)) && !flagIgnore.Bool(cmd) {
154			if err := v.Validate(cue.Concrete(true)); err != nil {
155				errHeader()
156				exitOnErr(cmd, err, false)
157				continue
158			}
159		}
160
161		f := internal.ToFile(v.Syntax(syn...))
162		f.Filename = id
163		err := e.EncodeFile(f)
164		if err != nil {
165			errHeader()
166			exitOnErr(cmd, err, false)
167		}
168	}
169	exitOnErr(cmd, iter.err(), true)
170
171	err = e.Close()
172	exitOnErr(cmd, err, true)
173
174	return nil
175}
176