diff --git a/lib/pure/os.nim b/lib/pure/os.nim index fd618e93cf9d5..019caa0b2bfbd 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -2735,6 +2735,9 @@ proc exclFilePermissions*(filename: string, ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) +when not defined(windows): + import std/private/shlexutils + proc parseCmdLine*(c: string): seq[string] {. noSideEffect, rtl, extern: "nos$1".} = ## Splits a `command line`:idx: into several components. @@ -2774,16 +2777,17 @@ proc parseCmdLine*(c: string): seq[string] {. ## * `paramCount proc <#paramCount>`_ ## * `paramStr proc <#paramStr,int>`_ ## * `commandLineParams proc <#commandLineParams>`_ - - result = @[] - var i = 0 - var a = "" - while true: - setLen(a, 0) - # eat all delimiting whitespace - while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) - if i >= c.len: break - when defined(windows): + when not defined(windows): + result = parseCmdLineImpl(c) + else: + result = @[] + var i = 0 + var a = "" + while true: + setLen(a, 0) + # eat all delimiting whitespace + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break # parse a single argument according to the above rules: var inQuote = false while i < c.len: @@ -2817,20 +2821,7 @@ proc parseCmdLine*(c: string): seq[string] {. else: a.add(c[i]) inc(i) - else: - case c[i] - of '\'', '\"': - var delim = c[i] - inc(i) # skip ' or " - while i < c.len and c[i] != delim: - add a, c[i] - inc(i) - if i < c.len: inc(i) - else: - while i < c.len and c[i] > ' ': - add(a, c[i]) - inc(i) - add(result, a) + add(result, a) when defined(nimdoc): # Common forward declaration docstring block for parameter retrieval procs. diff --git a/lib/std/private/shlexutils.nim b/lib/std/private/shlexutils.nim new file mode 100644 index 0000000000000..179ccf0b25416 --- /dev/null +++ b/lib/std/private/shlexutils.nim @@ -0,0 +1,89 @@ +#[ +KEY shlex +parseCmdLine D20200513T195153 +]# + +type State = enum + sInStart + sInRegular + sInSpace + sInSingleQuote + sInDoubleQuote + sFinished + +type ShlexError = enum + seOk + seMissingDoubleQuote + seMissingSingleQuote + +iterator shlex*(a: openArray[char], error: var ShlexError): string = + var i = 0 + var buf: string + var state = sInStart + var ready = false + error = seOk + while true: + # echo (i, state, buf) + if i >= a.len: + case state + of sInSingleQuote: + error = seMissingSingleQuote + of sInDoubleQuote: + error = seMissingDoubleQuote + else: + ready = true + state = sFinished + var c: char + if i < a.len: + c = a[i] + i.inc + case state + of sFinished: discard + of sInStart: + case c + of ' ': discard + of '\'': state = sInSingleQuote + of '\"': state = sInDoubleQuote + else: + state = sInRegular + buf.add c + of sInRegular: + case c + of ' ': ready = true + of '\'': state = sInSingleQuote + of '\"': state = sInDoubleQuote + else: buf.add c + of sInSingleQuote: + case c + of '\'': state = sInRegular + else: buf.add c + of sInDoubleQuote: + case c + of '\"': state = sInRegular + # of '\'': state = sInRegular + else: buf.add c + of sInSpace: + case c + of ' ': discard + of '\'': state = sInSingleQuote + of '\"': state = sInDoubleQuote + else: + state = sInRegular + buf.add c + if ready: + ready = false + # echo (buf,) + yield buf + buf.setLen 0 + if state != sFinished: + state = sInStart + if state == sFinished: + break + +proc parseCmdLineImpl*(a: string): seq[string] {.inline.} = + var err: ShlexError + for val in shlex(a, err): + if err == seOk: + result.add val + else: + raise newException(ValueError, $(a, err)) diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index dcb2d44f4578f..c1f11b5d8e529 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -696,3 +696,17 @@ block: # isAdmin if isAzure and defined(windows): doAssert isAdmin() # In Azure on POSIX tests run as a normal user if isAzure and defined(posix): doAssert not isAdmin() + + # import std/strutils + # from std/sequtils import toSeq + # from std/os import commandLineParams, quoteShellCommand, parseCmdLine +template main = + # xxx move all tests under here so they get tested in VM, for ones which can + block: # parseCmdLine, bug #14343 + let s = ["foo", "ba'r", "b\"az", "", "'", "''", "\"\'"] + let s2 = s.quoteShellCommand + let s3 = s2.parseCmdLine + doAssert s3 == s, $(s, s3, s2) + +static: main() +main()