feat: implement __rand_string generator and update jobrunner to use tmux

This commit is contained in:
2026-08-02 05:21:29 -04:00
parent 674a50ea05
commit ffff95f3b9
12 changed files with 2283 additions and 63 deletions
+99 -36
View File
@@ -5,24 +5,25 @@
# 08-terminal-management
#
# DEPENDENCIES
# screen, __jobrunner_sessions
# tmux, screen, __jobrunner_sessions
#
# SYNOPSIS
# jobrunner [<subcommand>] [<name>] [<command>...]
# jr [<subcommand>] [<name>] [<command>...]
# jobrunner [-t <tool>] [<subcommand>] [<name>] [<command>...]
# jr [-t <tool>] [<subcommand>] [<name>] [<command>...]
#
# DESCRIPTION
# Runs, lists, inspects, re-attaches to, and terminates named background
# jobs using GNU screen as the process engine. Unlike bkg and detach,
# which discard output, a jobrunner job keeps a live terminal you can
# return to later — it survives closing the shell, and `attach` restores
# it in any subsequent session.
# jobs using tmux or GNU screen as the process engine. Unlike bkg and
# detach, which discard output, a jobrunner job keeps a live terminal you
# can return to later — it survives closing the shell, and `attach`
# restores it in any subsequent session.
#
# Every subcommand has a matching flag form, and the common cases are
# inferred: no arguments lists jobs, a lone name attaches to it, and a
# name followed by a command runs it.
#
# ARGUMENTS
# -t, --tool <name> Force specific backend (tmux or screen)
# run, -r, --run <name> <cmd>... Start a named job in the background
# list, -l, --list List all managed background jobs
# attach, -a, --attach <name> Re-attach interactively to a job
@@ -33,11 +34,11 @@
# EXIT STATUS
# 0 Command succeeded, or no jobs are running
# 1 Invalid arguments, or the named job does not exist
# 127 screen is not installed
# 127 neither tmux nor screen is installed
#
# EXAMPLE
# jobrunner run build make -j8
# jobrunner backup rsync -a ./data remote:/backup/
# jobrunner -t screen run backup rsync -a ./data remote:/backup/
# jobrunner list
# jobrunner logs build
# jobrunner build
@@ -48,7 +49,7 @@
# Commands are executed directly rather than through a shell, so pipes and
# redirections must be wrapped explicitly, e.g.
# `jobrunner run sync fish -c 'a | b'`.
function jobrunner --description 'Manage detached background jobs with GNU screen'
function jobrunner --description 'Manage detached background jobs with tmux or GNU screen'
set -l c_head (set_color --bold cyan)
set -l c_cmd (set_color --bold white)
set -l c_arg (set_color cyan)
@@ -61,6 +62,29 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
set -l subcmds run list attach kill logs help \
-r --run -l --list -a --attach -k --kill -o --output -h --help
# ╭──────────────────────────────────────────────────────────╮
# │ Tool Extraction │
# ╰──────────────────────────────────────────────────────────╯
set -l tool ""
while test (count $argv) -gt 0
switch $argv[1]
case -t
set tool $argv[2]
set -e argv[1..2]
case --tool
set tool $argv[2]
set -e argv[1..2]
case '--tool=*'
set tool (string replace -- "--tool=" "" $argv[1])
set -e argv[1]
case '-t*'
set tool (string replace -r "^-t" "" $argv[1])
set -e argv[1]
case '*'
break
end
end
# ╭──────────────────────────────────────────────────────────╮
# │ Help │
# ╰──────────────────────────────────────────────────────────╯
@@ -68,7 +92,7 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
if set -q argv[1]; and contains -- $argv[1] help -h --help
echo "$c_head""Usage:$c_rst $c_cmd""jobrunner$c_rst $c_arg""[<subcommand>] [<name>] [<command>...]$c_rst"
echo
echo " Run and manage named background jobs backed by GNU screen."
echo " Run and manage named background jobs backed by tmux or GNU screen."
echo
echo "$c_head""Subcommands:$c_rst"
echo " $c_cmd""run$c_rst $c_arg""<name> <cmd>...$c_rst Start a named job in the background"
@@ -78,6 +102,7 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
echo " $c_cmd""logs$c_rst $c_arg""<name>$c_rst Print a job's output without attaching"
echo
echo "$c_head""Flags:$c_rst"
echo " $c_flag-t$c_rst, $c_flag--tool$c_rst $c_arg<name>$c_rst Force specific backend (tmux or screen)"
echo " $c_flag-r$c_rst, $c_flag--run$c_rst Same as $c_cmd""run$c_rst"
echo " $c_flag-l$c_rst, $c_flag--list$c_rst Same as $c_cmd""list$c_rst"
echo " $c_flag-a$c_rst, $c_flag--attach$c_rst Same as $c_cmd""attach$c_rst"
@@ -102,9 +127,24 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
# ╭──────────────────────────────────────────────────────────╮
# │ Dependency check │
# ╰──────────────────────────────────────────────────────────╯
if not command -q screen
echo "$c_err""jobrunner:$c_rst 'screen' is required but was not found in PATH." >&2
return 127
if test -n "$tool"
if not contains -- $tool tmux screen
echo "$c_err""jobrunner:$c_rst invalid tool '$c_arg$tool$c_rst', must be 'tmux' or 'screen'." >&2
return 1
end
if not command -q $tool
echo "$c_err""jobrunner:$c_rst '$tool' is required but was not found in PATH." >&2
return 127
end
else
if command -q tmux
set tool tmux
else if command -q screen
set tool screen
else
echo "$c_err""jobrunner:$c_rst neither 'tmux' nor 'screen' was found in PATH." >&2
return 127
end
end
# ╭──────────────────────────────────────────────────────────╮
@@ -116,7 +156,7 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
if not contains -- $cmd $subcmds
if test (count $argv) -eq 1
# A lone name attaches, but only if that job actually exists.
if contains -- $cmd (__jobrunner_sessions | string replace -r '\t.*$' '')
if contains -- $cmd (__jobrunner_sessions $tool | string replace -r '\t.*$' '')
set argv attach $argv
set cmd attach
else
@@ -148,19 +188,23 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
echo "$c_err""jobrunner:$c_rst job name may not contain '/'." >&2
return 1
end
if contains -- $name (__jobrunner_sessions | string replace -r '\t.*$' '')
if contains -- $name (__jobrunner_sessions $tool | string replace -r '\t.*$' '')
echo "$c_err""jobrunner:$c_rst job '$c_arg$name$c_rst' is already running." >&2
return 1
end
command screen -d -m -S $name $task
if test "$tool" = tmux
command tmux new-session -d -s $name $task
else
command screen -d -m -S $name $task
end
or begin
echo "$c_err""jobrunner:$c_rst failed to start job '$c_arg$name$c_rst'." >&2
return 1
end
set -l pid
for row in (__jobrunner_sessions)
for row in (__jobrunner_sessions $tool)
set -l f (string split \t -- $row)
test "$f[1]" = "$name"; and set pid $f[2]; and break
end
@@ -173,7 +217,7 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
end
case list -l --list
set -l rows (__jobrunner_sessions)
set -l rows (__jobrunner_sessions $tool)
if test (count $rows) -eq 0
echo "No background jobs running."
return 0
@@ -192,12 +236,16 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
return 1
end
set -l name $argv[2]
if not contains -- $name (__jobrunner_sessions | string replace -r '\t.*$' '')
if not contains -- $name (__jobrunner_sessions $tool | string replace -r '\t.*$' '')
echo "$c_err""jobrunner:$c_rst no such job '$c_arg$name$c_rst'." >&2
return 1
end
# -x attaches to an already-attached session instead of failing.
command screen -x $name
if test "$tool" = tmux
command tmux attach-session -t $name
else
# -x attaches to an already-attached session instead of failing.
command screen -x $name
end
case kill -k --kill
if test (count $argv) -ne 2
@@ -205,11 +253,15 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
return 1
end
set -l name $argv[2]
if not contains -- $name (__jobrunner_sessions | string replace -r '\t.*$' '')
if not contains -- $name (__jobrunner_sessions $tool | string replace -r '\t.*$' '')
echo "$c_err""jobrunner:$c_rst no such job '$c_arg$name$c_rst'." >&2
return 1
end
command screen -X -S $name quit
if test "$tool" = tmux
command tmux kill-session -t $name
else
command screen -X -S $name quit
end
or begin
echo "$c_err""jobrunner:$c_rst failed to terminate job '$c_arg$name$c_rst'." >&2
return 1
@@ -222,25 +274,36 @@ function jobrunner --description 'Manage detached background jobs with GNU scree
return 1
end
set -l name $argv[2]
if not contains -- $name (__jobrunner_sessions | string replace -r '\t.*$' '')
if not contains -- $name (__jobrunner_sessions $tool | string replace -r '\t.*$' '')
echo "$c_err""jobrunner:$c_rst no such job '$c_arg$name$c_rst'." >&2
return 1
end
# hardcopy -h dumps scrollback plus the visible screen to a file.
# ponytail: snapshot only; add `screen -L` logging if full
# since-start history is ever needed.
set -l dump (command mktemp)
command screen -X -S $name hardcopy -h $dump
or begin
set -l out
if test "$tool" = tmux
# capture-pane -S - -p outputs the scrollback and current pane directly to stdout.
set out (command tmux capture-pane -t $name -S - -p)
or begin
echo "$c_err""jobrunner:$c_rst could not read output of '$c_arg$name$c_rst'." >&2
return 1
end
else
# hardcopy -h dumps scrollback plus the visible screen to a file.
# ponytail: snapshot only; add `screen -L` logging if full
# since-start history is ever needed.
set -l dump (command mktemp)
command screen -X -S $name hardcopy -h $dump
or begin
command rm -f $dump
echo "$c_err""jobrunner:$c_rst could not read output of '$c_arg$name$c_rst'." >&2
return 1
end
set out (command cat $dump)
command rm -f $dump
echo "$c_err""jobrunner:$c_rst could not read output of '$c_arg$name$c_rst'." >&2
return 1
end
set -l out (command cat $dump)
command rm -f $dump
# hardcopy pads the dump out to the full window height.
# hardcopy (and sometimes tmux) pad the dump with empty lines.
while set -q out[1]; and test -z "$out[-1]"
set -e out[-1]
end