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 "os" 23 "reflect" 24 "strconv" 25 "strings" 26 "text/template" 27 "time" 28 "unicode" 29) 30 31var templateFuncs = template.FuncMap{ 32 "trim": strings.TrimSpace, 33 "trimRightSpace": trimRightSpace, 34 "trimTrailingWhitespaces": trimRightSpace, 35 "appendIfNotPresent": appendIfNotPresent, 36 "rpad": rpad, 37 "gt": Gt, 38 "eq": Eq, 39} 40 41var initializers []func() 42 43// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing 44// to automatically enable in CLI tools. 45// Set this to true to enable it. 46var EnablePrefixMatching = false 47 48// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default. 49// To disable sorting, set it to false. 50var EnableCommandSorting = true 51 52// MousetrapHelpText enables an information splash screen on Windows 53// if the CLI is started from explorer.exe. 54// To disable the mousetrap, just set this variable to blank string (""). 55// Works only on Microsoft Windows. 56var MousetrapHelpText = `This is a command line tool. 57 58You need to open cmd.exe and run it from there. 59` 60 61// MousetrapDisplayDuration controls how long the MousetrapHelpText message is displayed on Windows 62// if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed. 63// To disable the mousetrap, just set MousetrapHelpText to blank string (""). 64// Works only on Microsoft Windows. 65var MousetrapDisplayDuration = 5 * time.Second 66 67// AddTemplateFunc adds a template function that's available to Usage and Help 68// template generation. 69func AddTemplateFunc(name string, tmplFunc interface{}) { 70 templateFuncs[name] = tmplFunc 71} 72 73// AddTemplateFuncs adds multiple template functions that are available to Usage and 74// Help template generation. 75func AddTemplateFuncs(tmplFuncs template.FuncMap) { 76 for k, v := range tmplFuncs { 77 templateFuncs[k] = v 78 } 79} 80 81// OnInitialize sets the passed functions to be run when each command's 82// Execute method is called. 83func OnInitialize(y ...func()) { 84 initializers = append(initializers, y...) 85} 86 87// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. 88 89// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans, 90// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as 91// ints and then compared. 92func Gt(a interface{}, b interface{}) bool { 93 var left, right int64 94 av := reflect.ValueOf(a) 95 96 switch av.Kind() { 97 case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: 98 left = int64(av.Len()) 99 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 100 left = av.Int() 101 case reflect.String: 102 left, _ = strconv.ParseInt(av.String(), 10, 64) 103 } 104 105 bv := reflect.ValueOf(b) 106 107 switch bv.Kind() { 108 case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: 109 right = int64(bv.Len()) 110 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 111 right = bv.Int() 112 case reflect.String: 113 right, _ = strconv.ParseInt(bv.String(), 10, 64) 114 } 115 116 return left > right 117} 118 119// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. 120 121// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic. 122func Eq(a interface{}, b interface{}) bool { 123 av := reflect.ValueOf(a) 124 bv := reflect.ValueOf(b) 125 126 switch av.Kind() { 127 case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: 128 panic("Eq called on unsupported type") 129 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 130 return av.Int() == bv.Int() 131 case reflect.String: 132 return av.String() == bv.String() 133 } 134 return false 135} 136 137func trimRightSpace(s string) string { 138 return strings.TrimRightFunc(s, unicode.IsSpace) 139} 140 141// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. 142 143// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s. 144func appendIfNotPresent(s, stringToAppend string) string { 145 if strings.Contains(s, stringToAppend) { 146 return s 147 } 148 return s + " " + stringToAppend 149} 150 151// rpad adds padding to the right of a string. 152func rpad(s string, padding int) string { 153 template := fmt.Sprintf("%%-%ds", padding) 154 return fmt.Sprintf(template, s) 155} 156 157// tmpl executes the given template text on data, writing the result to w. 158func tmpl(w io.Writer, text string, data interface{}) error { 159 t := template.New("top") 160 t.Funcs(templateFuncs) 161 template.Must(t.Parse(text)) 162 return t.Execute(w, data) 163} 164 165// ld compares two strings and returns the levenshtein distance between them. 166func ld(s, t string, ignoreCase bool) int { 167 if ignoreCase { 168 s = strings.ToLower(s) 169 t = strings.ToLower(t) 170 } 171 d := make([][]int, len(s)+1) 172 for i := range d { 173 d[i] = make([]int, len(t)+1) 174 } 175 for i := range d { 176 d[i][0] = i 177 } 178 for j := range d[0] { 179 d[0][j] = j 180 } 181 for j := 1; j <= len(t); j++ { 182 for i := 1; i <= len(s); i++ { 183 if s[i-1] == t[j-1] { 184 d[i][j] = d[i-1][j-1] 185 } else { 186 min := d[i-1][j] 187 if d[i][j-1] < min { 188 min = d[i][j-1] 189 } 190 if d[i-1][j-1] < min { 191 min = d[i-1][j-1] 192 } 193 d[i][j] = min + 1 194 } 195 } 196 197 } 198 return d[len(s)][len(t)] 199} 200 201func stringInSlice(a string, list []string) bool { 202 for _, b := range list { 203 if b == a { 204 return true 205 } 206 } 207 return false 208} 209 210// CheckErr prints the msg with the prefix 'Error:' and exits with error code 1. If the msg is nil, it does nothing. 211func CheckErr(msg interface{}) { 212 if msg != nil { 213 fmt.Fprintln(os.Stderr, "Error:", msg) 214 os.Exit(1) 215 } 216} 217 218// WriteStringAndCheck writes a string into a buffer, and checks if the error is not nil. 219func WriteStringAndCheck(b io.StringWriter, s string) { 220 _, err := b.WriteString(s) 221 CheckErr(err) 222} 223