cli: Add global view arguments parser

Rather than modifying and relying on the existing Meta.process
argument extractor, we can more clearly handle global CLI flags using
a separate parser step. This allows us to explicitly configure the view
in the command.
This commit is contained in:
Alisdair McDiarmid 2021-02-10 17:31:43 -05:00
parent c5a6aa31d3
commit 57879bfb71
6 changed files with 114 additions and 10 deletions

43
command/arguments/view.go Normal file
View File

@ -0,0 +1,43 @@
package arguments
// View represents the global command-line arguments which configure the view.
type View struct {
// NoColor is used to disable the use of terminal color codes in all
// output.
NoColor bool
// CompactWarnings is used to coalesce duplicate warnings, to reduce the
// level of noise when multiple instances of the same warning are raised
// for a configuration.
CompactWarnings bool
}
// ParseView processes CLI arguments, returning a View value and a
// possibly-modified slice of arguments. If any of the supported flags are
// found, they will be removed from the slice.
func ParseView(args []string) (*View, []string) {
common := &View{}
// Keep track of the length of the returned slice. When we find an
// argument we support, i will not be incremented.
i := 0
for _, v := range args {
switch v {
case "-no-color":
common.NoColor = true
case "-compact-warnings":
common.CompactWarnings = true
default:
// Unsupported argument: move left to the current position, and
// increment the index.
args[i] = v
i++
}
}
// Reduce the slice to the number of unsupported arguments. Any remaining
// to the right of i have already been moved left.
args = args[:i]
return common, args
}

View File

@ -0,0 +1,62 @@
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestParseView(t *testing.T) {
testCases := map[string]struct {
args []string
want *View
wantArgs []string
}{
"nil": {
nil,
&View{NoColor: false, CompactWarnings: false},
nil,
},
"empty": {
[]string{},
&View{NoColor: false, CompactWarnings: false},
[]string{},
},
"none matching": {
[]string{"-foo", "bar", "-baz"},
&View{NoColor: false, CompactWarnings: false},
[]string{"-foo", "bar", "-baz"},
},
"no-color": {
[]string{"-foo", "-no-color", "-baz"},
&View{NoColor: true, CompactWarnings: false},
[]string{"-foo", "-baz"},
},
"compact-warnings": {
[]string{"-foo", "-compact-warnings", "-baz"},
&View{NoColor: false, CompactWarnings: true},
[]string{"-foo", "-baz"},
},
"both": {
[]string{"-foo", "-no-color", "-compact-warnings", "-baz"},
&View{NoColor: true, CompactWarnings: true},
[]string{"-foo", "-baz"},
},
"both, resulting in empty args": {
[]string{"-no-color", "-compact-warnings"},
&View{NoColor: true, CompactWarnings: true},
[]string{},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotArgs := ParseView(tc.args)
if *got != *tc.want {
t.Errorf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
if !cmp.Equal(gotArgs, tc.wantArgs) {
t.Errorf("unexpected args\n got: %#v\nwant: %#v", gotArgs, tc.wantArgs)
}
})
}
}

View File

@ -621,10 +621,6 @@ func (m *Meta) process(args []string) []string {
},
}
if m.View != nil {
m.View.EnableColor(m.Color)
}
return args
}

View File

@ -17,7 +17,9 @@ type OutputCommand struct {
}
func (c *OutputCommand) Run(rawArgs []string) int {
rawArgs = c.Meta.process(rawArgs)
// Parse and apply global view arguments
common, rawArgs := arguments.ParseView(rawArgs)
c.View.Configure(common)
// Parse and validate flags
args, diags := arguments.ParseOutput(rawArgs)

View File

@ -149,6 +149,7 @@ func TestOutput_emptyOutputs(t *testing.T) {
}
args := []string{
"-no-color",
"-state", statePath,
}
code := c.Run(args)

View File

@ -3,6 +3,7 @@ package views
import (
"fmt"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/tfdiags"
@ -41,11 +42,10 @@ func NewView(streams *terminal.Streams) *View {
}
}
// EnableColor controls the colorize implementation used by this view (and any
// other views which depend on it). This is called by the code which processes
// the global -no-color CLI flag, which happens after the view is initialized.
func (v *View) EnableColor(color bool) {
v.colorize.Disable = !color
// Configure applies the global view configuration flags.
func (v *View) Configure(view *arguments.View) {
v.colorize.Disable = view.NoColor
v.compactWarnings = view.CompactWarnings
}
// SetConfigSources overrides the default no-op callback with a new function