A thrusting hit will never find
Once upon a time I proposed a DebConf talk about how to write zsh completion functions, but it was rejected. Accordingly, I didn't waste any time preparing materials for it, so I never have anything to throw at people when they ask for some kind of introduction.
Here we have a fictitious program called arismom. Usage information from the fictitious manpage and the fictitious --help output is as follows:
Usage: arismom [OPTION]... [FILE]...
Do it Jersey style.
-a, --all do all those things
-b bubble
--bounce bonuce
--CoC=STYLE adhere to STYLE code of conduct
-d, --debian=PACKAGE dedicate actions to Debian PACKAGE
-e, --ensqualm=USER ensqualm USER first
-t, --tempdir=DIRECTORY spew temporary files into DIRECTORY
So let's cut to the quick. Create a file called _arismom somewhere in your function search path. This is described by the array $fpath. You can view its contents by typing print -l $fpath, and you can add a a directory to the beginning with fpath=(\~/.zsh/scratch $fpath) or to the end with fpath+=(\~/.zsh/scritch). For the purposes of this blog entry, we'll pretend you have a \~/.michaelbolton/squatch directory in your $fpath and that you are now editing \~/.michaelbolton/squatch/_arismom. The first line of the file, at the very tippy top, should read
#compdef arismom
This ensures that when the completion system boots up and finds your file, it will associate your function with the command arismom and complete options and arguments for it accordingly. Speaking of arguments, skip a line for aesthetic equilibrium, and invoke the _arguments utility function.
_arguments \
_arguments is sort of the cdbs of the zsh completion fleet. By the end of this blog entry, you'll have no idea how it works, and if you want to do anything particularly complex with it, you might encounter some resistance. For those of you unfamiliar with Z-Shell syntax or shell syntax in general, the trailing backslash means “I'm a-gonna feed you a ton of information about the command-line interface to arismom.” So tell it about the first option already.
'(-a --all)'{-a,--all}'[do all those things]' \
To oversimplify, this declares that -a and --all are options which produce the identical behavior of “do all those things”. Specifically, the part in parentheses says to not complete either -a or --all when either -a or --all is already on the command-line. The part in braces is merely brace expansion; for that reason it is outside of the single quotes. If you're unfamiliar with brace expansion, try print '(alice)'{bob,carnie}'[wilson]' to see how it expands. Finally, the phrase in brackets is an explanation of the option, which may or may not be displayed depending on your configuration. Next, do a short option that has no long option equivalent.
'-b[bubble]' \
A long option with no short option equivalent looks similar. Don't feel limited by upstream's inadequate descriptions, misspellings, or poor grammar.
'--bounce[amplify bounce level according to X-la algorithm]' \
Some options take arguments. Now we use colons.
'--CoC=[adhere to CoC]:CoC style:(mjg59 buxy ubuntu)' \
The first “column” is the same optspec by which you've been so excited thus far, and the part between the colons is the message or description of that which will be matched. The part after the last colon is the action; in this case you are specifying a list of possibilities within single parentheses. In most cases, you'll want to be more dynamic than a pre-defined list, and there are many helper functions all ready to serve you.
'(-d --debian)'{-d,--debian=}'[dedicate actions to Debian package]:package:_deb_packages avail' \
_deb_packages is a function that completes Debian packages; it can take avail, installed, or uninstalled to restrict which set of packages it offers. In this case we want it to complete any package available from your sources.
'(-e --ensqualm)'{-e,--ensqualm=}'[ensqualm user first]:user to ensqualm:_users' \
Here the _users function will complete usernames.
'(-t --tempdir)'{-t,--tempdir=}'[spew]:temp dir:_files -/' \
Normally the _files function will complete files, but you can tell it that you only want directories with the -/ option. Finally, we want to cover all the remaining arguments (which according to the fictitious usage information is a list of files). In this case, you happen to believe that files with the .nj extension are to be completed.
'*:NJ files:_files -g "*.nj"'
The -g option specifies a glob pattern to match files. Now the entire file should look like this.
#compdef arismom
_arguments \
'(-a --all)'{-a,--all}'[do all those things]' \
'-b[bubble]' \
'--bounce[amplify bounce level according to X-la algorithm]' \
'--CoC=[adhere to CoC]:CoC style:(mjg59 buxy ubuntu)' \
'(-d --debian)'{-d,--debian=}'[dedicate actions to Debian package]:package:_deb_packages avail' \
'(-e --ensqualm)'{-e,--ensqualm=}'[ensqualm user first]:user to ensqualm:_users' \
'(-t --tempdir)'{-t,--tempdir=}'[spew]:temp dir:_files -/' \
'*:NJ files:_files -g "*.nj"'
There you have it. Restart zsh and try tab-completing various things after arismom.
P.S. I expect bug reports containing functions for dpatch-edit-patch, pkill, and pgrep by tomorrow morning.
P.P.S. Why is Scott James Remnant still on Planet?