Skip to content

Commit

Permalink
🚸 Improved formatting of flags help menu (#1259)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatiasFrank authored Oct 3, 2024
1 parent 0d55c68 commit 5732e38
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 12 deletions.
57 changes: 47 additions & 10 deletions cmd/common/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"strconv"
"strings"

"github.com/fatih/color"
"github.com/rigdev/rig/pkg/utils"
"github.com/spf13/pflag"
"golang.org/x/term"
)
Expand Down Expand Up @@ -90,7 +92,8 @@ func groupedFlagUsages(cmd *pflag.FlagSet) string {
s := fmt.Sprintf(" %s Flags\n", name)
buf.Write([]byte(s))
}
buf.Write([]byte(wrappedFlagUsages(group.flags)))
s := flagUsages(group.flags)
buf.Write([]byte(s))
if idx != len(groups)-1 {
buf.Write([]byte("\n"))
}
Expand All @@ -99,19 +102,53 @@ func groupedFlagUsages(cmd *pflag.FlagSet) string {
return buf.String()
}

// Stolen from https://github.com/vmware-tanzu/community-edition/blob/138acbf49d492815d7f72055db0186c43888ae15/cli/cmd/plugin/unmanaged-cluster/cmd/utils.go#L74-L113
// Uses the users terminal size or width of 80 if cannot determine users width
//
//nolint:lll
func wrappedFlagUsages(cmd *pflag.FlagSet) string {
const maxWidth = 120

func flagUsages(flags *pflag.FlagSet) string {
fd := int(os.Stdout.Fd())
width := 80

// Get the terminal width and dynamically set
termWidth, _, err := term.GetSize(fd)
if err == nil {
width = termWidth
width = min(termWidth, maxWidth)
}

return cmd.FlagUsagesWrapped(width - 1)
var buffer bytes.Buffer
indent := " "
numFlags := 0
flags.VisitAll(func(_ *pflag.Flag) { numFlags++ })
idx := 0
flags.VisitAll(func(flag *pflag.Flag) {
idx++
if flag.Hidden {
return
}
header := ""
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
header = fmt.Sprintf("%s-%s, --%s", indent, flag.Shorthand, flag.Name)
} else {
header = fmt.Sprintf("%s--%s", indent, flag.Name)
}
if flag.NoOptDefVal != "" {
switch flag.Value.Type() {
case "string":
header += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
case "bool":
if flag.NoOptDefVal != "true" {
header += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
case "count":
if flag.NoOptDefVal != "+1" {
header += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
default:
header += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
}
color.New(color.Bold).Fprintln(&buffer, header+":")
buffer.WriteString(utils.WordWrap(flag.Usage, width, indent+indent))
if idx < numFlags {
buffer.WriteString("\n")
}
})
return buffer.String()
}
3 changes: 1 addition & 2 deletions cmd/common/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Flags:
{{- if .HasAvailableInheritedFlags}}
Global Flags:
{{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}
{{groupedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}
{{- end}}
{{- if .HasHelpSubCommands}}
Expand All @@ -54,7 +54,6 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
`

func SetupRoot(cmd *cobra.Command) {
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
cobra.AddTemplateFunc("groupedFlagUsages", groupedFlagUsages)
cmd.SetUsageTemplate(usageTemplate)

Expand Down
91 changes: 91 additions & 0 deletions pkg/utils/wrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package utils

import (
"bytes"
"strings"
"unicode"
)

func WordWrap(s string, width int, indent string) string {
var buffer bytes.Buffer

lines := strings.Split(s, "\n")

for _, line := range lines {
wrapLine(line, width, indent, &buffer)
}

return buffer.String()
}

func wrapLine(line string, width int, indent string, buffer *bytes.Buffer) {
if len(line) == 0 {
buffer.WriteString("\n")
return
}

var units []unit
curUnit := unit{
isWhitespace: unicode.IsSpace(rune(line[0])),
}
startIdx := 0
for idx := 0; idx < len(line); idx++ {
if unicode.IsSpace(rune(line[idx])) != curUnit.isWhitespace {
curUnit.s = line[startIdx:idx]
units = append(units, curUnit)
curUnit = unit{isWhitespace: !curUnit.isWhitespace}
startIdx = idx
}
}
curUnit.s = line[startIdx:]
units = append(units, curUnit)

curLineLength := 0
var curLine []unit
for idx, unit := range units {
if curLineLength+len(unit.s)+len(indent) > width {
writeUnits(curLine, width, indent, buffer)
curLineLength, curLine = 0, nil
}
if !unit.isWhitespace || idx == 0 || curLineLength != 0 {
curLine = append(curLine, unit)
curLineLength += len(unit.s)
}
}
writeUnits(curLine, width, indent, buffer)
}

func writeUnits(units []unit, width int, indent string, buffer *bytes.Buffer) {
if units[len(units)-1].isWhitespace {
units = units[:len(units)-1]
}

w := 0
for _, u := range units {
w += len(u.s)
}
missing := width - w - len(indent)

// Justify line
if missing < 10 {
idx := 0
for missing > 0 {
if !units[idx].isWhitespace && idx != len(units)-1 {
units[idx].s += " "
missing--
}
idx = (idx + 1) % len(units)
}
}

buffer.WriteString(indent)
for _, u := range units {
buffer.WriteString(u.s)
}
buffer.WriteString("\n")
}

type unit struct {
s string
isWhitespace bool
}
40 changes: 40 additions & 0 deletions pkg/utils/wrap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package utils

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_WordWrap(t *testing.T) {
//nolint:lll
s := `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
Here are some bullet points:
- point 1: hej
- point 2: hej again
- point 3: word word word word word word word word word word word word word word word word word word word word word`

expected := `Lorem Ipsum is simply dummy text of the printing and
typesetting industry. Lorem Ipsum has been the industry's
standard dummy text ever since the 1500s, when an unknown
printer took a galley of type and scrambled it to make a
type specimen book.
It has survived not only five centuries, but also the leap
into electronic typesetting, remaining essentially
unchanged. It was popularised in the 1960s with the release
of Letraset sheets containing Lorem Ipsum passages, and more
recently with desktop publishing software like Aldus
PageMaker including versions of Lorem Ipsum.
Here are some bullet points:
- point 1: hej
- point 2: hej again
- point 3: word word word word word word word word word
word word word word word word word word word word word word
`

wrapped := WordWrap(s, 60, "")
require.Equal(t, expected, wrapped)
}

0 comments on commit 5732e38

Please sign in to comment.