Day 16: Making tab completion setup seamless for users (App::shcompgen)

About the series: perlancar's 2014 Advent Calendar: Introduction to a selection of 24 modules which I published in 2014. Table of contents.

I've created a couple of frameworks that make it easy for Perl developers to add tab completion feature to their scripts (and am planning to write another one). One is Perinci::CmdLine, which I recommend and use a lot more myself but requires you to immerse yourself with the concepts Rinci metadata, Riap URLs, and all that. Another is Getopt::Long::Complete which is more suitable to casual Perl programmers familiar with de-facto and builtin module for options parsing in Perl, Getopt::Long. Getopt::Long::Complete allows you to use Getopt::Long interface; you just need to add a subroutine to tell how to complete option values and arguments.

On the user side, setting up (activating) tab completion is not so straightforward, until recently. I've now come up with shcompgen. This tool took a few iterations until it reaches the current form, and also a couple of hacks along the way (and a couple of minor quirks still remaining). But now the end result is that, after installing and setting up shcompgen (which involves just running shcompgen init and adding a single line to her ~/.bashrc), user can just install your distribution and immediately, in the current shell, she will have tab completion already activated for your scripts. For example:

% cpanm -n App::fatten
% fatten --in[tab][tab]
--include    --include-dist    --include-file
% fatten -t [tab][tab]
fatpacker    prereqscanner   prereqscanner_lite    prereqscanner_lite_recurse    prereqscanner_recurse    require

Isn't that nice? If you install tens+ CLI scripts like me, it's a godsend because I don't have to mess with any 'complete' shell command or start a new shell to activate completion. It becomes active right then and there ready to be used.

To accomplish this, there are several components involved. I'm going to talk only for bash at the moment, since that is currently the only supported shell by shcompgen, but support for other shells (fish and zsh) is along the way.

First, of course you have to write your CLI script using Perinci::CmdLine or Getopt::Long::Complete. Support for other CLI framework can be added in the future, should there be any demand or pull request.

Second, if you use Dist::Zilla to create your distribution, you'll want to add this plugin: GenShellCompletion. This plugin, admittedly quite hackish, will add a code to the generated Makefile.PL that will then add bits to the generated Makefile which will run shcompgen generate for your scripts and generate the shell completion scripts (in bash they will just be complete -C PROGNAME PROGNAME since Perinci::CmdLine- and Getopt::Long::Complete-based CLI scripts can tab-complete themselves.

These shell completion scripts go to ~/.config/bash/completions/ (there is currently still no convention of where to put per-user completion scripts in bash, so I picked this). The script will be autoloaded by bash because we've installed a handler in complete -F -D to search and load shell completion scripts for unknown commands.

So in the example above, when a user installs your dist (App::fatten), during make install after your script is installed to the final location, this command will be run: shcompgen generate --replace fatten which will then create a file ~/.config/bash/completions/fatten containing this line complete -C fatten fatten.

When a user type fatten --in and presses Tab, ~/.config/bash/completions/fatten will be loaded by the shell and the shell will know how to complete this command.

Leave a comment

About perlancar

user-pic #perl #indonesia