func (ui *UI) UserFriendlyDate(input time.Time) string {
	return input.Local().Format("Mon 02 Jan 15:04:05 MST 2006")
}
UserFriendlyDate converts the time to UTC and then formats it to ISO8601.
package ui
import (
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"time"
	"code.cloudfoundry.org/cli/command/translatableerror"
	"code.cloudfoundry.org/cli/util/configv3"
	"github.com/fatih/color"
	"github.com/lunixbochs/vtclean"
	runewidth "github.com/mattn/go-runewidth"
	"github.com/vito/go-interact/interact"
)
const LogTimestampFormat = "2006-01-02T15:04:05.00-0700"
const DefaultTableSpacePadding = 3
type Config interface {
	ColorEnabled() configv3.ColorSetting
	Locale() string
	IsTTY() bool
	TerminalWidth() int
}
type LogMessage interface {
	Message() string
	Type() string
	Timestamp() time.Time
	SourceType() string
	SourceInstance() string
}
type UI struct {
	In io.Reader
	Out io.Writer
	OutForInteration io.Writer
	Err io.Writer
	colorEnabled configv3.ColorSetting
	translate    TranslateFunc
	terminalLock *sync.Mutex
	fileLock     *sync.Mutex
	IsTTY         bool
	TerminalWidth int
	TimezoneLocation *time.Location
}
func NewUI(config Config) (*UI, error) {
	translateFunc, err := GetTranslationFunc(config)
	if err != nil {
		return nil, err
	}
	location := time.Now().Location()
	return &UI{
		In:               os.Stdin,
		Out:              color.Output,
		OutForInteration: os.Stdout,
		Err:              os.Stderr,
		colorEnabled:     config.ColorEnabled(),
		translate:        translateFunc,
		terminalLock:     &sync.Mutex{},
		fileLock:         &sync.Mutex{},
		IsTTY:            config.IsTTY(),
		TerminalWidth:    config.TerminalWidth(),
		TimezoneLocation: location,
	}, nil
}
func NewTestUI(in io.Reader, out io.Writer, err io.Writer) *UI {
	translationFunc, translateErr := generateTranslationFunc([]byte("[]"))
	if translateErr != nil {
		panic(translateErr)
	}
	return &UI{
		In:               in,
		Out:              out,
		OutForInteration: out,
		Err:              err,
		colorEnabled:     configv3.ColorDisabled,
		translate:        translationFunc,
		terminalLock:     &sync.Mutex{},
		fileLock:         &sync.Mutex{},
		TimezoneLocation: time.UTC,
	}
}
func (ui *UI) GetIn() io.Reader {
	return ui.In
}
func (ui *UI) GetOut() io.Writer {
	return ui.Out
}
func (ui *UI) GetErr() io.Writer {
	return ui.Err
}
func (ui *UI) DisplayBoolPrompt(defaultResponse bool, template string, templateValues ...map[string]interface{}) (bool, error) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	response := defaultResponse
	interactivePrompt := interact.NewInteraction(ui.TranslateText(template, templateValues...))
	interactivePrompt.Input = ui.In
	interactivePrompt.Output = ui.OutForInteration
	err := interactivePrompt.Resolve(&response)
	return response, err
}
func (ui *UI) DisplayPasswordPrompt(template string, templateValues ...map[string]interface{}) (string, error) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	var password interact.Password
	interactivePrompt := interact.NewInteraction(ui.TranslateText(template, templateValues...))
	interactivePrompt.Input = ui.In
	interactivePrompt.Output = ui.OutForInteration
	err := interactivePrompt.Resolve(interact.Required(&password))
	return string(password), err
}
func (ui *UI) DisplayError(err error) {
	var errMsg string
	if translatableError, ok := err.(translatableerror.TranslatableError); ok {
		errMsg = translatableError.Translate(ui.translate)
	} else {
		errMsg = err.Error()
	}
	fmt.Fprintf(ui.Err, "%s\n", errMsg)
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	fmt.Fprintf(ui.Out, "%s\n", ui.modifyColor(ui.TranslateText("FAILED"), color.New(color.FgRed, color.Bold)))
}
func (ui *UI) DisplayHeader(text string) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	fmt.Fprintf(ui.Out, "%s\n", ui.modifyColor(ui.TranslateText(text), color.New(color.Bold)))
}
func (ui *UI) DisplayKeyValueTable(prefix string, table [][]string, padding int) {
	rows := len(table)
	if rows == 0 {
		return
	}
	columns := len(table[0])
	if columns < 2 || !ui.IsTTY {
		ui.DisplayNonWrappingTable(prefix, table, padding)
		return
	}
	ui.displayWrappingTableWithWidth(prefix, table, padding)
}
func (ui *UI) DisplayLogMessage(message LogMessage, displayHeader bool) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	var header string
	if displayHeader {
		time := message.Timestamp().In(ui.TimezoneLocation).Format(LogTimestampFormat)
		header = fmt.Sprintf("%s [%s/%s] %s ",
			time,
			message.SourceType(),
			message.SourceInstance(),
			message.Type(),
		)
	}
	for _, line := range strings.Split(message.Message(), "\n") {
		logLine := fmt.Sprintf("%s%s", header, strings.TrimRight(line, "\r\n"))
		if message.Type() == "ERR" {
			logLine = ui.modifyColor(logLine, color.New(color.FgRed))
		}
		fmt.Fprintf(ui.Out, "   %s\n", logLine)
	}
}
func (ui *UI) DisplayNewline() {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	fmt.Fprintf(ui.Out, "\n")
}
func (ui *UI) DisplayNonWrappingTable(prefix string, table [][]string, padding int) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	if len(table) == 0 {
		return
	}
	var columnPadding []int
	rows := len(table)
	columns := len(table[0])
	for col := 0; col < columns; col++ {
		var max int
		for row := 0; row < rows; row++ {
			if strLen := wordSize(table[row][col]); max < strLen {
				max = strLen
			}
		}
		columnPadding = append(columnPadding, max+padding)
	}
	for row := 0; row < rows; row++ {
		fmt.Fprintf(ui.Out, prefix)
		for col := 0; col < columns; col++ {
			data := table[row][col]
			var addedPadding int
			if col+1 != columns {
				addedPadding = columnPadding[col] - wordSize(data)
			}
			fmt.Fprintf(ui.Out, "%s%s", data, strings.Repeat(" ", addedPadding))
		}
		fmt.Fprintf(ui.Out, "\n")
	}
}
func (ui *UI) DisplayOK() {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	fmt.Fprintf(ui.Out, "%s\n", ui.modifyColor(ui.TranslateText("OK"), color.New(color.FgGreen, color.Bold)))
}
func (ui *UI) DisplayTableWithHeader(prefix string, table [][]string, padding int) {
	if len(table) == 0 {
		return
	}
	for i, str := range table[0] {
		table[0][i] = ui.modifyColor(str, color.New(color.Bold))
	}
	ui.DisplayNonWrappingTable(prefix, table, padding)
}
func (ui *UI) DisplayText(template string, templateValues ...map[string]interface{}) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	fmt.Fprintf(ui.Out, "%s\n", ui.TranslateText(template, templateValues...))
}
func (ui *UI) DisplayTextWithFlavor(template string, templateValues ...map[string]interface{}) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	firstTemplateValues := getFirstSet(templateValues)
	for key, value := range firstTemplateValues {
		firstTemplateValues[key] = ui.modifyColor(fmt.Sprint(value), color.New(color.FgCyan, color.Bold))
	}
	fmt.Fprintf(ui.Out, "%s\n", ui.TranslateText(template, firstTemplateValues))
}
func (ui *UI) DisplayTextWithBold(template string, templateValues ...map[string]interface{}) {
	ui.terminalLock.Lock()
	defer ui.terminalLock.Unlock()
	firstTemplateValues := getFirstSet(templateValues)
	for key, value := range firstTemplateValues {
		firstTemplateValues[key] = ui.modifyColor(fmt.Sprint(value), color.New(color.Bold))
	}
	fmt.Fprintf(ui.Out, "%s\n", ui.TranslateText(template, firstTemplateValues))
}
func (ui *UI) DisplayWarning(template string, templateValues ...map[string]interface{}) {
	fmt.Fprintf(ui.Err, "%s\n", ui.TranslateText(template, templateValues...))
}
func (ui *UI) DisplayWarnings(warnings []string) {
	for _, warning := range warnings {
		fmt.Fprintf(ui.Err, "%s\n", ui.TranslateText(warning))
	}
}
func (ui *UI) RequestLoggerFileWriter(filePaths []string) *RequestLoggerFileWriter {
	return newRequestLoggerFileWriter(ui, ui.fileLock, filePaths)
}
func (ui *UI) RequestLoggerTerminalDisplay() *RequestLoggerTerminalDisplay {
	return newRequestLoggerTerminalDisplay(ui, ui.terminalLock)
}
func (ui *UI) TranslateText(template string, templateValues ...map[string]interface{}) string {
	return ui.translate(template, getFirstSet(templateValues))
}