14 "github.com/mattn/go-runewidth"
17 // Util.go is a collection of utility functions that are used throughout
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)
26 // NumOccurrences counts the number of occurrences of a byte in a string
27 func NumOccurrences(s string, c byte) int {
29 for i := 0; i < len(s); i++ {
37 // Spaces returns a string with n spaces
38 func Spaces(n int) string {
40 for i := 0; i < n; i++ {
46 // Min takes the min of two ints
47 func Min(a, b int) int {
54 // Max takes the max of two ints
55 func Max(a, b int) int {
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 {
71 return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
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'
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 {
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:])
94 // MakeRelative will attempt to make a relative path between path and base
95 func MakeRelative(path, base string) (string, error) {
97 rel, err := filepath.Rel(base, path)
106 // GetLeadingWhitespace returns the leading whitespace of the given string
107 func GetLeadingWhitespace(str string) string {
109 for _, c := range str {
110 if c == ' ' || c == '\t' {
119 // IsSpaces checks if a given string is only spaces
120 func IsSpaces(str string) bool {
121 for _, c := range str {
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' {
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) {
150 return strconv.ParseBool(str)
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)
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)
164 return time.Now(), false
166 return info.ModTime(), true
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)
173 for _, ch := range str {
176 ts := tabsize - (lineIdx % tabsize)
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 {
193 for _, ch := range str {
196 w = tabsize - (lineIdx % tabsize)
198 w = runewidth.RuneWidth(ch)
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])
218 func lcs(a, b string) string {
223 for i, r := range arunes {
224 if i >= len(brunes) {
236 func CommonSubstring(arr ...string) string {
239 for _, str := range arr[1:] {
240 commonStr = lcs(commonStr, str)
246 // Abs is a simple absolute value function for ints
247 func Abs(n int) int {
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()
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 {
263 var curQuote *bytes.Buffer
265 curArg := new(bytes.Buffer)
268 finishQuote := func() {
272 str := curQuote.String()
273 if unquoted, err := strconv.Unquote(str); err == nil {
276 curArg.WriteString(str)
280 appendResult := func() {
284 str := curArg.String()
285 result = append(result, str)
289 for _, r := range input {
290 if r == ' ' && curQuote == nil {
294 appendRuneToBuff := func() {
296 curQuote.WriteRune(r)
303 if r == '"' && curQuote == nil {
304 curQuote = new(bytes.Buffer)
307 if curQuote != nil && !escape {
311 } else if r == '\\' {
329 // JoinCommandArgs joins multiple command arguments and quote the strings if needed.
330 func JoinCommandArgs(args ...string) string {
331 buf := new(bytes.Buffer)
333 for _, arg := range args {
339 quoted := strconv.Quote(arg)
340 if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
341 buf.WriteString(quoted)