Skip to content

Commit e9bf33c

Browse files
committed
cmd/cue: repurpose exitOnErr into printError
The only call site with fatal=true was in buildInstances, which now returns an error just like all the others. exitOnErr is now printError, since fatal was always false at this point, and panicExit and recoverError can be deleted as unused now. We now "just" have two ways to stop a command with errors: returning an arbitrary error, and returning ErrPrintedError. Stopping a command by panicking with panicExit is no longer possible. Note that fmt.go replaces uses of the non-fatal exitOnErr with error returns which stop further processing. This is intentional; after recent refactors, `cue fmt` is now inconsistent in terms of which errors it considers fatal. For the time being, it makes sense for fmt to stop at the first error. If we want to start treating some errors as non-fatal, such as parsing or formatting errors, we should first have tests in place that show the tool continuing to format more files. Signed-off-by: Daniel Martí <[email protected]> Change-Id: I847be07f721fdb8cc64bfd6312b72615d4d70755 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1194538 Reviewed-by: Roger Peppe <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 64baa18 commit e9bf33c

File tree

6 files changed

+81
-83
lines changed

6 files changed

+81
-83
lines changed

cmd/cue/cmd/common.go

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@
1515
package cmd
1616

1717
import (
18-
"io"
1918
"os"
2019
"path/filepath"
2120
"regexp"
2221
"strconv"
2322
"strings"
24-
"testing"
2523

2624
"github.com/spf13/pflag"
2725
"golang.org/x/text/language"
28-
"golang.org/x/text/message"
2926

3027
"cuelang.org/go/cue"
3128
"cuelang.org/go/cue/ast"
@@ -82,34 +79,6 @@ func getLang() language.Tag {
8279
return language.Make(loc)
8380
}
8481

85-
// exitOnErr uses cue/errors to print any error to stderr,
86-
// and if fatal is true, it causes the command's exit code to be 1.
87-
// Note that os.Exit is not called, as that would prevent defers from running.
88-
//
89-
// TODO(mvdan): can we avoid the panicError and recover shenanigans?
90-
// They work, but they make the code flow somewhat confusing to follow.
91-
func exitOnErr(cmd *Command, err error, fatal bool) {
92-
if err == nil {
93-
return
94-
}
95-
96-
// Link x/text as our localizer.
97-
p := message.NewPrinter(getLang())
98-
format := func(w io.Writer, format string, args ...interface{}) {
99-
p.Fprintf(w, format, args...)
100-
}
101-
102-
cwd, _ := os.Getwd()
103-
errors.Print(cmd.Stderr(), err, &errors.Config{
104-
Format: format,
105-
Cwd: cwd,
106-
ToSlash: testing.Testing(),
107-
})
108-
if fatal {
109-
panicExit()
110-
}
111-
}
112-
11382
func loadFromArgs(args []string, cfg *load.Config) []*build.Instance {
11483
binst := load.Instances(args, cfg)
11584
if len(binst) == 0 {
@@ -168,9 +137,11 @@ func (b *buildPlan) instances() iterator {
168137
case len(b.orphaned) > 0:
169138
i = newStreamingIterator(b)
170139
case len(b.insts) > 0:
140+
insts, err := buildInstances(b.cmd, b.insts, false)
171141
i = &instanceIterator{
172142
inst: b.instance,
173-
a: buildInstances(b.cmd, b.insts, false),
143+
a: insts,
144+
e: err,
174145
i: -1,
175146
}
176147
default:
@@ -625,11 +596,14 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
625596
// TODO: ignore errors here for now until reporting of concreteness
626597
// of errors is correct.
627598
// See https://github.com/cue-lang/cue/issues/1483.
628-
inst := buildInstances(
599+
insts, err := buildInstances(
629600
p.cmd,
630601
[]*build.Instance{schema},
631-
true)[0]
632-
602+
true)
603+
if err != nil {
604+
return nil, err
605+
}
606+
inst := insts[0]
633607
if err := inst.err; err != nil {
634608
return nil, err
635609
}
@@ -729,12 +703,14 @@ func (b *buildPlan) parseFlags() (err error) {
729703
return nil
730704
}
731705

732-
func buildInstances(cmd *Command, binst []*build.Instance, ignoreErrors bool) []*instance {
706+
func buildInstances(cmd *Command, binst []*build.Instance, ignoreErrors bool) ([]*instance, error) {
733707
// TODO:
734708
// If there are no files and User is true, then use those?
735709
// Always use all files in user mode?
736710
instances, err := cmd.ctx.BuildInstances(binst)
737-
exitOnErr(cmd, err, true)
711+
if err != nil {
712+
return nil, err
713+
}
738714

739715
insts := make([]*instance, len(instances))
740716
for i, v := range instances {
@@ -748,15 +724,22 @@ func buildInstances(cmd *Command, binst []*build.Instance, ignoreErrors bool) []
748724
// TODO: remove ignoreErrors flag and always return here, leaving it up to
749725
// clients to check for errors down the road.
750726
if ignoreErrors || flagIgnore.Bool(cmd) {
751-
return insts
727+
return insts, nil
752728
}
753729

754730
for _, inst := range instances {
755731
// TODO: consider merging errors of multiple files, but ensure
756732
// duplicates are removed.
757-
exitOnErr(cmd, inst.Validate(), !flagIgnore.Bool(cmd))
733+
err := inst.Validate()
734+
if err != nil {
735+
if flagIgnore.Bool(cmd) {
736+
printError(cmd, err)
737+
} else {
738+
return nil, err
739+
}
740+
}
758741
}
759-
return insts
742+
return insts, nil
760743
}
761744

762745
func buildToolInstances(binst []*build.Instance) ([]*cue.Instance, error) {

cmd/cue/cmd/eval.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func runEval(cmd *Command, args []string) error {
132132
err := e.Encode(v)
133133
if err != nil {
134134
errHeader()
135-
exitOnErr(cmd, err, false)
135+
printError(cmd, err)
136136
}
137137
continue
138138
}
@@ -160,7 +160,7 @@ func runEval(cmd *Command, args []string) error {
160160
if e.IsConcrete() || flagConcrete.Bool(cmd) {
161161
if err := v.Validate(cue.Concrete(true)); err != nil {
162162
errHeader()
163-
exitOnErr(cmd, err, false)
163+
printError(cmd, err)
164164
continue
165165
}
166166
}
@@ -171,7 +171,7 @@ func runEval(cmd *Command, args []string) error {
171171
err := e.EncodeFile(f)
172172
if err != nil {
173173
errHeader()
174-
exitOnErr(cmd, err, false)
174+
printError(cmd, err)
175175
}
176176
}
177177
if err := iter.err(); err != nil {

cmd/cue/cmd/fmt.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,13 @@ given as explicit arguments.
7171
}
7272

7373
for _, inst := range builds {
74-
if inst.Err != nil {
74+
if err := inst.Err; err != nil {
7575
switch {
76-
case errors.As(inst.Err, new(*load.PackageError)) && len(inst.BuildFiles) != 0:
76+
case errors.As(err, new(*load.PackageError)) && len(inst.BuildFiles) != 0:
7777
// Ignore package errors if there are files to format.
78-
case errors.As(inst.Err, new(*load.NoFilesError)):
78+
case errors.As(err, new(*load.NoFilesError)):
7979
default:
80-
exitOnErr(cmd, inst.Err, false)
81-
continue
80+
return err
8281
}
8382
}
8483
for _, file := range inst.BuildFiles {
@@ -116,7 +115,9 @@ given as explicit arguments.
116115

117116
if path == "-" {
118117
contents, err := io.ReadAll(cmd.InOrStdin())
119-
exitOnErr(cmd, err, false)
118+
if err != nil {
119+
return err
120+
}
120121
file.Source = contents
121122
}
122123

@@ -131,13 +132,14 @@ given as explicit arguments.
131132
}
132133
for _, arg := range args {
133134
if arg == "-" {
134-
err := processFile(arg)
135-
exitOnErr(cmd, err, false)
135+
if err := processFile(arg); err != nil {
136+
return err
137+
}
136138
continue
137139
}
138140

139141
arg = filepath.Clean(arg)
140-
err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
142+
if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
141143
if err != nil {
142144
return err
143145
}
@@ -157,8 +159,9 @@ given as explicit arguments.
157159
}
158160

159161
return processFile(path)
160-
})
161-
exitOnErr(cmd, err, false)
162+
}); err != nil {
163+
return err
164+
}
162165
}
163166
}
164167

@@ -227,8 +230,9 @@ func formatFile(file *build.File, opts []format.Option, cwd string, doDiff, chec
227230
case file.Filename == "-":
228231
// already wrote the formatted source to stdout above
229232
default:
230-
err := os.WriteFile(file.Filename, formatted, 0644)
231-
exitOnErr(cmd, err, false)
233+
if err := os.WriteFile(file.Filename, formatted, 0644); err != nil {
234+
return false, err
235+
}
232236
}
233237
return true, nil
234238
}

cmd/cue/cmd/root.go

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"testing"
2626

2727
"github.com/spf13/cobra"
28+
"golang.org/x/text/message"
2829

2930
"cuelang.org/go/cue"
3031
"cuelang.org/go/cue/cuecontext"
@@ -300,7 +301,7 @@ type errWriter Command
300301

301302
func (w *errWriter) Write(b []byte) (int, error) {
302303
c := (*Command)(w)
303-
c.hasErr = true
304+
c.hasErr = len(b) > 0
304305
return c.Command.OutOrStderr().Write(b)
305306
}
306307

@@ -309,6 +310,9 @@ func (w *errWriter) Write(b []byte) (int, error) {
309310
// be used directly.
310311

311312
// Stderr returns a writer that should be used for error messages.
313+
// Writing to it will result in the command's exit code being 1.
314+
//
315+
// TODO(mvdan): provide an alternative to write to stderr without setting the exit code to 1.
312316
func (c *Command) Stderr() io.Writer {
313317
return (*errWriter)(c)
314318
}
@@ -331,17 +335,37 @@ func (c *Command) SetInput(r io.Reader) {
331335
c.root.SetIn(r)
332336
}
333337

334-
// ErrPrintedError indicates error messages have been printed to stderr.
338+
// ErrPrintedError indicates error messages have been printed directly to stderr,
339+
// and can be used so that the returned error itself isn't printed as well.
335340
var ErrPrintedError = errors.New("terminating because of errors")
336341

342+
// printError uses cue/errors to print an error to stderr when non-nil.
343+
func printError(cmd *Command, err error) {
344+
if err == nil {
345+
return
346+
}
347+
348+
// Link x/text as our localizer.
349+
p := message.NewPrinter(getLang())
350+
format := func(w io.Writer, format string, args ...interface{}) {
351+
p.Fprintf(w, format, args...)
352+
}
353+
354+
// TODO(mvdan): call os.Getwd only once at the start of the process.
355+
cwd, _ := os.Getwd()
356+
errors.Print(cmd.Stderr(), err, &errors.Config{
357+
Format: format,
358+
Cwd: cwd,
359+
ToSlash: testing.Testing(),
360+
})
361+
}
362+
337363
func (c *Command) Run(ctx context.Context) (err error) {
338364
// Three categories of commands:
339365
// - normal
340366
// - user defined
341367
// - help
342368
// For the latter two, we need to use the default loading.
343-
defer recoverError(&err)
344-
345369
if err := c.root.Execute(); err != nil {
346370
return err
347371
}
@@ -350,22 +374,3 @@ func (c *Command) Run(ctx context.Context) (err error) {
350374
}
351375
return nil
352376
}
353-
354-
func recoverError(err *error) {
355-
switch e := recover().(type) {
356-
case nil:
357-
case panicError:
358-
*err = e.Err
359-
default:
360-
panic(e)
361-
}
362-
// We use panic to escape, instead of os.Exit
363-
}
364-
365-
type panicError struct {
366-
Err error
367-
}
368-
369-
func panicExit() {
370-
panic(panicError{ErrPrintedError})
371-
}

cmd/cue/cmd/trim.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ func runTrim(cmd *Command, args []string) error {
100100
if binst == nil {
101101
return nil
102102
}
103-
instances := buildInstances(cmd, binst, false)
103+
instances, err := buildInstances(cmd, binst, false)
104+
if err != nil {
105+
return err
106+
}
104107

105108
dst := flagOutFile.String(cmd)
106109
if dst != "" && dst != "-" && !flagForce.Bool(cmd) {
@@ -130,7 +133,10 @@ func runTrim(cmd *Command, args []string) error {
130133

131134
cfg := *defCfg.loadCfg
132135
cfg.Overlay = overlay
133-
tinsts := buildInstances(cmd, load.Instances(args, &cfg), false)
136+
tinsts, err := buildInstances(cmd, load.Instances(args, &cfg), false)
137+
if err != nil {
138+
return err
139+
}
134140
if len(tinsts) != len(binst) {
135141
return errors.New("unexpected number of new instances")
136142
}

cmd/cue/cmd/vet.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func doVet(cmd *Command, args []string) error {
134134
"some instances are incomplete; use the -c flag to show errors or suppress this message")
135135
}
136136
}
137-
exitOnErr(cmd, err, false)
137+
printError(cmd, err)
138138
}
139139
if err := iter.err(); err != nil {
140140
return err
@@ -156,7 +156,7 @@ func vetFiles(cmd *Command, b *buildPlan) error {
156156

157157
// Always concrete when checking against concrete files.
158158
err := v.Validate(cue.Concrete(true))
159-
exitOnErr(cmd, err, false)
159+
printError(cmd, err)
160160
}
161161
if err := iter.err(); err != nil {
162162
return err

0 commit comments

Comments
 (0)