zsh autocompletion for a specific program - zsh

I have a program, let's call it myprog which takes a few options (-o, --option...) with or without argument.
Is there a way to configure zsh so when I use the <TAB> key it lists the options ? It is the case when I type for example ls -<TAB>.
Here is the beginning of the file .zshrc (I found it on the web, without understanding it...) :
# Completion
autoload -U compinit
compinit
zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}'
#compinstall
zstyle ':completion:*:descriptions' format '%U%B%d%b%u'
zstyle ':completion:*:warnings' format '%BSorry, no matches for: %d%b'
zstyle ':completion:*:sudo:*' command-path /usr/local/sbin /usr/local/bin \
/usr/sbin /usr/bin /sbin /bin /usr/X11R6/bin
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path ~/.zsh_cache
zmodload zsh/complist
setopt extendedglob
zstyle ':completion:*:*:kill:*:processes' list-colors "=(#b) #([0-9]#)*=36=31"
If this is possible (I'm sure it is !), is there a way to "distribute" this auto-completion, for example to all the people that will use myprog on their computer

What you can do is distribute a shell function that does the job of generating completion matches for your programme.
If that function is installed, Zsh users will only need the first couple of .zshrc lines to run compinit.
At a technical level, there's a $fpath array in zsh for functions that is analogous to $path. Functions are auto-loaded on demand. If you distribute myprog as software, you would typically have it install the function as $prefix/share/zsh/site-functions/_myprog where $prefix is the install prefix for your software. If you're a local system administrator and want to deploy it with a tool like ansible, check $fpath for the zsh on your OS. Often, you can install it to a file named /usr/local/share/zsh/site-functions/_myprog.
How to write a completion function is too big a subject for a single answer on this site but for options you can use _arguments to do much of the work. Look at some of the examples that come with zsh (they tend to be better quality than others you may find). As a starting point, for what you describe in the question, you might have:
#compdef myprog
_arguments \
'-o[descripton for option with no argument]' \
'--option=[description for option with an argument]:argument:(one two three)' \
'*:remaining argument:_files'
The first line tells compinit which commands (or other things like specific environment variable assignments) the function should complete arguments for.

Related

How would I create a Makefile that remotely updates itself?

I have a makefile that I've changed up a bit here to look more generalized
.PHONY:
bash hash.sh
all: .PHONY lab tests.zip
lab: .PHONY lab.cpp
g++ -o lab lab.cpp -g -Wall -std=c++11
tests.zip:
curl -L -O https://url/tests.zip
unzip tests.zip
tests: .PHONY lab tests.zip
bash scripts/test.bash lab.cpp
clean:
rm -rf scripts tests bitset-tests.zip
I am a TA for an entry level computer science course at my university, and I've created a makefile here for my students to use to compile and test their code seamlessly.
One thing I want to do though is to have the makefile update itself every time the remote repository has a new version of it. I know I could just have them update the file themselves, but my job is to make the students focus less on setting things up and more on just coding for now, since it's entry level. So for the purposes of this, I'm sticking with the idea I have.
Currently, I'm achieving this with a script hash.sh which fetches a hash of the makefile from the repo, and compares it to a hash of the makefile in the student's directory. If the hashes don't match, then the updated makefile is fetched and replaces the old one. This is done in the .PHONY recipe. I should also mention that I don't want to add a recipe that updates it like make update, because again I want the process to be seamless. You'd be surprised how many students wouldn't utilize that feature, so I want to build it into the ones they will use for sure.
Is there a better method for this, or am I doing something wrong with this one?
Thomas has the right idea, but you can't use .PHONY here because it would means the makefile is ALWAYS out of date; make knows this so it doesn't re-exec itself if its included makefile is marked .PHONY.
You need to create a way for make to know if the makefile was changed since the last time it was run locally. I recommend you do it like this:
<normal makefile here>
Makefile: FORCE
curl https://.../Makefile -o Makefile.tmp
cmp -s Makefile Makefile.tmp && rm -f Makefile.tmp || mv -f Makefile.tmp Makefile
FORCE:
What does this do? First it uses a FORCE target which is an old-school way to emulate a .PHONY target, which is always out of date, without actually using .PHONY (which as I mentioned above, is handled specially by GNU make in this situation).
Second it retrieves the Makefile but only updates the local makefile if it has changed. If it hasn't changed, it doesn't update the local makefile and so make won't re-exec itself.
The whole stuff with fetching a hash sounds overly complicated. If you're going to do a fetch anyway, why not unconditionally fetch the entire makefile? It saves a network round trip, which is probably the limiting factor; the actual data is probably just a few kB anyway.
If you're using curl, notice the --time-cond option, for example:
curl https://... --time-cond Makefile -o Makefile
This will only fetch and update the Makefile if it's newer than the mtime of the current file. It works by sending an If-Modified-Since HTTP header, so you'll need some cooperation from the server for this to work reliably.
If using GNU make, you can use another neat trick: Remake the makefile itself. If you have a rule whose target is Makefile, it will be executed before anything else happens, and make will re-read the updated Makefile before proceeding:
.PHONY: Makefile
Makefile:
curl https://... --time-cond Makefile -o Makefile
Note that this will lead to infinite loops if for whatever reason the --time-cond leads to an unconditional update, so it wouldn't hurt to guard against that:
.PHONY: Makefile
Makefile:
[[ ! -v MAKE_RESTARTS ]] && \
curl https://... --time-cond Makefile -o Makefile

how to enable zsh completion of path with hyphenated words

The setup
I have my code repos in folder ~/code.
Suppose I have these repos:
~/code/my-repo-awesome
~/code/my-repo-bad
~/code/my-repo-cool
ZSH has nice ability to complete paths.
For example, if I write the following
cd /u/bi/lo
then zsh will complete as follows:
cd /usr/bin/local
Question
Is it possible to configure zsh such that after typing the following
cd ~/co/m-r-c
it will complete to
cd ~/code/my-repo-cool
If so, how?
You can use matcher-list tag.
zstyle ':completion:*' matcher-list '' 'r:|-=* r:|=*'
For reference: http://zsh.sourceforge.net/Doc/Release/Completion-System.html#index-matcher_002dlist_002c-completion-style

Can't write function to replicate the functionality of Bash's 'help' command

I'm trying to replicate Bash's help command in my .zshrc. I thought this would work:
function help {
bash -c "help $#"
}
but this only works when I only pass a single argument -- for instance, help exit. It does not work when I pass options to help, such as help -m exit (which works in Bash). However, literally typing bash -c "help -m exit" works as expected. I imagine this has something to do with how quoting works in the shell, but I can't figure this out.
You can use
function help {
bash -c "help $*"
}
I suppose you need help for using zsh, not for bash. Bash doesn't know anything about zsh builtins and features, and the common builtins are different enough to warrant separate documentation pages.
The equivalent of bash's help builtin is run-help, but it is not active by default. It will also call man for you and comes with a few useful wrappers.
TLDR; put this in .zshrc
autoload -Uz run-help
autoload -Uz run-help-git
autoload -Uz run-help-ip
autoload -Uz run-help-openssl
autoload -Uz run-help-p4
autoload -Uz run-help-sudo
autoload -Uz run-help-svk
autoload -Uz run-help-svn
alias help=run-help
https://wiki.archlinux.org/index.php/zsh#Help_command

Does "tar" follow either the POSIX and/or GNU command line options conventions?

tar is one of those venerable programs that is literally on every single UNIX-like system out there. There is POSIX-compliant tar and there is GNU tar.
A typical way to use tar is a command like tar -czf archive.tar.gz dir. The option -c is to create a new archive, -z is to compress, and -f specifies the output filename. Note how -c and -z don't have arguments but -f does.
This page describes the POSIX and GNU command line conventions. Note the following:
Multiple options may follow a hyphen in a cluster if they do not take
arguments. Thus, -abc and -a -b -c are the same.
Does this mean that tar's command line arguments handling does not conform to neither the POSIX nor GNU conventions? Or am I misreading something?
You aren't misreading, Unix tar follows neither the POSIX nor the GNU standard.
When it appeared in Unix version 7, tar first arguments was defined as a key composed of one function letter (one of c r t u and x) followed by optional function modifiers. All of them are single letters and there must be no embedded space. Some of the function modifiers require a parameter which must appear in the following arguments, in the order of the modifiers.
Later, tar implementations did allow a hyphen as a prefix for the whole key, or one hyphen for the single function letters but the convention of having the parameters separated at the end of the function letters was kept for the last option (usually f), allowing it to have its own parameter.
Moreover, GNU tar supports three option styles, the old one with the initial key followed by key arguments, the single letter option syntax which is like the POSIX standard way of passing arguments, and the long option style where word options are introduced by two dashes.
GNU tar allows these syntaxes (beyond many others) for the same command:
tar czvbf 20 archive.tar.gz dir
tar -czvf archive.tar.gz -b 20 dir
tar -c -z -v -b 20 -f archive.tar.gz dir
tar --create --gzip --verbose --blocking-factor=20 --file=archive.tar.gz dir
tar --create --gzip --verbose --blocking-factor=20 --file archive.tar.gz dir
Combining different styles is possible with GNU tar but might produce unexpected results due to the variation in parsing rules.
Having fully converted from the original to the POSIX options standard syntax might have severely break compatibility with existing scripts using tar. That is probably one of the reasons the tar command wasn't standardized at all by POSIX and pax, which supports a POSIX standardized tar file format, was created.
Note that a similar issue exists with ps, which has two incompatible syntaxes, the BSD one (hyphenless) and the Unix one (with hyphens).

Expand aliases in non-interactive shells

In bash, we can use shopt -s expand_aliases to expand aliases in scripts.
What are the equivalent commands for zsh, csh, and tcsh? Do they even exist?
In focusing my efforts on zsh, I haven’t found such a command. I even tried sourcing the file with the aliases inside the script, but it did not work.
For zsh you can use setopt aliases
#!/usr/bin/zsh
alias hoo="echo bar"
unsetopt aliases
hoo # outputs `./test.zsh:5: command not found: hoo`
setopt aliases
hoo # outputs `bar`
see man zshoptions for detail.
For csh and tcsh, sourcing the files (source ${HOME}/.cshrc, for example) suffices.

Resources