├── .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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
58 |
59 |
60 |
61 |
Source
62 |
Edit
63 |
64 |
65 |
66 |
67 |
73 |
74 |
75 |
76 | AsyncProc , runGetLines , kill , pretty , runGetStreams , run , runDiscard , suspend , runDiscard , runCheck , terminate , runGetOutputStream , runGetLines , resume , run , getPid , running , runAssert , runAssert , RunningProcesses , runGetOutputStream , runGetOutput , runCheck , wait , start , runGetStreams , runGetOutput , start , LogFn , merge , pretty , assertSuccess , ProcArgsModifier , withoutLineEnd , ProcArgs , sh , ProcOption , OnErrorFn , merge , merge , deepCopy , ExecError , merge , formatErrorMsg , internalCmd , ProcResult , newEnvFromParent , ProcEnv , newEmptyEnv , toEnv , implicitAwait , tryOrFallBack
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
87 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
50 |
51 |
52 | Types
53 |
57 |
58 |
59 |
60 |
61 | Procs
62 |
73 |
74 |
75 |
76 |
77 | Converters
78 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
Source
93 |
Edit
94 |
95 |
96 |
97 |
98 |
99 |
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 |
116 |
117 |
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 |
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 |
172 |
173 |
174 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
175 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
50 |
51 | Imports
52 |
53 |
54 |
55 | Macros
56 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
Source
75 |
Edit
76 |
77 |
78 |
79 |
80 |
86 |
87 |
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 |
123 |
124 |
125 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
126 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
50 |
51 |
52 | Types
53 |
65 |
66 |
67 |
68 |
69 | Procs
70 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
ExecError = OSError
101 |
102 |
103 |
104 |
105 |
106 |
107 |
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 |
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 |
179 |
180 |
181 | Made with Nim. Generated: 2024-05-01 10:58:36 UTC
182 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
50 |
52 | Imports
53 |
54 |
55 |
56 | Types
57 |
64 |
65 |
66 |
67 |
68 | Procs
69 |
70 |
74 |
78 |
82 |
86 |
92 |
96 |
100 |
112 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Source
126 |
Edit
127 |
128 |
129 |
130 |
SIGINT is ignored in main process, preventing it from being accidentally killed
131 |
137 |
138 |
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 |
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 | 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 |
296 |
297 |
298 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
299 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
50 |
51 | Imports
52 |
53 |
54 |
55 | Types
56 |
64 |
65 |
66 |
67 |
68 | Procs
69 |
70 |
74 |
78 |
82 |
94 |
106 |
112 |
116 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
Source
132 |
Edit
133 |
134 |
135 |
136 |
137 |
143 |
144 |
145 |
146 |
147 |
BuilderFlags = enum
148 | InteractiveStdin , InteractiveOut , CaptureStdin , CaptureStdout , CaptureStderr ,
149 | MergeStderr
150 |
151 |
152 |
153 | Source
154 | Edit
155 |
156 |
157 |
158 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
190 |
204 |
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 |
306 |
307 |
308 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
309 |
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 | Theme:
28 |
29 | 🌗 Match OS
30 | 🌑 Dark
31 | 🌕 Light
32 |
33 |
34 |
39 |
40 | Search:
41 |
42 |
43 | Group by:
44 |
45 | Section
46 | Type
47 |
48 |
49 |
55 |
56 |
57 |
58 |
Source
59 |
Edit
60 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Made with Nim. Generated: 2024-05-05 13:06:37 UTC
78 |
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 |
--------------------------------------------------------------------------------