33#
44# zsh-async
55#
6- # version: 1.6.2
6+ # version: 1.7.0
77# author: Mathias Fredriksson
88# url: https://github.com/mafredri/zsh-async
99#
1010
11- typeset -g ASYNC_VERSION=1.6.2
11+ typeset -g ASYNC_VERSION=1.7.0
1212# Produce debug output from zsh-async when set to 1.
1313typeset -g ASYNC_DEBUG=${ASYNC_DEBUG:- 0}
1414
15+ # Execute commands that can manipulate the environment inside the async worker. Return output via callback.
16+ _async_eval () {
17+ local ASYNC_JOB_NAME
18+ # Rename job to _async_eval and redirect all eval output to cat running
19+ # in _async_job. Here, stdout and stderr are not separated for
20+ # simplicity, this could be improved in the future.
21+ {
22+ eval " $@ "
23+ } & > >( ASYNC_JOB_NAME=[async/eval] _async_job ' cat' )
24+ }
25+
1526# Wrapper for jobs executed by the async worker, gives output in parseable format with execution time
1627_async_job () {
1728 # Disable xtrace as it would mangle the output.
@@ -26,6 +37,7 @@ _async_job() {
2637 # block, after the command block has completed, the stdin for `cat` is
2738 # closed, causing stderr to be appended with a $'\0' at the end to mark the
2839 # end of output from this job.
40+ local jobname=${ASYNC_JOB_NAME:- $1 }
2941 local stdout stderr ret tok
3042 {
3143 stdout=$( eval " $@ " )
@@ -36,7 +48,7 @@ _async_job() {
3648 read -r -k 1 -p tok || exit 1
3749
3850 # Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
39- print -r -n - $' \0' ${(q)1 } $ret ${(q)stdout} $duration
51+ print -r -n - $' \0' ${(q)jobname } $ret ${(q)stdout} $duration
4052 } 2> >( stderr=$( cat) && print -r -n - " " ${(q)stderr} $' \0' )
4153
4254 # Unlock mutex by inserting a token.
@@ -132,7 +144,7 @@ _async_worker() {
132144 coproc_pid=0 # Reset pid.
133145 }
134146
135- local request
147+ local request do_eval=0
136148 local -a cmd
137149 while : ; do
138150 # Wait for jobs sent by async_job.
@@ -147,8 +159,9 @@ _async_worker() {
147159
148160 # Check for non-job commands sent to worker
149161 case $request in
150- _unset_trap) notify_parent=0; continue ;;
151- _killjobs) killjobs; continue ;;
162+ _unset_trap) notify_parent=0; continue ;;
163+ _killjobs) killjobs; continue ;;
164+ _async_eval* ) do_eval=1;;
152165 esac
153166
154167 # Parse the request using shell parsing (z) to allow commands
@@ -181,18 +194,27 @@ _async_worker() {
181194 print -n -p " t"
182195 fi
183196
184- # Run job in background, completed jobs are printed to stdout.
185- _async_job $cmd &
186- # Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
187- storage[$job ]=" $! "
197+ if (( do_eval )) ; then
198+ shift cmd # Strip _async_eval from cmd.
199+ _async_eval $cmd
200+ do_eval=0
201+ else
202+ # Run job in background, completed jobs are printed to stdout.
203+ _async_job $cmd &
204+ # Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
205+ storage[$job ]=" $! "
206+ fi
188207
189208 processing=0 # Disable guard.
190209 done
191210}
192211
193212#
194- # Get results from finnished jobs and pass it to the to callback function. This is the only way to reliably return the
195- # job name, return code, output and execution time and with minimal effort.
213+ # Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the
214+ # job name, return code, output and execution time and with minimal effort.
215+ #
216+ # If the async process buffer becomes corrupt, the callback will be invoked with the first argument being `[async]` (job
217+ # name), non-zero return code and fifth argument describing the error (stderr).
196218#
197219# usage:
198220# async_process_results <worker_name> <callback_function>
@@ -252,7 +274,7 @@ async_process_results() {
252274 else
253275 # In case of corrupt data, invoke callback with *async* as job
254276 # name, non-zero exit status and an error message on stderr.
255- $callback " async" 1 " " 0 " $0 :$LINENO : error: bad format, got ${# items} items (${(q)items} )" $has_next
277+ $callback " [ async] " 1 " " 0 " $0 :$LINENO : error: bad format, got ${# items} items (${(q)items} )" $has_next
256278 fi
257279 done
258280 done
@@ -299,6 +321,30 @@ async_job() {
299321 zpty -w $worker " $cmd " $' \0'
300322}
301323
324+ #
325+ # Evaluate a command (like async_job) inside the async worker, then worker environment can be manipulated. For example,
326+ # issuing a cd command will change the PWD of the worker which will then be inherited by all future async jobs.
327+ #
328+ # Output will be returned via callback, job name will be [async/eval].
329+ #
330+ # usage:
331+ # async_worker_eval <worker_name> <my_function> [<function_params>]
332+ #
333+ async_worker_eval () {
334+ setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings
335+
336+ local worker=$1 ; shift
337+
338+ local -a cmd
339+ cmd=(" $@ " )
340+ if (( $# cmd > 1 )) ; then
341+ cmd=(${(q)cmd} ) # Quote special characters in multi argument commands.
342+ fi
343+
344+ # Quote the cmd in case RC_EXPAND_PARAM is set.
345+ zpty -w $worker " _async_eval $cmd " $' \0'
346+ }
347+
302348# This function traps notification signals and calls all registered callbacks
303349_async_notify_trap () {
304350 setopt localoptions noshwordsplit
0 commit comments