]> git.lizzy.rs Git - micro.git/blob - cmd/micro/shellwords/shellwords.go
Merge pull request #1135 from whilei/gofmt-2018-Jun-17-00-39
[micro.git] / cmd / micro / shellwords / shellwords.go
1 package shellwords
2
3 import (
4         "bytes"
5         "errors"
6         "os"
7         "regexp"
8 )
9
10 var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
11
12 func isSpace(r rune) bool {
13         switch r {
14         case ' ', '\t', '\r', '\n':
15                 return true
16         }
17         return false
18 }
19
20 func replaceEnv(s string) string {
21         return envRe.ReplaceAllStringFunc(s, func(s string) string {
22                 s = s[1:]
23                 if s[0] == '{' {
24                         s = s[1 : len(s)-1]
25                 }
26                 return os.Getenv(s)
27         })
28 }
29
30 type Parser struct {
31         Position int
32 }
33
34 func NewParser() *Parser {
35         return &Parser{0}
36 }
37
38 func (p *Parser) Parse(line string) ([]string, error) {
39         args := []string{}
40         buf := ""
41         var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
42         backtick := ""
43
44         pos := -1
45         got := false
46
47 loop:
48         for i, r := range line {
49                 if escaped {
50                         buf += string(r)
51                         escaped = false
52                         continue
53                 }
54
55                 if r == '\\' {
56                         if singleQuoted {
57                                 buf += string(r)
58                         } else {
59                                 escaped = true
60                         }
61                         continue
62                 }
63
64                 if isSpace(r) {
65                         if singleQuoted || doubleQuoted || backQuote || dollarQuote {
66                                 buf += string(r)
67                                 backtick += string(r)
68                         } else if got {
69                                 buf = replaceEnv(buf)
70                                 args = append(args, buf)
71                                 buf = ""
72                                 got = false
73                         }
74                         continue
75                 }
76
77                 switch r {
78                 case '`':
79                         if !singleQuoted && !doubleQuoted && !dollarQuote {
80                                 if backQuote {
81                                         out, err := shellRun(backtick)
82                                         if err != nil {
83                                                 return nil, err
84                                         }
85                                         buf = out
86                                 }
87                                 backtick = ""
88                                 backQuote = !backQuote
89                                 continue
90                                 backtick = ""
91                                 backQuote = !backQuote
92                         }
93                 case ')':
94                         if !singleQuoted && !doubleQuoted && !backQuote {
95                                 if dollarQuote {
96                                         out, err := shellRun(backtick)
97                                         if err != nil {
98                                                 return nil, err
99                                         }
100                                         buf = out
101                                 }
102                                 backtick = ""
103                                 dollarQuote = !dollarQuote
104                                 continue
105                                 backtick = ""
106                                 dollarQuote = !dollarQuote
107                         }
108                 case '(':
109                         if !singleQuoted && !doubleQuoted && !backQuote {
110                                 if !dollarQuote && len(buf) > 0 && buf == "$" {
111                                         dollarQuote = true
112                                         buf += "("
113                                         continue
114                                 } else {
115                                         return nil, errors.New("invalid command line string")
116                                 }
117                         }
118                 case '"':
119                         if !singleQuoted && !dollarQuote {
120                                 doubleQuoted = !doubleQuoted
121                                 continue
122                         }
123                 case '\'':
124                         if !doubleQuoted && !dollarQuote {
125                                 singleQuoted = !singleQuoted
126                                 continue
127                         }
128                 case ';', '&', '|', '<', '>':
129                         if !(escaped || singleQuoted || doubleQuoted || backQuote) {
130                                 pos = i
131                                 break loop
132                         }
133                 }
134
135                 got = true
136                 buf += string(r)
137                 if backQuote || dollarQuote {
138                         backtick += string(r)
139                 }
140         }
141
142         buf = replaceEnv(buf)
143         args = append(args, buf)
144
145         if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
146                 return nil, errors.New("invalid command line string")
147         }
148
149         p.Position = pos
150
151         return args, nil
152 }
153
154 func Split(line string) ([]string, error) {
155         return NewParser().Parse(line)
156 }
157
158 func Join(args ...string) string {
159         var buf bytes.Buffer
160         for i, w := range args {
161                 if i != 0 {
162                         buf.WriteByte(' ')
163                 }
164                 if w == "" {
165                         buf.WriteString("''")
166                         continue
167                 }
168
169                 for _, b := range w {
170                         switch b {
171                         case ' ', '\t', '\r', '\n':
172                                 buf.WriteByte('\\')
173                                 buf.WriteString(string(b))
174                         default:
175                                 buf.WriteString(string(b))
176                         }
177                 }
178         }
179         return buf.String()
180 }