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