]> git.lizzy.rs Git - micro.git/blob - cmd/micro/util.go
Merge pull request #489 from november-eleven/refactor/plugin-name
[micro.git] / cmd / micro / util.go
1 package main
2
3 import (
4         "bytes"
5         "os"
6         "path/filepath"
7         "reflect"
8         "runtime"
9         "strconv"
10         "strings"
11         "time"
12         "unicode/utf8"
13
14         "github.com/mattn/go-runewidth"
15 )
16
17 // Util.go is a collection of utility functions that are used throughout
18 // the program
19
20 // Count returns the length of a string in runes
21 // This is exactly equivalent to utf8.RuneCountInString(), just less characters
22 func Count(s string) int {
23         return utf8.RuneCountInString(s)
24 }
25
26 // NumOccurrences counts the number of occurrences of a byte in a string
27 func NumOccurrences(s string, c byte) int {
28         var n int
29         for i := 0; i < len(s); i++ {
30                 if s[i] == c {
31                         n++
32                 }
33         }
34         return n
35 }
36
37 // Spaces returns a string with n spaces
38 func Spaces(n int) string {
39         var str string
40         for i := 0; i < n; i++ {
41                 str += " "
42         }
43         return str
44 }
45
46 // Min takes the min of two ints
47 func Min(a, b int) int {
48         if a > b {
49                 return b
50         }
51         return a
52 }
53
54 // Max takes the max of two ints
55 func Max(a, b int) int {
56         if a > b {
57                 return a
58         }
59         return b
60 }
61
62 // IsWordChar returns whether or not the string is a 'word character'
63 // If it is a unicode character, then it does not match
64 // Word characters are defined as [A-Za-z0-9_]
65 func IsWordChar(str string) bool {
66         if len(str) > 1 {
67                 // Unicode
68                 return true
69         }
70         c := str[0]
71         return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
72 }
73
74 // IsWhitespace returns true if the given rune is a space, tab, or newline
75 func IsWhitespace(c rune) bool {
76         return c == ' ' || c == '\t' || c == '\n'
77 }
78
79 // Contains returns whether or not a string array contains a given string
80 func Contains(list []string, a string) bool {
81         for _, b := range list {
82                 if b == a {
83                         return true
84                 }
85         }
86         return false
87 }
88
89 // Insert makes a simple insert into a string at the given position
90 func Insert(str string, pos int, value string) string {
91         return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
92 }
93
94 // MakeRelative will attempt to make a relative path between path and base
95 func MakeRelative(path, base string) (string, error) {
96         if len(path) > 0 {
97                 rel, err := filepath.Rel(base, path)
98                 if err != nil {
99                         return path, err
100                 }
101                 return rel, nil
102         }
103         return path, nil
104 }
105
106 // GetLeadingWhitespace returns the leading whitespace of the given string
107 func GetLeadingWhitespace(str string) string {
108         ws := ""
109         for _, c := range str {
110                 if c == ' ' || c == '\t' {
111                         ws += string(c)
112                 } else {
113                         break
114                 }
115         }
116         return ws
117 }
118
119 // IsSpaces checks if a given string is only spaces
120 func IsSpaces(str string) bool {
121         for _, c := range str {
122                 if c != ' ' {
123                         return false
124                 }
125         }
126
127         return true
128 }
129
130 // IsSpacesOrTabs checks if a given string contains only spaces and tabs
131 func IsSpacesOrTabs(str string) bool {
132         for _, c := range str {
133                 if c != ' ' && c != '\t' {
134                         return false
135                 }
136         }
137
138         return true
139 }
140
141 // ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
142 // as 'true' and 'false' respectively
143 func ParseBool(str string) (bool, error) {
144         if str == "on" {
145                 return true, nil
146         }
147         if str == "off" {
148                 return false, nil
149         }
150         return strconv.ParseBool(str)
151 }
152
153 // EscapePath replaces every path separator in a given path with a %
154 func EscapePath(path string) string {
155         path = filepath.ToSlash(path)
156         return strings.Replace(path, "/", "%", -1)
157 }
158
159 // GetModTime returns the last modification time for a given file
160 // It also returns a boolean if there was a problem accessing the file
161 func GetModTime(path string) (time.Time, bool) {
162         info, err := os.Stat(path)
163         if err != nil {
164                 return time.Now(), false
165         }
166         return info.ModTime(), true
167 }
168
169 // StringWidth returns the width of a string where tabs count as `tabsize` width
170 func StringWidth(str string, tabsize int) int {
171         sw := runewidth.StringWidth(str)
172         lineIdx := 0
173         for _, ch := range str {
174                 switch ch {
175                 case '\t':
176                         ts := tabsize - (lineIdx % tabsize)
177                         sw += ts - 1
178                         lineIdx += ts
179                 case '\n':
180                         lineIdx = 0
181                 default:
182                         lineIdx++
183                 }
184         }
185         return sw
186 }
187
188 // WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
189 // that have a width larger than 1 (this also counts tabs as `tabsize` width)
190 func WidthOfLargeRunes(str string, tabsize int) int {
191         count := 0
192         lineIdx := 0
193         for _, ch := range str {
194                 var w int
195                 if ch == '\t' {
196                         w = tabsize - (lineIdx % tabsize)
197                 } else {
198                         w = runewidth.RuneWidth(ch)
199                 }
200                 if w > 1 {
201                         count += (w - 1)
202                 }
203                 if ch == '\n' {
204                         lineIdx = 0
205                 } else {
206                         lineIdx += w
207                 }
208         }
209         return count
210 }
211
212 // RunePos returns the rune index of a given byte index
213 // This could cause problems if the byte index is between code points
214 func runePos(p int, str string) int {
215         return utf8.RuneCountInString(str[:p])
216 }
217
218 func lcs(a, b string) string {
219         arunes := []rune(a)
220         brunes := []rune(b)
221
222         lcs := ""
223         for i, r := range arunes {
224                 if i >= len(brunes) {
225                         break
226                 }
227                 if r == brunes[i] {
228                         lcs += string(r)
229                 } else {
230                         break
231                 }
232         }
233         return lcs
234 }
235
236 func CommonSubstring(arr ...string) string {
237         commonStr := arr[0]
238
239         for _, str := range arr[1:] {
240                 commonStr = lcs(commonStr, str)
241         }
242
243         return commonStr
244 }
245
246 // Abs is a simple absolute value function for ints
247 func Abs(n int) int {
248         if n < 0 {
249                 return -n
250         }
251         return n
252 }
253
254 // FuncName returns the name of a given function object
255 func FuncName(i interface{}) string {
256         return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
257 }
258
259 // SplitCommandArgs separates multiple command arguments which may be quoted.
260 // The returned slice contains at least one string
261 func SplitCommandArgs(input string) []string {
262         var result []string
263         var curQuote *bytes.Buffer
264
265         curArg := new(bytes.Buffer)
266         escape := false
267
268         finishQuote := func() {
269                 if curQuote == nil {
270                         return
271                 }
272                 str := curQuote.String()
273                 if unquoted, err := strconv.Unquote(str); err == nil {
274                         str = unquoted
275                 }
276                 curArg.WriteString(str)
277                 curQuote = nil
278         }
279
280         appendResult := func() {
281                 finishQuote()
282                 escape = false
283
284                 str := curArg.String()
285                 result = append(result, str)
286                 curArg.Reset()
287         }
288
289         for _, r := range input {
290                 if r == ' ' && curQuote == nil {
291                         appendResult()
292                 } else {
293                         runeHandled := false
294                         appendRuneToBuff := func() {
295                                 if curQuote != nil {
296                                         curQuote.WriteRune(r)
297                                 } else {
298                                         curArg.WriteRune(r)
299                                 }
300                                 runeHandled = true
301                         }
302
303                         if r == '"' && curQuote == nil {
304                                 curQuote = new(bytes.Buffer)
305                                 appendRuneToBuff()
306                         } else {
307                                 if curQuote != nil && !escape {
308                                         if r == '"' {
309                                                 appendRuneToBuff()
310                                                 finishQuote()
311                                         } else if r == '\\' {
312                                                 appendRuneToBuff()
313                                                 escape = true
314                                                 continue
315                                         }
316                                 }
317                         }
318                         if !runeHandled {
319                                 appendRuneToBuff()
320                         }
321                 }
322
323                 escape = false
324         }
325         appendResult()
326         return result
327 }
328
329 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
330 func JoinCommandArgs(args ...string) string {
331         buf := new(bytes.Buffer)
332         first := true
333         for _, arg := range args {
334                 if first {
335                         first = false
336                 } else {
337                         buf.WriteRune(' ')
338                 }
339                 quoted := strconv.Quote(arg)
340                 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
341                         buf.WriteString(quoted)
342                 } else {
343                         buf.WriteString(arg)
344                 }
345         }
346
347         return buf.String()
348 }