github.com/mutagen-io/mutagen@v0.18.0-rc1/cmd/output.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/fatih/color"
     8  
     9  	"github.com/mutagen-io/mutagen/pkg/prompting"
    10  )
    11  
    12  // StatusLinePrinter provides printing facilities for dynamically updating
    13  // status lines in the console. It supports colorized printing.
    14  type StatusLinePrinter struct {
    15  	// UseStandardError causes the printer to use standard error for its output
    16  	// instead of standard output (the default).
    17  	UseStandardError bool
    18  	// Color, if non-nil, will be used for colorizing output (if possible).
    19  	Color *color.Color
    20  	// populated indicates whether or not the printer has printed any non-empty
    21  	// content to the status line.
    22  	populated bool
    23  }
    24  
    25  // Print prints a message to the status line, overwriting any existing content.
    26  // Color escape sequences are supported. Messages will be truncated to a
    27  // platform-dependent maximum length and padded appropriately.
    28  func (p *StatusLinePrinter) Print(message string) {
    29  	// Determine the output stream to use. We print to color-supporting output
    30  	// streams to ensure that color escape sequences are properly handled.
    31  	output := color.Output
    32  	if p.UseStandardError {
    33  		output = color.Error
    34  	}
    35  
    36  	// Print the message.
    37  	if p.Color != nil {
    38  		p.Color.Fprintf(output, statusLineFormat, message)
    39  	} else {
    40  		fmt.Fprintf(output, statusLineFormat, message)
    41  	}
    42  
    43  	// Update our populated status. The line is always populated in this case
    44  	// because even an empty message will be padded with spaces.
    45  	// TODO: We could possibly make this more precise, e.g. tracking whether or
    46  	// not message is empty or contains only spaces. In cases like these,
    47  	// BreakIfPopulated could potentially just return the cursor to the beginning
    48  	// of the line instead of printing a newline. But it's a bit unclear what
    49  	// the semantics of this should look like, what types of whitespace should
    50  	// be classified as empty, etc. For example, an empty line might be used as
    51  	// a visual delimiter, or a message could contain tabs and/or newlines.
    52  	p.populated = true
    53  }
    54  
    55  // Clear clears any content on the status line and moves the cursor back to the
    56  // beginning of the line.
    57  func (p *StatusLinePrinter) Clear() {
    58  	// Determine the output stream to use.
    59  	output := os.Stdout
    60  	if p.UseStandardError {
    61  		output = os.Stderr
    62  	}
    63  
    64  	// Wipe out any existing content and return the cursor to the beginning of
    65  	// the line.
    66  	fmt.Fprintf(output, statusLineClearFormat, "")
    67  
    68  	// Update our populated status.
    69  	p.populated = false
    70  }
    71  
    72  // BreakIfPopulated prints a newline character if the current line is non-empty.
    73  func (p *StatusLinePrinter) BreakIfPopulated() {
    74  	// Only perform an operation if the status line is populated with content.
    75  	if p.populated {
    76  		// Determine the output stream to use.
    77  		output := os.Stdout
    78  		if p.UseStandardError {
    79  			output = os.Stderr
    80  		}
    81  
    82  		// Print a line break.
    83  		fmt.Fprintln(output)
    84  
    85  		// Update our populated status.
    86  		p.populated = false
    87  	}
    88  }
    89  
    90  // StatusLinePrompter adapts a StatusLinePrinter to act as a Mutagen prompter.
    91  // The printer will be used to perform messaging and PromptCommandLine will be
    92  // used to perform prompting.
    93  type StatusLinePrompter struct {
    94  	// Printer is the underlying printer.
    95  	Printer *StatusLinePrinter
    96  }
    97  
    98  // Message implements prompting.Prompter.Message.
    99  func (p *StatusLinePrompter) Message(message string) error {
   100  	// Print the message.
   101  	p.Printer.Print(message)
   102  
   103  	// Success.
   104  	return nil
   105  }
   106  
   107  // Prompt implements prompting.Prompter.Prompt.
   108  func (p *StatusLinePrompter) Prompt(message string) (string, error) {
   109  	// If there's any existing content in the printer, then keep it in place and
   110  	// start a new line of output. We do this (as opposed to clearing the line)
   111  	// because that content most likely provides some context for the prompt.
   112  	//
   113  	// HACK: This is somewhat of a heuristic that relies on knowledge of how
   114  	// Mutagen's internal prompting/messaging works in practice.
   115  	p.Printer.BreakIfPopulated()
   116  
   117  	// Perform command line prompting.
   118  	//
   119  	// TODO: Should we respect the printer's UseStandardError field here? The
   120  	// gopass package (used by the prompting package) doesn't provide a way to
   121  	// specify its output stream, so there's not a trivial way to implement it,
   122  	// but in practice the UseStandardError field is only used for daemon
   123  	// auto-start output to avoid corrupting output streams in formatted list
   124  	// and monitor commands (which won't generate prompts), so there's no case
   125  	// at the moment where ignoring the UseStandardError setting causes issues.
   126  	return prompting.PromptCommandLine(message)
   127  }