// Copyright 2016-2018, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmdutil import ( "bufio" "fmt" "os" "runtime" "strconv" "strings" "golang.org/x/crypto/ssh/terminal" "github.com/pulumi/pulumi/pkg/util/ciutil" ) // Emoji controls whether emojis will by default be printed in the output. // While some Linux systems can display Emoji's in the terminal by default, we restrict this to just macOS, like Yarn. var Emoji = (runtime.GOOS == "darwin") // EmojiOr returns the emoji string e if emojis are enabled, or the string or if emojis are disabled. func EmojiOr(e, or string) string { if Emoji && Interactive() { return e } return or } // DisableInteractive may be set to true in order to disable prompts. This is useful when running in a non-attended // scenario, such as in continuous integration, or when using the Pulumi CLI/SDK in a programmatic way. var DisableInteractive bool // Interactive returns true if we should be running in interactive mode. That is, we have an interactive terminal // session, interactivity hasn't been explicitly disabled, and we're not running in a known CI system. func Interactive() bool { return !DisableInteractive && InteractiveTerminal() && !ciutil.IsCI() } // InteractiveTerminal returns true if the current terminal session is interactive. func InteractiveTerminal() bool { // If there's a 'TERM' variable and the terminal is 'dumb', then disable interactive mode. if v := strings.ToLower(os.Getenv("TERM")); v == "dumb" { return false } // if we're piping in stdin, we're clearly not interactive, as there's no way for a user to // provide input. If we're piping stdout, we also can't be interactive as there's no way for // users to see prompts to interact with them. return terminal.IsTerminal(int(os.Stdin.Fd())) && terminal.IsTerminal(int(os.Stdout.Fd())) } // ReadConsole reads the console with the given prompt text. func ReadConsole(prompt string) (string, error) { if prompt != "" { fmt.Print(prompt + ": ") } reader := bufio.NewReader(os.Stdin) raw, err := reader.ReadString('\n') if err != nil { return "", err } return RemoveTrailingNewline(raw), nil } // IsTruthy returns true if the given string represents a CLI input interpreted as "true". func IsTruthy(s string) bool { return s == "1" || strings.EqualFold(s, "true") } // RemoveTrailingNewline removes a trailing newline from a string. On windows, we'll remove either \r\n or \n, on other // platforms, we just remove \n. func RemoveTrailingNewline(s string) string { s = strings.TrimSuffix(s, "\n") if runtime.GOOS == "windows" { s = strings.TrimSuffix(s, "\r") } return s } type Table struct { Headers []string Rows []TableRow // Rows of the table. Prefix string // Optional prefix to print before each row } // TableRow is a row in a table we want to print. It can be a series of a columns, followed // by an additional line of information. type TableRow struct { Columns []string // Columns of the row AdditionalInfo string // an optional line of information to print after the row } // PrintTable prints a grid of rows and columns. Width of columns is automatically determined by // the max length of the items in each column. A default gap of two spaces is printed between each // column. func PrintTable(table Table) { PrintTableWithGap(table, " ") } // PrintTableWithGap prints a grid of rows and columns. Width of columns is automatically determined // by the max length of the items in each column. A gap can be specified between the columns. func PrintTableWithGap(table Table, columnGap string) { columnCount := len(table.Headers) // Figure out the preferred column width for each column. It will be set to the max length of // any item in that column. preferredColumnWidths := make([]int, columnCount) allRows := []TableRow{{ Columns: table.Headers, }} allRows = append(allRows, table.Rows...) for rowIndex, row := range allRows { columns := row.Columns if len(columns) != len(preferredColumnWidths) { panic(fmt.Sprintf( "Error printing table. Column count of row %v didn't match header column count. %v != %v", rowIndex, len(columns), len(preferredColumnWidths))) } for columnIndex, val := range columns { preferredColumnWidths[columnIndex] = max(preferredColumnWidths[columnIndex], len(val)) } } format := "" for i, maxWidth := range preferredColumnWidths { if i < len(preferredColumnWidths)-1 { format += "%-" + strconv.Itoa(maxWidth+len(columnGap)) + "s" } else { // do not want whitespace appended to the last column. It would cause wrapping on lines // that were not actually long if some other line was very long. format += "%s" } } format += "\n" columns := make([]interface{}, columnCount) for _, row := range allRows { for columnIndex, value := range row.Columns { // Now, ensure we have the requested gap between columns as well. if columnIndex < columnCount-1 { value += columnGap } columns[columnIndex] = value } fmt.Printf(table.Prefix+format, columns...) if row.AdditionalInfo != "" { fmt.Print(row.AdditionalInfo) } } } func max(a, b int) int { if a > b { return a } return b }