Skip to content

Instantly share code, notes, and snippets.

@eddy-22
Forked from mattmc3/optparsing_demo.zsh
Last active August 3, 2023 15:11
Show Gist options
  • Save eddy-22/b2aa9a551a6cb31d45f1b48f051d9b89 to your computer and use it in GitHub Desktop.
Save eddy-22/b2aa9a551a6cb31d45f1b48f051d9b89 to your computer and use it in GitHub Desktop.
Zsh option parsing example
function yy() {
zmodload zsh/zutil
function breakout() { echo -e "$1" && exit 1}
local flag_url
local output="./%(artist)s/%(title)s.%(ext)s"
local flag_song
local flag_format='bestaudio[ext=m4a]'
zparseopts -D -F -K -- \
{u,-url}:=flag_url \
{f,-format}=flag_format \
{s,-song}=flag_song ||
return 1
[[ -z $flag_url ]] && echo "USING CLIPBOARD URL!" && flag_url="$(pbpaste)"
[[ ! -z $flag_song ]] && output="./${flag_song}.(ext)s"
[[ -z $url ]] && breakout "Copy or specify a URL first!"
local CMD=()
CMD+=(
yt-dlp
"--format"
"$flag_format"
"--output"
"$output"
'--no-playlist'
'--embed-thumbnail'
'--'
"$url"
)
${CMD[@]}
}
# Manual opt parsing example
#
# Features:
# - supports short and long flags (ie: -v|--verbose)
# - supports short and long key/value options (ie: -f <file> | --filename <file>)
# - supports short and long key/value options with equals assignment (ie: -f=<file> | --filename=<file>)
# - does NOT support short option chaining (ie: -vh)
# - everything after -- is positional even if it looks like an option (ie: -f)
# - once we hit an arg that isn't an option flag, everything after that is considered positional
function optparsing_demo() {
local positional=()
local flag_verbose=false
local filename=myfile
local usage=(
"optparsing_demo [-h|--help]"
"optparsing_demo [-v|--verbose] [-f|--filename=<file>] [<message...>]"
)
opterr() { echo >&2 "optparsing_demo: Unknown option '$1'" }
while (( $# )); do
case $1 in
--) shift; positional+=("${@[@]}"); break ;;
-h|--help) printf "%s\n" $usage && return ;;
-v|--verbose) flag_verbose=true ;;
-f|--filename) shift; filename=$1 ;;
-f=*|--filename=*) filename="${1#*=}" ;;
-*) opterr $1 && return 2 ;;
*) positional+=("${@[@]}"); break ;;
esac
shift
done
echo "--verbose: $flag_verbose"
echo "--filename: $filename"
echo "positional: $positional"
}
# zparseopts
#
# Resources:
# - https://xpmo.gitlab.io/post/using-zparseopts/
# - https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#index-zparseopts
#
# Features:
# - supports short and long flags (ie: -v|--verbose)
# - supports short and long key/value options (ie: -f <file> | --filename <file>)
# - does NOT support short and long key/value options with equals assignment (ie: -f=<file> | --filename=<file>)
# - supports short option chaining (ie: -vh)
# - everything after -- is positional even if it looks like an option (ie: -f)
# - once we hit an arg that isn't an option flag, everything after that is considered positional
function zparseopts_demo() {
local flag_help flag_verbose
local arg_filename=(myfile) # set a default
local usage=(
"zparseopts_demo [-h|--help]"
"zparseopts_demo [-v|--verbose] [-f|--filename=<file>] [<message...>]"
)
# -D pulls parsed flags out of $@
# -E allows flags/args and positionals to be mixed, which we don't want in this example
# -F says fail if we find a flag that wasn't defined
# -M allows us to map option aliases (ie: h=flag_help -help=h)
# -K allows us to set default values without zparseopts overwriting them
# Remember that the first dash is automatically handled, so long options are -opt, not --opt
zmodload zsh/zutil
zparseopts -D -F -K -- \
{h,-help}=flag_help \
{v,-verbose}=flag_verbose \
{f,-filename}:=arg_filename ||
return 1
[[ -z "$flag_help" ]] || { print -l $usage && return }
if (( $#flag_verbose )); then
print "verbose mode"
fi
echo "--verbose: $flag_verbose"
echo "--filename: $arg_filename[-1]"
echo "positional: $@"
}

Here are some examples of the manual parsing function in action...

Call with no args

$ optparsing_demo
--verbose: false
--filename: myfile
positional:

Call with both short and long options, as well as positional args

$ optparsing_demo --verbose -f test.txt foo
--verbose: true
--filename: test.txt
positional: foo

Call with -- to pass positionals that look like flags

$ optparsing_demo --filename=test.txt -- -v --verbose -f --filename are acceptable options
--verbose: false
--filename: test.txt
positional: -v --verbose -f --filename are acceptable options

Called incorrectly with positionals before intended opts

$ optparsing_demo do not put positionals before opts --verbose --filename=mynewfile
--verbose: false
--filename: myfile
positional: do not put positionals before opts --verbose --filename=mynewfile

This method of opt parsing does not support flag chaining like getopt does

$ optparsing_demo -vh
optparsing_demo: Unknown option '-vh'

Here are some examples of the zparseopt version in action...

Call with no args

$ zparseopts_demo
--verbose:
--filename: myfile
positional:

Call with both short and long options, as well as positional args

$ zparseopts_demo --verbose -f test.txt foo
--verbose: --verbose
--filename: test.txt
positional: foo

Call with -- to pass positionals that look like flags

$ zparseopts_demo --filename test.txt -- -v --verbose -f --filename are acceptable options
--verbose:
--filename: test.txt
positional: -v --verbose -f --filename are acceptable options

Called incorrectly with positionals before intended opts. If you want this, zparseopts supports it with the -E flag.

$ zparseopts_demo do not put positionals before opts --verbose --filename=mynewfile
--verbose:
--filename: myfile
positional: do not put positionals before opts --verbose --filename=mynewfile

This method of opt parsing does supports flag chaining like getopt does

$ zparseopts_demo -vh
zparseopts_demo [-h|--help]
zparseopts_demo [-v|--verbose] [-f|--filename=<file>] [<message...>]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment