github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/views/view.go (about)

     1  package views
     2  
     3  import (
     4  	"github.com/iaas-resource-provision/iaas-rpc/internal/command/arguments"
     5  	"github.com/iaas-resource-provision/iaas-rpc/internal/command/format"
     6  	"github.com/iaas-resource-provision/iaas-rpc/internal/terminal"
     7  	"github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags"
     8  	"github.com/mitchellh/colorstring"
     9  )
    10  
    11  // View is the base layer for command views, encapsulating a set of I/O
    12  // streams, a colorize implementation, and implementing a human friendly view
    13  // for diagnostics.
    14  type View struct {
    15  	streams  *terminal.Streams
    16  	colorize *colorstring.Colorize
    17  
    18  	compactWarnings bool
    19  
    20  	// When this is true it's a hint that RPC is being run indirectly
    21  	// via a wrapper script or other automation and so we may wish to replace
    22  	// direct examples of commands to run with more conceptual directions.
    23  	// However, we only do this on a best-effort basis, typically prioritizing
    24  	// the messages that users are most likely to see.
    25  	runningInAutomation bool
    26  
    27  	// This unfortunate wart is required to enable rendering of diagnostics which
    28  	// have associated source code in the configuration. This function pointer
    29  	// will be dereferenced as late as possible when rendering diagnostics in
    30  	// order to access the config loader cache.
    31  	configSources func() map[string][]byte
    32  }
    33  
    34  // Initialize a View with the given streams, a disabled colorize object, and a
    35  // no-op configSources callback.
    36  func NewView(streams *terminal.Streams) *View {
    37  	return &View{
    38  		streams: streams,
    39  		colorize: &colorstring.Colorize{
    40  			Colors:  colorstring.DefaultColors,
    41  			Disable: true,
    42  			Reset:   true,
    43  		},
    44  		configSources: func() map[string][]byte { return nil },
    45  	}
    46  }
    47  
    48  // SetRunningInAutomation modifies the view's "running in automation" flag,
    49  // which causes some slight adjustments to certain messages that would normally
    50  // suggest specific RPC commands to run, to make more conceptual gestures
    51  // instead for situations where the user isn't running RPC directly.
    52  //
    53  // For convenient use during initialization (in conjunction with NewView),
    54  // SetRunningInAutomation returns the reciever after modifying it.
    55  func (v *View) SetRunningInAutomation(new bool) *View {
    56  	v.runningInAutomation = new
    57  	return v
    58  }
    59  
    60  func (v *View) RunningInAutomation() bool {
    61  	return v.runningInAutomation
    62  }
    63  
    64  // Configure applies the global view configuration flags.
    65  func (v *View) Configure(view *arguments.View) {
    66  	v.colorize.Disable = view.NoColor
    67  	v.compactWarnings = view.CompactWarnings
    68  }
    69  
    70  // SetConfigSources overrides the default no-op callback with a new function
    71  // pointer, and should be called when the config loader is initialized.
    72  func (v *View) SetConfigSources(cb func() map[string][]byte) {
    73  	v.configSources = cb
    74  }
    75  
    76  // Diagnostics renders a set of warnings and errors in human-readable form.
    77  // Warnings are printed to stdout, and errors to stderr.
    78  func (v *View) Diagnostics(diags tfdiags.Diagnostics) {
    79  	diags.Sort()
    80  
    81  	if len(diags) == 0 {
    82  		return
    83  	}
    84  
    85  	diags = diags.ConsolidateWarnings(1)
    86  
    87  	// Since warning messages are generally competing
    88  	if v.compactWarnings {
    89  		// If the user selected compact warnings and all of the diagnostics are
    90  		// warnings then we'll use a more compact representation of the warnings
    91  		// that only includes their summaries.
    92  		// We show full warnings if there are also errors, because a warning
    93  		// can sometimes serve as good context for a subsequent error.
    94  		useCompact := true
    95  		for _, diag := range diags {
    96  			if diag.Severity() != tfdiags.Warning {
    97  				useCompact = false
    98  				break
    99  			}
   100  		}
   101  		if useCompact {
   102  			msg := format.DiagnosticWarningsCompact(diags, v.colorize)
   103  			msg = "\n" + msg + "\nTo see the full warning notes, run RPC without -compact-warnings.\n"
   104  			v.streams.Print(msg)
   105  			return
   106  		}
   107  	}
   108  
   109  	for _, diag := range diags {
   110  		var msg string
   111  		if v.colorize.Disable {
   112  			msg = format.DiagnosticPlain(diag, v.configSources(), v.streams.Stderr.Columns())
   113  		} else {
   114  			msg = format.Diagnostic(diag, v.configSources(), v.colorize, v.streams.Stderr.Columns())
   115  		}
   116  
   117  		if diag.Severity() == tfdiags.Error {
   118  			v.streams.Eprint(msg)
   119  		} else {
   120  			v.streams.Print(msg)
   121  		}
   122  	}
   123  }
   124  
   125  // HelpPrompt is intended to be called from commands which fail to parse all
   126  // of their CLI arguments successfully. It refers users to the full help output
   127  // rather than rendering it directly, which can be overwhelming and confusing.
   128  func (v *View) HelpPrompt(command string) {
   129  	v.streams.Eprintf(helpPrompt, command)
   130  }
   131  
   132  const helpPrompt = `
   133  For more help on using this command, run:
   134    terraform %s -help
   135  `
   136  
   137  // outputColumns returns the number of text character cells any non-error
   138  // output should be wrapped to.
   139  //
   140  // This is the number of columns to use if you are calling v.streams.Print or
   141  // related functions.
   142  func (v *View) outputColumns() int {
   143  	return v.streams.Stdout.Columns()
   144  }
   145  
   146  // errorColumns returns the number of text character cells any error
   147  // output should be wrapped to.
   148  //
   149  // This is the number of columns to use if you are calling v.streams.Eprint
   150  // or related functions.
   151  func (v *View) errorColumns() int {
   152  	return v.streams.Stderr.Columns()
   153  }
   154  
   155  // outputHorizRule will call v.streams.Println with enough horizontal line
   156  // characters to fill an entire row of output.
   157  //
   158  // If UI color is enabled, the rule will get a dark grey coloring to try to
   159  // visually de-emphasize it.
   160  func (v *View) outputHorizRule() {
   161  	v.streams.Println(format.HorizontalRule(v.colorize, v.outputColumns()))
   162  }