blob: 93cb7ca31b11b64daee10bc410c0415cee3173de [file] [log] [blame]
package msg
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"sync"
"github.com/Masterminds/vcs"
)
// Messenger provides the underlying implementation that displays output to
// users.
type Messenger struct {
sync.Mutex
// Quiet, if true, suppresses chatty levels, like Info.
Quiet bool
// IsDebugging, if true, shows Debug.
IsDebugging bool
// NoColor, if true, will not use color in the output.
NoColor bool
// Stdout is the location where this prints output.
Stdout io.Writer
// Stderr is the location where this prints logs.
Stderr io.Writer
// Stdin is the location where input is read.
Stdin io.Reader
// PanicOnDie if true Die() will panic instead of exiting.
PanicOnDie bool
// The default exit code to use when dyping
ecode int
// If an error was been sent.
hasErrored bool
}
// NewMessenger creates a default Messenger to display output.
func NewMessenger() *Messenger {
m := &Messenger{
Quiet: false,
IsDebugging: false,
NoColor: false,
Stdout: os.Stdout,
Stderr: os.Stderr,
Stdin: os.Stdin,
PanicOnDie: false,
ecode: 1,
}
return m
}
// Default contains a default Messenger used by package level functions
var Default = NewMessenger()
// Info logs information
func (m *Messenger) Info(msg string, args ...interface{}) {
if m.Quiet {
return
}
prefix := m.Color(Green, "[INFO]\t")
m.Msg(prefix+msg, args...)
}
// Info logs information using the Default Messenger
func Info(msg string, args ...interface{}) {
Default.Info(msg, args...)
}
// Debug logs debug information
func (m *Messenger) Debug(msg string, args ...interface{}) {
if m.Quiet || !m.IsDebugging {
return
}
prefix := "[DEBUG]\t"
m.Msg(prefix+msg, args...)
}
// Debug logs debug information using the Default Messenger
func Debug(msg string, args ...interface{}) {
Default.Debug(msg, args...)
}
// Warn logs a warning
func (m *Messenger) Warn(msg string, args ...interface{}) {
prefix := m.Color(Yellow, "[WARN]\t")
m.Msg(prefix+msg, args...)
}
// Warn logs a warning using the Default Messenger
func Warn(msg string, args ...interface{}) {
Default.Warn(msg, args...)
}
// Err logs an error.
func (m *Messenger) Err(msg string, args ...interface{}) {
prefix := m.Color(Red, "[ERROR]\t")
m.Msg(prefix+msg, args...)
m.hasErrored = true
}
// Err logs anderror using the Default Messenger
func Err(msg string, args ...interface{}) {
Default.Err(msg, args...)
}
// Die prints an error message and immediately exits the application.
// If PanicOnDie is set to true a panic will occur instead of os.Exit being
// called.
func (m *Messenger) Die(msg string, args ...interface{}) {
m.Err(msg, args...)
if m.PanicOnDie {
panic("trapped a Die() call")
}
os.Exit(m.ecode)
}
// Die prints an error message and immediately exits the application using the
// Default Messenger. If PanicOnDie is set to true a panic will occur instead of
// os.Exit being called.
func Die(msg string, args ...interface{}) {
Default.Die(msg, args...)
}
// ExitCode sets the exit code used by Die.
//
// The default is 1.
//
// Returns the old error code.
func (m *Messenger) ExitCode(e int) int {
m.Lock()
old := m.ecode
m.ecode = e
m.Unlock()
return old
}
// ExitCode sets the exit code used by Die using the Default Messenger.
//
// The default is 1.
//
// Returns the old error code.
func ExitCode(e int) int {
return Default.ExitCode(e)
}
// Msg prints a message with optional arguments, that can be printed, of
// varying types.
func (m *Messenger) Msg(msg string, args ...interface{}) {
// When operations in Glide are happening concurrently messaging needs to be
// locked to avoid displaying one message in the middle of another one.
m.Lock()
defer m.Unlock()
// Get rid of the annoying fact that messages need \n at the end, but do
// it in a backward compatible way.
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}
if len(args) == 0 {
fmt.Fprint(m.Stderr, msg)
} else {
fmt.Fprintf(m.Stderr, msg, args...)
}
// If an arg is a vcs error print the output if in debug mode. This is
// capured here rather than calling Debug because concurrent operations
// could cause other messages to appear between the initial error and the
// debug output by unlocking and calling Debug.
if len(args) != 0 && !m.Quiet && m.IsDebugging {
if err, ok := args[len(args)-1].(error); ok {
switch t := err.(type) {
case *vcs.LocalError:
fmt.Fprintf(m.Stderr, "[DEBUG]\tOutput was: %s", strings.TrimSpace(t.Out()))
case *vcs.RemoteError:
fmt.Fprintf(m.Stderr, "[DEBUG]\tOutput was: %s", strings.TrimSpace(t.Out()))
}
}
}
}
// Msg prints a message with optional arguments, that can be printed, of
// varying types using the Default Messenger.
func Msg(msg string, args ...interface{}) {
Default.Msg(msg, args...)
}
// Puts formats a message and then prints to Stdout.
//
// It does not prefix the message, does not color it, or otherwise decorate it.
//
// It does add a line feed.
func (m *Messenger) Puts(msg string, args ...interface{}) {
// When operations in Glide are happening concurrently messaging needs to be
// locked to avoid displaying one message in the middle of another one.
m.Lock()
defer m.Unlock()
fmt.Fprintf(m.Stdout, msg, args...)
fmt.Fprintln(m.Stdout)
}
// Puts formats a message and then prints to Stdout using the Default Messenger.
//
// It does not prefix the message, does not color it, or otherwise decorate it.
//
// It does add a line feed.
func Puts(msg string, args ...interface{}) {
Default.Puts(msg, args...)
}
// Print prints exactly the string given.
//
// It prints to Stdout.
func (m *Messenger) Print(msg string) {
// When operations in Glide are happening concurrently messaging needs to be
// locked to avoid displaying one message in the middle of another one.
m.Lock()
defer m.Unlock()
fmt.Fprint(m.Stdout, msg)
}
// Print prints exactly the string given using the Default Messenger.
//
// It prints to Stdout.
func Print(msg string) {
Default.Print(msg)
}
// HasErrored returns if Error has been called.
//
// This is useful if you want to known if Error was called to exit with a
// non-zero exit code.
func (m *Messenger) HasErrored() bool {
return m.hasErrored
}
// HasErrored returns if Error has been called on the Default Messenger.
//
// This is useful if you want to known if Error was called to exit with a
// non-zero exit code.
func HasErrored() bool {
return Default.HasErrored()
}
// Color returns a string in a certain color if colors are enabled and
// available on that platform.
func Color(code, msg string) string {
return Default.Color(code, msg)
}
// PromptUntil provides a prompt until one of the passed in strings has been
// entered and return is hit. Note, the comparisons are case insensitive meaning
// Y == y. The returned value is the one from the passed in options (same case).
func (m *Messenger) PromptUntil(opts []string) (string, error) {
reader := bufio.NewReader(os.Stdin)
for {
text, err := reader.ReadString('\n')
if err != nil {
return "", err
}
for _, c := range opts {
if strings.EqualFold(c, strings.TrimSpace(text)) {
return c, nil
}
}
}
}
// PromptUntil provides a prompt until one of the passed in strings has been
// entered and return is hit. Note, the comparisons are case insensitive meaning
// Y == y. The returned value is the one from the passed in options (same case).
// Uses the default setup.
func PromptUntil(opts []string) (string, error) {
return Default.PromptUntil(opts)
}
// PromptUntilYorN provides a prompt until the user chooses yes or no. This is
// not case sensitive and they can input other options such as Y or N.
// In the response Yes is bool true and No is bool false.
func (m *Messenger) PromptUntilYorN() bool {
res, err := m.PromptUntil([]string{"y", "yes", "n", "no"})
if err != nil {
m.Die("Error processing response: %s", err)
}
if res == "y" || res == "yes" {
return true
}
return false
}
// PromptUntilYorN provides a prompt until the user chooses yes or no. This is
// not case sensitive and they can input other options such as Y or N.
// In the response Yes is bool true and No is bool false.
// Uses the default setup.
func PromptUntilYorN() bool {
return Default.PromptUntilYorN()
}