Skip to content

Instantly share code, notes, and snippets.

@nicholaswmin
Last active July 9, 2026 01:38
Show Gist options
  • Select an option

  • Save nicholaswmin/ddbf7d5da7e3dab95c38f704b6d98631 to your computer and use it in GitHub Desktop.

Select an option

Save nicholaswmin/ddbf7d5da7e3dab95c38f704b6d98631 to your computer and use it in GitHub Desktop.
zsh function mapping files to public stable URL

s3.zsh

upload file(s) to S3

install

tmp=$(mktemp /tmp/s3-install.XXXXXX) &&
curl -fsSL -m 30 -o "$tmp" \
  https://gist.githubusercontent.com/nicholaswmin/ddbf7d5da7e3dab95c38f704b6d98631/raw/install.zsh &&
zsh "$tmp"
rc=$?
rm -f "$tmp"
exit "$rc"

This preserves curl failures. curl ... | zsh can exit 0 on fetch errors unless the caller already has pipefail.

This installs s3.zsh to:

${XDG_DATA_HOME:-$HOME/.local/share}/s3-zsh/s3.zsh

and adds a guarded source block to:

${ZDOTDIR:-$HOME}/.zshrc

Then load it:

source "${ZDOTDIR:-$HOME}/.zshrc"

usage

usage: s3 upload|up|u FILE... [--force] [aws-flags...]
       s3 delete|del|d FILE|URL... [aws-flags...]

test

zsh tester.zsh test
#!/usr/bin/env zsh
emulate -L zsh
setopt err_return no_unset pipe_fail
local -r GIST_USER='nicholaswmin'
local -r GIST_ID='ddbf7d5da7e3dab95c38f704b6d98631'
local -r RAW_BASE="https://gist.githubusercontent.com/$GIST_USER/$GIST_ID/raw"
local -r INSTALL_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/s3-zsh"
local -r TARGET="$INSTALL_ROOT/s3.zsh"
local -r ZDOTDIR_PATH="${ZDOTDIR:-$HOME}"
local -r ZSHRC="$ZDOTDIR_PATH/.zshrc"
local -r BLOCK_BEGIN='# >>> s3.zsh >>>'
local -r BLOCK_END='# <<< s3.zsh <<<'
local -r SOURCE_LINE='[[ -r "${XDG_DATA_HOME:-$HOME/.local/share}/s3-zsh/s3.zsh" ]] && source "${XDG_DATA_HOME:-$HOME/.local/share}/s3-zsh/s3.zsh"'
need() {
command -v "$1" >/dev/null 2>&1 || {
print -ru2 -- "error: missing command: $1"
return 1
}
}
rewrite_zshrc() {
local tmp_rc
tmp_rc=$(mktemp "${TMPDIR:-/tmp}/s3-zshrc.XXXXXX")
awk \
-v begin="$BLOCK_BEGIN" \
-v end="$BLOCK_END" \
-v source_line="$SOURCE_LINE" \
'
$0 == begin {
in_block = 1
if (!wrote_block) {
print begin
print source_line
print end
wrote_block = 1
}
next
}
in_block {
if ($0 == end) {
in_block = 0
}
next
}
index($0, "s3-zsh/s3.zsh") {
saw_source = 1
}
{
print
}
END {
if (!wrote_block && !saw_source) {
if (NR > 0) {
print ""
}
print begin
print source_line
print end
}
}
' "$ZSHRC" > "$tmp_rc"
mv "$tmp_rc" "$ZSHRC"
}
need curl
need awk
need mktemp
mkdir -p "$INSTALL_ROOT"
[[ -f "$ZSHRC" ]] || : > "$ZSHRC"
local tmp_target
tmp_target=$(mktemp "${TMPDIR:-/tmp}/s3-zsh.XXXXXX")
curl -fsSL "$RAW_BASE/s3.zsh" -o "$tmp_target"
mv "$tmp_target" "$TARGET"
rewrite_zshrc
print -r -- "installed: $TARGET"
print -r -- "loaded from: $ZSHRC"
print -r -- "next: source \"$ZSHRC\""
source "${0:A:h}/../s3.zsh"
typeset -g AWS_CALL=''
typeset -ga AWS_CALLS=()
typeset -gA EXISTING
typeset -gi AWS_EXIT=0
typeset -gi HEAD_EXIT=1
aws() {
if [[ $1 == s3api && $2 == head-object ]]; then
[[ -n ${EXISTING[$6]-} ]] && return 0
return $HEAD_EXIT
fi
AWS_CALL="$@"
AWS_CALLS+=("$*")
return $AWS_EXIT
}
tmp=$(command mktemp -d -t s3.test.XXXXXX)
tmp=${tmp:A}
trap 'command rm -rf -- "$tmp"' EXIT
print -r -- pixel > "$tmp/pic.png"
print -r -- pixel > "$tmp/b.png"
print -r -- pixel > "$tmp/c.png"
command mkdir -p "$tmp/deep/dir"
print -r -- pixel > "$tmp/deep/dir/img.png"
esc=$'\e'
out='' err='' ret=0
run() {
"$@" >$tmp/out 2>$tmp/err
ret=$?
out=$(<$tmp/out)
err=$(<$tmp/err)
}
plan 76
# --- help: -h / --help -------------------------------------------------
run s3 -h
is $ret 0 'help(-h): exits 0'
is "$out" "usage: s3 upload|up|u FILE... [--force] [aws-flags...]
s3 delete|del|d FILE|URL... [aws-flags...]" 'help(-h): usage on stdout'
is "$err" '' 'help(-h): nothing on stderr'
run s3 --help
is $ret 0 'help(--help): exits 0'
is "$out" "usage: s3 upload|up|u FILE... [--force] [aws-flags...]
s3 delete|del|d FILE|URL... [aws-flags...]" 'help(--help): usage on stdout'
is "$err" '' 'help(--help): nothing on stderr'
# --- invalid usage -------------------------------------------------------
run s3
is $ret 2 'no args: exits 2'
like "$err" '*unknown command:*' 'no args: stderr names unknown command'
like "$out" '*usage: s3 upload*' 'no args: usage on stdout'
run s3 frobnicate
is $ret 2 'bad verb: exits 2'
like "$err" '*unknown command: frobnicate*' 'bad verb: stderr names it'
like "$out" '*usage: s3 upload*' 'bad verb: usage on stdout'
run s3 upload
is $ret 2 'upload no arg: exits 2'
is "$err" "usage: s3 upload|up|u FILE... [--force] [aws-flags...]
s3 delete|del|d FILE|URL... [aws-flags...]" 'upload no arg: usage on stderr'
is "$out" '' 'upload no arg: nothing on stdout'
run s3 delete
is $ret 2 'delete no arg: exits 2'
is "$err" "usage: s3 upload|up|u FILE... [--force] [aws-flags...]
s3 delete|del|d FILE|URL... [aws-flags...]" 'delete no arg: usage on stderr'
is "$out" '' 'delete no arg: nothing on stdout'
# --- upload: missing local file ------------------------------------------
run s3 upload "$tmp/nope.png"
is $ret 1 'upload missing file: exits 1'
like "$err" "*no such file: $tmp/nope.png*" 'upload missing file: stderr names it'
is "$out" '' 'upload missing file: nothing on stdout'
# --- upload: success ------------------------------------------------------
AWS_EXIT=0
run s3 upload "$tmp/pic.png"
is $ret 0 'upload success: exits 0'
is "$out" 'https://bp-articles.s3.eu-west-1.amazonaws.com/pic.png' \
'upload success: exact URL on stdout'
like "$err" '*uploading*' 'upload success: progress line on stderr'
like "$err" '*done*' 'upload success: done line on stderr'
like "$AWS_CALL" '*s3 cp*' 'upload success: calls aws s3 cp'
like "$AWS_CALL" '*s3://bp-articles/pic.png*' 'upload success: correct s3 uri'
like "$AWS_CALL" "*--cache-control*public, max-age=31536000, immutable*" \
'upload success: cache-control header'
run s3 upload "$tmp/deep/dir/img.png"
is "$out" 'https://bp-articles.s3.eu-west-1.amazonaws.com/img.png' \
'upload nested path: key uses basename only'
run s3 upload "$tmp/pic.png" --dryrun
like "$AWS_CALL" '*--dryrun*' 'upload: extra flags passed through to aws'
run s3 up "$tmp/pic.png"
is "$out" 'https://bp-articles.s3.eu-west-1.amazonaws.com/pic.png' \
'up alias: behaves identically to upload'
# --- upload: clobber protection ------------------------------------------
HEAD_EXIT=0
AWS_CALL=''
run s3 upload "$tmp/pic.png"
is $ret 1 'clobber: refuses when key exists'
like "$err" '*pic.png already exists*use --force*' 'clobber: refusal message'
is "$out" '' 'clobber: nothing on stdout'
is "$AWS_CALL" '' 'clobber: aws cp never invoked'
run s3 upload "$tmp/pic.png" --force
is $ret 0 'clobber: --force overrides refusal'
is "$out" 'https://bp-articles.s3.eu-west-1.amazonaws.com/pic.png' \
'clobber: --force still returns correct url'
unlike "$AWS_CALL" '*--force*' 'clobber: --force stripped, not passed to aws'
HEAD_EXIT=1
# --- upload: multiple files ------------------------------------------
AWS_CALLS=()
run s3 upload "$tmp/pic.png" "$tmp/b.png"
is $ret 0 'multi-upload: exits 0 when all succeed'
like "$out" '*/pic.png*' 'multi-upload: first url on stdout'
like "$out" '*/b.png*' 'multi-upload: second url on stdout'
is ${#AWS_CALLS} 2 'multi-upload: both files actually uploaded'
AWS_CALLS=()
run s3 upload "$tmp/pic.png" "$tmp/nope.png" "$tmp/b.png"
is $ret 1 'multi-upload: nonzero exit if any file missing'
like "$err" "*no such file: $tmp/nope.png*" 'multi-upload: missing file named on stderr'
is ${#AWS_CALLS} 2 'multi-upload: the two valid files still uploaded'
AWS_CALLS=()
EXISTING=(b.png 1)
run s3 upload "$tmp/pic.png" "$tmp/b.png" "$tmp/c.png"
is $ret 1 'multi-upload: nonzero exit if any file clobbers'
like "$err" '*b.png already exists*' 'multi-upload: clobber names the right file'
is ${#AWS_CALLS} 2 'multi-upload: non-conflicting files still uploaded'
AWS_CALLS=()
run s3 upload "$tmp/pic.png" "$tmp/b.png" --force
is $ret 0 'multi-upload: --force applies to whole batch'
is ${#AWS_CALLS} 2 'multi-upload: --force uploaded all files despite conflict'
EXISTING=()
run s3 upload --force
is $ret 2 'multi-upload: flag with no files is a usage error'
# --- upload: aws failure ----------------------------------------------
AWS_EXIT=1
run s3 upload "$tmp/pic.png"
is $ret 1 'upload aws failure: exits 1'
like "$err" "*upload failed: $tmp/pic.png*" 'upload aws failure: stderr message'
is "$out" '' 'upload aws failure: nothing on stdout'
AWS_EXIT=0
# --- delete: key resolution ------------------------------------------
run s3 delete pic.png
like "$AWS_CALL" '*s3://bp-articles/pic.png*' \
'delete by bare filename: correct key'
run s3 del pic.png
like "$AWS_CALL" '*s3://bp-articles/pic.png*' \
'del alias: behaves identically to delete'
run s3 delete /some/other/dir/pic.png
like "$AWS_CALL" '*s3://bp-articles/pic.png*' \
'delete by filepath: basename-only key'
run s3 delete https://bp-articles.s3.eu-west-1.amazonaws.com/pic.png
like "$AWS_CALL" '*s3://bp-articles/pic.png*' \
'delete by url: prefix-stripped key'
# --- delete: success / failure -----------------------------------------
AWS_EXIT=0
run s3 delete pic.png
is $ret 0 'delete success: exits 0'
is "$out" '' 'delete success: nothing on stdout'
like "$err" '*done*' 'delete success: done line on stderr'
AWS_EXIT=1
run s3 delete pic.png
is $ret 1 'delete aws failure: exits 1'
like "$err" '*delete failed: pic.png*' 'delete aws failure: stderr message'
AWS_EXIT=0
run s3 delete pic.png --dryrun
like "$AWS_CALL" '*--dryrun*' 'delete: extra flags passed through to aws'
# --- delete: multiple assets ------------------------------------------
AWS_CALLS=()
run s3 delete pic.png b.png
is $ret 0 'multi-delete: exits 0 when all succeed'
is ${#AWS_CALLS} 2 'multi-delete: both keys deleted'
like "$err" '*2 asset\(s\)*' 'multi-delete: header counts assets'
AWS_CALLS=()
run s3 delete pic.png https://bp-articles.s3.eu-west-1.amazonaws.com/c.png
is ${#AWS_CALLS} 2 'multi-delete: mixed filename and url both resolve'
like "${AWS_CALLS[2]}" '*s3://bp-articles/c.png*' \
'multi-delete: url arg resolved to key'
AWS_EXIT=1
run s3 delete pic.png b.png
is $ret 1 'multi-delete: nonzero exit if any delete fails'
AWS_EXIT=0
run s3 delete --dryrun
is $ret 2 'multi-delete: flags with no assets is a usage error'
# --- color gating --------------------------------------------------------
NO_COLOR=1 run s3 upload "$tmp/pic.png"
unlike "$err" "*${esc}\[*" 'NO_COLOR=1: no escape codes on stderr'
FORCE_COLOR=1 run s3 upload "$tmp/pic.png"
like "$err" "*${esc}\[*" 'FORCE_COLOR=1: escape codes present on stderr'
NO_COLOR=1 FORCE_COLOR=1 run s3 upload "$tmp/pic.png"
unlike "$err" "*${esc}\[*" 'NO_COLOR beats FORCE_COLOR: no escape codes'
# --- config overrides ----------------------------------------------------
run s3 upload "$tmp/pic.png"
is "$out" 'https://bp-articles.s3.eu-west-1.amazonaws.com/pic.png' \
'default bucket/region reflected in url'
S3_BUCKET=custom-bucket S3_REGION=us-east-1 run s3 upload "$tmp/pic.png"
is "$out" 'https://custom-bucket.s3.us-east-1.amazonaws.com/pic.png' \
'custom S3_BUCKET/S3_REGION reflected in url'
finish
# upload file(s) to S3; outputs stable URL
s3() {
emulate -L zsh
local -r HELP="usage: s3 upload|up|u FILE... [--force] [aws-flags...]
s3 delete|del|d FILE|URL... [aws-flags...]"
local -r S3_BUCKET=${S3_BUCKET:-bp-articles}
local -r S3_REGION=${S3_REGION:-eu-west-1}
local -r URL_BASE="https://$S3_BUCKET.s3.$S3_REGION.amazonaws.com"
local -r CACHE_CONTROL='public, max-age=31536000, immutable'
local c_mute='' c_green='' c_reset=''
[[ ( -t 2 || -n ${FORCE_COLOR-} ) && -z ${NO_COLOR-} ]] &&
c_mute=${(%):-%F{8}} c_green=${(%):-%F{green}} c_reset=${(%):-%f}
_s3_split() {
local -i split=${all[(i)-*]}
items=($all[1,split-1])
rest=($all[split,-1])
}
local verb=$1
(( $# )) && shift
case $verb in
-h|--help)
print -r -- $HELP; return 0
;;
upload|up|u)
local -a all=($@) items rest
_s3_split
(( ${#items} )) || { print -ru2 -- $HELP; return 2 }
local -i idx=${rest[(I)--force]}
(( idx )) && rest[idx]=()
print -ru2 -- "${c_mute}uploading ${#items} asset(s) to s3://$S3_BUCKET${c_reset}"
local -i failed=0
local file key
for file in $items; do
if [[ ! -f $file ]]; then
print -ru2 -- "error: no such file: $file"
(( failed++ ))
continue
fi
key=${file:t}
if (( ! idx )) && aws s3api head-object --bucket $S3_BUCKET --key $key \
>/dev/null 2>&1
then
print -ru2 -- "error: $key already exists, use --force to overwrite"
(( failed++ ))
continue
fi
print -ru2 -- "${c_mute}uploading $file...${c_reset}"
if aws s3 cp $file s3://$S3_BUCKET/$key \
--cache-control $CACHE_CONTROL \
$rest \
>&2
then
print -r -- "$URL_BASE/$key"
else
print -ru2 -- "error: upload failed: $file"
(( failed++ ))
fi
done
print -ru2 -- "${c_green}done${c_reset}"
return $(( failed > 0 ))
;;
delete|del|d)
local -a all=($@) items rest
_s3_split
(( ${#items} )) || { print -ru2 -- $HELP; return 2 }
print -ru2 -- "${c_mute}deleting ${#items} asset(s) from s3://$S3_BUCKET${c_reset}"
local -i failed=0
local arg key
for arg in $items; do
[[ $arg == $URL_BASE/* ]] && key=${arg#$URL_BASE/} || key=${arg:t}
print -ru2 -- "${c_mute}deleting $key...${c_reset}"
aws s3 rm s3://$S3_BUCKET/$key $rest >&2 ||
{ print -ru2 -- "error: delete failed: $key"; (( failed++ )) }
done
print -ru2 -- "${c_green}done${c_reset}"
return $(( failed > 0 ))
;;
*)
print -ru2 -- "error: unknown command: $verb"
print -r -- $HELP; return 2
;;
esac
}
#!/usr/bin/env zsh
#
# tester.zsh -- tiny TAP 14 test runner for zsh
#
# Author: https://github.com/nicholaswmin
# License: MIT
#
# Discover and run *.test.zsh files. Each file runs in its own subshell
# with the assertion API preloaded, so test files call the assertors
# directly -- no `source` line.
#
# Usage:
# tester.zsh <dir> run every *.test.zsh under <dir>, recursively
# tester.zsh -h, --help print this help and exit
#
# A test file:
# plan 3
# is "hello" "hello" "literal equality"
# like "hello world" "*world*" "anchored glob"
# unlike "abc" "x*" "negated glob"
# finish
#
# assertions:
# plan N declare N assertions; emits 1..N
# is actual expected desc literal string equality
# like actual pattern desc anchored zsh glob match
# unlike actual pattern desc inverse of like
# runs N cmd... desc run cmd; assert exit N; sets REPLY=output
# runs_like N pat cmd... desc run cmd; assert exit N and glob match
# ok desc low-level pass
# not_ok desc low-level fail
# diag msg TAP comment
# register name cond diag define a custom assertor
# finish end the file; sets its exit status
#
# Custom assertors:
# - `register name cond diag` creates a function named `name`
# - on call, $1..$N are predicate args; last positional is the desc
# - cond is a test condition; diag is the failure message
# - single-quote cond and diag so $1, $2 expand at call time
#
# register file_exists '[[ -f $1 ]]' 'expected file at: $1'
# register matches '[[ $1 == $2 ]]' 'expected $2, got $1'
# file_exists /etc/hostname "hostname is a file"
# matches "a" "a" "literal equality"
#
# Exit codes:
# 0 every test file passed
# 1 one or more files failed, or no test files were found
# 2 usage error (bad option, missing/extra operand, bad directory)
#
# Behaviour:
# - discovery is recursive: <dir>/**/*.test.zsh, sorted, plain files only
# - each file runs in its own subshell, so `finish`'s exit cannot leak
# into the runner and per-file counters start from zero
# - each file emits an independent TAP 14 stream on stdout (version
# line + plan + assertions); progress and summary go to stderr
# - a file that never calls `finish` fails (its plan stays unverified)
# - color is emitted only when stderr is a terminal, from the ANSI palette
# - assertion semantics: plan under/overflow fails the run; "#", CR, LF
# in descriptions are stripped (with a warning); "like" is anchored --
# use "*foo*" for substrings
emulate -L zsh
setopt err_return pipe_fail no_unset extended_glob warn_create_global
typeset -gi __tester_num=0
typeset -gi __tester_plan=0
typeset -gi __tester_failed=0
# --- assertion API ---------------------------------------------------------
# Preloaded into every test file's subshell. Each uses `emulate -L zsh` so a
# test file's setopt cannot leak in.
plan() {
emulate -L zsh
__tester_plan=$1
print -r -- "1..$__tester_plan"
}
diag() {
emulate -L zsh
local line
for line in ${(f)${*//$'\r'/}}; do
print -r -- "# $line"
done
}
_tester_sanitise() {
emulate -L zsh
local raw=$1 clean
clean=${${${raw//$'\r'/}//$'\n'/ }//\#/}
if [[ $clean != $raw ]]; then
print -ru2 -- "tester: warning: stripped unsafe chars from description: $raw"
fi
REPLY=$clean
}
ok() {
emulate -L zsh
local REPLY
_tester_sanitise $1
(( ++__tester_num ))
print -r -- "ok $__tester_num - $REPLY"
}
not_ok() {
emulate -L zsh
local REPLY
_tester_sanitise $1
(( ++__tester_num ))
(( ++__tester_failed ))
print -r -- "not ok $__tester_num - $REPLY"
}
is() {
emulate -L zsh
local actual=$1 expected=$2 desc=$3
if [[ $actual == $expected ]]; then
ok $desc
return 0
fi
not_ok $desc
diag "expected: $expected"
diag "got: $actual"
return 1
}
like() {
emulate -L zsh
local actual=$1 pattern=$2 desc=$3
if [[ $actual == ${~pattern} ]]; then
ok $desc
return 0
fi
not_ok $desc
diag "expected pattern: $pattern"
diag "got: $actual"
return 1
}
unlike() {
emulate -L zsh
local actual=$1 pattern=$2 desc=$3
if [[ $actual != ${~pattern} ]]; then
ok $desc
return 0
fi
not_ok $desc
diag "unexpected pattern: $pattern"
diag "got: $actual"
return 1
}
runs() {
emulate -L zsh
local -i expected=$1; shift
local desc=${@[-1]}
local -a cmd=( ${@[1,-2]} )
local out
local -i actual=0
out=$( $cmd 2>&1 ) || actual=$?
REPLY=$out
if (( actual == expected )); then
ok $desc
return 0
fi
not_ok $desc
diag "expected exit: $expected"
diag "got: $actual"
diag "output: $out"
return 1
}
runs_like() {
emulate -L zsh
local -i expected=$1
local pattern=$2
shift 2
local desc=${@[-1]}
local -a cmd=( ${@[1,-2]} )
local out
local -i actual=0
out=$( $cmd 2>&1 ) || actual=$?
REPLY=$out
if (( actual == expected )) && [[ $out == ${~pattern} ]]; then
ok $desc
return 0
fi
not_ok $desc
(( actual != expected )) && {
diag "expected exit: $expected"
diag "got: $actual"
}
[[ $out != ${~pattern} ]] && {
diag "expected pattern: $pattern"
diag "got: $out"
}
return 1
}
register() {
emulate -L zsh
local name=$1 cond=$2 diag_msg=${3-}
[[ -z $name || -z $cond ]] && {
print -ru2 -- "tester: register: name and condition required"
return 2
}
case $name in
plan|is|like|unlike|runs|runs_like|ok|not_ok|diag|finish|register|_tester_sanitise)
print -ru2 -- "tester: register: cannot shadow built-in: $name"
return 2
;;
esac
functions[$name]="
emulate -L zsh
local desc=\${@[-1]}
if $cond; then ok \$desc; return 0; fi
not_ok \$desc
[[ -n \"$diag_msg\" ]] && diag \"$diag_msg\"
return 1
"
}
finish() {
emulate -L zsh
(( __tester_num != __tester_plan )) &&
diag "planned $__tester_plan but ran $__tester_num"
exit $(( __tester_failed > 0 || __tester_num != __tester_plan ))
}
# --- runner ----------------------------------------------------------------
typeset -g _C_RED='' _C_GREEN='' _C_MUTE='' _C_RESET=''
if [[ -t 2 && -z ${NO_COLOR-} ]]; then
_C_RED=${(%):-%F{red}}
_C_GREEN=${(%):-%F{green}}
_C_MUTE=${(%):-%F{8}}
_C_RESET=${(%):-%f}
fi
# _say <color-var-name> <msg...> -- write a colored line to stderr
_say() {
emulate -L zsh
local color=$1; shift
print -ru2 -- "${(P)color}$*${_C_RESET}"
}
_usage() {
emulate -L zsh
print -r -- 'tester.zsh -- tiny zsh test runner
usage:
tester.zsh <dir> run every *.test.zsh under <dir>, recursively
tester.zsh -h, --help print this help and exit
A test file calls the assertion API directly -- no source line:
plan 2
is "$(id -un)" root "running as root"
like "$PWD" "/*" "absolute cwd"
finish
assertions:
plan N declare N assertions; emits 1..N
is actual expected desc literal string equality
like actual pattern desc anchored zsh glob match
unlike actual pattern desc inverse of like
runs N cmd... desc run cmd; assert exit N; sets REPLY=output
runs_like N pat cmd... desc run cmd; assert exit N and glob match
ok desc low-level pass
not_ok desc low-level fail
diag msg TAP comment
register name cond diag define a custom assertor
finish end the file; sets its exit status
exit status:
0 every test file passed
1 a file failed, or no test files were found
2 usage error'
}
# usage error: message, blank line, help -- all to stderr; exit 2
_die() {
emulate -L zsh
_say _C_RED "tester: $*"
print -ru2 --
_usage >&2
exit 2
}
# Run one test file. The subshell inherits the API, contains `finish`'s exit,
# and starts from the zeroed global counters. `finish` is what validates the
# plan, so reaching past `source` on a clean run means it was never called.
_run_file() {
emulate -L zsh
print -r -- 'TAP version 14'
source $1 &&
_say _C_RED "tester: $1: finished without calling finish"
exit 1
}
main() {
emulate -L zsh
local -a opt_help
zparseopts -D -E -- h=opt_help -help=opt_help
(( $#opt_help )) && { _usage; return 0 }
local arg
for arg in $argv; do
[[ $arg == -* ]] && _die "unknown option: $arg"
done
(( $#argv == 0 )) && _die "missing directory operand"
(( $#argv > 1 )) && _die "too many arguments"
local dir=$argv[1]
[[ -e $dir ]] || _die "no such file or directory: $dir"
[[ -d $dir ]] || _die "not a directory: $dir"
local -a files=( $dir/**/*.test.zsh(.N) )
(( $#files )) || {
_say _C_RED "tester: no *.test.zsh files under: $dir"
return 1
}
local plural=s
(( $#files == 1 )) && plural=''
_say _C_MUTE "running $#files test file$plural under $dir ..."
integer passed=0 failed=0
local f
for f in $files; do
if ( _run_file $f ); then
(( ++passed ))
_say _C_MUTE "ok $f"
else
(( ++failed ))
_say _C_RED "FAIL $f"
fi
done
local prefix=all\
(( $#files == 1 )) && prefix=''
(( failed == 0 )) && {
_say _C_GREEN "${prefix}$#files file$plural passed"
return 0
}
_say _C_RED "$failed of $#files file$plural failed"
return 1
}
main $@ || exit $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment