├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── asyncproc.nimble ├── config.nims ├── example.nim ├── htmldocs ├── _._ │ └── src │ │ ├── asyncproc.html │ │ ├── asyncproc.idx │ │ └── asyncproc │ │ ├── exports │ │ ├── asyncprocimpl.html │ │ ├── asyncprocimpl.idx │ │ ├── procargs.html │ │ ├── procargs.idx │ │ ├── procargsresult.html │ │ ├── procargsresult.idx │ │ ├── procenv.html │ │ ├── procenv.idx │ │ ├── procmacro.html │ │ ├── procmacro.idx │ │ ├── procresult.html │ │ └── procresult.idx │ │ └── private │ │ ├── childproc_posix.html │ │ ├── childproc_posix.idx │ │ ├── streamsbuilder.html │ │ └── streamsbuilder.idx ├── asyncproc.html ├── asyncproc.idx ├── asyncproc.nim ├── dochack.js ├── importbuilder ├── nimdoc.out.css └── theindex.html ├── nimble.json ├── src ├── asyncproc.nim └── asyncproc │ ├── exports │ ├── asyncprocimpl.nim │ ├── procargsresult.nim │ ├── procenv.nim │ └── procmacro.nim │ └── private │ ├── childproc_posix.nim │ └── streamsbuilder.nim └── tests ├── config.nims └── tasyncproc.nim /.gitattributes: -------------------------------------------------------------------------------- 1 | htmldocs/** linguist-vendored 2 | htmldocs/**.html linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | tests/* 4 | !tests/*.nim 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alogani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asyncproc 2 | 3 | Asynchronous child process spawner, with high flexibility on its stream handling for input/output/erro handles. 4 | It was specially built to close the gap from shell language and nim. 5 | 6 | ## Features 7 | 8 | - Developped with interactivness in mind (you can easily mixed user input/output, and automated input) 9 | - Developped with remote execution in mind: running a command remotly (ssh), on a chrooted system or locally should need to modify only one line of the whole code 10 | - Concise: even complex configurations can be one-lined (see also implicitAsync macro to avoid repetition of await keyword) 11 | - Flexible and straightforward. All options can be tweaked using a flag : 12 | - Can be easily put in true foreground, keeping the ability to terminal process control (ctrl+c, ctrl+z in linux) and to have shell history 13 | - Can capture input/output/output error streams, even when put on foreground 14 | - Can separate error stream or keep it merged with preserving writing order 15 | - Have other facilities to help logging, printing what is done, managing process environment, making daemons (command surviving its parent), etc. 16 | - Powerful streams manipulation thanks to [asyncio](https://github.com/Alogani/asyncio) library 17 | 18 | ## Getting started 19 | 20 | ### Installation 21 | 22 | `nimble install asyncproc` 23 | 24 | ### Example usage (linux) 25 | 26 | ``` 27 | import std/options 28 | import asyncproc 29 | import asyncio/asyncstring 30 | 31 | const myFile = "myfile.txt" 32 | 33 | proc main() {.async.} = 34 | ## Create a file 35 | await sh.runDiscard(@["touch", myFile]) 36 | 37 | ## Check the file has been created 38 | echo true == (await sh.runCheck(@["test", "-f", myFile])) 39 | 40 | ## List its content 41 | echo await sh.runGetOutput(@["cat", myFile]) 42 | 43 | ## List all files in current directory 44 | echo await sh.runGetLines(@["ls"]) 45 | 46 | ## Write to the file 47 | await sh.runDiscard(@["dd", "of=" & myFile], input = some AsyncString.new("Hello world\n").AsyncIoBase, toRemove = { Interactive }) 48 | 49 | waitFor main() 50 | ``` 51 | 52 | #### To go further 53 | 54 | Full docs can be browsered [here](https://htmlpreview.github.io/?https://github.com/Alogani/asyncproc/blob/main/htmldocs/asyncproc.html) 55 | 56 | Please see [tests](https://github.com/Alogani/asyncproc/tree/main/tests) to see more usages. 57 | 58 | You can also see [shellcmd](https://github.com/Alogani/shellcmd) source code to view what you can do and how with asyncproc api 59 | 60 | ## Before using it 61 | 62 | - **Unstable API** : How you use asyncproc is susceptible to change. It could occur to serve [shellcmd](https://github.com/Alogani/shellcmd) library development. If you want to use as a pre-release, please only install and use a tagged version and don't update frequently until v1.0.0 is reached. Releases with breaking change will make the second number of semver be updated (eg: v0.1.1 to v0.2.0) 63 | - Only available in unix. Support for windows is not in the priority list 64 | - Only support one async backend: std/asyncdispatch (_This project is *not* related to [chronos/asyncproc](https://github.com/status-im/nim-chronos/blob/master/chronos/asyncproc.nim)_) 65 | - Don't expect more performance. Although development is focused to avoid unecessary or costly operations, asynchronous code has a large overhead and is usually far slower than sync one in many situations. Furthermore concisness and flexibilty are proritized 66 | -------------------------------------------------------------------------------- /asyncproc.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.4.4" 4 | author = "alogani" 5 | description = "Flexible child process spawner with strong async features" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 2.0.2" 13 | requires "aloganimisc ~= 0.1.1" 14 | requires "asyncio ~= 0.6.2" 15 | requires "asyncsync ~= 0.5.0" 16 | 17 | task reinstall, "Reinstalls this package": 18 | var path = "~/.nimble/pkgs2/" & projectName() & "-*" 19 | exec("rm -rf " & path) 20 | exec("nimble install") 21 | 22 | task genDocs, "Build the docs": 23 | ## importBuilder source code: https://github.com/Alogani/shellcmd-examples/blob/main/src/importbuilder.nim 24 | let githubUrl = "https://github.com/Alogani/asyncio" 25 | let bundlePath = "htmldocs/" & projectName() & ".nim" 26 | exec("./htmldocs/importbuilder --build src " & bundlePath & " --discardExports") 27 | exec("nim doc --git.url:" & githubUrl & " --git.commit:v" & $version & " --project --index:on --outdir:htmldocs " & bundlePath) 28 | 29 | task genDocsAndPush, "genDocs -> git push": 30 | genDocsTask() 31 | exec("git add htmldocs") 32 | exec("git commit -m 'Update docs'") 33 | exec("git push") 34 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | # begin Nimble config (version 2) 2 | when withDir(thisDir(), system.fileExists("nimble.paths")): 3 | include "nimble.paths" 4 | # end Nimble config 5 | -------------------------------------------------------------------------------- /example.nim: -------------------------------------------------------------------------------- 1 | import std/options 2 | import asyncproc 3 | import asyncio/asyncstring 4 | 5 | const myFile = "myfile.txt" 6 | 7 | proc main() {.async.} = 8 | ## Create a file 9 | await sh.runDiscard(@["touch", myFile]) 10 | 11 | ## Check the file has been created 12 | echo true == (await sh.runCheck(@["test", "-f", myFile])) 13 | 14 | ## List its content 15 | echo await sh.runGetOutput(@["cat", myFile]) 16 | 17 | ## List all files in current directory 18 | echo await sh.runGetLines(@["ls"]) 19 | 20 | ## Write to the file 21 | await sh.runDiscard(@["dd", "of=" & myFile], input = some AsyncString.new("Hello world\n").AsyncIoBase, toRemove = { Interactive }) 22 | 23 | waitFor main() -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 58 | 59 |
60 | 81 |
82 | 83 | 88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc.idx: -------------------------------------------------------------------------------- 1 | nimTitle asyncproc _._/src/asyncproc.html module src/asyncproc 0 2 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/asyncprocimpl.idx: -------------------------------------------------------------------------------- 1 | nimTitle asyncprocimpl _._/src/asyncproc/exports/asyncprocimpl.html module src/asyncproc/exports/asyncprocimpl 0 2 | nim AsyncProc _._/src/asyncproc/exports/asyncprocimpl.html#AsyncProc type AsyncProc 16 3 | nim RunningProcesses _._/src/asyncproc/exports/asyncprocimpl.html#RunningProcesses var RunningProcesses 31 4 | nim start _._/src/asyncproc/exports/asyncprocimpl.html#start,ProcArgs,seq[string],ProcArgsModifier proc start(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier): AsyncProc 39 5 | nim start _._/src/asyncproc/exports/asyncprocimpl.html#start,ProcArgs,seq[string],set[ProcOption],set[ProcOption] proc start(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn)): AsyncProc 94 6 | nim wait _._/src/asyncproc/exports/asyncprocimpl.html#wait,AsyncProc,Future[void] proc wait(self: AsyncProc; cancelFut: Future[void] = nil): Future[ProcResult] 103 7 | nim getPid _._/src/asyncproc/exports/asyncprocimpl.html#getPid,AsyncProc proc getPid(p: AsyncProc): int 146 8 | nim running _._/src/asyncproc/exports/asyncprocimpl.html#running,AsyncProc proc running(p: AsyncProc): bool 147 9 | nim suspend _._/src/asyncproc/exports/asyncprocimpl.html#suspend,AsyncProc proc suspend(p: AsyncProc) 148 10 | nim resume _._/src/asyncproc/exports/asyncprocimpl.html#resume,AsyncProc proc resume(p: AsyncProc) 149 11 | nim terminate _._/src/asyncproc/exports/asyncprocimpl.html#terminate,AsyncProc proc terminate(p: AsyncProc) 150 12 | nim kill _._/src/asyncproc/exports/asyncprocimpl.html#kill,AsyncProc proc kill(p: AsyncProc) 152 13 | nim pretty _._/src/asyncproc/exports/asyncprocimpl.html#pretty,AsyncProc proc pretty(self: AsyncProc): string 155 14 | nim run _._/src/asyncproc/exports/asyncprocimpl.html#run,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc run(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[ProcResult] 162 15 | nim run _._/src/asyncproc/exports/asyncprocimpl.html#run,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc run(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n ProcResult] 166 16 | nim runAssert _._/src/asyncproc/exports/asyncprocimpl.html#runAssert,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runAssert(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[ProcResult] 176 17 | nim runAssert _._/src/asyncproc/exports/asyncprocimpl.html#runAssert,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runAssert(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n ProcResult] 180 18 | nim runDiscard _._/src/asyncproc/exports/asyncprocimpl.html#runDiscard,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runDiscard(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[void] 190 19 | nim runDiscard _._/src/asyncproc/exports/asyncprocimpl.html#runDiscard,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runDiscard(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n void] 202 20 | nim runCheck _._/src/asyncproc/exports/asyncprocimpl.html#runCheck,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runCheck(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[bool] 212 21 | nim runCheck _._/src/asyncproc/exports/asyncprocimpl.html#runCheck,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runCheck(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n bool] 222 22 | nim runGetOutput _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutput,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runGetOutput(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[string] 232 23 | nim runGetOutput _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutput,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runGetOutput(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n string] 247 24 | nim runGetLines _._/src/asyncproc/exports/asyncprocimpl.html#runGetLines,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runGetLines(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): Future[seq[string]] 257 25 | nim runGetLines _._/src/asyncproc/exports/asyncprocimpl.html#runGetLines,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runGetLines(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): Future[\n seq[string]] 263 26 | nim runGetOutputStream _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutputStream,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runGetOutputStream(sh: ProcArgs; cmd: seq[string];\n argsModifier: ProcArgsModifier; cancelFut: Future[void] = nil): (\n AsyncIoBase, Future[void]) 272 27 | nim runGetOutputStream _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutputStream,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runGetOutputStream(sh: ProcArgs; cmd: seq[string];\n prefixCmd = none(seq[string]); toAdd: set[ProcOption] = {};\n toRemove: set[ProcOption] = {}; input = none(AsyncIoBase);\n output = none(AsyncIoBase); outputErr = none(AsyncIoBase);\n env = none(ProcEnv); envModifier = none(ProcEnv);\n workingDir = none(string); processName = none(string);\n logFn = none(LogFn); onErrorFn = none(OnErrorFn);\n cancelFut: Future[void] = nil): (AsyncIoBase, Future[void]) 290 28 | nim runGetStreams _._/src/asyncproc/exports/asyncprocimpl.html#runGetStreams,ProcArgs,seq[string],ProcArgsModifier,Future[void] proc runGetStreams(sh: ProcArgs; cmd: seq[string]; argsModifier: ProcArgsModifier;\n cancelFut: Future[void] = nil): (AsyncIoBase, AsyncIoBase,\n Future[void]) 299 29 | nim runGetStreams _._/src/asyncproc/exports/asyncprocimpl.html#runGetStreams,ProcArgs,seq[string],set[ProcOption],set[ProcOption],Future[void] proc runGetStreams(sh: ProcArgs; cmd: seq[string]; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {}; toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase); output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase); env = none(ProcEnv);\n envModifier = none(ProcEnv); workingDir = none(string);\n processName = none(string); logFn = none(LogFn);\n onErrorFn = none(OnErrorFn); cancelFut: Future[void] = nil): (\n AsyncIoBase, AsyncIoBase, Future[void]) 317 30 | nimgrp run _._/src/asyncproc/exports/asyncprocimpl.html#run-procs-all proc 161 31 | nimgrp rungetlines _._/src/asyncproc/exports/asyncprocimpl.html#runGetLines-procs-all proc 256 32 | nimgrp rungetstreams _._/src/asyncproc/exports/asyncprocimpl.html#runGetStreams-procs-all proc 299 33 | nimgrp start _._/src/asyncproc/exports/asyncprocimpl.html#start-procs-all proc 39 34 | nimgrp runassert _._/src/asyncproc/exports/asyncprocimpl.html#runAssert-procs-all proc 175 35 | nimgrp rungetoutput _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutput-procs-all proc 231 36 | nimgrp rundiscard _._/src/asyncproc/exports/asyncprocimpl.html#runDiscard-procs-all proc 189 37 | nimgrp runcheck _._/src/asyncproc/exports/asyncprocimpl.html#runCheck-procs-all proc 211 38 | nimgrp rungetoutputstream _._/src/asyncproc/exports/asyncprocimpl.html#runGetOutputStream-procs-all proc 272 39 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procargs.idx: -------------------------------------------------------------------------------- 1 | nimTitle procargs _._/src/asyncproc/exports/procargs.html module src/asyncproc/exports/procargs 0 2 | nim Interactive _._/src/asyncproc/exports/procargs.html#Interactive ProcOption.Interactive 15 3 | nim QuoteArgs _._/src/asyncproc/exports/procargs.html#QuoteArgs ProcOption.QuoteArgs 15 4 | nim MergeStderr _._/src/asyncproc/exports/procargs.html#MergeStderr ProcOption.MergeStderr 15 5 | nim CaptureOutput _._/src/asyncproc/exports/procargs.html#CaptureOutput ProcOption.CaptureOutput 15 6 | nim CaptureOutputErr _._/src/asyncproc/exports/procargs.html#CaptureOutputErr ProcOption.CaptureOutputErr 15 7 | nim CaptureInput _._/src/asyncproc/exports/procargs.html#CaptureInput ProcOption.CaptureInput 15 8 | nim UseParentEnv _._/src/asyncproc/exports/procargs.html#UseParentEnv ProcOption.UseParentEnv 15 9 | nim Daemon _._/src/asyncproc/exports/procargs.html#Daemon ProcOption.Daemon 15 10 | nim DryRun _._/src/asyncproc/exports/procargs.html#DryRun ProcOption.DryRun 15 11 | nim ShowCommand _._/src/asyncproc/exports/procargs.html#ShowCommand ProcOption.ShowCommand 15 12 | nim AskConfirmation _._/src/asyncproc/exports/procargs.html#AskConfirmation ProcOption.AskConfirmation 15 13 | nim WithLogging _._/src/asyncproc/exports/procargs.html#WithLogging ProcOption.WithLogging 15 14 | nim SetEnvOnCmdLine _._/src/asyncproc/exports/procargs.html#SetEnvOnCmdLine ProcOption.SetEnvOnCmdLine 15 15 | nim KeepStreamOpen _._/src/asyncproc/exports/procargs.html#KeepStreamOpen ProcOption.KeepStreamOpen 15 16 | nim ProcOption _._/src/asyncproc/exports/procargs.html#ProcOption enum ProcOption 15 17 | nim ProcArgs _._/src/asyncproc/exports/procargs.html#ProcArgs type ProcArgs 52 18 | nim LogFn _._/src/asyncproc/exports/procargs.html#LogFn type LogFn 82 19 | nim ProcArgsModifier _._/src/asyncproc/exports/procargs.html#ProcArgsModifier object ProcArgsModifier 84 20 | nim sh _._/src/asyncproc/exports/procargs.html#sh let sh 106 21 | nim internalCmd _._/src/asyncproc/exports/procargs.html#internalCmd let internalCmd 107 22 | nim deepCopy _._/src/asyncproc/exports/procargs.html#deepCopy,ProcArgs proc deepCopy(self: ProcArgs): ProcArgs 112 23 | nim merge _._/src/asyncproc/exports/procargs.html#merge,ProcArgs,ProcArgsModifier proc merge(procArgs: ProcArgs; modifier: ProcArgsModifier): ProcArgs 119 24 | nim merge _._/src/asyncproc/exports/procargs.html#merge,ProcArgsModifier,ProcArgsModifier proc merge(a, b: ProcArgsModifier): ProcArgsModifier 144 25 | nim merge _._/src/asyncproc/exports/procargs.html#merge,T,set[ProcOption],set[ProcOption] proc merge[T: ProcArgs or ProcArgsModifier](a: T; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {};\n toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase);\n output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase);\n env = none(ProcEnv);\n envModifier = none(ProcEnv);\n workingDir = none(string);\n processName = none(string);\n logFn = none(LogFn);\n onErrorFn = none(OnErrorFn)): T 174 26 | nimgrp merge _._/src/asyncproc/exports/procargs.html#merge-procs-all proc 119 27 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procargsresult.idx: -------------------------------------------------------------------------------- 1 | nimTitle procargsresult _._/src/asyncproc/exports/procargsresult.html module src/asyncproc/exports/procargsresult 0 2 | nim Interactive _._/src/asyncproc/exports/procargsresult.html#Interactive ProcOption.Interactive 14 3 | nim QuoteArgs _._/src/asyncproc/exports/procargsresult.html#QuoteArgs ProcOption.QuoteArgs 14 4 | nim MergeStderr _._/src/asyncproc/exports/procargsresult.html#MergeStderr ProcOption.MergeStderr 14 5 | nim CaptureOutput _._/src/asyncproc/exports/procargsresult.html#CaptureOutput ProcOption.CaptureOutput 14 6 | nim CaptureOutputErr _._/src/asyncproc/exports/procargsresult.html#CaptureOutputErr ProcOption.CaptureOutputErr 14 7 | nim CaptureInput _._/src/asyncproc/exports/procargsresult.html#CaptureInput ProcOption.CaptureInput 14 8 | nim NoParentEnv _._/src/asyncproc/exports/procargsresult.html#NoParentEnv ProcOption.NoParentEnv 14 9 | nim Daemon _._/src/asyncproc/exports/procargsresult.html#Daemon ProcOption.Daemon 14 10 | nim DryRun _._/src/asyncproc/exports/procargsresult.html#DryRun ProcOption.DryRun 14 11 | nim ShowCommand _._/src/asyncproc/exports/procargsresult.html#ShowCommand ProcOption.ShowCommand 14 12 | nim AskConfirmation _._/src/asyncproc/exports/procargsresult.html#AskConfirmation ProcOption.AskConfirmation 14 13 | nim WithLogging _._/src/asyncproc/exports/procargsresult.html#WithLogging ProcOption.WithLogging 14 14 | nim SetEnvOnCmdLine _._/src/asyncproc/exports/procargsresult.html#SetEnvOnCmdLine ProcOption.SetEnvOnCmdLine 14 15 | nim KeepStreamOpen _._/src/asyncproc/exports/procargsresult.html#KeepStreamOpen ProcOption.KeepStreamOpen 14 16 | nim RegisterProcess _._/src/asyncproc/exports/procargsresult.html#RegisterProcess ProcOption.RegisterProcess 14 17 | nim ProcOption _._/src/asyncproc/exports/procargsresult.html#ProcOption enum ProcOption # Interactiv… 14 18 | nim ProcResult _._/src/asyncproc/exports/procargsresult.html#ProcResult type ProcResult 56 19 | nim ExecError _._/src/asyncproc/exports/procargsresult.html#ExecError type ExecError 69 20 | nim LogFn _._/src/asyncproc/exports/procargsresult.html#LogFn type LogFn 72 21 | nim OnErrorFn _._/src/asyncproc/exports/procargsresult.html#OnErrorFn type OnErrorFn 73 22 | nim ProcArgs _._/src/asyncproc/exports/procargsresult.html#ProcArgs type ProcArgs # prefixCm… 75 23 | nim ProcArgsModifier _._/src/asyncproc/exports/procargsresult.html#ProcArgsModifier object ProcArgsModifier 111 24 | nim sh _._/src/asyncproc/exports/procargsresult.html#sh let sh 135 25 | nim internalCmd _._/src/asyncproc/exports/procargsresult.html#internalCmd let internalCmd 136 26 | nim deepCopy _._/src/asyncproc/exports/procargsresult.html#deepCopy,ProcArgs proc deepCopy(self: ProcArgs): ProcArgs 142 27 | nim merge _._/src/asyncproc/exports/procargsresult.html#merge,ProcArgs,ProcArgsModifier proc merge(procArgs: ProcArgs; modifier: ProcArgsModifier): ProcArgs 145 28 | nim merge _._/src/asyncproc/exports/procargsresult.html#merge,ProcArgsModifier,ProcArgsModifier proc merge(a, b: ProcArgsModifier): ProcArgsModifier 170 29 | nim merge _._/src/asyncproc/exports/procargsresult.html#merge,T,set[ProcOption],set[ProcOption] proc merge[T: ProcArgs or ProcArgsModifier](a: T; prefixCmd = none(seq[string]);\n toAdd: set[ProcOption] = {};\n toRemove: set[ProcOption] = {};\n input = none(AsyncIoBase);\n output = none(AsyncIoBase);\n outputErr = none(AsyncIoBase);\n env = none(ProcEnv);\n envModifier = none(ProcEnv);\n workingDir = none(string);\n processName = none(string);\n logFn = none(LogFn);\n onErrorFn = none(OnErrorFn)): T 200 30 | nim formatErrorMsg _._/src/asyncproc/exports/procargsresult.html#formatErrorMsg,ProcResult proc formatErrorMsg(procResult: ProcResult): string 256 31 | nim pretty _._/src/asyncproc/exports/procargsresult.html#pretty,ProcResult proc pretty(procResult: ProcResult): string 271 32 | nim assertSuccess _._/src/asyncproc/exports/procargsresult.html#assertSuccess,Future[ProcResult] proc assertSuccess(res: Future[ProcResult]): Future[ProcResult] 278 33 | nim merge _._/src/asyncproc/exports/procargsresult.html#merge,varargs[ProcResult] proc merge(allResults: varargs[ProcResult]): ProcResult 290 34 | nim withoutLineEnd _._/src/asyncproc/exports/procargsresult.html#withoutLineEnd,string proc withoutLineEnd(s: string): string 339 35 | nimgrp merge _._/src/asyncproc/exports/procargsresult.html#merge-procs-all proc 145 36 | heading prefixCmd _._/src/asyncproc/exports/procargsresult.html#prefixcmd prefixCmd 0 37 | heading options _._/src/asyncproc/exports/procargsresult.html#options options 0 38 | heading Interactive _._/src/asyncproc/exports/procargsresult.html#interactive Interactive 0 39 | heading QuoteArgs _._/src/asyncproc/exports/procargsresult.html#quoteargs QuoteArgs 0 40 | heading MergeStderr _._/src/asyncproc/exports/procargsresult.html#mergestderr MergeStderr 0 41 | heading Daemon _._/src/asyncproc/exports/procargsresult.html#daemon Daemon 0 42 | heading SetEnvOnCmdLine (low level option) _._/src/asyncproc/exports/procargsresult.html#setenvoncmdline-low-level-option SetEnvOnCmdLine (low level option) 0 43 | heading RegisterProcess _._/src/asyncproc/exports/procargsresult.html#registerprocess RegisterProcess 0 44 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procenv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc/exports/procenv 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc/exports/procenv

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 89 | 90 |
91 |
92 | Source   93 | Edit   94 | 95 |
96 | 97 |

98 |
99 |

Types

100 |
101 |
102 |
ProcEnv = Table[string, string]
103 |
104 | 105 | Interface to set up subprocess env Not used to modify terminal env 106 | Source   107 | Edit   108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |

Procs

116 |
117 |
118 |
119 |
proc newEmptyEnv(): ProcEnv {....raises: [], tags: [], forbids: [].}
120 |
121 | 122 | 123 | Source   124 | Edit   125 | 126 |
127 |
128 | 129 |
130 |
131 |
132 |
proc newEnvFromParent(): ProcEnv {....raises: [], tags: [ReadEnvEffect],
133 |                                    forbids: [].}
134 |
135 | 136 | Create an env object filled with parent environment Can make a the command pretty long if used with SetEnvOnCmdLine flag, but should not cause issue 137 | Source   138 | Edit   139 | 140 |
141 |
142 | 143 |
144 | 145 |
146 |
147 |
148 |

Converters

149 |
150 |
151 |
152 |
converter toEnv(table: Table[string, string]): ProcEnv {....raises: [], tags: [],
153 |     forbids: [].}
154 |
155 | 156 | 157 | Source   158 | Edit   159 | 160 |
161 |
162 | 163 |
164 | 165 |
166 |
167 | 168 |
169 |
170 | 171 | 176 |
177 |
178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procenv.idx: -------------------------------------------------------------------------------- 1 | nimTitle procenv _._/src/asyncproc/exports/procenv.html module src/asyncproc/exports/procenv 0 2 | nim ProcEnv _._/src/asyncproc/exports/procenv.html#ProcEnv type ProcEnv 7 3 | nim toEnv _._/src/asyncproc/exports/procenv.html#toEnv.c,Table[string,string] converter toEnv(table: Table[string, string]): ProcEnv 12 4 | nim newEmptyEnv _._/src/asyncproc/exports/procenv.html#newEmptyEnv proc newEmptyEnv(): ProcEnv 15 5 | nim newEnvFromParent _._/src/asyncproc/exports/procenv.html#newEnvFromParent proc newEnvFromParent(): ProcEnv 18 6 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procmacro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc/exports/procmacro 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc/exports/procmacro

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 71 | 72 |
73 |
74 | Source   75 | Edit   76 | 77 |
78 | 79 |

80 |
81 |

Imports

82 |
83 | procargsresult 84 |
85 |
86 |
87 |

Macros

88 |
89 |
90 |
91 |
macro implicitAwait(identNames: static[seq[string]]; body: untyped): untyped
92 |
93 | 94 | 95 | Source   96 | Edit   97 | 98 |
99 |
100 | 101 |
102 |
103 |
104 |
macro tryOrFallBack(cmd: untyped; body: untyped)
105 |
106 | 107 | Command is only valid if await(cmd).exitCode is valid Can be good to encapsulate multiple statements, but OnErrorFn is more reliable 108 | Source   109 | Edit   110 | 111 |
112 |
113 | 114 |
115 | 116 |
117 |
118 | 119 |
120 |
121 | 122 | 127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procmacro.idx: -------------------------------------------------------------------------------- 1 | nimTitle procmacro _._/src/asyncproc/exports/procmacro.html module src/asyncproc/exports/procmacro 0 2 | nim tryOrFallBack _._/src/asyncproc/exports/procmacro.html#tryOrFallBack.m,untyped,untyped macro tryOrFallBack(cmd: untyped; body: untyped) 14 3 | nim implicitAwait _._/src/asyncproc/exports/procmacro.html#implicitAwait.m,static[seq[string]],untyped macro implicitAwait(identNames: static[seq[string]]; body: untyped): untyped 55 4 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procresult.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc/exports/procresult 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc/exports/procresult

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 89 | 90 |
91 |
92 | 93 |
94 | 95 |

96 |
97 |

Types

98 |
99 |
100 |
ExecError = OSError
101 |
102 | 103 | 104 | 105 |
106 |
107 |
108 |
OnErrorFn = proc (res: ProcResult): ProcResult
109 |
110 | 111 | 112 | 113 |
114 |
115 |
116 |
ProcResult = ref object
117 |   cmd*: seq[string]
118 |   output*: string
119 |   outputErr*: string
120 |   input*: string
121 |   success*: bool
122 |   exitCode*: int
123 |   
124 |
125 | 126 | 127 | 128 |
129 |
130 | 131 |
132 |
133 |
134 |

Procs

135 |
136 |
137 |
138 |
proc assertSuccess(self: ProcResult): ProcResult {.discardable,
139 |     ...raises: [Exception, OSError], tags: [RootEffect], forbids: [].}
140 |
141 | 142 | 143 | 144 |
145 |
146 | 147 |
148 |
149 |
150 |
proc merge(allResults: varargs[ProcResult]): ProcResult {....raises: [], tags: [],
151 |     forbids: [].}
152 |
153 | 154 | 155 | 156 |
157 |
158 | 159 |
160 |
161 |
162 |
proc withoutLineEnd(s: string): string {....raises: [], tags: [], forbids: [].}
163 |
164 | 165 | 166 | 167 |
168 |
169 | 170 |
171 | 172 |
173 |
174 | 175 |
176 |
177 | 178 | 183 |
184 |
185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/exports/procresult.idx: -------------------------------------------------------------------------------- 1 | nimTitle procresult _._/src/asyncproc/exports/procresult.html module src/asyncproc/exports/procresult 0 2 | nim ExecError _._/src/asyncproc/exports/procresult.html#ExecError type ExecError 6 3 | nim ProcResult _._/src/asyncproc/exports/procresult.html#ProcResult type ProcResult 8 4 | nim OnErrorFn _._/src/asyncproc/exports/procresult.html#OnErrorFn type OnErrorFn 17 5 | nim assertSuccess _._/src/asyncproc/exports/procresult.html#assertSuccess,ProcResult proc assertSuccess(self: ProcResult): ProcResult 19 6 | nim merge _._/src/asyncproc/exports/procresult.html#merge,varargs[ProcResult] proc merge(allResults: varargs[ProcResult]): ProcResult 20 7 | nim withoutLineEnd _._/src/asyncproc/exports/procresult.html#withoutLineEnd,string proc withoutLineEnd(s: string): string 21 8 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/private/childproc_posix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc/private/childproc_posix 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc/private/childproc_posix

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 122 | 123 |
124 |
125 | Source   126 | Edit   127 | 128 |
129 | 130 |

SIGINT is ignored in main process, preventing it from being accidentally killed

131 |
132 |

Imports

133 |
134 | streamsbuilder 135 |
136 |
137 |
138 |

Types

139 |
140 |
141 |
ChildProc = object
142 |   pid*: Pid
143 |   exitCode*: cint
144 |   hasExited*: bool
145 |   
146 |
147 | 148 | 149 | Source   150 | Edit   151 | 152 |
153 |
154 | 155 |
156 |
157 |
158 |

Procs

159 |
160 |
161 |
162 |
proc getPid(p: ChildProc): int {....raises: [], tags: [], forbids: [].}
163 |
164 | 165 | 166 | Source   167 | Edit   168 | 169 |
170 |
171 | 172 |
173 |
174 |
175 |
proc kill(p: ChildProc) {....raises: [OSError], tags: [], forbids: [].}
176 |
177 | 178 | 179 | Source   180 | Edit   181 | 182 |
183 |
184 | 185 |
186 |
187 |
188 |
proc resume(p: ChildProc) {....raises: [OSError], tags: [], forbids: [].}
189 |
190 | 191 | 192 | Source   193 | Edit   194 | 195 |
196 |
197 | 198 |
199 |
200 |
201 |
proc running(p: var ChildProc): bool {....raises: [OSError], tags: [], forbids: [].}
202 |
203 | 204 | 205 | Source   206 | Edit   207 | 208 |
209 |
210 | 211 |
212 |
213 |
214 |
proc startProcess(command: string; args: seq[string]; passFds = defaultPassFds;
215 |                   env = initTable(); workingDir = ""; daemon = false;
216 |                   fakePty = false): ChildProc {....raises: [OSError],
217 |     tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], forbids: [].}
218 |
219 | 220 | 221 |

args

args0 should be the process name. Not providing it result in undefined behaviour ### env if env is nil, use parent process ### file_descriptors parent process is responsible for creating and closing its pipes ends ### daemonize if false: will be closed with parent process else: will survive (but no action on fds is done)

222 | 223 | Source   224 | Edit   225 | 226 |
227 |
228 | 229 |
230 |
231 |
232 |
proc suspend(p: ChildProc) {....raises: [OSError], tags: [], forbids: [].}
233 |
234 | 235 | 236 | Source   237 | Edit   238 | 239 |
240 |
241 | 242 |
243 |
244 |
245 |
proc terminate(p: ChildProc) {....raises: [OSError], tags: [], forbids: [].}
246 |
247 | 248 | 249 | Source   250 | Edit   251 | 252 |
253 |
254 | 255 |
256 |
257 |
258 |
proc toChildStream(streamsBuilder: StreamsBuilder): tuple[
259 |     passFds: seq[tuple[src: FileHandle, dest: FileHandle]],
260 |     captures: tuple[input, output, outputErr: Future[string]], useFakePty: bool,
261 |     closeWhenCapturesFlushed: seq[AsyncIoBase], afterSpawn: proc () {.closure.},
262 |     afterWait: proc (): Future[void] {.closure.}] {.
263 |     ...raises: [OSError, Exception, ValueError], tags: [RootEffect, TimeEffect],
264 |     forbids: [].}
265 |
266 | 267 | 268 | Source   269 | Edit   270 | 271 |
272 |
273 | 274 |
275 |
276 |
277 |
proc wait(p: var ChildProc): int {....raises: [OSError], tags: [], forbids: [].}
278 |
279 | 280 | Without it, the pid won't be recycled Block main thread 281 | Source   282 | Edit   283 | 284 |
285 |
286 | 287 |
288 | 289 |
290 |
291 | 292 |
293 |
294 | 295 | 300 |
301 |
302 | 303 | 304 | 305 | 306 | 307 | 308 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/private/childproc_posix.idx: -------------------------------------------------------------------------------- 1 | nimTitle childproc_posix _._/src/asyncproc/private/childproc_posix.html module src/asyncproc/private/childproc_posix 0 2 | nim ChildProc _._/src/asyncproc/private/childproc_posix.html#ChildProc object ChildProc 17 3 | nim toChildStream _._/src/asyncproc/private/childproc_posix.html#toChildStream,StreamsBuilder proc toChildStream(streamsBuilder: StreamsBuilder): tuple[\n passFds: seq[tuple[src: FileHandle, dest: FileHandle]],\n captures: tuple[input, output, outputErr: Future[string]], useFakePty: bool,\n closeWhenCapturesFlushed: seq[AsyncIoBase], afterSpawn: proc () {.closure.},\n afterWait: proc (): Future[void] {.closure.}] 58 4 | nim wait _._/src/asyncproc/private/childproc_posix.html#wait,ChildProc proc wait(p: var ChildProc): int 157 5 | nim startProcess _._/src/asyncproc/private/childproc_posix.html#startProcess,string,seq[string],string proc startProcess(command: string; args: seq[string]; passFds = defaultPassFds;\n env = initTable(); workingDir = ""; daemon = false; fakePty = false): ChildProc # arg… 186 6 | nim getPid _._/src/asyncproc/private/childproc_posix.html#getPid,ChildProc proc getPid(p: ChildProc): int 269 7 | nim running _._/src/asyncproc/private/childproc_posix.html#running,ChildProc proc running(p: var ChildProc): bool 272 8 | nim suspend _._/src/asyncproc/private/childproc_posix.html#suspend,ChildProc proc suspend(p: ChildProc) 276 9 | nim resume _._/src/asyncproc/private/childproc_posix.html#resume,ChildProc proc resume(p: ChildProc) 279 10 | nim terminate _._/src/asyncproc/private/childproc_posix.html#terminate,ChildProc proc terminate(p: ChildProc) 282 11 | nim kill _._/src/asyncproc/private/childproc_posix.html#kill,ChildProc proc kill(p: ChildProc) 285 12 | heading args _._/src/asyncproc/private/childproc_posix.html#args args 0 13 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/private/streamsbuilder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | src/asyncproc/private/streamsbuilder 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

src/asyncproc/private/streamsbuilder

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 128 | 129 |
130 |
131 | Source   132 | Edit   133 | 134 |
135 | 136 |

137 |
138 |

Imports

139 |
140 | ../exports/procargsresult 141 |
142 |
143 |
144 |

Types

145 |
146 |
147 |
BuilderFlags = enum
148 |   InteractiveStdin, InteractiveOut, CaptureStdin, CaptureStdout, CaptureStderr,
149 |   MergeStderr
150 |
151 | 152 | 153 | Source   154 | Edit   155 | 156 |
157 |
158 |
159 |
StreamsBuilder = ref object
160 |   flags*: set[BuilderFlags]
161 |   
162 |
163 | 164 | 165 | Source   166 | Edit   167 | 168 |
169 |
170 | 171 |
172 |
173 |
174 |

Procs

175 |
176 |
177 |
178 |
proc addStreamtoStderr(builder: StreamsBuilder; newStream: AsyncIoBase) {.
179 |     ...raises: [], tags: [], forbids: [].}
180 |
181 | 182 | 183 | Source   184 | Edit   185 | 186 |
187 |
188 | 189 |
190 |
191 |
192 |
proc addStreamToStdinChain(builder: StreamsBuilder; newStream: AsyncIoBase) {.
193 |     ...raises: [], tags: [], forbids: [].}
194 |
195 | 196 | 197 | Source   198 | Edit   199 | 200 |
201 |
202 | 203 |
204 |
205 |
206 |
proc addStreamtoStdout(builder: StreamsBuilder; newStream: AsyncIoBase) {.
207 |     ...raises: [], tags: [], forbids: [].}
208 |
209 | 210 | 211 | Source   212 | Edit   213 | 214 |
215 |
216 | 217 |
218 |
219 |
220 |
proc buildToChildFile(builder: StreamsBuilder; closeEvent: Future[void]): tuple[
221 |     stdFiles: tuple[stdin, stdout, stderr: AsyncFile],
222 |     captures: tuple[input, output, outputErr: Future[string]],
223 |     transferWaiters: seq[Future[void]],
224 |     closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase]] {.
225 |     ...raises: [Exception, ValueError, OSError], tags: [RootEffect, TimeEffect],
226 |     forbids: [].}
227 |
228 | 229 | 230 | Source   231 | Edit   232 | 233 |
234 |
235 | 236 |
237 |
238 |
239 |
proc buildToStreams(builder: StreamsBuilder): tuple[
240 |     streams: tuple[stdin, stdout, stderr: AsyncIoBase],
241 |     captures: tuple[input, output, outputErr: Future[string]],
242 |     transferWaiters: seq[Future[void]],
243 |     closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase]] {.
244 |     ...raises: [Exception, ValueError, OSError], tags: [RootEffect], forbids: [].}
245 |
246 | 247 | Closing is handled internally, so output should not be closed using anything else as toClose return value 248 | Source   249 | Edit   250 | 251 |
252 |
253 | 254 |
255 |
256 |
257 |
proc init(T: type StreamsBuilder; stdin, stdout, stderr: AsyncIoBase;
258 |           keepStreamOpen, mergeStderr: bool): StreamsBuilder
259 |
260 | 261 | 262 | Source   263 | Edit   264 | 265 |
266 |
267 | 268 |
269 |
270 |
271 |
proc nonStandardStdin(builder: StreamsBuilder): bool {....raises: [], tags: [],
272 |     forbids: [].}
273 |
274 | 275 | if stdin is open and will not be equal to stdinAsync -> check before any build occurs 276 | Source   277 | Edit   278 | 279 |
280 |
281 | 282 |
283 |
284 |
285 |
proc toPassFds(stdin, stdout, stderr: AsyncFile): seq[
286 |     tuple[src: FileHandle, dest: FileHandle]] {....raises: [], tags: [],
287 |     forbids: [].}
288 |
289 | 290 | 291 | Source   292 | Edit   293 | 294 |
295 |
296 | 297 |
298 | 299 |
300 |
301 | 302 |
303 |
304 | 305 | 310 |
311 |
312 | 313 | 314 | 315 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /htmldocs/_._/src/asyncproc/private/streamsbuilder.idx: -------------------------------------------------------------------------------- 1 | nimTitle streamsbuilder _._/src/asyncproc/private/streamsbuilder.html module src/asyncproc/private/streamsbuilder 0 2 | nim StreamsBuilder _._/src/asyncproc/private/streamsbuilder.html#StreamsBuilder type StreamsBuilder 7 3 | nim InteractiveStdin _._/src/asyncproc/private/streamsbuilder.html#InteractiveStdin BuilderFlags.InteractiveStdin 14 4 | nim InteractiveOut _._/src/asyncproc/private/streamsbuilder.html#InteractiveOut BuilderFlags.InteractiveOut 14 5 | nim CaptureStdin _._/src/asyncproc/private/streamsbuilder.html#CaptureStdin BuilderFlags.CaptureStdin 14 6 | nim CaptureStdout _._/src/asyncproc/private/streamsbuilder.html#CaptureStdout BuilderFlags.CaptureStdout 14 7 | nim CaptureStderr _._/src/asyncproc/private/streamsbuilder.html#CaptureStderr BuilderFlags.CaptureStderr 14 8 | nim MergeStderr _._/src/asyncproc/private/streamsbuilder.html#MergeStderr BuilderFlags.MergeStderr 14 9 | nim BuilderFlags _._/src/asyncproc/private/streamsbuilder.html#BuilderFlags enum BuilderFlags 14 10 | nim init _._/src/asyncproc/private/streamsbuilder.html#init,typeStreamsBuilder,AsyncIoBase,AsyncIoBase,AsyncIoBase,bool,bool proc init(T: type StreamsBuilder; stdin, stdout, stderr: AsyncIoBase;\n keepStreamOpen, mergeStderr: bool): StreamsBuilder 24 11 | nim addStreamToStdinChain _._/src/asyncproc/private/streamsbuilder.html#addStreamToStdinChain,StreamsBuilder,AsyncIoBase proc addStreamToStdinChain(builder: StreamsBuilder; newStream: AsyncIoBase) 39 12 | nim addStreamtoStdout _._/src/asyncproc/private/streamsbuilder.html#addStreamtoStdout,StreamsBuilder,AsyncIoBase proc addStreamtoStdout(builder: StreamsBuilder; newStream: AsyncIoBase) 59 13 | nim addStreamtoStderr _._/src/asyncproc/private/streamsbuilder.html#addStreamtoStderr,StreamsBuilder,AsyncIoBase proc addStreamtoStderr(builder: StreamsBuilder; newStream: AsyncIoBase) 62 14 | nim buildToStreams _._/src/asyncproc/private/streamsbuilder.html#buildToStreams,StreamsBuilder proc buildToStreams(builder: StreamsBuilder): tuple[\n streams: tuple[stdin, stdout, stderr: AsyncIoBase],\n captures: tuple[input, output, outputErr: Future[string]],\n transferWaiters: seq[Future[void]],\n closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase]] 133 15 | nim buildToChildFile _._/src/asyncproc/private/streamsbuilder.html#buildToChildFile,StreamsBuilder,Future[void] proc buildToChildFile(builder: StreamsBuilder; closeEvent: Future[void]): tuple[\n stdFiles: tuple[stdin, stdout, stderr: AsyncFile],\n captures: tuple[input, output, outputErr: Future[string]],\n transferWaiters: seq[Future[void]],\n closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase]] 162 16 | nim toPassFds _._/src/asyncproc/private/streamsbuilder.html#toPassFds,AsyncFile,AsyncFile,AsyncFile proc toPassFds(stdin, stdout, stderr: AsyncFile): seq[\n tuple[src: FileHandle, dest: FileHandle]] 197 17 | nim nonStandardStdin _._/src/asyncproc/private/streamsbuilder.html#nonStandardStdin,StreamsBuilder proc nonStandardStdin(builder: StreamsBuilder): bool 205 18 | -------------------------------------------------------------------------------- /htmldocs/asyncproc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | htmldocs/asyncproc 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

htmldocs/asyncproc

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 55 | 56 |
57 | 72 |
73 | 74 | 79 |
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /htmldocs/asyncproc.idx: -------------------------------------------------------------------------------- 1 | nimTitle asyncproc asyncproc.html module htmldocs/asyncproc 0 2 | -------------------------------------------------------------------------------- /htmldocs/asyncproc.nim: -------------------------------------------------------------------------------- 1 | import ../src/asyncproc 2 | import ../src/asyncproc/exports/asyncprocimpl 3 | import ../src/asyncproc/exports/procargsresult 4 | import ../src/asyncproc/exports/procenv 5 | import ../src/asyncproc/exports/procmacro 6 | -------------------------------------------------------------------------------- /htmldocs/importbuilder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alogani/asyncproc/5bd0bb7871a68f1f1a8f5b996f400690d8927373/htmldocs/importbuilder -------------------------------------------------------------------------------- /nimble.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asyncproc", 3 | "url": "https://github.com/Alogani/asyncproc", 4 | "method": "git", 5 | "tags": [ 6 | "library", 7 | "childprocess", 8 | "async", 9 | ], 10 | "description": "Flexible child process spawner with strong async features", 11 | "license": "MIT", 12 | "web": "https://github.com/Alogani/asyncproc" 13 | }, 14 | -------------------------------------------------------------------------------- /src/asyncproc.nim: -------------------------------------------------------------------------------- 1 | import asyncproc/exports/[asyncprocimpl, procargsresult, procenv, procmacro] 2 | export asyncprocimpl, procargsresult, procenv, procmacro 3 | 4 | import asyncsync, asyncio 5 | export asyncsync, asyncio 6 | -------------------------------------------------------------------------------- /src/asyncproc/exports/asyncprocimpl.nim: -------------------------------------------------------------------------------- 1 | import std/[options, strutils] 2 | import asyncio 3 | import asyncsync, asyncsync/osevents 4 | 5 | import ./procargsresult {.all.} 6 | import ./procenv {.all.} 7 | import ../private/streamsbuilder 8 | 9 | when defined(windows): 10 | raise newException(LibraryError) 11 | else: 12 | import ../private/childproc_posix 13 | 14 | 15 | type 16 | AsyncProc* = ref object 17 | ## It corresponds to the running process. It can be obtained with startProcess. 18 | ## It must also be waited with wait() to cleanup its resource (opened files, memory, etc.). 19 | ## It provides basic process manipulation, like kill/suspend/terminate/running/getPid. 20 | ## Because this module provides complex IO handling, its standard streams are not accessible and should not be direclty used. 21 | ## To manipulate its IO, use input/output/outputErr (be creative with AsyncIo library) and flags from ProcArgs. 22 | childProc: ChildProc 23 | fullCmd: seq[string] # for debugging purpose 24 | cmd: seq[string] # for debugging purpose 25 | procArgs: ProcArgs # for debugging purpose 26 | captureStreams: tuple[input, output, outputErr: Future[string]] 27 | closeWhenCapturesFlushed: seq[AsyncIoBase] 28 | hasExitedEvent: ProcessEvent 29 | afterWaitCleanup: proc(): Future[void] 30 | 31 | var RunningProcesses*: seq[AsyncProc] 32 | ## RunningProcesses list process that have been spawned with Register flag and have not been waited. 33 | ## Process are listed in order of spawn 34 | ## Process are not guaranted to exist if `wait` has not been called 35 | 36 | proc askYesNo(sh: ProcArgs, text: string): bool 37 | 38 | 39 | proc start*(sh: ProcArgs, cmd: seq[string], 40 | argsModifier: ProcArgsModifier): AsyncProc = 41 | ## It is the responsability of the caller to close its end of AsyncStream, AsyncPipe, etc. to avoid deadlocks 42 | ## Some commands will only work well if stdin is connected to a pseudo terminal: if parent is inside a terminal, 43 | ## then don't use CaptureInput, and use either Interactive with stdin = nil or stdin = stdinAsync 44 | if cmd.len() == 0: 45 | raise newException(ExecError, "Can't run an empty command") 46 | var 47 | args = sh.merge(argsModifier) 48 | cmdBuilt = args.buildCommand(cmd) 49 | processName = (if args.processName == "": cmdBuilt[ 50 | 0] else: args.processName) 51 | env = ( 52 | if SetEnvOnCmdLine in args.options: 53 | # Already included on cmdline 54 | newEmptyEnv() 55 | elif NoParentEnv in args.options: 56 | args.env 57 | else: 58 | newEnvFromParent().mergeEnv(args.env) 59 | ) 60 | if AskConfirmation in args.options: 61 | if not sh.askYesNo("You are about to run following command:\n" & 62 | ">>> " & $cmdBuilt & "\nDo you confirm ?"): 63 | return AsyncProc(childProc: ChildProc(), fullCmd: cmdBuilt, 64 | procArgs: args) 65 | elif ShowCommand in args.options: 66 | echo ">>> ", cmdBuilt 67 | if DryRun in args.options: 68 | return AsyncProc(childProc: ChildProc(), fullCmd: cmdBuilt, 69 | procArgs: args) 70 | 71 | #[ Parent stream incluse logic ]# 72 | var streamsBuilder = StreamsBuilder.init(args.input, args.output, args.outputErr, 73 | KeepStreamOpen in args.options, MergeStderr in args.options) 74 | if Interactive in args.options: 75 | streamsBuilder.flags.incl {InteractiveStdin, InteractiveOut} 76 | if CaptureInput in args.options: 77 | streamsBuilder.flags.incl CaptureStdin 78 | if CaptureOutput in args.options: 79 | streamsBuilder.flags.incl CaptureStdout 80 | if CaptureOutputErr in args.options and MergeStderr notin args.options: 81 | streamsBuilder.flags.incl CaptureStderr 82 | let (passFds, captures, useFakePty, closeWhenCapturesFlushed, afterSpawn, afterWait) = 83 | streamsBuilder.toChildStream() 84 | let childProc = startProcess(cmdBuilt[0], @[processName] & cmdBuilt[1..^1], 85 | passFds, env, args.workingDir, 86 | Daemon in args.options, fakePty = useFakePty, IgnoreInterrupt in args.options) 87 | afterSpawn() 88 | result = AsyncProc( 89 | childProc: childProc, 90 | fullCmd: cmdBuilt, 91 | cmd: cmd, 92 | procArgs: args, 93 | captureStreams: captures, 94 | closeWhenCapturesFlushed: closeWhenCapturesFlushed, 95 | afterWaitCleanup: afterWait, 96 | ) 97 | if RegisterProcess in args.options: 98 | RunningProcesses.add result 99 | 100 | proc start*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 101 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 102 | input = none(AsyncIoBase), output = none(AsyncIoBase), 103 | outputErr = none(AsyncIoBase), 104 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 105 | string), 106 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 107 | OnErrorFn)): AsyncProc = 108 | start(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 109 | toRemove: toRemove, 110 | input: input, output: output, outputErr: outputErr, 111 | env: env, envModifier: envModifier, workingDir: workingDir, 112 | processName: processName, logFn: logFn, onErrorFn: onErrorFn)) 113 | 114 | proc wait*(self: AsyncProc, cancelFut: Future[void] = nil): Future[ 115 | ProcResult] {.async.} = 116 | ## Permits to wait for the subprocess to finish in a async way 117 | ## Mandatory to clean up its resource (opened files, memory, etc.) (apart if the process is intended to be a daemon) 118 | ## Will not close or kill the subprocess, so it can be a deadlock if subprocess is waiting for input (solution is to close it) 119 | ## However this function doesn't need to be called to flush process output (automatically done to avoid pipie size limit deadlock) 120 | ## Raise OsError if cancelFut is triggered 121 | if not self.childProc.hasExited: 122 | if self.hasExitedEvent == nil: 123 | self.hasExitedEvent = ProcessEvent.new(self.childProc.getPid()) 124 | if not await self.hasExitedEvent.getFuture().wait(cancelFut): 125 | raise newException(OsError, "Timeout expired") 126 | let exitCode = self.childProc.wait() 127 | if RegisterProcess in self.procArgs.options: 128 | RunningProcesses.delete(RunningProcesses.find(self)) 129 | await self.afterWaitCleanup() 130 | result = ProcResult( 131 | fullCmd: self.fullCmd, 132 | cmd: self.cmd, 133 | input: 134 | (if self.captureStreams.input != nil: 135 | await self.captureStreams.input 136 | else: 137 | ""), 138 | output: 139 | (if self.captureStreams.output != nil: 140 | await self.captureStreams.output 141 | else: 142 | ""), 143 | outputErr: 144 | (if self.captureStreams.outputErr != nil: 145 | await self.captureStreams.outputErr 146 | else: 147 | ""), 148 | exitCode: exitCode, 149 | success: exitCode == 0, 150 | procArgs: self.procArgs, 151 | ) 152 | result.setOnErrorFn(self.procArgs.onErrorFn) # Private field 153 | for stream in self.closeWhenCapturesFlushed: 154 | stream.close() 155 | if self.procArgs.logFn != nil: 156 | self.procArgs.logFn(result) 157 | 158 | proc getPid*(p: AsyncProc): int = p.childProc.getPid() 159 | proc running*(p: AsyncProc): bool = p.childProc.running() 160 | proc suspend*(p: AsyncProc) = p.childProc.suspend() 161 | proc resume*(p: AsyncProc) = p.childProc.resume() 162 | proc terminate*(p: AsyncProc) = p.childProc.terminate() 163 | ## wait() should be called after terminate to clean up resource 164 | proc kill*(p: AsyncProc) = p.childProc.kill() 165 | ## wait() should be called after kill to clean up resource 166 | 167 | proc pretty*(self: AsyncProc): string = 168 | return "## AsyncProcess: " & $self.fullCmd & 169 | "\n- Pid: " & $self.getPid() & 170 | "\n- Running: " & $self.running() & 171 | "\n- Options: " & $self.procArgs.options 172 | 173 | proc run*(sh: ProcArgs, cmd: seq[string], argsModifier: ProcArgsModifier, 174 | cancelFut: Future[void] = nil): Future[ProcResult] {.async.} = 175 | ## A sugar to combine `sh.start(...).wait()` in a single call 176 | await sh.start(cmd, argsModifier).wait(cancelFut) 177 | 178 | proc run*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 179 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 180 | input = none(AsyncIoBase), output = none(AsyncIoBase), 181 | outputErr = none(AsyncIoBase), 182 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 183 | string), 184 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 185 | OnErrorFn), cancelFut: Future[void] = nil): Future[ProcResult] = 186 | run(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 187 | toRemove: toRemove, 188 | input: input, output: output, outputErr: outputErr, 189 | env: env, envModifier: envModifier, workingDir: workingDir, 190 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 191 | 192 | proc runAssert*(sh: ProcArgs, cmd: seq[string], argsModifier: ProcArgsModifier, 193 | cancelFut: Future[void] = nil): Future[ProcResult] {.async.} = 194 | ## A sugar to combine `sh.start(...).wait(...).assertSuccess()` in a single call 195 | await sh.start(cmd, argsModifier).wait(cancelFut).assertSuccess() 196 | 197 | proc runAssert*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 198 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 199 | input = none(AsyncIoBase), output = none(AsyncIoBase), 200 | outputErr = none(AsyncIoBase), 201 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 202 | string), 203 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 204 | OnErrorFn), cancelFut: Future[void] = nil): Future[ProcResult] = 205 | runAssert(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 206 | toRemove: toRemove, 207 | input: input, output: output, outputErr: outputErr, 208 | env: env, envModifier: envModifier, workingDir: workingDir, 209 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 210 | 211 | proc runDiscard*(sh: ProcArgs, cmd: seq[string], argsModifier: ProcArgsModifier, 212 | cancelFut: Future[void] = nil): Future[void] {.async.} = 213 | ## A sugar to combine `discard sh.start(...).wait(...).assertSuccess()` in a single call 214 | when defined(release): 215 | discard await sh.start(cmd, 216 | argsModifier.merge(toRemove = {CaptureInput, CaptureOutput, 217 | CaptureOutputErr}) 218 | ).wait(cancelFut).assertSuccess() 219 | else: 220 | # Easier debugging 221 | discard await sh.start(cmd, 222 | argsModifier.merge(toRemove = {CaptureInput, CaptureOutput}) 223 | ).wait(cancelFut).assertSuccess() 224 | 225 | proc runDiscard*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 226 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 227 | input = none(AsyncIoBase), output = none(AsyncIoBase), 228 | outputErr = none(AsyncIoBase), 229 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 230 | string), 231 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 232 | OnErrorFn), cancelFut: Future[void] = nil): Future[void] = 233 | runDiscard(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 234 | toRemove: toRemove, 235 | input: input, output: output, outputErr: outputErr, 236 | env: env, envModifier: envModifier, workingDir: workingDir, 237 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 238 | 239 | proc runCheck*(sh: ProcArgs, cmd: seq[string], argsModifier: ProcArgsModifier, 240 | cancelFut: Future[void] = nil): Future[bool] {.async.} = 241 | ## A sugar to combine `sh.start(...).wait(...).success` in a single call 242 | await(sh.start(cmd, 243 | argsModifier.merge(toRemove = (when defined(release): 244 | {MergeStderr, CaptureInput, CaptureOutputErr} 245 | else: 246 | {MergeStderr, CaptureInput} 247 | )) 248 | ).wait(cancelFut)).success 249 | 250 | proc runCheck*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 251 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 252 | input = none(AsyncIoBase), output = none(AsyncIoBase), 253 | outputErr = none(AsyncIoBase), 254 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 255 | string), 256 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 257 | OnErrorFn), cancelFut: Future[void] = nil): Future[bool] = 258 | runCheck(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 259 | toRemove: toRemove, 260 | input: input, output: output, outputErr: outputErr, 261 | env: env, envModifier: envModifier, workingDir: workingDir, 262 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 263 | 264 | proc runGetOutput*(sh: ProcArgs, cmd: seq[string], 265 | argsModifier: ProcArgsModifier, 266 | cancelFut: Future[void] = nil): Future[string] {.async.} = 267 | ## A sugar to combine `withoutLineEnd await sh.runAssert(...).output` in a single call 268 | ## - Can raise ExecError if not successful 269 | ## - LineEnd is always stripped, because it is usually unawanted. 270 | ## Use sh.run if this comportement is not wanted 271 | ## - OutputErr stream is not included (removed in release mode, 272 | ## but captured in debug mode -> only show on error) 273 | withoutLineEnd await(sh.start(cmd, 274 | argsModifier.merge( 275 | toAdd = {CaptureOutput}, 276 | toRemove = (when defined(release): 277 | {MergeStderr, CaptureInput, CaptureOutputErr} 278 | else: 279 | {MergeStderr, CaptureInput} 280 | ) 281 | )).wait(cancelFut).assertSuccess()).output 282 | 283 | proc runGetOutput*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[ 284 | string]), toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 285 | input = none(AsyncIoBase), output = none(AsyncIoBase), 286 | outputErr = none(AsyncIoBase), 287 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 288 | string), 289 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 290 | OnErrorFn), cancelFut: Future[void] = nil): Future[string] = 291 | runGetOutput(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 292 | toRemove: toRemove, 293 | input: input, output: output, outputErr: outputErr, 294 | env: env, envModifier: envModifier, workingDir: workingDir, 295 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 296 | 297 | proc runGetLines*(sh: ProcArgs, cmd: seq[string], 298 | argsModifier: ProcArgsModifier, 299 | cancelFut: Future[void] = nil): Future[seq[string]] {.async.} = 300 | ## A sugar to combine `await splitLines sh.runGetOutput(...)` in a single call 301 | ## - Can raise ExecError if not successful 302 | ## - OutputErr stream is not included (removed in release mode, but captured in debug mode -> only show on error) 303 | splitLines withoutLineEnd await sh.runGetOutput(cmd, argsModifier) 304 | 305 | proc runGetLines*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[string]), 306 | toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 307 | input = none(AsyncIoBase), output = none(AsyncIoBase), 308 | outputErr = none(AsyncIoBase), 309 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 310 | string), 311 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 312 | OnErrorFn), cancelFut: Future[void] = nil): Future[seq[string]] = 313 | runGetLines(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 314 | toRemove: toRemove, 315 | input: input, output: output, outputErr: outputErr, 316 | env: env, envModifier: envModifier, workingDir: workingDir, 317 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 318 | 319 | proc runGetOutputStream*(sh: ProcArgs, cmd: seq[string], 320 | argsModifier: ProcArgsModifier, 321 | cancelFut: Future[void] = nil): (AsyncIoBase, Future[void]) = 322 | ## A sugar doing this: `await splitLines sh.run(..., output = stream)` in a single call 323 | ## - Can raise ExecError if not successful 324 | ## - Second return value is a future that will be finished when the subprocess has been waited 325 | ## - OutputErr stream is not included (removed in release mode, but captured in debug mode -> only show on error) 326 | var pipe = AsyncPipe.new() 327 | var finishFut = sh.runDiscard(cmd, 328 | argsModifier.merge( 329 | toRemove = (when defined(release): 330 | {MergeStderr, Interactive, CaptureOutput, CaptureInput, CaptureOutputErr} 331 | else: 332 | {MergeStderr, Interactive, CaptureOutput, CaptureInput} 333 | ), 334 | output = some pipe.AsyncIoBase 335 | ), cancelFut) 336 | return (pipe.reader, finishFut) 337 | 338 | proc runGetOutputStream*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[ 339 | string]), toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 340 | input = none(AsyncIoBase), output = none(AsyncIoBase), 341 | outputErr = none(AsyncIoBase), 342 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 343 | string), 344 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 345 | OnErrorFn), cancelFut: Future[void] = nil): (AsyncIoBase, 346 | Future[void]) = 347 | runGetOutputStream(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, 348 | toAdd: toAdd, toRemove: toRemove, 349 | input: input, output: output, outputErr: outputErr, 350 | env: env, envModifier: envModifier, workingDir: workingDir, 351 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 352 | 353 | proc runGetStreams*(sh: ProcArgs, cmd: seq[string], 354 | argsModifier: ProcArgsModifier, 355 | cancelFut: Future[void] = nil): (AsyncIoBase, AsyncIoBase, Future[void]) = 356 | ## - Similar to runGetOutputStream, but returns both output stream and outputErr stream 357 | ## - OutputErr stream is not included (removed in release mode, but captured in debug mode -> only show on error) 358 | var outputPipe = AsyncPipe.new() 359 | var outputErrPipe = AsyncPipe.new() 360 | var finishFut = sh.runDiscard(cmd, 361 | argsModifier.merge( 362 | toRemove = (when defined(release): 363 | {MergeStderr, Interactive, CaptureOutput, CaptureInput, CaptureOutputErr} 364 | else: 365 | {MergeStderr, Interactive, CaptureOutput, CaptureInput} 366 | ), 367 | output = some outputPipe.AsyncIoBase, 368 | outputErr = some outputErrPipe.AsyncIoBase 369 | ), cancelFut) 370 | return (outputPipe.reader, outputErrPipe.reader, finishFut) 371 | 372 | proc runGetStreams*(sh: ProcArgs, cmd: seq[string], prefixCmd = none(seq[ 373 | string]), toAdd: set[ProcOption] = {}, toRemove: set[ProcOption] = {}, 374 | input = none(AsyncIoBase), output = none(AsyncIoBase), 375 | outputErr = none(AsyncIoBase), 376 | env = none(ProcEnv), envModifier = none(ProcEnv), workingDir = none( 377 | string), 378 | processName = none(string), logFn = none(LogFn), onErrorFn = none( 379 | OnErrorFn), cancelFut: Future[void] = nil): (AsyncIoBase, 380 | AsyncIoBase, Future[void]) = 381 | runGetStreams(sh, cmd, ProcArgsModifier(prefixCmd: prefixCmd, toAdd: toAdd, 382 | toRemove: toRemove, 383 | input: input, output: output, outputErr: outputErr, 384 | env: env, envModifier: envModifier, workingDir: workingDir, 385 | processName: processName, logFn: logFn, onErrorFn: onErrorFn), cancelFut) 386 | 387 | proc askYesNo(sh: ProcArgs, text: string): bool = 388 | # A more complete version is available in myshellcmd/ui 389 | while true: 390 | stdout.write(text) 391 | stdout.write " [sh/y/n]? " 392 | let response = readLine(stdin).normalize() 393 | if response in ["y", "yes"]: 394 | return true 395 | if response in ["n", "no"]: 396 | return false 397 | if response in ["sh", "shell", "bash"]: 398 | discard waitFor sh.run(@["bash", "--norc", "-i"], ProcArgsModifier( 399 | toAdd: {Interactive}, 400 | envModifier: some {"PS1": "bash$ "}.toTable() 401 | )) 402 | stdout.write("\n") 403 | else: 404 | echo "Response is not in available choice. Please try again.\n" 405 | -------------------------------------------------------------------------------- /src/asyncproc/exports/procargsresult.nim: -------------------------------------------------------------------------------- 1 | import std/[options, strutils, sequtils] 2 | from std/os import quoteShell 3 | 4 | import asyncio 5 | import aloganimisc/[deep, strmisc] 6 | 7 | import ./procenv {.all.} 8 | 9 | export quoteShell, options 10 | 11 | # It has been decided to rely heavily on Options for finer user control and simplicity of implementation 12 | 13 | type 14 | ProcOption* = enum 15 | ##[ 16 | ### Daemon 17 | The childproc will not be killed with its parent and continue to live afterwards 18 | 19 | ### QuoteArgs 20 | - ensure command arguments are arguments and not code 21 | - Essential if arguments come from user input 22 | - If you don't use it, please use quoteShell function to sanitize input 23 | - Not called if prefixCmd is not set, because posix exec can only accept arguments 24 | 25 | ### IgnoreInterrupt (low level option) 26 | Ctrl-c (SIGINT on linux) on foreground process (either by using Interactive or input = stdinAsync) will don't kill the process 27 | 28 | ### Interactive 29 | * First: parent's streams will be added to input/output/outputErr of child 30 | * This will not affect Capture options. 31 | * This will not affect that user can also set child input/output/outpterr (which will take priority): 32 | * if user set input, it will result in AsyncChainReader.new(userInput, stdinAsync) 33 | * if user set output/outputErr, it will result in AsyncTeeWriter.new(userInput, stdout/stderrAsync) 34 | * Secondly: it will ensure (on posix) that childproc stdin act as a fake terminal if needed (mandatory for repls like bash, python, etc) 35 | - only if custom input has been defined or CaptureInput is set 36 | - might not be as stable as using directly stdin 37 | 38 | 39 | ### MergeStderr 40 | - Output and OutputErr will designate the same pipe, ensuring the final output is ordered chronogically 41 | - Not having outputErr makes error less explicit 42 | 43 | ### RegisterProcess 44 | Put the spawn process in a global variable named RunningProcesses until it is waited. Useful for tracking or global Process manipulation 45 | 46 | ### SetEnvOnCmdLine (low level option) 47 | - Instead of giving childProc an environment, it will be given an empty environment 48 | - And env will be put on commandline. eg: `@["ssh", "user@localhost", "export MYVAR=MYVAL MYVAR2=MYVAL2; command"]` 49 | ]## 50 | AskConfirmation, 51 | CaptureInput, CaptureOutput, CaptureOutputErr, 52 | Daemon, DryRun, IgnoreInterrupt, Interactive, 53 | KeepStreamOpen, MergeStderr, 54 | QuoteArgs, NoParentEnv, RegisterProcess, 55 | SetEnvOnCmdLine, ShowCommand, WithLogging, 56 | 57 | 58 | const defaultRunFlags = {QuoteArgs, ShowCommand, Interactive, CaptureOutput, 59 | CaptureOutputErr, WithLogging} 60 | 61 | type 62 | ProcResult* = ref object 63 | ## Data structures containing all informations captured. 64 | ## fullCmd includes procArgs.prefixCmd, whereas cmd don't 65 | fullCmd*: seq[string] 66 | cmd*: seq[string] 67 | output*: string 68 | outputErr*: string 69 | input*: string 70 | success*: bool 71 | exitCode*: int 72 | procArgs*: ProcArgs 73 | onErrorFn: OnErrorFn 74 | 75 | ExecError* = OSError 76 | ## ExecError can only be raised when assertSuccess is used (or any run function relying on it) 77 | 78 | LogFn* = proc(res: ProcResult) 79 | OnErrorFn* = proc(res: ProcResult): Future[ProcResult] 80 | 81 | ProcArgs* = ref object 82 | ##[ 83 | ### prefixCmd 84 | Command that is put before the actual command being run. Must be capable of evaluating a command, like: 85 | - `@["sh", "-c"]` 86 | - `@["chroot", "path"]` 87 | - `@["ssh, "address"]` 88 | ### options 89 | All the high level tweaking available for the childProcess 90 | ### env 91 | If isNone, parent's env will be used, otherwise, will be used even if empty (resulting in no env) 92 | ### processName 93 | The name of the process as it will be available in ps/top commands. Not very useful. Doesn't hide the arguments. 94 | ### logFn 95 | This proc will be called only if WithLogging option is set and after childproc have been awaited, no matter if result is success or not 96 | ### onErrorFn 97 | This proc will be called if childproc quit with an error code and assertSucces is called 98 | (or any function calling it like runAssert, runGetOutput, etc) 99 | ### input, output, outputErr: 100 | - low level arguments to fine tweak child standard streams. Try to use options if possible 101 | - it could be any stream defined in mylib/asyncio 102 | - if closeWhenOver option is set to true and ProcArgs is used multile times, 103 | will create a deceptive behaviour (non zero exit on child proc which need streams) 104 | - deepCopy ProcArgs will have little effect on those arguments if they are associated with FileHandle 105 | (eg: AsyncFile, AsyncPipe, stdinAsync, etc) because FileHandle is a global value 106 | 107 | deepCopy is the way to go to create a new ProcArgs with same argument 108 | ]## 109 | prefixCmd* = newSeq[string]() 110 | options* = defaultRunFlags 111 | env*: ProcEnv 112 | workingDir* = "" 113 | processName* = "" 114 | logFn*: LogFn 115 | onErrorFn*: OnErrorFn 116 | input*: AsyncIoBase 117 | output*: AsyncIoBase 118 | outputErr*: AsyncIoBase 119 | 120 | ProcArgsModifier* = object 121 | ##[ 122 | Utility struct to help modify ProcArgs in a API-like way/functional style, while maintaining fine control and explicitness 123 | 124 | .. warning:: Option is very picky on the types given 125 | - to pass a stream, be sure, to cast it to AsyncIoBase, eg: `some AsyncString.new("data").AsyncIoBase` 126 | - to pass a callback, be sure to cast it to the appropriate function, eg: `some LogFn(proc(res: ProcResult) = ...)` 127 | ]## 128 | prefixCmd*: Option[seq[string]] 129 | toAdd*: set[ProcOption] 130 | toRemove*: set[ProcOption] 131 | input*: Option[AsyncIoBase] 132 | output*: Option[AsyncIoBase] 133 | outputErr*: Option[AsyncIoBase] 134 | env*: Option[ProcEnv] ## replace the original one 135 | envModifier*: Option[ProcEnv] 136 | ## envModifier don't replace the original one, but is merged with it 137 | workingDir*: Option[string] 138 | processName*: Option[string] 139 | logFn*: Option[LogFn] 140 | onErrorFn*: Option[OnErrorFn] 141 | 142 | 143 | let 144 | sh* = ProcArgs() ## Default ProcArgs object to avoid final user to create a ProcArg. Shall not be modified directly but copied rather 145 | internalCmd* = when defined(release): 146 | ProcArgsModifier(toRemove: {Interactive, MergeStderr, ShowCommand, 147 | CaptureInput, CaptureOutput, CaptureOutputErr}) 148 | else: 149 | ProcArgsModifier(toAdd: {CaptureOutputErr}, toRemove: {Interactive, 150 | MergeStderr, ShowCommand, CaptureInput, CaptureOutput}) 151 | ## A ProcArgsModifier that adjust default flags for unimportant or common commands by suppressing echoing, interactivness and error capture 152 | 153 | proc deepCopy*(self: ProcArgs): ProcArgs = 154 | deep.deepCopy(result, self) 155 | 156 | proc merge*(procArgs: ProcArgs, modifier: ProcArgsModifier): ProcArgs = 157 | ProcArgs( 158 | prefixCmd: if modifier.prefixCmd.isSome: modifier.prefixCmd.get( 159 | ) else: procArgs.prefixCmd, 160 | options: procArgs.options + modifier.toAdd - modifier.toRemove, 161 | input: if modifier.input.isSome: modifier.input.get( 162 | ) else: procArgs.input, 163 | output: if modifier.output.isSome: modifier.output.get( 164 | ) else: procArgs.output, 165 | outputErr: if modifier.outputErr.isSome: modifier.outputErr.get( 166 | ) else: procArgs.outputErr, 167 | env: (if modifier.env.isSome: 168 | if modifier.envModifier.isSome(): 169 | mergeEnv(modifier.env.get(), modifier.envModifier.get()) 170 | else: 171 | modifier.env.get() 172 | else: 173 | if modifier.envModifier.isSome(): 174 | mergeEnv(procArgs.env, modifier.envModifier.get()) 175 | else: 176 | procArgs.env 177 | ), 178 | workingDir: if modifier.workingDir.isSome: modifier.workingDir.get( 179 | ) else: procArgs.workingDir, 180 | processName: if modifier.processName.isSome: modifier.processName.get( 181 | ) else: procArgs.processName, 182 | logFn: if modifier.logFn.isSome: modifier.logFn.get( 183 | ) else: procArgs.logFn, 184 | onErrorFn: if modifier.onErrorFn.isSome: modifier.onErrorFn.get( 185 | ) else: procArgs.onErrorFn, 186 | ) 187 | 188 | proc merge*(a, b: ProcArgsModifier): ProcArgsModifier = 189 | ProcArgsModifier( 190 | prefixCmd: if b.prefixCmd.isSome: b.prefixCmd else: a.prefixCmd, 191 | toAdd: a.toAdd + b.toAdd, # options to remove take priority 192 | toRemove: (a.toRemove - b.toAdd) + b.toRemove, 193 | input: if b.input.isSome: b.input else: a.input, 194 | output: if b.output.isSome: b.output else: a.output, 195 | outputErr: if b.outputErr.isSome: b.outputErr else: a.outputErr, 196 | env: (if b.env.isSome: 197 | if a.env.isSome: 198 | some(mergeEnv(a.env.get(), b.env.get())) 199 | else: 200 | b.env 201 | else: 202 | a.env), 203 | envModifier: (if b.envModifier.isSome: 204 | if a.envModifier.isSome: 205 | some(mergeEnv(a.envModifier.get(), b.envModifier.get())) 206 | else: 207 | b.envModifier 208 | else: 209 | a.envModifier), 210 | workingDir: if b.workingDir.isSome: b.workingDir else: a.workingDir, 211 | processName: if b.processName.isSome: b.processName else: a.processName, 212 | logFn: if b.logFn.isSome: b.logFn else: a.logFn, 213 | onErrorFn: if b.onErrorFn.isSome: b.onErrorFn else: a.onErrorFn, 214 | ) 215 | 216 | proc merge*[T: ProcArgs or ProcArgsModifier](a: T, 217 | prefixCmd = none(seq[string]), 218 | toAdd: set[ProcOption] = {}, 219 | toRemove: set[ProcOption] = {}, 220 | input = none(AsyncIoBase), 221 | output = none(AsyncIoBase), 222 | outputErr = none(AsyncIoBase), 223 | env = none(ProcEnv), 224 | envModifier = none(ProcEnv), 225 | workingDir = none(string), 226 | processName = none(string), 227 | logFn = none(LogFn), 228 | onErrorFn = none(OnErrorFn) 229 | ): T = 230 | a.merge( 231 | ProcArgsModifier( 232 | prefixCmd: prefixCmd, 233 | toAdd: toAdd, 234 | toRemove: toRemove, 235 | input: input, 236 | output: output, 237 | outputErr: outputErr, 238 | env: env, 239 | envModifier: envModifier, 240 | workingDir: workingDir, 241 | processName: processName, 242 | logFn: logFn, 243 | onErrorFn: onErrorFn, 244 | ) 245 | ) 246 | 247 | proc buildCommand(procArgs: ProcArgs, postfixCmd: seq[string]): seq[ 248 | string] {.used.} = 249 | if procArgs.prefixCmd.len() == 0: 250 | if SetEnvOnCmdLine in procArgs.options: 251 | raise newException(OsError, "Can't apply " & $SetEnvOnCmdLine & " option if not prefixCmd has been given") 252 | # No quotation needed in this case 253 | return postfixCmd 254 | result.add procArgs.prefixCmd 255 | var stringCmd: string 256 | stringCmd.add ( 257 | if SetEnvOnCmdLine in procArgs.options: 258 | (if NoParentEnv in procArgs.options: 259 | procArgs.env 260 | else: 261 | newEnvFromParent().mergeEnv(procArgs.env) 262 | ).toShellFormat(QuoteArgs in procArgs.options) 263 | else: "" 264 | ) 265 | stringCmd.add (if QuoteArgs in procArgs.options: 266 | postfixCmd.map(proc(arg: string): string = quoteShell(arg)) 267 | else: 268 | postfixCmd 269 | ).join(" ") 270 | result.add stringCmd 271 | 272 | proc formatErrorMsg*(procResult: ProcResult): string = 273 | ## Convenience method to transform procResult in human readable form 274 | ## Tails errorOutput 275 | let errData = tail(10, if procResult.outputErr != "": 276 | procResult.outputErr 277 | else: 278 | procResult.output) 279 | return "## ProcResult: " & $procResult.fullCmd & 280 | "\nExitCode: " & $procResult.exitCode & (if errData == "": 281 | "" 282 | else: 283 | "\n*** COMMAND DATA TAIL ***\n" & errData.repr() & 284 | "\n*** END OF DATA ***\n" 285 | ) 286 | 287 | proc pretty*(procResult: ProcResult): string = 288 | ## To format it more human readable. Subject to change 289 | return "## ProcResult: " & $procResult.fullCmd & 290 | "\n- Input: " & procResult.input.repr() & 291 | "\n- Output: " & procResult.output.repr() & 292 | "\n- OutputErr: " & procResult.outputErr.repr() 293 | 294 | proc assertSuccess*(res: Future[ProcResult]): Future[ProcResult] {.async.} = 295 | ## If ProcResult is unsuccessful, try to replace it by OnErrorFn callback 296 | ## If onErrorFn is unsuccessful or doesn't exists, raise an ExecError 297 | let awaitedResult = await res 298 | if awaitedResult.success: 299 | return awaitedResult 300 | elif awaitedResult.onErrorFn != nil: 301 | let newProcResult = await awaitedResult.onErrorFn(awaitedResult) 302 | if newProcResult.success: 303 | return newProcResult 304 | raise newException(ExecError, awaitedResult.formatErrorMsg()) 305 | 306 | proc merge*(allResults: varargs[ProcResult]): ProcResult = 307 | ## Add together captured streams, keep the max exitCode 308 | ## If one result is not a success, all while be unsuccessful 309 | ## But discard following args: options, onErrorFn 310 | result = ProcResult() 311 | var length: int 312 | 313 | length = allResults.len() - 1 # separator 314 | for i in 0..high(allResults): inc(length, allResults[i].fullCmd.len()) 315 | result.fullCmd = newSeqofCap[string](length) 316 | result.fullCmd.add allResults[0].fullCmd 317 | for i in 1..high(allResults): 318 | result.fullCmd.add allResults[i].fullCmd 319 | result.fullCmd.add "\n" 320 | 321 | for i in 0..high(allResults): inc(length, allResults[i].cmd.len()) 322 | result.cmd = newSeqofCap[string](length) 323 | result.cmd.add allResults[0].cmd 324 | for i in 1..high(allResults): 325 | result.cmd.add allResults[i].cmd 326 | result.cmd.add "\n" 327 | 328 | length = allResults.len() - 1 # separator 329 | for i in 0..high(allResults): inc(length, allResults[i].output.len()) 330 | result.output = newStringOfCap(length) 331 | result.output.add allResults[0].output 332 | for i in 1..high(allResults): 333 | result.output.add allResults[i].output 334 | result.output.add "\n" 335 | 336 | length = allResults.len() - 1 # separator 337 | for i in 0..high(allResults): inc(length, allResults[i].outputErr.len()) 338 | result.outputErr = newStringOfCap(length) 339 | result.outputErr.add allResults[0].outputErr 340 | for i in 1..high(allResults): 341 | result.outputErr.add allResults[i].outputErr 342 | result.outputErr.add "\n" 343 | 344 | length = allResults.len() - 1 # separator 345 | for i in 0..high(allResults): inc(length, allResults[i].input.len()) 346 | result.input = newStringOfCap(length) 347 | result.input.add allResults[0].input 348 | for i in 1..high(allResults): 349 | result.input.add allResults[i].input 350 | result.input.add "\n" 351 | 352 | result.exitCode = foldl(allResults, max(a, b.exitCode), 0) 353 | result.success = result.exitCode != 0 354 | 355 | proc withoutLineEnd*(s: string): string = 356 | result = s 357 | result.stripLineEnd() 358 | 359 | proc setOnErrorFn(self: ProcResult, onErrorFn: OnErrorFn) {.used.} = 360 | self.onErrorFn = onErrorFn 361 | -------------------------------------------------------------------------------- /src/asyncproc/exports/procenv.nim: -------------------------------------------------------------------------------- 1 | import std/[tables, envvars] 2 | from std/os import quoteShell 3 | 4 | export quoteShell, tables 5 | 6 | type 7 | ProcEnv* = Table[string, string] 8 | ## Interface to set up subprocess env 9 | ## Not used to modify terminal env 10 | 11 | 12 | converter toEnv*(table: Table[string, string]): ProcEnv = 13 | ProcEnv(table) 14 | 15 | proc newEmptyEnv*(): ProcEnv = 16 | discard 17 | 18 | proc newEnvFromParent*(): ProcEnv = 19 | ## Create an env object filled with parent environment 20 | ## Can make a the command pretty long if used with SetEnvOnCmdLine flag, but should not cause issue 21 | for k, v in envPairs(): 22 | result[k] = v 23 | 24 | proc mergeEnv(first, second: ProcEnv): ProcEnv {.used.} = 25 | ## Second will overwrite first 26 | if second.len() == 0: 27 | return first 28 | if first.len() == 0: 29 | return second 30 | for k, v in second.pairs(): 31 | result[k] = v 32 | 33 | proc toShellFormat(env: ProcEnv, quoteArgs: bool): string {.used.} = 34 | var exportCmd: string 35 | if quoteArgs: 36 | for (key, val) in env.pairs(): 37 | exportCmd.add " " & quoteShell(key) & "=" & quoteShell(val) 38 | else: 39 | for (key, val) in env.pairs(): 40 | exportCmd.add " " & key & "=" & val 41 | if exportCmd != "": 42 | return "export" & exportCmd & "; " 43 | -------------------------------------------------------------------------------- /src/asyncproc/exports/procmacro.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, asyncdispatch] 2 | import ./procargsresult 3 | 4 | #[ 5 | TODO: 6 | - test if it behaves correctly : 7 | implicitAsync(@["sh"]): 8 | sh.mergeArgs(blah).run() 9 | sh.runGetLine().output 10 | - provide a way to register line info for debugging purpose: 11 | getLineContent() -> give it to sh 12 | ]# 13 | 14 | macro tryOrFallBack*(cmd: untyped, body: untyped) = 15 | ## Command is only valid if await(`cmd`).exitCode is valid 16 | ## Can be good to encapsulate multiple statements, but OnErrorFn is more reliable 17 | let bodyrepr = body.repr 18 | quote do: 19 | while true: 20 | try: 21 | `body` 22 | break 23 | except Exception as err: 24 | echo "\nAn error occured while executing:", `bodyrepr`, "\n" 25 | echo getStackTrace() 26 | echo "FALLBACK TO SHELL, ExitCode meaning: 0 == retry, 1 == quit, 125 == skip" 27 | let procRes = `cmd` 28 | let exitCode = when procRes is Future[ProcResult]: 29 | await(procRes).exitCode 30 | else: 31 | procRes.exitCode 32 | if exitCode == 125: 33 | break 34 | elif exitCode != 0: 35 | raise err 36 | 37 | proc implicitAwaitHelper(identNames: seq[string], node: NimNode): NimNode = 38 | var nodeCopy = node.copyNimNode() 39 | if node.kind in [nnkCall, nnkCommand] and node[0].kind == nnkDotExpr: 40 | if node[0][0].kind in [nnkCall, nnkCommand] and 41 | node[0][0][0].kind == nnkDotExpr and 42 | node[0][0][0][0].strVal in identNames: 43 | nodeCopy.add node[0].copyNimTree() 44 | for child in node[1..^1]: 45 | nodeCopy.add implicitAwaitHelper(identNames, child) 46 | return newCall("await", nodeCopy) 47 | if node[0][0].kind == nnkIdent and node[0][0].strVal in identNames: 48 | for child in node: 49 | nodeCopy.add implicitAwaitHelper(identNames, child) 50 | return newCall("await", nodeCopy) 51 | for child in node: 52 | nodeCopy.add implicitAwaitHelper(identNames, child) 53 | return nodeCopy 54 | 55 | macro implicitAwait*(identNames: static[seq[string]], body: untyped): untyped = 56 | #[ Replace only on method call syntax 57 | To escape : use procedural call syntax or put ident name inside parenthesis 58 | Can understand only one merge eg: sh.merge(...).run(...), but no await is implictly put inside merge 59 | If put inside async function, will be called twice... 60 | ]# 61 | result = implicitAwaitHelper(identNames, body) 62 | -------------------------------------------------------------------------------- /src/asyncproc/private/childproc_posix.nim: -------------------------------------------------------------------------------- 1 | import std/[os, exitprocs, posix, termios, bitops, options] 2 | import std/[tables, strutils] 3 | import asyncio 4 | 5 | import ./streamsbuilder 6 | 7 | ## SIGINT is ignored in main process, preventing it from being accidentally killed 8 | ## warning..: 9 | ## when using `nim r program.nim`, program.nim is a child process of choosenim and is catched by choosenim 10 | ## https://github.com/nim-lang/Nim/issues/23573 11 | onSignal(SIGINT): 12 | discard 13 | 14 | # Library 15 | var PR_SET_PDEATHSIG {.importc, header: "".}: cint 16 | proc prctl(option, argc2: cint): cint {.varargs, header: "".} 17 | proc openpty(master, slave: var cint; slave_name: cstring; arg1, 18 | arg2: pointer): cint {.importc, header: "".} 19 | 20 | 21 | type ChildProc* = object 22 | pid*: Pid 23 | exitCode*: cint 24 | hasExited*: bool 25 | cleanUps: seq[proc()] 26 | 27 | const defaultPassFds = @[ 28 | (src: FileHandle(0), dest: FileHandle(0)), 29 | (src: FileHandle(1), dest: FileHandle(1)), 30 | (src: FileHandle(2), dest: FileHandle(2)), 31 | ] 32 | 33 | var TermiosBackup: Option[Termios] 34 | 35 | proc newPtyPair(): tuple[master, slave: AsyncFile] = 36 | var master, slave: cint 37 | # default termios param shall be ok 38 | if openpty(master, slave, nil, nil, nil) == -1: raiseOSError(osLastError()) 39 | return (AsyncFile.new(master), AsyncFile.new(slave)) 40 | 41 | proc restoreTerminal() = 42 | if TermiosBackup.isSome(): 43 | if tcsetattr(STDIN_FILENO, TCSANOW, addr TermiosBackup.get()) == -1: 44 | raiseOSError(osLastError()) 45 | 46 | proc newChildTerminalPair(): tuple[master, slave: AsyncFile] = 47 | if TermiosBackup.isNone(): 48 | TermiosBackup = some Termios() 49 | if tcGetAttr(STDIN_FILENO, addr TermiosBackup.get()) == 50 | -1: raiseOSError(osLastError()) 51 | addExitProc(proc() = restoreTerminal()) 52 | var newParentTermios: Termios 53 | # Make the parent raw 54 | newParentTermios = TermiosBackup.get() 55 | newParentTermios.c_lflag.clearMask(ICANON) 56 | newParentTermios.c_lflag.clearMask(ISIG) 57 | newParentTermios.c_lflag.clearMask(ECHO) 58 | newParentTermios.c_cc[VMIN] = 1.char 59 | newParentTermios.c_cc[VTIME] = 0.char 60 | if tcsetattr(STDIN_FILENO, TCSANOW, addr newParentTermios) == 61 | -1: raiseOSError(osLastError()) 62 | return newPtyPair() 63 | 64 | proc toChildStream*(streamsBuilder: StreamsBuilder): tuple[ 65 | passFds: seq[tuple[src: FileHandle; dest: FileHandle]]; 66 | captures: tuple[input, output, outputErr: Future[string]]; 67 | useFakePty: bool; 68 | closeWhenCapturesFlushed: seq[AsyncIoBase]; 69 | afterSpawn: proc() {.closure.}; 70 | afterWait: proc(): Future[void] {.closure.}; 71 | ] = 72 | if streamsBuilder.nonStandardStdin(): 73 | var (master, slave) = newChildTerminalPair() 74 | var closeEvent = newFuture[void]() 75 | if MergeStderr in streamsBuilder.flags: 76 | var (streams, captures, transferWaiters, closeWhenWaited, closeWhenCapturesFlushed 77 | ) = streamsBuilder.buildToStreams() 78 | discard streams.stdin.transfer(master, closeEvent) 79 | transferWaiters.add master.transfer(streams.stdout) 80 | closeWhenCapturesFlushed.add master 81 | return ( 82 | passFds: toPassFds(slave, slave, slave), 83 | captures: captures, 84 | useFakePty: true, 85 | closeWhenCapturesFlushed: closeWhenCapturesFlushed, 86 | afterSpawn: proc() = slave.close(), 87 | afterWait: proc(): Future[void] {.async.} = 88 | closeEvent.complete() 89 | await all(transferWaiters) 90 | for s in closeWhenWaited: 91 | s.close() 92 | restoreTerminal() 93 | ) 94 | else: 95 | var stdoutCapture = AsyncPipe.new() 96 | var stderrCapture = AsyncPipe.new() 97 | streamsBuilder.addStreamtoStdout(slave) 98 | streamsBuilder.addStreamtoStderr(slave) 99 | streamsBuilder.flags.excl InteractiveOut 100 | var (streams, captures, transferWaiters, closeWhenWaited, closeWhenCapturesFlushed 101 | ) = streamsBuilder.buildToStreams() 102 | transferWaiters.add (stdoutCapture.transfer(streams.stdout) and 103 | stderrCapture.transfer(streams.stderr)).then(proc() {.async.} = 104 | await stdoutCapture.clear() and stderrCapture.clear() 105 | slave.close() 106 | stdoutCapture.close() 107 | stderrCapture.close()) 108 | transferWaiters.add master.transfer(stderrAsync).then(proc() {.async.} = master.close()) 109 | discard streams.stdin.transfer(master, closeEvent) 110 | return ( 111 | passFds: toPassFds(slave, stdoutCapture.writer, 112 | stderrCapture.writer), 113 | captures: captures, 114 | useFakePty: true, 115 | closeWhenCapturesFlushed: closeWhenCapturesFlushed, 116 | afterSpawn: (proc() = 117 | stdoutCapture.writer.close() 118 | stderrCapture.writer.close() 119 | ), 120 | afterWait: proc(): Future[void] {.async.} = 121 | closeEvent.complete() 122 | await all(transferWaiters) 123 | for s in closeWhenWaited: 124 | s.close() 125 | restoreTerminal() 126 | ) 127 | else: 128 | var closeEvent = newFuture[void]() 129 | var (stdFiles, captures, transferWaiters, closeWhenWaited, closeWhenCapturesFlushed 130 | ) = streamsBuilder.buildToChildFile(closeEvent) 131 | return ( 132 | passFds: toPassFds(stdFiles.stdin, stdFiles.stdout, 133 | stdFiles.stderr), 134 | captures: captures, 135 | useFakePty: false, 136 | closeWhenCapturesFlushed: closeWhenCapturesFlushed, 137 | afterSpawn: (proc() = 138 | for stream in [stdFiles.stdin, stdFiles.stdout, stdFiles.stderr]: 139 | let streamIdx = closeWhenWaited.find(stream) 140 | if streamIdx != -1: 141 | closeWhenWaited[streamIdx].close() 142 | closeWhenWaited.del(streamIdx) 143 | ), 144 | afterWait: proc(): Future[void] {.async.} = 145 | closeEvent.complete() 146 | await all(transferWaiters) 147 | for s in closeWhenWaited: 148 | s.close() 149 | ) 150 | 151 | 152 | proc waitImpl(p: var ChildProc; hang: bool) = 153 | if p.hasExited: 154 | return 155 | var status: cint 156 | let errorCode = waitpid(p.pid, status, if hang: 0 else: WNOHANG) 157 | if errorCode == p.pid: 158 | if WIFEXITED(status) or WIFSIGNALED(status): 159 | p.hasExited = true 160 | p.exitCode = WEXITSTATUS(status) 161 | elif errorCode == 0'i32: 162 | discard ## Assume the process is still up and running 163 | else: 164 | raiseOSError(osLastError()) 165 | 166 | proc wait*(p: var ChildProc): int = 167 | ## Without it, the pid won't be recycled 168 | ## Block main thread 169 | p.waitImpl(true) 170 | return p.exitCode 171 | 172 | proc envToCStringArray(t: Table[string, string]): cstringArray = 173 | ## from std/osproc 174 | result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) 175 | var i = 0 176 | for key, val in pairs(t): 177 | var x = key & "=" & val 178 | result[i] = cast[cstring](alloc(x.len+1)) 179 | copyMem(result[i], addr(x[0]), x.len+1) 180 | inc(i) 181 | 182 | proc readAll(fd: FileHandle): string = 183 | let bufferSize = 1024 184 | result = newString(bufferSize) 185 | var totalCount: int 186 | while true: 187 | let bytesCount = posix.read(fd, addr(result[totalCount]), bufferSize) 188 | if bytesCount == 0: 189 | break 190 | totalCount += bytesCount 191 | result.setLen(totalCount + bufferSize) 192 | result.setLen(totalCount) 193 | 194 | 195 | proc startProcess*(command: string; args: seq[string]; 196 | passFds = defaultPassFds; env = initTable[string, string](); 197 | workingDir = ""; daemon = false; fakePty = false; 198 | ignoreInterrupt = false): ChildProc = 199 | ##[ 200 | ### args 201 | args[0] should be the process name. Not providing it result in undefined behaviour 202 | ### env 203 | if env is nil, use parent process 204 | ### file_descriptors 205 | parent process is responsible for creating and closing its pipes ends 206 | ### daemonize 207 | if false: will be closed with parent process 208 | else: will survive (but no action on fds is done) 209 | ]## 210 | var fdstoKeep = newSeq[FileHandle](passFds.len()) 211 | for i in 0..high(passFds): 212 | fdstoKeep[i] = passFds[i][1] 213 | # Nim objects to C objects 214 | var sysArgs = allocCStringArray(args) 215 | defer: deallocCStringArray(sysArgs) 216 | var sysEnv = envToCStringArray(env) 217 | defer: (if sysEnv != nil: deallocCStringArray(sysEnv)) 218 | # Error pipe for catching inside child 219 | var errorPipes: array[2, cint] 220 | if pipe(errorPipes) != 0'i32: 221 | raiseOSError(osLastError()) 222 | let ppidBeforeFork = getCurrentProcessId() 223 | let pid = fork() 224 | if pid == 0'i32: # Child 225 | try: 226 | var childPid = getCurrentProcessId() 227 | # Working dir 228 | if workingDir.len > 0'i32: 229 | setCurrentDir(workingDir) 230 | # IO handling 231 | for (src, dest) in passFds: 232 | if src != dest: 233 | let exitCode = dup2(src, dest) 234 | if exitCode < 0'i32: raiseOSError(osLastError()) 235 | for (_, file) in walkDir("/proc/" & $childPid & "/fd/", 236 | relative = true): 237 | let fd = file.parseInt().cint 238 | if fd notin fdstoKeep and fd != errorPipes[1]: 239 | discard close(fd) 240 | # Daemon 241 | if fakePty and not daemon: 242 | if setsid() < 0'i32: raiseOSError(osLastError()) 243 | elif daemon: 244 | # recommanded to close standard fds 245 | discard umask(0) 246 | if setsid() < 0'i32: raiseOSError(osLastError()) 247 | signal(SIGHUP, SIG_IGN) 248 | else: 249 | let exitCode = prctl(PR_SET_PDEATHSIG, SIGHUP) 250 | if exitCode < 0'i32 or getppid() != ppidBeforeFork: 251 | exitnow(1) 252 | # Misc 253 | if ignoreInterrupt: 254 | signal(SIGINT, SIG_IGN) 255 | discard close(errorPipes[1]) 256 | except: 257 | let errMsg = getCurrentExceptionMsg() 258 | discard write(errorPipes[1], addr(errMsg[0]), errMsg.len()) 259 | discard close(errorPipes[1]) # Could have been using fnctl FD_CLOEXEC 260 | exitnow(1) 261 | # Should be safe (or too hard to catch) from here 262 | # Exec 263 | when defined(uClibc) or defined(linux) or defined(haiku): 264 | let exe = findExe(command) 265 | if sysEnv != nil: 266 | discard execve(exe.cstring, sysArgs, sysEnv) 267 | else: 268 | discard execv(exe.cstring, sysArgs) 269 | else: # MacOs mainly 270 | if sysEnv != nil: 271 | var environ {.importc.}: cstringArray 272 | environ = sysEnv 273 | discard execvp(command.cstring, sysArgs) 274 | exitnow(1) 275 | 276 | # Child error handling 277 | if pid < 0: raiseOSError(osLastError()) 278 | discard close(errorPipes[1]) 279 | var errorMsg = readAll(errorPipes[0]) 280 | discard close(errorPipes[0]) 281 | if errorMsg.len() != 0: raise newException(OSError, errorMsg) 282 | return ChildProc(pid: pid, hasExited: false) 283 | 284 | proc getPid*(p: ChildProc): int = 285 | p.pid 286 | 287 | proc running*(p: var ChildProc): bool = 288 | p.waitImpl(false) 289 | return not p.hasExited 290 | 291 | proc suspend*(p: ChildProc) = 292 | if posix.kill(p.pid, SIGSTOP) != 0'i32: raiseOSError(osLastError()) 293 | 294 | proc resume*(p: ChildProc) = 295 | if posix.kill(p.pid, SIGCONT) != 0'i32: raiseOSError(osLastError()) 296 | 297 | proc terminate*(p: ChildProc) = 298 | if posix.kill(p.pid, SIGTERM) != 0'i32: raiseOSError(osLastError()) 299 | 300 | proc kill*(p: ChildProc) = 301 | if posix.kill(p.pid, SIGKILL) != 0'i32: raiseOSError(osLastError()) 302 | 303 | -------------------------------------------------------------------------------- /src/asyncproc/private/streamsbuilder.nim: -------------------------------------------------------------------------------- 1 | import std/deques 2 | import asyncio 3 | 4 | 5 | type 6 | StreamsBuilder* = ref object 7 | flags*: set[BuilderFlags] 8 | stdin, stdout, stderr: AsyncIoBase 9 | closeWhenWaited: seq[AsyncIoBase] 10 | transferWaiters: seq[Future[void]] 11 | closeWhenCapturesFlushed: seq[AsyncIoBase] 12 | 13 | BuilderFlags* {.pure.} = enum 14 | InteractiveStdin, InteractiveOut, 15 | CaptureStdin, CaptureStdout, CaptureStderr 16 | MergeStderr 17 | 18 | let delayedStdoutAsync = AsyncIoDelayed.new(stdoutAsync, 1) 19 | let delayedStderrAsync = AsyncIoDelayed.new(stderrAsync, 2) 20 | 21 | 22 | 23 | proc init*(T: type StreamsBuilder; stdin, stdout, stderr: AsyncioBase; 24 | keepStreamOpen, mergeStderr: bool): StreamsBuilder = 25 | result = StreamsBuilder( 26 | flags: (if mergeStderr: {BuilderFlags.MergeStderr} else: {}), 27 | stdin: stdin, 28 | stdout: stdout, 29 | stderr: stderr, 30 | ) 31 | if not keepStreamOpen: 32 | if stdin != nil: 33 | result.closeWhenWaited.add stdin 34 | if stdout != nil: 35 | result.closeWhenWaited.add stdout 36 | if stderr != nil: 37 | result.closeWhenWaited.add stderr 38 | 39 | proc addStreamToStdinChain*(builder: StreamsBuilder; newStream: AsyncIoBase) = 40 | if builder.stdin == nil: 41 | builder.stdin = newStream 42 | elif builder.stdin == newStream: 43 | discard 44 | elif builder.stdin of AsyncChainReader: 45 | AsyncChainReader(builder.stdin).addReader newStream 46 | else: 47 | builder.stdin = AsyncChainReader.new(builder.stdin, newStream) 48 | 49 | proc addStreamtoOutImpl(stream: var AsyncIoBase; newStream: AsyncIoBase) = 50 | if stream == nil: 51 | stream = newStream 52 | elif stream == newStream: 53 | discard 54 | elif stream of AsyncTeeWriter: 55 | AsyncTeeWriter(stream).writers.add(newStream) 56 | else: 57 | stream = AsyncTeeWriter.new(stream, newStream) 58 | 59 | proc addStreamtoStdout*(builder: StreamsBuilder; newStream: AsyncIoBase) = 60 | builder.stdout.addStreamtoOutImpl(newStream) 61 | 62 | proc addStreamtoStderr*(builder: StreamsBuilder; newStream: AsyncIoBase) = 63 | builder.stderr.addStreamtoOutImpl(newStream) 64 | 65 | proc buildStdinInteractive(builder: StreamsBuilder) = 66 | builder.addStreamToStdinChain(stdinAsync) 67 | 68 | 69 | 70 | # Builders must be called in that order 71 | 72 | proc buildOutInteractiveImpl(stream: var AsyncIoBase; stdStream, 73 | stdStreamDelayed: AsyncIoBase) = 74 | if stream == nil: 75 | stream = stdStreamDelayed 76 | elif stream == stdStream: 77 | discard 78 | elif stream of AsyncTeeWriter: 79 | AsyncTeeWriter(stream).writers.add(stdStreamDelayed) 80 | else: 81 | stream = AsyncTeeWriter.new(stream, stdStreamDelayed) 82 | 83 | proc buildOutInteractive(builder: StreamsBuilder) = 84 | if BuilderFlags.MergeStderr in builder.flags: 85 | builder.stdout.buildOutInteractiveImpl(stdoutAsync, stdoutAsync) 86 | builder.stderr = builder.stdout 87 | else: 88 | builder.stdout.buildOutInteractiveImpl(stdoutAsync, delayedStdoutAsync) 89 | builder.stderr.buildOutInteractiveImpl(stderrAsync, delayedStderrAsync) 90 | 91 | proc buildStdinCapture(builder: StreamsBuilder): Future[string] = 92 | if builder.stdin == nil: 93 | return 94 | let captureIo = AsyncStream.new() 95 | builder.stdin = AsyncTeeReader.new(builder.stdin, captureIo) 96 | builder.closeWhenWaited.add captureIo.writer 97 | return captureIo.readAll() 98 | 99 | proc buildOutCaptureImpl(builder: StreamsBuilder; 100 | stream: var AsyncIoBase): Future[string] = 101 | if stream != nil: 102 | let captureIo = AsyncStream.new() 103 | if stream of AsyncTeeWriter: 104 | AsyncTeeWriter(stream).writers.add(captureIo) 105 | else: 106 | stream = AsyncTeeWriter.new(stream, captureIo.writer) 107 | builder.closeWhenWaited.add captureIo.writer 108 | builder.closeWhenCapturesFlushed.add captureIo.reader 109 | return captureIo.reader.readAll() 110 | else: 111 | var pipe = AsyncPipe.new() 112 | stream = pipe.writer 113 | builder.closeWhenWaited.add pipe.writer 114 | builder.closeWhenCapturesFlushed.add pipe.reader 115 | return pipe.reader.readAll() 116 | 117 | proc buildStdoutCapture(builder: StreamsBuilder): Future[string] = 118 | builder.buildOutCaptureImpl(builder.stdout) 119 | 120 | proc buildStderrCapture(builder: StreamsBuilder): Future[string] = 121 | builder.buildOutCaptureImpl(builder.stderr) 122 | 123 | proc buildImpl(builder: StreamsBuilder): tuple[captures: tuple[input, output, 124 | outputErr: Future[string]]] = 125 | if InteractiveStdin in builder.flags: 126 | builder.buildStdinInteractive() 127 | if InteractiveOut in builder.flags: 128 | builder.buildOutInteractive() 129 | if CaptureStdin in builder.flags: 130 | result.captures.input = builder.buildStdinCapture() 131 | if CaptureStdout in builder.flags: 132 | result.captures.output = builder.buildStdoutCapture() 133 | if CaptureStderr in builder.flags: 134 | result.captures.outputErr = builder.buildStderrCapture() 135 | 136 | proc buildToStreams*(builder: StreamsBuilder): tuple[ 137 | streams: tuple[stdin, stdout, stderr: AsyncIoBase]; 138 | captures: tuple[input, output, outputErr: Future[string]]; 139 | transferWaiters: seq[Future[void]]; 140 | closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase] 141 | ] = 142 | ## Closing is handled internally, so output should not be closed using anything else as toClose return value 143 | let (captures) = builder.buildImpl() 144 | return ( 145 | (builder.stdin, builder.stdout, builder.stderr), 146 | captures, 147 | builder.transferWaiters, 148 | builder.closeWhenWaited, 149 | builder.closeWhenCapturesFlushed 150 | ) 151 | 152 | proc buildOutChildFileImpl(builder: StreamsBuilder; 153 | stream: AsyncIoBase): AsyncFile = 154 | if stream == nil: 155 | return nil 156 | elif stream of AsyncFile: 157 | return AsyncFile(stream) 158 | elif stream of AsyncPipe: 159 | return AsyncPipe(stream).writer 160 | else: 161 | var pipe = AsyncPipe.new() 162 | builder.transferWaiters.add pipe.reader.transfer(stream).then(proc() {.async.} = pipe.reader.close()) 163 | builder.closeWhenWaited.add pipe.writer 164 | return pipe.writer 165 | 166 | proc buildToChildFile*(builder: StreamsBuilder; closeEvent: Future[ 167 | void]): tuple[ 168 | stdFiles: tuple[stdin, stdout, stderr: Asyncfile]; 169 | captures: tuple[input, output, outputErr: Future[string]]; 170 | transferWaiters: seq[Future[void]]; 171 | closeWhenWaited, closeWhenCapturesFlushed: seq[AsyncIoBase] 172 | ] = 173 | let (captures) = builder.buildImpl() 174 | let stdin = 175 | if builder.stdin == nil: 176 | nil 177 | elif builder.stdin of AsyncPipe: 178 | AsyncPipe(builder.stdin).reader 179 | elif builder.stdin of AsyncFile: 180 | AsyncFile(builder.stdin) 181 | else: 182 | var pipe = AsyncPipe.new() 183 | builder.transferWaiters.add builder.stdin.transfer(pipe.writer, 184 | closeEvent).then(proc() {.async.} = 185 | pipe.writer.close() 186 | ) 187 | builder.closeWhenWaited.add pipe.reader 188 | pipe.reader 189 | let stdout = builder.buildOutChildFileImpl(builder.stdout) 190 | let stderr = 191 | if BuilderFlags.MergeStderr in builder.flags: 192 | stdout 193 | else: 194 | builder.buildOutChildFileImpl(builder.stderr) 195 | return ( 196 | (stdin, stdout, stderr), 197 | captures, 198 | builder.transferWaiters, 199 | builder.closeWhenWaited, 200 | builder.closeWhenCapturesFlushed 201 | ) 202 | 203 | proc toPassFds*(stdin, stdout, stderr: AsyncFile): seq[tuple[src: FileHandle; 204 | dest: FileHandle]] = 205 | if stdin != nil: 206 | result.add (stdin.fd, 0.FileHandle) 207 | if stdout != nil: 208 | result.add (stdout.fd, 1.FileHandle) 209 | if stderr != nil: 210 | result.add (stderr.fd, 2.FileHandle) 211 | 212 | proc nonStandardStdin*(builder: StreamsBuilder): bool = 213 | ## if stdin is open and will not be equal to stdinAsync -> check before any build occurs 214 | (InteractiveStdin in builder.flags or builder.stdin == stdinAsync) and 215 | CaptureStdin in builder.flags 216 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/tasyncproc.nim: -------------------------------------------------------------------------------- 1 | import asyncproc 2 | import asyncio 3 | import std/[os, sequtils, envvars, strutils] 4 | 5 | import std/unittest 6 | 7 | var sh = ProcArgs(options: {QuoteArgs, CaptureOutput, CaptureOutputErr, NoParentEnv}) 8 | var shWithPrefix = ProcArgs(prefixCmd: @["sh", "-c"], options: {QuoteArgs, 9 | CaptureOutput, CaptureOutputErr}) 10 | var shUnquotedWithPrefix = ProcArgs(prefixCmd: @["sh", "-c"], options: { 11 | CaptureOutput, CaptureOutputErr}) 12 | var shMergedStderr = shUnquotedWithPrefix.merge(toAdd = {MergeStderr}) 13 | 14 | let pid = getCurrentProcessId() 15 | proc getFdCount(): int = 16 | toSeq(walkDir("/proc/" & $pid & "/fd", relative = true)).len() 17 | 18 | proc main() {.async.} = 19 | test "Basic IO": 20 | check (await sh.runCheck(@["true"])) 21 | check not (await sh.runCheck(@["false"])) 22 | check (await sh.runGetOutput(@["echo", "Hello"])) == "Hello" 23 | check (await sh.runGetOutput(@["echo", "-n", "Hello"])) == "Hello" 24 | check (await sh.runGetLines(@["echo", "line1\nline2"])) == @["line1", "line2"] 25 | check getFdCount() == 5 26 | 27 | test "workingDir": 28 | let workingDir = "/home" 29 | check (await sh.runGetOutput(@["pwd"], ProcArgsModifier( 30 | workingDir: some workingDir))) == workingDir 31 | check getFdCount() == 5 32 | 33 | test "logFn": 34 | var loggedVal: string 35 | discard await sh.run(@["echo", "Hello"], ProcArgsModifier( 36 | toAdd: {CaptureOutput, WithLogging}, 37 | logFn: some proc(res: ProcResult) = 38 | loggedVal = withoutLineEnd res.output 39 | )) 40 | check loggedVal == "Hello" 41 | check getFdCount() == 5 42 | 43 | test "No Output": 44 | var shNoOutput = ProcArgs(options: {QuoteArgs}) 45 | check not (await shNoOutput.run(@["echo", "Hello"])).success 46 | check getFdCount() == 5 47 | 48 | test "With Tee output": 49 | var outStream = AsyncStream.new() 50 | check (await sh.runGetOutput(@["echo", "Hello"], ProcArgsModifier( 51 | output: some outStream.writer.AsyncIoBase))) == "Hello" 52 | check (await outStream.readAll()) == "Hello\n" 53 | check getFdCount() == 5 54 | 55 | test "implicit await": 56 | var sh2 = sh.deepCopy() 57 | implicitAwait(@["sh2"]): 58 | check sh2.runGetOutput(@["echo", "Hello"]) == "Hello" 59 | check (await sh.runGetOutput(@["echo", "Hello"])) == "Hello" 60 | check getFdCount() == 5 61 | 62 | test "environment": 63 | var shWithParentEnv = sh.deepCopy().merge(toRemove = {NoParentEnv}) 64 | check (await sh.runGetOutput(@["env"])) == "" 65 | check (await sh.runGetOutput(@["env"], ProcArgsModifier(env: some { 66 | "VAR": "VALUE"}.toTable))) == "VAR=VALUE" 67 | putEnv("KEY", "VALUE") 68 | check "KEY=VALUE" in (await shWithParentEnv.runGetLines(@["env"])) 69 | check getFdCount() == 5 70 | 71 | test "with interpreter: Quoted": 72 | check (await shWithPrefix.runGetOutput(@["echo", "Hello"])) == "Hello" 73 | check not (await shWithPrefix.run(@["echo Hello"])).success 74 | check getFdCount() == 5 75 | 76 | test "with interpreter: Unquoted": 77 | check (await shUnquotedWithPrefix.runGetOutput(@["echo", "Hello"])) == "Hello" 78 | check (await shUnquotedWithPrefix.runGetOutput(@["echo Hello"])) == "Hello" 79 | check getFdCount() == 5 80 | 81 | test "Stderr": 82 | check (await shUnquotedWithPrefix.run(@["echo Hello >&2"])).output == "" 83 | check (await shUnquotedWithPrefix.run(@["echo Hello >&2"])).outputErr == "Hello\n" 84 | check (await shUnquotedWithPrefix.merge(toRemove = { 85 | CaptureOutputErr}).run(@["echo Hello >&2"])).outputErr == "" 86 | check (await shMergedStderr.run(@["echo Hello"])).output == "Hello\n" 87 | check (await shMergedStderr.run(@["echo Hello >&2"])).output == "Hello\n" 88 | check (await shMergedStderr.run(@["echo Hello >&2"])).outputErr == "" 89 | check getFdCount() == 5 90 | 91 | test "EnvOnComdline: Quoted": 92 | # sh add a few env variable 93 | check "VAR=VALUE" in (await shWithPrefix.runGetLines(@["env"], 94 | ProcArgsModifier( 95 | env: some {"VAR": "VALUE"}.toTable, 96 | toAdd: {SetEnvOnCmdLine} 97 | ))) 98 | # Side effect, because quoted, space is well captured 99 | check "VAR=VALUE SPACED" in (await shWithPrefix.runGetLines(@["env"], 100 | ProcArgsModifier( 101 | env: some {"VAR": "VALUE SPACED"}.toTable, 102 | toAdd: {SetEnvOnCmdLine} 103 | ))) 104 | check getFdCount() == 5 105 | 106 | test "EnvOnComdline: Unquoted": 107 | # sh add a few env variable 108 | check "VAR=VALUE" in (await shUnquotedWithPrefix.runGetLines(@["env"], 109 | ProcArgsModifier( 110 | env: some {"VAR": "VALUE"}.toTable, 111 | toAdd: {SetEnvOnCmdLine} 112 | ))) 113 | # >>> @["sh", "-c", "export VAR=VALUE SPACED; env"] 114 | check "VAR=VALUE SPACED" notin (await shUnquotedWithPrefix.runGetLines( 115 | @["env"], ProcArgsModifier( 116 | env: some {"VAR": "VALUE SPACED"}.toTable, 117 | toAdd: {SetEnvOnCmdLine} 118 | ))) 119 | check getFdCount() == 5 120 | 121 | test "Interactive with input capture": 122 | var sh2 = ProcArgs(prefixCmd: @["sh", "-c"], options: {Interactive, 123 | CaptureInput, CaptureOutput, CaptureOutputErr}) 124 | var sh2Merged = sh2.merge(toAdd = {MergeStderr}) 125 | 126 | var outputStr = (await sh2Merged.run(@["echo Hello"])).output 127 | check outputStr == "Hello\r\n" 128 | check outputStr.withoutLineEnd() == "Hello" 129 | check (await sh2.run(@["echo Hello"])).output == "Hello\n" 130 | discard await stdoutAsync.write "Please provide an input: " 131 | var procRes = await sh2.run(@["read a; echo $a"]) 132 | check procRes.input == procRes.output 133 | check getFdCount() == 5 134 | 135 | 136 | waitFor main() 137 | --------------------------------------------------------------------------------