69b534ece0
In certain cases, such as the GIT_SSH_COMMAND environment variable, we'll need to pass data to the shell. Since some of our arguments contain spaces, we'll need to quote those values. Introduce a function to quote strings so that they're suitable for the shell. For simplicity's sake, assume anything beyond identifier characters, dot, at sign, and hyphen-minus needs to be quoted and use single quotes for doing so. Handle single quotes in a string in the same way as Git does.
140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// quoteFieldRe greedily matches between matching pairs of '', "", or
|
|
// non-word characters.
|
|
quoteFieldRe = regexp.MustCompile("'(.*)'|\"(.*)\"|(\\S*)")
|
|
)
|
|
|
|
// QuotedFields is an alternative to strings.Fields (see:
|
|
// https://golang.org/pkg/strings#Fields) that respects spaces between matching
|
|
// pairs of quotation delimeters.
|
|
//
|
|
// For instance, the quoted fields of the string "foo bar 'baz etc'" would be:
|
|
// []string{"foo", "bar", "baz etc"}
|
|
//
|
|
// Whereas the same argument given to strings.Fields, would return:
|
|
// []string{"foo", "bar", "'baz", "etc'"}
|
|
func QuotedFields(s string) []string {
|
|
submatches := quoteFieldRe.FindAllStringSubmatch(s, -1)
|
|
out := make([]string, 0, len(submatches))
|
|
|
|
for _, matches := range submatches {
|
|
// if a leading or trailing space is found, ignore that
|
|
if matches[0] == "" {
|
|
continue
|
|
}
|
|
|
|
// otherwise, find the first non-empty match (inside balanced
|
|
// quotes, or a space-delimited string)
|
|
var str string
|
|
for _, m := range matches[1:] {
|
|
if len(m) > 0 {
|
|
str = m
|
|
break
|
|
}
|
|
}
|
|
|
|
out = append(out, str)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// ShellQuote returns a copied string slice where each element is quoted
|
|
// suitably for sh.
|
|
func ShellQuote(strs []string) []string {
|
|
dup := make([]string, 0, len(strs))
|
|
|
|
for _, str := range strs {
|
|
// Quote anything that looks slightly complicated.
|
|
if shellWordRe.FindStringIndex(str) == nil {
|
|
dup = append(dup, "'"+strings.Replace(str, "'", "'\\''", -1)+"'")
|
|
} else {
|
|
dup = append(dup, str)
|
|
}
|
|
}
|
|
return dup
|
|
}
|
|
|
|
// Ljust returns a copied string slice where each element is left justified to
|
|
// match the width of the longest element in the set.
|
|
func Ljust(strs []string) []string {
|
|
llen := len(Longest(strs))
|
|
|
|
dup := make([]string, len(strs), cap(strs))
|
|
copy(dup, strs)
|
|
|
|
for i, str := range strs {
|
|
width := MaxInt(0, llen-len(str))
|
|
padding := strings.Repeat(" ", width)
|
|
|
|
dup[i] = str + padding
|
|
}
|
|
|
|
return dup
|
|
}
|
|
|
|
// Rjust returns a copied string slice where each element is right justified to
|
|
// match the width of the longest element in the set.
|
|
func Rjust(strs []string) []string {
|
|
llen := len(Longest(strs))
|
|
|
|
dup := make([]string, len(strs), cap(strs))
|
|
copy(dup, strs)
|
|
|
|
for i, str := range strs {
|
|
width := MaxInt(0, llen-len(str))
|
|
padding := strings.Repeat(" ", width)
|
|
|
|
dup[i] = padding + str
|
|
}
|
|
|
|
return dup
|
|
}
|
|
|
|
// Longest returns the longest element in the string slice in O(n) time and O(1)
|
|
// space. If strs is empty or nil, an empty string will be returned.
|
|
func Longest(strs []string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
|
|
var longest string
|
|
var llen int
|
|
for _, str := range strs {
|
|
if len(str) >= llen {
|
|
longest = str
|
|
llen = len(longest)
|
|
}
|
|
}
|
|
|
|
return longest
|
|
}
|
|
|
|
// Indent returns a string which prepends "\t" TAB characters to the beginning
|
|
// of each line in the given string "str".
|
|
func Indent(str string) string {
|
|
indented := strings.Replace(str, "\n", "\n\t", -1)
|
|
if len(indented) > 0 {
|
|
indented = "\t" + indented
|
|
}
|
|
|
|
return indented
|
|
}
|
|
|
|
var (
|
|
tabRe = regexp.MustCompile(`(?m)^[ \t]+`)
|
|
shellWordRe = regexp.MustCompile(`\A[A-Za-z0-9_@.-]+\z`)
|
|
)
|
|
|
|
// Undent removes all leading tabs in the given string "str", line-wise.
|
|
func Undent(str string) string {
|
|
return tabRe.ReplaceAllString(str, "")
|
|
}
|