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 }