cobra.go

  1// Copyright © 2013 Steve Francia <spf@spf13.com>.
  2//
  3// Licensed under the Apache License, Version 2.0 (the "License");
  4// you may not use this file except in compliance with the License.
  5// You may obtain a copy of the License at
  6// http://www.apache.org/licenses/LICENSE-2.0
  7//
  8// Unless required by applicable law or agreed to in writing, software
  9// distributed under the License is distributed on an "AS IS" BASIS,
 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11// See the License for the specific language governing permissions and
 12// limitations under the License.
 13
 14// Commands similar to git, go tools and other modern CLI tools
 15// inspired by go, go-Commander, gh and subcommand
 16
 17package cobra
 18
 19import (
 20	"fmt"
 21	"io"
 22	"reflect"
 23	"strconv"
 24	"strings"
 25	"text/template"
 26	"unicode"
 27)
 28
 29var templateFuncs = template.FuncMap{
 30	"trim":                    strings.TrimSpace,
 31	"trimRightSpace":          trimRightSpace,
 32	"trimTrailingWhitespaces": trimRightSpace,
 33	"appendIfNotPresent":      appendIfNotPresent,
 34	"rpad":                    rpad,
 35	"gt":                      Gt,
 36	"eq":                      Eq,
 37}
 38
 39var initializers []func()
 40
 41// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
 42// to automatically enable in CLI tools.
 43// Set this to true to enable it.
 44var EnablePrefixMatching = false
 45
 46// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
 47// To disable sorting, set it to false.
 48var EnableCommandSorting = true
 49
 50// MousetrapHelpText enables an information splash screen on Windows
 51// if the CLI is started from explorer.exe.
 52// To disable the mousetrap, just set this variable to blank string ("").
 53// Works only on Microsoft Windows.
 54var MousetrapHelpText string = `This is a command line tool.
 55
 56You need to open cmd.exe and run it from there.
 57`
 58
 59// AddTemplateFunc adds a template function that's available to Usage and Help
 60// template generation.
 61func AddTemplateFunc(name string, tmplFunc interface{}) {
 62	templateFuncs[name] = tmplFunc
 63}
 64
 65// AddTemplateFuncs adds multiple template functions that are available to Usage and
 66// Help template generation.
 67func AddTemplateFuncs(tmplFuncs template.FuncMap) {
 68	for k, v := range tmplFuncs {
 69		templateFuncs[k] = v
 70	}
 71}
 72
 73// OnInitialize sets the passed functions to be run when each command's
 74// Execute method is called.
 75func OnInitialize(y ...func()) {
 76	initializers = append(initializers, y...)
 77}
 78
 79// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
 80
 81// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
 82// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
 83// ints and then compared.
 84func Gt(a interface{}, b interface{}) bool {
 85	var left, right int64
 86	av := reflect.ValueOf(a)
 87
 88	switch av.Kind() {
 89	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
 90		left = int64(av.Len())
 91	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 92		left = av.Int()
 93	case reflect.String:
 94		left, _ = strconv.ParseInt(av.String(), 10, 64)
 95	}
 96
 97	bv := reflect.ValueOf(b)
 98
 99	switch bv.Kind() {
100	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
101		right = int64(bv.Len())
102	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
103		right = bv.Int()
104	case reflect.String:
105		right, _ = strconv.ParseInt(bv.String(), 10, 64)
106	}
107
108	return left > right
109}
110
111// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
112
113// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
114func Eq(a interface{}, b interface{}) bool {
115	av := reflect.ValueOf(a)
116	bv := reflect.ValueOf(b)
117
118	switch av.Kind() {
119	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
120		panic("Eq called on unsupported type")
121	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
122		return av.Int() == bv.Int()
123	case reflect.String:
124		return av.String() == bv.String()
125	}
126	return false
127}
128
129func trimRightSpace(s string) string {
130	return strings.TrimRightFunc(s, unicode.IsSpace)
131}
132
133// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
134
135// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
136func appendIfNotPresent(s, stringToAppend string) string {
137	if strings.Contains(s, stringToAppend) {
138		return s
139	}
140	return s + " " + stringToAppend
141}
142
143// rpad adds padding to the right of a string.
144func rpad(s string, padding int) string {
145	template := fmt.Sprintf("%%-%ds", padding)
146	return fmt.Sprintf(template, s)
147}
148
149// tmpl executes the given template text on data, writing the result to w.
150func tmpl(w io.Writer, text string, data interface{}) error {
151	t := template.New("top")
152	t.Funcs(templateFuncs)
153	template.Must(t.Parse(text))
154	return t.Execute(w, data)
155}
156
157// ld compares two strings and returns the levenshtein distance between them.
158func ld(s, t string, ignoreCase bool) int {
159	if ignoreCase {
160		s = strings.ToLower(s)
161		t = strings.ToLower(t)
162	}
163	d := make([][]int, len(s)+1)
164	for i := range d {
165		d[i] = make([]int, len(t)+1)
166	}
167	for i := range d {
168		d[i][0] = i
169	}
170	for j := range d[0] {
171		d[0][j] = j
172	}
173	for j := 1; j <= len(t); j++ {
174		for i := 1; i <= len(s); i++ {
175			if s[i-1] == t[j-1] {
176				d[i][j] = d[i-1][j-1]
177			} else {
178				min := d[i-1][j]
179				if d[i][j-1] < min {
180					min = d[i][j-1]
181				}
182				if d[i-1][j-1] < min {
183					min = d[i-1][j-1]
184				}
185				d[i][j] = min + 1
186			}
187		}
188
189	}
190	return d[len(s)][len(t)]
191}
192
193func stringInSlice(a string, list []string) bool {
194	for _, b := range list {
195		if b == a {
196			return true
197		}
198	}
199	return false
200}