Improve template mechanisms

* Delete Eq, Gt, appendIfNotPresent and trim functions

* Add "[flags]" in UseLine

* Simplify other functions

* Simplify templates

Minor performance improvement.
Benchmark for command with 4 flags and one child command:
benchmark                   old ns/op     new ns/op     delta
BenchmarkCmdUsageFunc-4     335860        319290        -4.93%

benchmark                   old allocs     new allocs     delta
BenchmarkCmdUsageFunc-4     562            543            -3.38%

benchmark                   old bytes     new bytes     delta
BenchmarkCmdUsageFunc-4     21623         21037         -2.71%
This commit is contained in:
Albert Nigmatzianov 2017-04-23 14:55:23 +02:00
parent d83a1d7ccd
commit 4d2c4afa04
2 changed files with 31 additions and 94 deletions

View file

@ -19,20 +19,14 @@ package cobra
import ( import (
"fmt" "fmt"
"io" "io"
"reflect"
"strconv"
"strings" "strings"
"text/template" "text/template"
"unicode" "unicode"
) )
var templateFuncs = template.FuncMap{ var templateFuncs = template.FuncMap{
"trim": strings.TrimSpace, "trimTrailingWhitespaces": trimTrailingWhitespaces,
"trimRightSpace": trimRightSpace,
"appendIfNotPresent": appendIfNotPresent,
"rpad": rpad, "rpad": rpad,
"gt": Gt,
"eq": Eq,
} }
var initializers []func() var initializers []func()
@ -65,64 +59,10 @@ func OnInitialize(y ...func()) {
initializers = append(initializers, y...) initializers = append(initializers, y...)
} }
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans, func trimTrailingWhitespaces(s string) string {
// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
// ints and then compared.
func Gt(a interface{}, b interface{}) bool {
var left, right int64
av := reflect.ValueOf(a)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
left = int64(av.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
left = av.Int()
case reflect.String:
left, _ = strconv.ParseInt(av.String(), 10, 64)
}
bv := reflect.ValueOf(b)
switch bv.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
right = int64(bv.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
right = bv.Int()
case reflect.String:
right, _ = strconv.ParseInt(bv.String(), 10, 64)
}
return left > right
}
// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
func Eq(a interface{}, b interface{}) bool {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
panic("Eq called on unsupported type")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return av.Int() == bv.Int()
case reflect.String:
return av.String() == bv.String()
}
return false
}
func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace) return strings.TrimRightFunc(s, unicode.IsSpace)
} }
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
func appendIfNotPresent(s, stringToAppend string) string {
if strings.Contains(s, stringToAppend) {
return s
}
return s + " " + stringToAppend
}
// rpad adds padding to the right of a string. // rpad adds padding to the right of a string.
func rpad(s string, padding int) string { func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding) template := fmt.Sprintf("%%-%ds", padding)
@ -131,10 +71,8 @@ func rpad(s string, padding int) string {
// tmpl executes the given template text on data, writing the result to w. // tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) error { func tmpl(w io.Writer, text string, data interface{}) error {
t := template.New("top") t := template.New("top").Funcs(templateFuncs)
t.Funcs(templateFuncs) return template.Must(t.Parse(text)).Execute(w, data)
template.Must(t.Parse(text))
return t.Execute(w, data)
} }
// ld compares two strings and returns the levenshtein distance between them. // ld compares two strings and returns the levenshtein distance between them.

View file

@ -330,23 +330,23 @@ func (c *Command) UsageTemplate() string {
return c.parent.UsageTemplate() return c.parent.UsageTemplate()
} }
return `Usage:{{if .Runnable}} return `Usage:{{if .Runnable}}
{{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases: Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}} {{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples: Examples:
{{ .Example }}{{end}}{{if .HasAvailableSubCommands}} {{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags: Flags:
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasAvailableInheritedFlags}} {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags: Global Flags:
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
@ -364,7 +364,7 @@ func (c *Command) HelpTemplate() string {
if c.HasParent() { if c.HasParent() {
return c.parent.HelpTemplate() return c.parent.HelpTemplate()
} }
return `{{with or .Long .Short }}{{. | trim}} return `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
} }
@ -863,34 +863,34 @@ func (c *Command) Print(i ...interface{}) {
// Println is a convenience method to Println to the defined output, fallback to Stderr if not set. // Println is a convenience method to Println to the defined output, fallback to Stderr if not set.
func (c *Command) Println(i ...interface{}) { func (c *Command) Println(i ...interface{}) {
str := fmt.Sprintln(i...) c.Print(fmt.Sprintln(i...))
c.Print(str)
} }
// Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set. // Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set.
func (c *Command) Printf(format string, i ...interface{}) { func (c *Command) Printf(format string, i ...interface{}) {
str := fmt.Sprintf(format, i...) c.Print(fmt.Sprintf(format, i...))
c.Print(str)
} }
// CommandPath returns the full path to this command. // CommandPath returns the full path to this command.
func (c *Command) CommandPath() string { func (c *Command) CommandPath() string {
str := c.Name() if c.HasParent() {
x := c return c.Parent().CommandPath() + " " + c.Name()
for x.HasParent() {
str = x.parent.Name() + " " + str
x = x.parent
} }
return str return c.Name()
} }
// UseLine puts out the full usage for a given command (including parents). // UseLine puts out the full usage for a given command (including parents).
func (c *Command) UseLine() string { func (c *Command) UseLine() string {
str := "" var useline string
if c.HasParent() { if c.HasParent() {
str = c.parent.CommandPath() + " " useline = c.parent.CommandPath() + " " + c.Use
} else {
useline = c.Use
} }
return str + c.Use if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
useline += " [flags]"
}
return useline
} }
// DebugFlags used to determine which flags have been assigned to which commands // DebugFlags used to determine which flags have been assigned to which commands
@ -936,15 +936,14 @@ func (c *Command) DebugFlags() {
// Name returns the command's name: the first word in the use line. // Name returns the command's name: the first word in the use line.
func (c *Command) Name() string { func (c *Command) Name() string {
if c.name != "" { if c.name == "" {
return c.name
}
name := c.Use name := c.Use
i := strings.Index(name, " ") i := strings.Index(name, " ")
if i >= 0 { if i >= 0 {
name = name[:i] name = name[:i]
} }
c.name = name c.name = name
}
return c.name return c.name
} }