Compare commits

..
Author SHA1 Message Date
Robby RussellandClaude Opus 5 419fc5e9b5 fix(shopify): parse every theme and keep match variables local
The dynamic theme completion split the `theme list --json` payload on
"${json//\{/$'\n'}", which substitutes a literal $'\n' rather than a newline.
Only the first theme was ever offered. Split on a newline held in a variable
instead.

Both completion helpers used =~ and (#b) backreferences without declaring
$match, leaving match, MATCH, mbegin and mend set in the user's shell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNDR8NjvPSgHbjcQurMHW9
2026-09-04 14:51:22 -07:00
Robby RussellandClaude Opus 5 338a1131c8 feat(shopify): add plugin
The Shopify CLI ships no completion generator of its own: there is no
`shopify completion` or `shopify autocomplete` command, and the published
package does not use @oclif/plugin-autocomplete. So the completion is
generated from `shopify commands --json` and checked in, as plugins/cdk and
plugins/terraform already do.

Targets Shopify CLI 4.x. Covers all 102 commands with descriptions, and their
flags, short forms and allowed values. `--environment` completes from the
local shopify.theme.toml or shopify.app.toml, offline.

The plugin also confirms before three irreversible operations: `theme push`
with --live/--publish, `theme delete`, and `app deploy --allow-deletes`.
The prompt is skipped when --force is present or stdin is not a terminal, so
scripts and CI are unaffected, and it can be disabled entirely with
`zstyle ':omz:plugins:shopify' confirm-destructive no`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNDR8NjvPSgHbjcQurMHW9
2026-09-04 14:45:00 -07:00
29 changed files with 2384 additions and 932 deletions
-1
View File
@@ -38,7 +38,6 @@ jobs:
./lib/*.zsh \
./plugins/*/*.plugin.zsh \
./plugins/*/_* \
./templates/generators/*/*.zsh-template \
./themes/*.zsh-theme; do
zsh -n "$file" || return 1
done
-4
View File
@@ -346,10 +346,6 @@ directory.
If you have many functions that go well together, you can put them as a `XYZ.plugin.zsh` file in the
`custom/plugins/` directory and then enable this plugin.
The quickest way to start one is `omz generate plugin <name>`. It creates the plugin directory with a
commented `<name>.plugin.zsh`, a `README.md`, and (for plugins that wrap a command-line tool) a completion
skeleton, then tells you how to try it out.
If you would like to override the functionality of a plugin distributed with Oh My Zsh, create a plugin of the
same name in the `custom/plugins/` directory and it will be loaded instead of the one in `plugins/`.
+9 -382
View File
@@ -24,7 +24,6 @@ function _omz {
local -a cmds subcmds
cmds=(
'changelog:Print the changelog'
'generate:Run a generator, e.g. to create a custom plugin'
'help:Usage information'
'plugin:Manage plugins'
'pr:Manage Oh My Zsh Pull Requests'
@@ -42,8 +41,6 @@ function _omz {
changelog) local -a refs
refs=("${(@f)$(builtin cd -q "$ZSH"; command git for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)}")
_describe 'command' refs ;;
generate) subcmds=('plugin:Create a custom plugin')
_describe 'generator' subcmds ;;
plugin) subcmds=(
'disable:Disable plugin(s)'
'enable:Enable plugin(s)'
@@ -59,8 +56,6 @@ function _omz {
esac
elif (( CURRENT == 4 )); then
case "${words[2]}::${words[3]}" in
generate::plugin)
_omz::generate::plugin::complete_options ;;
plugin::(disable|enable|load))
local -aU valid_plugins
@@ -89,8 +84,6 @@ function _omz {
esac
elif (( CURRENT > 4 )); then
case "${words[2]}::${words[3]}" in
generate::plugin)
_omz::generate::plugin::complete_options ;;
plugin::(enable|disable|load))
local -aU valid_plugins
@@ -176,16 +169,15 @@ Usage: omz <command> [options]
Available commands:
help Print this help message
changelog Print the changelog
generate <generator> Run a generator, e.g. to create a custom plugin
plugin <command> Manage plugins
pr <command> Manage Oh My Zsh Pull Requests
reload Reload the current zsh session
shop Open the Oh My Zsh shop
theme <command> Manage themes
update Update Oh My Zsh
version Show the version
help Print this help message
changelog Print the changelog
plugin <command> Manage plugins
pr <command> Manage Oh My Zsh Pull Requests
reload Reload the current zsh session
shop Open the Oh My Zsh shop
theme <command> Manage themes
update Update Oh My Zsh
version Show the version
EOF
}
@@ -210,371 +202,6 @@ EOF
ZSH="$ZSH" command zsh -f "$ZSH/tools/changelog.sh" "$version" "${2:-}" "$format"
}
function _omz::generate {
(( $# > 0 && $+functions[$0::$1] )) || {
cat >&2 <<EOF
Usage: ${(j: :)${(s.::.)0#_}} <generator> [options]
Available generators:
plugin [<name>] Create a custom plugin in \$ZSH_CUSTOM/plugins
EOF
return 1
}
local command="$1"
shift
$0::$command "$@"
}
function _omz::generate::plugin {
setopt localoptions extendedglob
local -A opts
zparseopts -D -E -A opts -- d: -description: c: -command: -no-completion -enable y -yes h -help || return 1
if (( ${+opts[-h]} || ${+opts[--help]} )); then
_omz::generate::plugin::usage
return 0
fi
# zparseopts -E leaves anything it doesn't recognise in $@
local arg
for arg in "$@"; do
if [[ "$arg" == -* ]]; then
_omz::log error "unknown option '$arg'."
_omz::generate::plugin::usage
return 1
fi
done
if (( $# > 1 )); then
_omz::generate::plugin::usage
return 1
fi
# Only ask questions when there is someone there to answer them
local interactive=0
if [[ -o interactive && -t 0 ]] && (( ! ${+opts[-y]} && ! ${+opts[--yes]} )); then
interactive=1
fi
local custom="${ZSH_CUSTOM:-${ZSH:+$ZSH/custom}}"
if [[ -z "$custom" ]]; then
_omz::log error "\$ZSH is not set. Is Oh My Zsh loaded?"
return 1
fi
local templates="$ZSH/templates/generators/plugin"
if [[ ! -d "$templates" ]]; then
_omz::log error "templates not found at '${templates/#$HOME/\~}'. Try running 'omz update'."
return 1
fi
# Check we can write to $ZSH_CUSTOM/plugins, or the closest directory that exists
local probe="$custom/plugins"
while [[ ! -e "$probe" && "$probe" != "${probe:h}" ]]; do
probe="${probe:h}"
done
if [[ ! -w "$probe" ]]; then
_omz::log error "cannot write to '${probe/#$HOME/\~}'. Set \$ZSH_CUSTOM to a directory you own."
return 1
fi
## Gather answers. Nothing is written until all of them are in.
local name="$1" attempts=0
if [[ -n "$name" ]]; then
_omz::generate::plugin::validate_name "$name" || return 1
elif (( interactive )); then
while true; do
_omz::generate::plugin::ask "Plugin name" || return 1
name="$REPLY"
_omz::generate::plugin::validate_name "$name" && break
if (( ++attempts >= 3 )); then
_omz::log error "giving up."
return 1
fi
done
else
_omz::generate::plugin::usage
return 1
fi
local dir="$custom/plugins/$name"
if [[ -e "$dir" ]]; then
_omz::log error "'$name' already exists at '${dir/#$HOME/\~}'. Remove it or pick another name."
return 1
fi
if [[ -d "$ZSH/plugins/$name" ]]; then
_omz::log warn "'$name' is also a built-in plugin. Your custom plugin will override it."
if (( interactive )); then
_omz::confirm "Continue? [y/N] "
if [[ "$REPLY" != [yY] ]]; then
_omz::log info "aborted."
return 1
fi
fi
fi
local description="${opts[-d]:-${opts[--description]:-}}"
if [[ -z "$description" ]]; then
description="$name plugin for Oh My Zsh"
if (( interactive )); then
_omz::generate::plugin::ask "Short description" "$description" || return 1
description="$REPLY"
fi
fi
# The description goes into a comment and a README line: keep it on one line
description="${description//[[:cntrl:]]/ }"
local cmd="${opts[-c]:-${opts[--command]:-}}"
if [[ -n "$cmd" ]]; then
_omz::generate::plugin::validate_command "$cmd" || return 1
elif (( interactive )); then
local default_cmd=""
(( $+commands[$name] )) && default_cmd="$name"
attempts=0
while true; do
if [[ -n "$default_cmd" ]]; then
_omz::generate::plugin::ask "Command-line tool this plugin wraps (or 'none')" "$default_cmd" || return 1
else
_omz::generate::plugin::ask "Command-line tool this plugin wraps (blank for none)" || return 1
fi
cmd="$REPLY"
[[ "$cmd" != (none|-) ]] || cmd=""
[[ -n "$cmd" ]] || break
_omz::generate::plugin::validate_command "$cmd" && break
if (( ++attempts >= 3 )); then
_omz::log error "giving up."
return 1
fi
done
fi
# completion: "" (none), "file" (a _<cmd> skeleton) or "cached" (the tool generates it)
local completion="" compgen=""
if [[ -n "$cmd" ]] && (( ! ${+opts[--no-completion]} )); then
completion=file
if (( interactive )); then
_omz::confirm "Add a completion function for $cmd? [Y/n] "
if [[ "$REPLY" == [nN] ]]; then
completion=""
else
_omz::confirm "Does $cmd generate its own zsh completion (e.g. '$cmd completion zsh')? [y/N] "
if [[ "$REPLY" == [yY] ]]; then
completion=cached
_omz::generate::plugin::ask "Command that prints the completion script" "$cmd completion zsh" || return 1
compgen="$REPLY"
fi
fi
fi
fi
local enable=${+opts[--enable]}
if (( interactive && ! enable )); then
_omz::confirm "Add $name to plugins=() in your .zshrc now? [y/N] "
[[ "$REPLY" != [yY] ]] || enable=1
fi
## Write the files. If anything fails, remove only what we created.
local -a created
local block="" cache="" ok=0
{
if [[ ! -d "$custom/plugins" ]]; then
command mkdir -p "$custom/plugins" || return 1
_omz::generate::plugin::created "$custom/plugins"
fi
command mkdir "$dir" || return 1
_omz::generate::plugin::created "$dir"
local plugin_tpl=plugin.zsh-template readme_tpl=README.md-template
if [[ -z "$cmd" ]]; then
plugin_tpl=plugin-generic.zsh-template
readme_tpl=README-generic.md-template
elif [[ "$completion" == cached ]]; then
block="$(_omz::generate::plugin::template completion-cached.zsh-template)" || return 1
cache="$(_omz::generate::plugin::template README-cache.md-template)" || return 1
else
block="$(_omz::generate::plugin::template completion-note.zsh-template)" || return 1
fi
_omz::generate::plugin::render "$plugin_tpl" "$dir/$name.plugin.zsh" || return 1
_omz::generate::plugin::render "$readme_tpl" "$dir/README.md" || return 1
if [[ "$completion" == file ]]; then
_omz::generate::plugin::render completion.zsh-template "$dir/_$cmd" || return 1
fi
ok=1
} always {
if (( ! ok )); then
_omz::log error "could not generate the plugin. Cleaning up..."
(( ${#created} )) && command rm -f -- "${created[@]}"
[[ ! -d "$dir" ]] || command rmdir -- "$dir" 2>/dev/null
fi
}
print
_omz::log info "plugin '$name' generated."
print
print -r -- "Next steps:"
print
print -r -- " 1. Edit it: ${EDITOR:-vim} ${dir/#$HOME/\~}/$name.plugin.zsh"
print -r -- " 2. Try it now: omz plugin load $name"
(( enable )) || print -r -- " 3. Keep it: omz plugin enable $name"
print
print -r -- "Docs: https://github.com/ohmyzsh/ohmyzsh/wiki/Customization#overriding-and-adding-plugins"
# Last thing we do: in an interactive shell this restarts zsh
if (( enable )); then
print
if (( ${plugins[(Ie)$name]} )); then
# Already enabled (e.g. a custom override of a built-in): just reload
_omz::log info "'$name' is already in your plugins list."
[[ ! -o interactive ]] || _omz::reload
else
_omz::plugin::enable "$name"
fi
fi
}
function _omz::generate::plugin::usage {
cat >&2 <<EOF
Usage: omz generate plugin [<name>] [options]
Creates a custom plugin in \$ZSH_CUSTOM/plugins/<name>. Anything not given as an
option is asked for interactively. In a non-interactive shell (or with --yes)
defaults are used instead.
Options:
-d, --description <text> One-line description for the README and file header
-c, --command <cmd> Command-line tool this plugin wraps. Adds a check
that it is installed and scaffolds its completion
--no-completion Don't scaffold a completion function
--enable Add the plugin to plugins=() in .zshrc when done
(restarts your shell)
-y, --yes Never prompt; use defaults for anything not given
-h, --help Show this help
EOF
}
function _omz::generate::plugin::complete_options {
local -a opts
opts=(
'--description:One-line description for the README'
'--command:Command-line tool this plugin wraps'
'--no-completion:Skip the completion scaffold'
'--enable:Enable the plugin when done'
'--yes:Never prompt'
)
_describe -o 'options' opts
}
# The name ends up in a mkdir path and, via --enable, inside the awk script that
# rewrites .zshrc. This is the one place it gets checked.
function _omz::generate::plugin::validate_name {
setopt localoptions extendedglob
local name="$1"
if [[ -z "$name" ]]; then
_omz::log error "plugin name cannot be empty."
elif (( ${#name} > 64 )); then
_omz::log error "plugin name is too long (64 characters max)."
elif [[ "$name" != "${name:l}" ]]; then
_omz::log error "plugin names must be lowercase. Did you mean '${name:l}'?"
elif [[ "$name" != [a-z0-9][a-z0-9_-]# ]]; then
_omz::log error "invalid plugin name: use only lowercase letters, digits, '-' and '_', starting with a letter or digit."
else
return 0
fi
return 1
}
# The command name ends up in $+commands[...], _comps[...] and a filename
function _omz::generate::plugin::validate_command {
setopt localoptions extendedglob
if (( ${#1} > 64 )) || [[ "$1" != [[:alnum:]_][[:alnum:]_.+-]# ]]; then
_omz::log error "invalid command name: use only letters, digits, '.', '_', '+' and '-'."
return 1
fi
}
# Ask a question and leave the answer in $REPLY. An empty answer takes the
# default. Returns 1 when there is no more input (Ctrl-D).
function _omz::generate::plugin::ask {
setopt localoptions extendedglob
local question="$1" default="$2"
[[ -z "$default" ]] || question+=" [$default]"
_omz::log prompt "$question: " "omz::generate::plugin"
if ! builtin read -r; then
print >&2
_omz::log info "aborted." "omz::generate::plugin"
return 1
fi
[[ -n "$REPLY" ]] || REPLY="$default"
# Answers are single-line values: flatten control characters and trim
REPLY="${REPLY//[[:cntrl:]]/ }"
REPLY="${${REPLY##[[:space:]]#}%%[[:space:]]#}"
}
# Print a template file with its %placeholders% turned into private markers,
# or explain which one is missing
function _omz::generate::plugin::template {
if [[ ! -f "$templates/$1" ]]; then
_omz::log error "missing template '${templates/#$HOME/\~}/$1'." "omz::generate::plugin"
return 1
fi
local content="$(<"$templates/$1")" token m=$'\x1f'
for token in completion cache name command description compgen; do
content="${content//\%${token}\%/${m}${token}${m}}"
done
print -r -- "$content"
}
# Fill in the placeholders of a template and write it to a file. Values come
# from the caller: $name, $cmd, $description, $compgen, $block and $cache.
#
# Placeholders are matched as the markers that ::template produced, and the
# marker character is stripped from every value, so a value that happens to
# look like a placeholder (e.g. -d '%name%') is written out literally.
function _omz::generate::plugin::render {
setopt localoptions extendedglob
local content m=$'\x1f'
content="$(_omz::generate::plugin::template "$1")" || return 1
# Blocks go first: they are templates too and carry markers of their own
content="${content//${m}completion${m}/$block}"
content="${content//${m}cache${m}/$cache}"
content="${content//${m}name${m}/${name//$m/}}"
content="${content//${m}command${m}/${cmd//$m/}}"
content="${content//${m}description${m}/${description//$m/}}"
content="${content//${m}compgen${m}/${compgen//$m/}}"
# Drop blank lines left behind by an empty placeholder at the end
content="${content%%$'\n'##}"
created+=("$2")
print -r -- "$content" > "$2" || return 1
_omz::generate::plugin::created "$2"
}
function _omz::generate::plugin::created {
# Colour the verb with print -P, but print the path with print -r so that a
# '%' somewhere in the path isn't treated as a prompt escape.
# Keep the output plain when it is being piped.
if [[ -t 1 ]]; then
print -Pn " %F{green}create%f "
else
print -n " create "
fi
print -r -- "${1/#$HOME/\~}"
}
function _omz::plugin {
(( $# > 0 && $+functions[$0::$1] )) || {
cat >&2 <<EOF
-198
View File
@@ -1,198 +0,0 @@
#!/usr/bin/zsh -df
# Tests for `omz generate plugin`. Run with: zsh lib/tests/generate-plugin.test.zsh
ZSH="${0:A:h:h:h}"
ZSH_CUSTOM="$(mktemp -d)"
trap 'command rm -rf "$ZSH_CUSTOM"' EXIT
source "$ZSH/lib/cli.zsh"
failures=0
pass() { print -u2 "\e[32mSuccess\e[0m: $1" }
fail() { print -u2 "\e[31mError\e[0m: $1"; (( failures++ )) }
# assert_files <plugin> <expected files, space separated>
assert_files() {
local actual="$(print -l "$ZSH_CUSTOM"/plugins/$1/*(N:t) "$ZSH_CUSTOM"/plugins/$1/_*(N:t) | sort -u | tr '\n' ' ')"
local expected="$(print -l ${=2} | sort -u | tr '\n' ' ')"
if [[ "$actual" == "$expected" ]]; then
pass "$1 has files: $expected"
else
fail "$1 has files '$actual', expected '$expected'"
fi
}
# assert_syntax <plugin>: every generated file must pass zsh -n, like CI does
assert_syntax() {
local file
for file in "$ZSH_CUSTOM"/plugins/$1/*.plugin.zsh(N) "$ZSH_CUSTOM"/plugins/$1/_*(N); do
if zsh -n "$file"; then
pass "${file:t} passes zsh -n"
else
fail "${file:t} fails zsh -n"
fi
done
}
# assert_contains <file> <literal string>
assert_contains() {
if grep -qF -- "$2" "$1"; then
pass "${1:t} contains '$2'"
else
fail "${1:t} does not contain '$2'"
fi
}
## Templates must be valid zsh even before substitution (CI checks this too)
for file in "$ZSH"/templates/generators/plugin/*.zsh-template; do
if zsh -n "$file"; then
pass "template ${file:t} passes zsh -n"
else
fail "template ${file:t} fails zsh -n"
fi
done
## A plugin that wraps a command
description='Shortcuts & stuff $(id) `id` 100%'
if omz generate plugin foo -d "$description" -c foo --yes >/dev/null 2>&1; then
pass "generates a command-wrapping plugin"
else
fail "generating a command-wrapping plugin failed"
fi
assert_files foo "README.md _foo foo.plugin.zsh"
assert_syntax foo
assert_contains "$ZSH_CUSTOM/plugins/foo/README.md" 'plugins=(... foo)'
assert_contains "$ZSH_CUSTOM/plugins/foo/README.md" "$description"
assert_contains "$ZSH_CUSTOM/plugins/foo/foo.plugin.zsh" "$description"
assert_contains "$ZSH_CUSTOM/plugins/foo/foo.plugin.zsh" '$+commands[foo]'
assert_contains "$ZSH_CUSTOM/plugins/foo/_foo" '#compdef foo'
if grep -q '%[a-z]*%' "$ZSH_CUSTOM"/plugins/foo/*; then
fail "foo still has unfilled placeholders"
else
pass "foo has no unfilled placeholders"
fi
## Values that look like placeholders are written literally
omz generate plugin lit -d '%compgen% and %name% stay' -c lit --yes >/dev/null 2>&1
assert_contains "$ZSH_CUSTOM/plugins/lit/README.md" '%compgen% and %name% stay'
assert_contains "$ZSH_CUSTOM/plugins/lit/lit.plugin.zsh" '%compgen% and %name% stay'
## A plugin with no command
if omz generate plugin bar --yes >/dev/null 2>&1; then
pass "generates a generic plugin"
else
fail "generating a generic plugin failed"
fi
assert_files bar "README.md bar.plugin.zsh"
assert_syntax bar
assert_contains "$ZSH_CUSTOM/plugins/bar/README.md" 'This plugin does not add any aliases.'
assert_contains "$ZSH_CUSTOM/plugins/bar/bar.plugin.zsh" 'bar plugin for Oh My Zsh'
## --no-completion, and the rails-style output
output="$(omz generate plugin baz -c baz --no-completion --yes 2>/dev/null)"
assert_files baz "README.md baz.plugin.zsh"
create_lines=$(print -r -- "$output" | grep -c '^ create ')
if (( create_lines == 3 )); then
pass "prints one create line per path"
else
fail "expected 3 create lines, got $create_lines"
fi
if [[ "$output" == *"omz plugin load baz"* ]]; then
pass "prints next steps"
else
fail "next steps missing from output"
fi
## Bad names are rejected without touching the filesystem
before="$(print -l "$ZSH_CUSTOM"/plugins/*(N:t))"
for bad in '' 'Foo' 'a b' '../evil' 'a/b' '.hidden' '.' '..' '-x' 'foo`id`' 'foo"$(id)"' $'a\nb' "$(printf 'x%.0s' {1..65})"; do
if omz generate plugin "$bad" --yes >/dev/null 2>&1; then
fail "accepted bad name ${(qq)bad}"
else
pass "rejected bad name ${(qq)bad}"
fi
done
after="$(print -l "$ZSH_CUSTOM"/plugins/*(N:t))"
if [[ "$before" == "$after" ]]; then
pass "bad names created nothing"
else
fail "bad names changed \$ZSH_CUSTOM/plugins"
fi
## Bad command names are rejected
for bad in 'a b' '../x' 'foo;id' 'foo$(id)'; do
if omz generate plugin qux -c "$bad" --yes >/dev/null 2>&1; then
fail "accepted bad command ${(qq)bad}"
else
pass "rejected bad command ${(qq)bad}"
fi
done
## Existing plugin, unknown option, missing templates
if omz generate plugin foo --yes >/dev/null 2>&1; then
fail "overwrote an existing plugin"
else
pass "refuses to overwrite an existing plugin"
fi
if omz generate plugin quux --bogus --yes >/dev/null 2>&1; then
fail "accepted an unknown option"
else
pass "rejects an unknown option"
fi
if ( ZSH=/nonexistent; omz generate plugin quux --yes >/dev/null 2>&1 ); then
fail "ran without templates"
else
pass "fails cleanly when templates are missing"
fi
[[ -e "$ZSH_CUSTOM/plugins/quux" ]] && fail "quux was created by a failed run"
## A failure after the first file is written removes everything it created
broken="$(mktemp -d)"
mkdir -p "$broken/templates/generators/plugin" "$broken/plugins"
cp "$ZSH/templates/generators/plugin/plugin.zsh-template" \
"$ZSH/templates/generators/plugin/completion-note.zsh-template" "$broken/templates/generators/plugin/"
# README.md-template is missing, so the plugin file is written and then the README fails
if ( ZSH="$broken"; omz generate plugin partial -c partial --yes >/dev/null 2>&1 ); then
fail "succeeded with a missing README template"
else
pass "fails when a later template is missing"
fi
if [[ -e "$ZSH_CUSTOM/plugins/partial" ]]; then
fail "partial plugin directory was left behind: $(ls "$ZSH_CUSTOM/plugins/partial")"
else
pass "cleans up the partial plugin directory"
fi
command rm -rf "$broken"
## The prompt helper
_omz::generate::plugin::ask "Q" "dflt" 2>/dev/null < <(print '')
[[ "$REPLY" == "dflt" ]] && pass "ask: empty answer takes the default" || fail "ask: got '$REPLY', expected 'dflt'"
_omz::generate::plugin::ask "Q" "dflt" 2>/dev/null < <(print ' hi there ')
[[ "$REPLY" == "hi there" ]] && pass "ask: trims whitespace" || fail "ask: got '$REPLY', expected 'hi there'"
if _omz::generate::plugin::ask "Q" 2>/dev/null < /dev/null; then
fail "ask: did not fail on EOF"
else
pass "ask: fails on EOF"
fi
print -u2
if (( failures )); then
print -u2 "\e[31m$failures test(s) failed\e[0m"
exit 1
fi
print -u2 "\e[32mAll tests passed\e[0m"
+1 -1
View File
@@ -69,7 +69,7 @@ function asr() {
local -a available_regions
available_regions=($(aws_regions))
if [[ -z "${available_regions[(r)$1]}" ]]; then
echo "${fg[red]}Available regions: \n$(aws_regions)${reset_color}" >&2
echo "${fg[red]}Available regions: \n$(aws_regions)"
return 1
fi
-18
View File
@@ -1,18 +0,0 @@
# Grafana CLI plugin
This plugin adds completion for the [Grafana CLI (gcx)](https://github.com/grafana/gcx).
To use it, add `gcx` to the plugins array in your zshrc file:
```zsh
plugins=(... gcx)
```
This plugin does not add any aliases.
## Cache
This plugin caches the completion script and automatically updates it when the
plugin is loaded, which is usually when you start a new terminal emulator.
The cache is stored at `$ZSH_CACHE_DIR/completions/_gcx`.
-18
View File
@@ -1,18 +0,0 @@
# Autocompletion for the Grafana CLI (gcx).
if (( ! $+commands[gcx] )); then
return
fi
# If the completion file doesn't exist yet, we need to autoload it and
# bind it to `gcx`. Otherwise, compinit will have already done that.
if [[ ! -f "$ZSH_CACHE_DIR/completions/_gcx" ]]; then
typeset -g -A _comps
autoload -Uz _gcx
_comps[gcx]=_gcx
fi
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_gcx"
zf_mv -f -- =( gcx completion zsh ) "$TMPPREFIX"
} &|
-1
View File
@@ -42,7 +42,6 @@ plugins=(... laravel)
| `pamen` | `php artisan make:enum` |
| `pami` | `php artisan make:interface` |
| `pamtr` | `php artisan make:trait` |
| `pamv` | `php artisan make:view` |
## Clears
-1
View File
@@ -31,7 +31,6 @@ alias pamcl='php artisan make:class'
alias pamen='php artisan make:enum'
alias pami='php artisan make:interface'
alias pamtr='php artisan make:trait'
alias pamv='php artisan make:view'
# Clears
+5 -12
View File
@@ -56,26 +56,19 @@ if zstyle -T ':omz:plugins:pipenv' auto-shell; then
# deactivate shell if Pipfile doesn't exist and not in a subdir
if [[ ! -f "$PWD/Pipfile" ]]; then
if [[ "$PIPENV_ACTIVE" == 1 ]]; then
# Compare whole path components: `$pipfile_dir"*` also matches unrelated
# siblings that merely start with the same text (…/proj -> …/proj-docs),
# which would keep the virtualenv active outside the project.
local project_dir="${pipfile_dir%/}"
if [[ "$PWD" != "$project_dir" && "$PWD" != "$project_dir"/* ]]; then
if [[ "$PWD" != "$pipfile_dir"* ]]; then
unset PIPENV_ACTIVE pipfile_dir
deactivate
fi
fi
fi
# activate the shell if Pipfile exists and its virtualenv is usable
# activate the shell if Pipfile exists
if [[ "$PIPENV_ACTIVE" != 1 ]]; then
if [[ -f "$PWD/Pipfile" ]]; then
local venv_path
if venv_path="$(pipenv --venv 2>/dev/null)" && [[ -n "$venv_path" && -f "$venv_path/bin/activate" ]]; then
export pipfile_dir="$PWD"
source "$venv_path/bin/activate"
export PIPENV_ACTIVE=1
fi
export pipfile_dir="$PWD"
source "$(pipenv --venv)/bin/activate"
export PIPENV_ACTIVE=1
fi
fi
}
+2 -4
View File
@@ -1,9 +1,7 @@
# Poetry Environment Plugin
This plugin automatically activates a Poetry environment when you enter a
directory containing both `pyproject.toml` and `poetry.lock`. It keeps the
environment active in project subdirectories, switches to nested Poetry
projects, and deactivates it when you leave the project tree.
This plugin automatically changes poetry environment when you cd into or out of the project directory.
Note: Script looks for pyproject.toml file to determine poetry if its a poetry environment
To use it, add `poetry-env` to the plugins array in your zshrc file:
+5 -14
View File
@@ -1,24 +1,15 @@
_togglePoetryShell() {
# Determine if currently in a Poetry-managed directory
local in_poetry_dir=0
local in_active_poetry_dir=0
if [[ -f "$PWD/pyproject.toml" && -f "$PWD/poetry.lock" ]]; then
in_poetry_dir=1
fi
# Deactivate when leaving the active project or entering a nested project
if [[ $poetry_active -eq 1 ]]; then
local project_dir="${poetry_dir%/}"
if [[ "$PWD" == "$project_dir" || "$PWD" == "$project_dir"/* ]]; then
in_active_poetry_dir=1
fi
if [[ $in_active_poetry_dir -eq 0 ]] ||
{ [[ $in_poetry_dir -eq 1 ]] && [[ "$PWD" != "$poetry_dir" ]]; }; then
export poetry_active=0
unset poetry_dir
(( $+functions[deactivate] )) && deactivate
fi
# Deactivate the current environment if moving out of a Poetry directory or into a different Poetry directory
if [[ $poetry_active -eq 1 ]] && { [[ $in_poetry_dir -eq 0 ]] || [[ "$PWD" != "$poetry_dir"* ]]; }; then
export poetry_active=0
unset poetry_dir
(( $+functions[deactivate] )) && deactivate
fi
# Activate the environment if in a Poetry directory and no environment is currently active
-22
View File
@@ -1,22 +0,0 @@
# ruff plugin
This plugin automatically installs [ruff](https://github.com/astral-sh/ruff)'s completions for you,
and keeps them up to date. It also adds convenient aliases for common usage.
To use it, add `ruff` to the plugins array in your zshrc file:
```zsh
plugins=(... ruff)
```
## Aliases
| Alias | Command | Description |
| :---- | -------------------- | :----------------------------------------------------------- |
| ruc | `ruff check` | Run Ruff linter on the given files or directories |
| rucf | `ruff check --fix` | Run Ruff linter and fix auto-fixable issues |
| ruf | `ruff format` | Run the Ruff formatter on the given files or directories |
| rufc | `ruff format --check`| Check if files are already formatted without making changes |
| rur | `ruff rule` | Explain a rule (or all rules) |
| rul | `ruff linter` | List all supported upstream linters |
| rucl | `ruff clean` | Clear any caches in the current directory and subdirectories |
-27
View File
@@ -1,27 +0,0 @@
# Return immediately if ruff is not found
if (( ! ${+commands[ruff]} )); then
return
fi
alias ruc='ruff check'
alias rucf='ruff check --fix'
alias ruf='ruff format'
alias rufc='ruff format --check'
alias rur='ruff rule'
alias rul='ruff linter'
alias rucl='ruff clean'
# If the completion file doesn't exist yet, we need to autoload it and
# bind it. Otherwise, compinit will have already done that.
if [[ ! -f "$ZSH_CACHE_DIR/completions/_ruff" ]]; then
typeset -g -A _comps
autoload -Uz _ruff
_comps[ruff]=_ruff
fi
# Overwrites the file each time as completions might change with ruff versions.
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_ruff"
zf_mv -f -- =( ruff generate-shell-completion zsh ) "$TMPPREFIX"
} &|
+112
View File
@@ -0,0 +1,112 @@
# Shopify CLI plugin
This plugin adds aliases, helper functions and completion for the
[Shopify CLI](https://shopify.dev/docs/api/shopify-cli).
To use it, add `shopify` to the plugins array in your zshrc file:
```zsh
plugins=(... shopify)
```
## Requirements
[Shopify CLI](https://shopify.dev/docs/api/shopify-cli) 4.0 or newer, installed
and on your `PATH`. The plugin does nothing if it is not:
```zsh
npm install -g @shopify/cli
# or
brew tap shopify/shopify && brew install shopify-cli
```
## Aliases
| Alias | Command | Description |
| :----- | :-------- | :--------------------- |
| `shop` | `shopify` | The Shopify CLI itself |
## Functions
| Function | Description |
| :------------- | :--------------------------------------------------------------------------------- |
| `shopd` | Runs `theme dev`, `app dev` or `hydrogen dev`, whichever fits the current project |
| `shopi` | Shows what the current project is connected to |
| `shopify_here` | Prints the kind of Shopify project you are in and the commands worth running in it |
| `shopify` | Wraps the CLI to confirm before irreversible commands. See Settings |
`shopd` and `shopi` work out which topic applies by looking at the project you
are standing in, so you do not have to remember whether this directory is a
theme, an app or a Hydrogen storefront. They complete like the command they
stand in for, so in a theme `shopd --<TAB>` offers the flags of
`shopify theme dev`.
## Completion
Every command completes with a description of what it does, at every level, and
so do their flags, short forms and allowed values. `--environment` completes
from the `[environments.*]` sections of your local `shopify.theme.toml` or
`shopify.app.toml`.
The Shopify CLI ships no completion generator of its own, so `_shopify` is
generated from `shopify commands --json` and kept in this repository. It targets
Shopify CLI 4.x.
## Settings
Set these with `zstyle` in your zshrc, before Oh My Zsh is sourced.
### Confirming destructive commands
```zsh
zstyle ':omz:plugins:shopify' confirm-destructive no
```
By default the plugin asks for confirmation before three irreversible
operations:
- `shopify theme push` with `--live`/`-l` or `--publish`/`-p`, which overwrites
or publishes the live storefront
- `shopify theme delete`, which the CLI's own help describes as impossible to
undo
- `shopify app deploy --allow-deletes`, which can permanently remove extensions
Nothing else is intercepted, and the prompt is skipped when the command already
carries `--force`/`-f` or when stdin is not a terminal, so scripts, pipelines
and CI are unaffected. Set the style to `no` to turn the prompt off entirely.
### Completing theme IDs
```zsh
zstyle ':omz:plugins:shopify' dynamic-theme-completion yes
```
Off by default. When enabled, `--theme` is completed from
`shopify theme list --json`, showing each theme's name and role next to its ID
instead of making you copy IDs by hand. It is opt-in because it runs the CLI and
makes a network call when you press <kbd>Tab</kbd>, and because it needs you to
be authenticated. Results are cached for five minutes.
### Disabling the aliases
This works for every Oh My Zsh plugin:
```zsh
zstyle ':omz:plugins:shopify' aliases no
```
## Caveats
- `shopify` is a shell function here. Run `command shopify` to reach the binary
directly.
- When a project has its own copy of the CLI in `node_modules/.bin/shopify`,
that one is used in preference to the global install. App and Hydrogen
projects normally pin the CLI in `package.json`, and running a different
global version against them is a common source of confusing errors.
- `--password` and `--store-password` are unrelated, and you often need both.
`--password` is the CLI's own authentication token, from the Theme Access app
or the Admin API. `--store-password` is the password for a
password-protected storefront.
- `shopify theme push` deletes remote files that are missing locally, and
`shopify theme pull` deletes local files that are missing remotely, unless you
pass `--nodelete`/`-n`. The plugin does not prompt for these.
File diff suppressed because it is too large Load Diff
+204
View File
@@ -0,0 +1,204 @@
# Shopify CLI: completion, aliases and guard rails.
# https://shopify.dev/docs/api/shopify-cli
if (( ! $+commands[shopify] )); then
return
fi
# Prefer a project-local CLI: app and Hydrogen projects pin their own version.
# The walk stops at $HOME so a stray ~/node_modules cannot hijack every project.
# $commands avoids resolving back to the wrapper function below.
function _shopify_bin() {
local dir="$PWD"
while [[ -n "$dir" && "$dir" != "/" && "$dir" != "$HOME" ]]; do
if [[ -x "$dir/node_modules/.bin/shopify" ]]; then
print -r -- "$dir/node_modules/.bin/shopify"
return 0
fi
dir="${dir:h}"
done
print -r -- "$commands[shopify]"
}
# Print the project type in $PWD: hydrogen, app or theme.
# Hydrogen is tested first because those projects usually carry a
# shopify.app.toml too. shopify.theme.toml is not a theme marker: it only
# exists once environments are configured.
function _shopify_project_type() {
local dir="$PWD"
local -a app_config
while [[ -n "$dir" && "$dir" != "/" && "$dir" != "$HOME" ]]; do
if [[ -x "$dir/node_modules/.bin/h2" ]] ||
[[ -f "$dir/package.json" && "$(<"$dir/package.json")" == *'"@shopify/hydrogen"'* ]]; then
print -r -- hydrogen
return 0
fi
app_config=("$dir"/shopify.app*.toml(N))
if (( $#app_config )); then
print -r -- app
return 0
fi
if [[ -f "$dir/config/settings_schema.json" && -f "$dir/layout/theme.liquid" ]]; then
print -r -- theme
return 0
fi
dir="${dir:h}"
done
return 1
}
# Does this invocation need confirming? Sets REPLY to the reason.
function _shopify_is_destructive() {
zstyle -T ':omz:plugins:shopify' confirm-destructive || return 1
# Nobody to answer: CI and pipelines are unaffected.
[[ -t 0 ]] || return 1
local topic="$1" subcommand="$2" arg
local -a long
local short=""
# Short flags can be bundled, so collect them as characters: -al counts as l.
for arg in "${@[3,-1]}"; do
[[ "$arg" == "--" ]] && break
case "$arg" in
--*) long+=("${arg%%=*}") ;;
-?*) short+="${${arg%%=*}#-}" ;;
esac
done
# An explicit force flag states the intent already.
if (( long[(I)--force] )) || [[ "$short" == *f* ]]; then
return 1
fi
case "$topic $subcommand" in
"theme push")
if (( long[(I)--live] || long[(I)--publish] )) || [[ "$short" == *[lp]* ]]; then
REPLY="this overwrites or publishes the live theme on the storefront."
return 0
fi
;;
"theme delete")
REPLY="deleting a theme cannot be undone."
return 0
;;
"app deploy")
if (( long[(I)--allow-deletes] )); then
REPLY="this can permanently remove app extensions."
return 0
fi
;;
esac
return 1
}
# Adds a confirmation prompt before irreversible operations. Everything else
# passes straight through, so `theme dev` stays interactive and pipes stay clean.
function shopify() {
local REPLY
if _shopify_is_destructive "$@"; then
print -u2 -- "shopify: $REPLY"
if ! read -q "?Continue? [y/N] "; then
print -u2 -- ""
return 130
fi
print -u2 -- ""
fi
"$(_shopify_bin)" "$@"
}
# Print the commands worth running in the current directory.
function shopify_here() {
local project_type
project_type="$(_shopify_project_type)"
case "$project_type" in
theme)
print -- "Shopify theme project."
print -- " shopify theme dev Preview locally with live reload"
print -- " shopify theme check Lint the theme"
print -- " shopify theme list List the themes on the store"
print -- " shopify theme pull Download a remote theme into this folder"
print -- " shopify theme push Upload this folder to a remote theme"
;;
app)
print -- "Shopify app project."
print -- " shopify app dev Run the app against a development store"
print -- " shopify app info Show how the app is configured"
print -- " shopify app deploy Deploy the app and its extensions"
print -- " shopify app logs Stream app logs"
;;
hydrogen)
print -- "Hydrogen storefront."
print -- " shopify hydrogen dev Run the storefront locally"
print -- " shopify hydrogen build Build for production"
print -- " shopify hydrogen deploy Deploy to Oxygen"
print -- " shopify hydrogen link Link this project to a storefront"
;;
*)
print -- "No Shopify project found here."
print -- " shopify theme init Start a new theme"
print -- " shopify app init Start a new app"
print -- " shopify hydrogen init Start a new Hydrogen storefront"
;;
esac
}
# Run the dev server for whichever kind of project this is.
function shopd() {
local project_type
if ! project_type="$(_shopify_project_type)"; then
print -u2 -- "shopd: not inside a Shopify theme, app or Hydrogen project."
return 1
fi
shopify "$project_type" dev "$@"
}
# Show what the current project is connected to.
function shopi() {
local project_type
if ! project_type="$(_shopify_project_type)"; then
print -u2 -- "shopi: not inside a Shopify theme, app or Hydrogen project."
return 1
fi
case "$project_type" in
hydrogen) shopify hydrogen list "$@" ;;
*) shopify "$project_type" info "$@" ;;
esac
}
# Complete the helpers like the commands they stand in for.
if (( $+functions[compdef] )); then
_shopd() {
local project_type
project_type="$(_shopify_project_type)" || return 1
words=(shopify "$project_type" dev ${words[2,-1]})
(( CURRENT += 2 ))
_shopify
}
_shopi() {
local project_type
project_type="$(_shopify_project_type)" || return 1
if [[ "$project_type" == hydrogen ]]; then
words=(shopify hydrogen list ${words[2,-1]})
else
words=(shopify "$project_type" info ${words[2,-1]})
fi
(( CURRENT += 2 ))
_shopify
}
compdef _shopd shopd
compdef _shopi shopi
fi
alias shop='shopify'
@@ -19,7 +19,6 @@
ssm_run_sudo() {sudo $_sublime_merge_path "$@" >/dev/null 2>&1}
alias ssm=ssm_run_sudo
alias sm=sm_run
alias smerge="'$_sublime_merge_path'"
break
fi
done
@@ -34,7 +33,6 @@
if [[ -a $_sublime_merge_path ]]; then
subm () { "$_sublime_merge_path" "$@" }
alias sm=subm
alias smerge="'$_sublime_merge_path'"
break
fi
done
@@ -47,7 +45,6 @@
if [[ -a $_sublime_merge_path ]]; then
subm () { "$_sublime_merge_path" "$@" }
alias sm=subm
alias smerge="'$_sublime_merge_path'"
break
fi
done
-10
View File
@@ -1,10 +0,0 @@
# uv Environment Plugin
This plugin automatically activates the uv virtual environment when you cd into a project directory, and deactivates it when you cd out.
Note: Script looks for pyproject.toml and uv.lock files to determine if it's a uv project
To use it, add `uv-env` to the plugins array in your zshrc file:
```zsh
plugins=(... uv-env)
```
-26
View File
@@ -1,26 +0,0 @@
_toggleUvShell() {
local in_uv_dir=0
if [[ -f "$PWD/pyproject.toml" && -f "$PWD/uv.lock" ]]; then
in_uv_dir=1
fi
if [[ $uv_active -eq 1 ]] && { [[ $in_uv_dir -eq 0 ]] || [[ "$PWD" != "$uv_dir" && "$PWD" != "$uv_dir"/* ]]; }; then
typeset -g uv_active=0
unset uv_dir
(( $+functions[deactivate] )) && deactivate
fi
if [[ $in_uv_dir -eq 1 ]] && [[ $uv_active -ne 1 ]]; then
local venv_dir="${UV_PROJECT_ENVIRONMENT:-$PWD/.venv}"
[[ "$venv_dir" != /* ]] && venv_dir="$PWD/$venv_dir"
if [[ -f "${venv_dir}/bin/activate" ]]; then
typeset -g uv_active=1
typeset -g uv_dir="$PWD"
source "${venv_dir}/bin/activate"
fi
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook chpwd _toggleUvShell
_toggleUvShell
-1
View File
@@ -47,7 +47,6 @@ _box_arguments=(
'help:COMMAND List subcommands'
'list:Lists all installed boxes'
'outdated:Checks if a box has newer version'
'prune:Removes old versions of installed boxes'
'remove:NAME Removes a box from the system'
'repackage:NAME PROVIDER VERSION Repackages an installed box into a `.box` file'
'update:Updates box to a newer version, if available'
@@ -1,7 +0,0 @@
## Cache
This plugin caches the completion script and automatically updates it when the
plugin is loaded, which is usually when you start a new terminal emulator.
The cache is stored at `$ZSH_CACHE_DIR/completions/_%command%`.
@@ -1,11 +0,0 @@
# %name% plugin
%description%
To use it, add `%name%` to the plugins array in your zshrc file:
```zsh
plugins=(... %name%)
```
This plugin does not add any aliases.
@@ -1,20 +0,0 @@
# %name% plugin
%description%
To use it, add `%name%` to the plugins array in your zshrc file:
```zsh
plugins=(... %name%)
```
## Aliases
| Alias | Command | Description |
| :----------- | ------------------ | :-------------------- |
| `%command%s` | `%command% status` | Show %command% status |
## Requirements
This plugin requires [%command%](https://example.com) to be installed.
%cache%
@@ -1,22 +0,0 @@
#
# Completion
#
# %command% generates its own zsh completion, so we cache it instead of
# maintaining a `_%command%` file by hand.
#
# On the first shell after installing this plugin the cache file doesn't exist
# yet, so compinit hasn't bound it. Do that manually here.
if [[ ! -f "$ZSH_CACHE_DIR/completions/_%command%" ]]; then
typeset -g -A _comps
autoload -Uz _%command%
_comps[%command%]=_%command%
fi
# Regenerate the cache in the background so it never blocks shell startup.
# TMPPREFIX puts the temp file next to the destination, which keeps the move
# atomic.
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_%command%"
zf_mv -f -- =( %compgen% ) "$TMPPREFIX"
} &|
@@ -1,6 +0,0 @@
#
# Completion
#
# Oh My Zsh adds this directory to $fpath, so a file named `_%command%` next to
# this one is picked up automatically as the completion for %command%.
#
@@ -1,32 +0,0 @@
#compdef %command%
#
# Completion for %command%.
#
# This is a starting point. Delete what you don't need. Two references:
# https://zsh.sourceforge.io/Doc/Release/Completion-System.html
# https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
local -a subcommands
subcommands=(
'status:Show the current status'
'run:Run something'
'help:Show help for a command'
)
_arguments -C \
'(-h --help)'{-h,--help}'[show help]' \
'(-v --version)'{-v,--version}'[show the version]' \
'1: :->subcommand' \
'*:: :->args'
case $state in
subcommand)
_describe -t commands '%command% subcommand' subcommands
;;
args)
case $words[1] in
status) _arguments '--short[one-line output]' ;;
run) _files ;;
esac
;;
esac
@@ -1,48 +0,0 @@
# %name% plugin
#
# %description%
#
# This file is sourced by Oh My Zsh in every new shell, so keep it fast.
# Avoid running external commands at the top level unless you really need to.
#
# Wrapping a command-line tool? Guard everything so people who don't have it
# installed don't end up with broken aliases:
#
# if (( ! $+commands[sometool] )); then
# return
# fi
#
# Aliases
#
# Document every alias in README.md. Short, obvious, and few beats many.
#
# alias %name%='echo "hello from %name%"'
#
# Functions
#
# Anything that needs arguments in the middle, or more than one command,
# belongs here rather than in an alias.
#
# function %name%_hello() {
# echo "hello, ${1:-world}"
# }
#
# Completion
#
# Oh My Zsh adds this directory to $fpath, so a file named `_sometool` next to
# this one is picked up automatically as the completion for `sometool`.
#
#
# Need the path to this plugin's own directory (for data files, a lib/ dir...)?
# This is the standard way to get it, since $0 is not reliable on its own:
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
#
# 0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}"
# 0="${${(M)0:#/*}:-$PWD/$0}"
# source "${0:A:h}/lib/helpers.zsh"
@@ -1,43 +0,0 @@
# %name% plugin
#
# %description%
#
# This file is sourced by Oh My Zsh in every new shell, so keep it fast.
# Avoid running external commands at the top level unless you really need to.
# Only define anything if the tool is actually installed. Without this guard,
# people who don't have %command% get aliases that fail with "command not found".
if (( ! $+commands[%command%] )); then
return
fi
#
# Aliases
#
# Document every alias in README.md. Short, obvious, and few beats many.
# The one below is just an example: replace it with something %command% can do.
#
alias %command%s='%command% status'
#
# Functions
#
# Anything that needs arguments in the middle, or more than one command,
# belongs here rather than in an alias.
#
# function %name%_current() {
# %command% status --short "$@"
# }
%completion%
#
# Need the path to this plugin's own directory (for data files, a lib/ dir...)?
# This is the standard way to get it, since $0 is not reliable on its own:
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
#
# 0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}"
# 0="${${(M)0:#/*}:-$PWD/$0}"
# source "${0:A:h}/lib/helpers.zsh"