From 37c866e1e16a6d2dded11ba93c2e04af3764a139 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Wed, 21 Jun 2017 10:21:21 -0700 Subject: cmd/fscrypt: setup, encrypt, unlock commands This commit adds in the framework for adding commands and subcommands to the fscrypt tool. This commit adds in the "setup", "encrypt", and "unlock" commands. Additional information can be found by running: fscrypt --help. This commit defines how flags are parsed and errors are handled. It also creates an extensible framework for prompting the user for information. Change-Id: I159d7f44ee2b2bbc5e072f0802850e082d9a13ce --- cmd/fscrypt/format.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 cmd/fscrypt/format.go (limited to 'cmd/fscrypt/format.go') diff --git a/cmd/fscrypt/format.go b/cmd/fscrypt/format.go new file mode 100644 index 0000000..39cc71f --- /dev/null +++ b/cmd/fscrypt/format.go @@ -0,0 +1,162 @@ +/* + * format.go - Contains all the functionality for formatting the command line + * output. This includes formatting the description and flags so that the whole + * text is <= LineLength characters. + * + * 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 main + +import ( + "bytes" + "fmt" + "os" + "regexp" + "strings" + "unicode/utf8" + + "github.com/urfave/cli" + "golang.org/x/crypto/ssh/terminal" + + "fscrypt/util" +) + +var ( + // lineLength is the maximum width of fscrypt's formatted output. It is + // usually the width of the terminal. + lineLength int + fallbackLineLength = 80 // fallback is punch cards + maxLineLength = 120 + // IndentLength is the number spaces to indent by. + indentLength = 2 + // length of the longest shortDisplay for a flag + maxShortDisplay int + // how much the a flag's usage text needs to be moved over + flagPaddingLength int +) + +// We use the init() function to compute our longest short display length. This +// is then used to compute the formatting and padding strings. This ensures we +// will always have room to display our flags, and the flag descriptions always +// appear in the same place. +func init() { + for _, flag := range allFlags { + displayLength := utf8.RuneCountInString(shortDisplay(flag)) + if displayLength > maxShortDisplay { + maxShortDisplay = displayLength + } + } + + // Pad usage enough so the flags have room. + flagPaddingLength = maxShortDisplay + 2*indentLength + + // We use the width of the terminal unless we cannot get the width. + width, _, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + lineLength = fallbackLineLength + } else { + lineLength = util.MinInt(width, maxLineLength) + } + +} + +// Flags that conform to this interface can be used with an urfave/cli +// application and can be printed in the correct format. +type prettyFlag interface { + cli.Flag + GetArgName() string + GetUsage() string +} + +// How a flag should appear on the command line. We have two formats: +// --name +// --name=ARG_NAME +// The ARG_NAME appears if the prettyFlag's GetArgName() method returns a +// non-empty string. The returned string from shortDisplay() does not include +// any leading or trailing whitespace. +func shortDisplay(f prettyFlag) string { + if argName := f.GetArgName(); argName != "" { + return fmt.Sprintf("--%s=%s", f.GetName(), argName) + } + return fmt.Sprintf("--%s", f.GetName()) +} + +// How our flags should appear when displaying their usage. An example would be: +// +// --help Prints help screen for commands and subcommands. +// +// If a default is specified, this if appended to the usage. Example: +// +// --legacy Allow for support of older kernels with ext4 +// (before v4.8) and F2FS (before v4.6) filesystems. +// (default: true) +// +func longDisplay(f prettyFlag, defaultString ...string) string { + usage := f.GetUsage() + if len(defaultString) > 0 { + usage += fmt.Sprintf(" (default: %v)", defaultString[0]) + } + + // We pad the the shortDisplay on the right with enough spaces to equal + // the longest flag's display + shortDisp := shortDisplay(f) + length := utf8.RuneCountInString(shortDisp) + shortDisp += strings.Repeat(" ", maxShortDisplay-length) + + return indent + shortDisp + indent + wrapText(usage, flagPaddingLength) +} + +// Regex that determines if we are starting an ordered list +var listRegex = regexp.MustCompile(`^\([\d]+\)$`) + +// Takes an input string text, and wraps the text so that each line begins with +// padding spaces (except for the first line), 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, padding 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 - padding + delimiter := strings.Repeat(" ", padding) + for i, word := range strings.Fields(text) { + wordLen := utf8.RuneCountInString(word) + switch { + case i == 0: + // No delimiter for the first line + buffer.WriteString(word) + spaceLeft = maxTextLen - wordLen + case listRegex.Match([]byte(word)): + // Add an additional line to separate list items. + buffer.WriteString("\n") + fallthrough + case wordLen+1 > spaceLeft: + // If no room left, write the word on the next line. + buffer.WriteString("\n") + buffer.WriteString(delimiter) + buffer.WriteString(word) + spaceLeft = maxTextLen - wordLen + default: + // Write word on this line + buffer.WriteByte(' ') + buffer.WriteString(word) + spaceLeft -= 1 + wordLen + } + } + + return buffer.String() +} -- cgit v1.2.3