summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2020-05-13 04:45:36 -0700
committerGitHub <noreply@github.com>2020-05-13 13:45:36 +0200
commit041ee92bba0ba3f361218eb20ceeeee6eec85f91 (patch)
tree1a55acb85d5a9d15f836aaa1a0930193bdebf17c
parent1648f1dd9955c848f8fbaf46a89798ece21460cc (diff)
downloadNim-041ee92bba0ba3f361218eb20ceeeee6eec85f91.tar.gz
`osproc.execCmdEx` now takes an optional `input` for stdin, `env`, workingDir (#14211)
* `osproc.execCmdEx` now takes an optional `input` for stdin

* execCmdEx now also takes an optional ``workingDir` and `env`
-rw-r--r--changelog.md4
-rw-r--r--lib/pure/osproc.nim43
-rw-r--r--lib/system.nim3
-rw-r--r--tests/stdlib/tosproc.nim15
4 files changed, 51 insertions, 14 deletions
diff --git a/changelog.md b/changelog.md
index d54ed4c8c..d6be5a9b7 100644
--- a/changelog.md
+++ b/changelog.md
@@ -92,6 +92,10 @@
 - The callback that is passed to `system.onThreadDestruction` must now be `.raises: []`.
 
 
+- `osproc.execCmdEx` now takes an optional `input` for stdin.
+- `osproc.execCmdEx` now takes an optional `input` for stdin, `workingDir` and `env`
+  parameters.
+
 ## Language changes
 - In the newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this:
 
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index f90b310ea..3e3391d52 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -1438,12 +1438,16 @@ elif not defined(useNimRtl):
 
 
 proc execCmdEx*(command: string, options: set[ProcessOption] = {
-                poStdErrToStdOut, poUsePath}): tuple[
+                poStdErrToStdOut, poUsePath}, env: StringTableRef = nil,
+                workingDir = "", input = ""): tuple[
                 output: TaintedString,
                 exitCode: int] {.tags:
                 [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
-  ## A convenience proc that runs the `command`, grabs all its output and
-  ## exit code and returns both.
+  ## A convenience proc that runs the `command`, and returns its `output` and
+  ## `exitCode`. `env` and `workingDir` params behave as for `startProcess`.
+  ## If `input.len > 0`, it is passed as stdin.
+  ## Note: this could block if `input.len` is greater than your OS's maximum
+  ## pipe buffer size.
   ##
   ## See also:
   ## * `execCmd proc <#execCmd,string>`_
@@ -1452,17 +1456,32 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = {
   ## * `execProcess proc
   ##   <#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_
   ##
-  ## Example:
-  ##
-  ## .. code-block:: Nim
-  ##  let (outp, errC) = execCmdEx("nim c -r mytestfile.nim")
-
-  var p = startProcess(command, options = options + {poEvalCommand})
+  runnableExamples:
+    var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4")
+    import strutils, strtabs
+    stripLineEnd(result[0]) ## portable way to remove trailing newline, if any
+    doAssert result == ("12", 0)
+    doAssert execCmdEx("ls --nonexistant").exitCode != 0
+    when defined(posix):
+      assert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0)
+      assert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0)
+
+  when (NimMajor, NimMinor, NimPatch) < (1, 3, 5):
+    doAssert input.len == 0
+    doAssert workingDir.len == 0
+    doAssert env == nil
+
+  var p = startProcess(command, options = options + {poEvalCommand},
+    workingDir = workingDir, env = env)
   var outp = outputStream(p)
 
-  # There is no way to provide input for the child process
-  # anymore. Closing it will create EOF on stdin instead of eternal
-  # blocking.
+  if input.len > 0:
+    # There is no way to provide input for the child process
+    # anymore. Closing it will create EOF on stdin instead of eternal
+    # blocking.
+    # Writing in chunks would require a selectors (eg kqueue/epoll) to avoid
+    # blocking on io.
+    inputStream(p).write(input)
   close inputStream(p)
 
   result = (TaintedString"", -1)
diff --git a/lib/system.nim b/lib/system.nim
index 8ce79aa2f..6b24bdcb8 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -2068,8 +2068,9 @@ const
     ## is the minor number of Nim's version.
     ## Odd for devel, even for releases.
 
-  NimPatch* {.intdefine.}: int = 3
+  NimPatch* {.intdefine.}: int = 5
     ## is the patch number of Nim's version.
+    ## Odd for devel, even for releases.
 
 import system/dollars
 export dollars
diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim
index 73cec0cb7..36afd6a23 100644
--- a/tests/stdlib/tosproc.nim
+++ b/tests/stdlib/tosproc.nim
@@ -1,7 +1,7 @@
 # test the osproc module
 
 import stdtest/specialpaths
-import "../.." / compiler/unittest_light
+import "$nim" / compiler/unittest_light
 
 when defined(case_testfile): # compiled test file for child process
   from posix import exitnow
@@ -119,3 +119,16 @@ else:
 
     var result = startProcessTest("nim r --hints:off -", options = {}, input = "echo 3*4")
     doAssert result == ("12\n", 0)
+
+  import std/strtabs
+  block execProcessTest:
+    var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4")
+    stripLineEnd(result[0])
+    doAssert result == ("12", 0)
+    doAssert execCmdEx("ls --nonexistant").exitCode != 0
+    when false:
+      # bug: on windows, this raises; on posix, passes
+      doAssert execCmdEx("nonexistant").exitCode != 0
+    when defined(posix):
+      doAssert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0)
+      doAssert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0)