Skip to content

Commit 1a25e5f

Browse files
committed
Added flag parsing logic for text editor command line, and more tests
1 parent 28e4ec5 commit 1a25e5f

File tree

4 files changed

+94
-24
lines changed

4 files changed

+94
-24
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ tests/
1515
.DS_Store
1616
testing/
1717
createfiles.sh
18-
.vscode
18+
.vscode
19+
run.sh

main.go

+60-21
Original file line numberDiff line numberDiff line change
@@ -169,29 +169,65 @@ func guessEditorCommand() (string, error) {
169169
}
170170

171171
// Returns the executable path and arguments
172-
func parseEditorCommand(editorCmd string) (string, []string) {
173-
// Tokenize the editor path
174-
token := strings.Split(editorCmd, " ")
175-
var commandString string
176-
// Iterate over all tokens
177-
var pos int
178-
for i := 0;i < cap(token);i++ {
179-
// If the token is NOT a flag append a space to compensate for the trimmed spaces
180-
// BUG: If more than one space occurs in a directory name, this will fail
181-
if !(strings.HasPrefix(token[i], "-")) && !(strings.HasPrefix(token[i], "/")) {
182-
token[i] += string(' ')
183-
commandString += token[i]
184-
} else {
185-
// If the token IS a flag, break. We will handle it differently.
186-
pos = i
187-
break
172+
func parseEditorCommand(editorCmd string) (string, []string, error) {
173+
var args []string
174+
state := "start"
175+
current := ""
176+
quote := "\""
177+
for i := 0; i < len(editorCmd); i++ {
178+
c := editorCmd[i]
179+
180+
if state == "quotes" {
181+
if string(c) != quote {
182+
current += string(c)
183+
} else {
184+
args = append(args, current)
185+
current = ""
186+
state = "start"
187+
}
188+
continue
189+
}
190+
191+
if c == '"' || c == '\'' {
192+
state = "quotes"
193+
quote = string(c)
194+
continue
195+
}
196+
197+
if state == "arg" {
198+
if c == ' ' || c == '\t' {
199+
args = append(args, current)
200+
current = ""
201+
state = "start"
202+
} else {
203+
current += string(c)
204+
}
205+
continue
206+
}
207+
208+
if c != ' ' && c != '\t' {
209+
state = "arg"
210+
current += string(c)
188211
}
189212
}
190-
// Make a slice to hold the arguments to the editor
191-
var args []string
192-
args = append(args, token[pos:]...)
193213

194-
return strings.Trim(commandString, "\n\r\t "), args
214+
if state == "quotes" {
215+
return "", []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", editorCmd))
216+
}
217+
218+
if current != "" {
219+
args = append(args, current)
220+
}
221+
222+
if len(args) <= 0 {
223+
return "", []string{}, errors.New("Empty command line")
224+
}
225+
226+
if len(args) == 1 {
227+
return args[0], []string{}, nil
228+
}
229+
230+
return args[0], args[1:], nil
195231
}
196232

197233
func editFile(filePath string) error {
@@ -207,7 +243,10 @@ func editFile(filePath string) error {
207243
}
208244
}
209245

210-
commandString, args := parseEditorCommand(editorCmd)
246+
commandString, args, err := parseEditorCommand(editorCmd)
247+
if err != nil {
248+
return err
249+
}
211250

212251
args = append(args, filePath)
213252
// Run the properly formed command

main_test.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ func Test_parseEditorCommand(t *testing.T) {
271271
editorCmd string
272272
executable string
273273
args []string
274+
hasError bool
274275
}
275276

276277
var testCases []TestCase
@@ -279,34 +280,63 @@ func Test_parseEditorCommand(t *testing.T) {
279280
editorCmd: "subl",
280281
executable: "subl",
281282
args: []string{},
283+
hasError: false,
282284
})
283285

284286
testCases = append(testCases, TestCase{
285287
editorCmd: "/usr/bin/vim -f",
286288
executable: "/usr/bin/vim",
287289
args: []string{ "-f" },
290+
hasError: false,
288291
})
289292

290293
testCases = append(testCases, TestCase{
291294
editorCmd: "\"F:\\Sublime Text 3\\sublime_text.exe\" /n /w",
292295
executable: "F:\\Sublime Text 3\\sublime_text.exe",
293296
args: []string{ "/n", "/w" },
297+
hasError: false,
294298
})
295299

296300
testCases = append(testCases, TestCase{
297301
editorCmd: "subl -w --command \"something with spaces\"",
298302
executable: "subl",
299303
args: []string{ "-w", "--command", "something with spaces" },
304+
hasError: false,
300305
})
301306

302307
testCases = append(testCases, TestCase{
303308
editorCmd: "notepad.exe /PT",
304309
executable: "notepad.exe",
305310
args: []string{ "/PT" },
311+
hasError: false,
312+
})
313+
314+
testCases = append(testCases, TestCase{
315+
editorCmd: "vim -e \"unclosed quote",
316+
executable: "",
317+
args: []string{},
318+
hasError: true,
319+
})
320+
321+
testCases = append(testCases, TestCase{
322+
editorCmd: "subl -e 'unclosed single-quote",
323+
executable: "",
324+
args: []string{},
325+
hasError: true,
326+
})
327+
328+
testCases = append(testCases, TestCase{
329+
editorCmd: "",
330+
executable: "",
331+
args: []string{},
332+
hasError: true,
306333
})
307334

308335
for _, testCase := range testCases {
309-
executable, args := parseEditorCommand(testCase.editorCmd)
336+
executable, args, err := parseEditorCommand(testCase.editorCmd)
337+
if (err != nil && !testCase.hasError) || (err == nil && testCase.hasError) {
338+
t.Errorf("Error status did not match: %b: %s", testCase.hasError, err)
339+
}
310340
if executable != testCase.executable {
311341
t.Errorf("Expected '%s', got '%s'", testCase.executable, executable)
312342
}

version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package main
22

33
import "fmt"
44

5-
const VERSION = "1.4.0"
5+
const VERSION = "1.5.0"
66

77
func handleVersionCommand(opts *CommandLineOptions, args []string) error {
88
fmt.Println(VERSION)

0 commit comments

Comments
 (0)