aboutsummaryrefslogtreecommitdiff
path: root/cmd/format.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/format.go')
-rw-r--r--cmd/format.go193
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)
+ }
+}