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