diff options
| author | Joseph Richey <joerichey94@gmail.com> | 2017-10-16 10:50:53 -0700 |
|---|---|---|
| committer | Joseph Richey <joerichey94@gmail.com> | 2017-10-19 02:22:25 -0700 |
| commit | b5cc60b2b974645f0d09721c292cd243d049cbcf (patch) | |
| tree | fbc44d87ba8dd83e2238ff8e217ea560f56b3091 /cmd/format.go | |
| parent | b4299090c3e503ba0c49a6086b1a46c218ca45f4 (diff) | |
Refactor almost complete
Diffstat (limited to 'cmd/format.go')
| -rw-r--r-- | cmd/format.go | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/cmd/format.go b/cmd/format.go new file mode 100644 index 0000000..69fd0e9 --- /dev/null +++ b/cmd/format.go @@ -0,0 +1,193 @@ +/* + * format.go - Functions for handling output formatting. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * 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 cmd + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "text/template" + "unicode/utf8" + + "github.com/google/fscrypt/util" + "golang.org/x/crypto/ssh/terminal" +) + +// Suffixes for questions with a yes or no default +const ( + defaultYesSuffix = "[Y/n]" + defaultNoSuffix = "[y/N]" +) + +// Variables which control how output is formmatted and where it goes. +var ( + // TabWidth is the number of spaces used to display a tab. + TabWidth = 8 + // LineLength is the maximum length of any output. If not set, the width + // of the terminal be detected and assigned to LineLength. + LineLength int + // FallbackLineLength is the LineLength used if detection fails. By + // default we fall back to punch cards. + FallbackLineLength = 80 + // MaxLineLength is the maximum allowed detected value of LineLength. + MaxLineLength = 120 + // Output is the io.Writer all commands should use for their normal + // output (errors should just return the appropriate error). If not set, + // it is automatically set based on the provided flags. + Output io.Writer +) + +// We use the width of the terminal unless we cannot get the width. +func init() { + if LineLength > 0 { + return + } + width, _, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + LineLength = FallbackLineLength + } else { + LineLength = util.MinInt(width, MaxLineLength) + } +} + +// Takes an input string text, and wraps the text so that each line begins with +// numTabs tabs (except the first line) and ends with a newline (except the last +// line), and each line has length less than lineLength. If the text contains a +// word which is too long, that word gets its own line. +func wrapText(text string, numTabs int) string { + // We use a buffer to format the wrapped text so we get O(n) runtime + var buffer bytes.Buffer + spaceLeft := 0 + maxTextLen := LineLength - numTabs*TabWidth + delimiter := strings.Repeat("\t", numTabs) + for i, word := range strings.Fields(text) { + wordLen := utf8.RuneCountInString(word) + if i == 0 { + buffer.WriteString(word) + spaceLeft = maxTextLen - wordLen + } else if wordLen >= spaceLeft { + // If no room left, write the word on the next line. + buffer.WriteString("\n") + buffer.WriteString(delimiter) + buffer.WriteString(word) + spaceLeft = maxTextLen - wordLen + } else { + // Write word on this line + buffer.WriteByte(' ') + buffer.WriteString(word) + spaceLeft -= 1 + wordLen + } + } + + return buffer.String() +} + +// Add words to this map if pluralization does not just involve adding an s. +var plurals = map[string]string{ + "policy": "policies", +} + +// Pluralize returns the correct pluralization of a work along with the +// specified count. This means Pluralize(1, "policy") = "1 policy" but +// Pluralize(2, "policy") = "2 policies". +func Pluralize(count int, word string) string { + if count == 1 { + return fmt.Sprintf("%d %s", count, word) + } + if plural, ok := plurals[word]; ok { + return fmt.Sprintf("%d %s", count, plural) + } + return fmt.Sprintf("%d %ss", count, word) +} + +// AskQuestion asks the user a yes or no question. Returning a boolean on a +// successful answer and an error if there was not a response from the user. +// Returns the defaultChoice on empty input (or in quiet mode). +func AskQuestion(question string, defaultChoice bool) (bool, error) { + // If in quiet mode, we just use the default. + if QuietFlag.Value { + return defaultChoice, nil + } + // Loop until failure or valid input. + for { + if defaultChoice { + fmt.Fprintf(Output, "%s %s ", question, defaultYesSuffix) + } else { + fmt.Fprintf(Output, "%s %s ", question, defaultNoSuffix) + } + + input, err := util.ReadLine() + if err != nil { + return false, err + } + + switch strings.ToLower(input) { + case "y", "yes": + return true, nil + case "n", "no": + return false, nil + case "": + return defaultChoice, nil + } + } +} + +// AskConfirmation asks the user for confirmation before performing a specific +// action. An error is returned if the user declines or IO fails. +func AskConfirmation(question, warning string, defaultChoice bool) error { + // All confirmations are "yes" if we are forcing. + if ForceFlag.Value { + return nil + } + + // Defaults of "no" require forcing. + if QuietFlag.Value { + if defaultChoice { + return nil + } + return ErrMustForce + } + + if warning != "" { + fmt.Fprintln(Output, wrapText("WARNING: "+warning, 0)) + } + + confirmed, err := AskQuestion(question, defaultChoice) + if err != nil { + return err + } + if !confirmed { + return ErrCanceled + } + return nil +} + +// ExecuteTemplate creates an anonymous template from the text, and runs it with +// the provided Context and writer. Panics if text cannot be executed. +func ExecuteTemplate(w io.Writer, text string, ctx *Context) { + tmpl := template.Must(template.New("").Funcs(template.FuncMap{ + "wrapText": wrapText, + }).Parse(text)) + if err := tmpl.Execute(w, ctx); err != nil { + panic(err) + } +} |