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