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