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