Home Architecture // Programmatic Completion


Using the complete action of rdiff-backup, one can quite easily write a shell completion script. It shouldn’t require to analyze all possible completions because rdiff-backup itself is doing the analysis. The completion "glue" for each shell only needs to prepare the results.

Note
the code has been written with 'bash' as guinea pig for the approach. The expectation is that the programmatic completion of other shells will have similar requirements and facilities. Should this prove wrong, additional requirements are welcome as enhancement issue.

1. Usage

The principle is rather simple: one calls rdiff-backup complete with the current arguments entered by the user on the command line, and rdiff-backup outputs a list of possible completions. The completion script then only needs to use this list as required by the shell.

rdiff-backup complete [--cword <index>] [--unique|--no-unique] -- <words...>

The 'words' are the words on the command line as currently entered by the user. The 'cword' option represents the zero-based index where the cursor of the user is currently placed.

Example 1
User input

$ rdiff-backup --verb⌷ (the represents the user’s cursor when they triggers the completion, e.g. with <TAB><TAB> under bash)

Completion script call

rdiff-backup complete --cword 1 -- rdiff-backup --verb

Completion values

--verbosity

Example 2
User input

$ rdiff-backup --verbosity ⌷ (notice the blank between verbosity and cursor)

Completion script call

rdiff-backup complete --cword 2 -- rdiff-backup --verbosity '' (notice accordingly the empty last word)

Completion values

a list of integers from 0 to 10 as possible parameter for --verbosity

Example 3
User input

$ rdiff-backup --verbosity⌷ 5

Completion script call

rdiff-backup complete --cword 1 -- rdiff-backup --verbosity 5 (notice the cword pointing at --verbosity)

Completion values

--verbosity again

Important
the double minus -- is absolutely required to avoid that the words are interpreted as options of the complete action.

Finally the --unique/--no-unique option toggles if the returned completion values should return values already entered on the command line. By default options are considered unique (see Limitations on this topic).

2. Return values

In the current stage, the returned completion values might be a mixture of old and new CLI options, as long as rdiff-backup doesn’t have enough information to differentiate them.

In all cases, the possible completion values are returned on stdout with one possible value on each line. The values are considered pre-sorted and shouldn’t be sorted by the completion script/toolset, but this decision remains to the judgement of the script’s author.

Important is that the last option in the list might have a specific meaning, and the special format ::action::. Currently there is only one possible action, and it is file. This action means that the completion script should remove this one option, and replace it with the list of files which could be used to complete the command line.

Example 4
User input

$ rdiff-backup backup D⌷

Completion script call

rdiff-backup complete --cword 2 -- rdiff-backup backup D

Completion values

::file::. It would be the responsibility of the completion script to replace it (for example) with Desktop, Documents and Downloads.

Tip
the bash built-in command complete resp. compgen offers the option -A file to do this. In our example, calling compgen -A file D in a typical Linux home drive would exactly return the three directories listed above.
Example 5
User input

$ rdiff-backup backup ⌷

Completion script call

rdiff-backup complete --cword 2 -- rdiff-backup backup '' (without first letter D)

Completion values

all possible backup options followed by the said ::file:: option, i.e. something like:

--acls
--carbonfile
[...]
--resource-forks
--user-mapping-file
::file::

Some options, like --user-mapping-file, followed by a filename would similarly trigger the output of the ::file:: parameter.

3. Limitations

3.1. Default value for cword

The default value for cword is -1, meaning the last word of the list. This is a rather sensitive approach should there be a shell completion not supporting this parameter.

3.2. Uniqueness of options

By default all parameters are considered 'unique' and only offered once to the user as completion. This limitation is due to the author’s reluctance to have an algorithm based on intrinsic knowledge of the options' semantic. As the actions ('backup', 'compare', etc…​) are actually plugins, the complete action plugin shouldn’t rely on knowing how to use their options, as this approach would break with each new plug-in.

3.3. Assumptions

Despite the above stated lack of knowledge about options' semantic, some assumptions are made, which could be broken if care isn’t taken:

  • an option which has a 'type' which isn’t a boolean is followed by a parameter

    • if this kind of option has 'choices', those are output by complete

    • if there is no choice but a type FileType or 'str' with additionally a 'metavar' ending in _FILE, then the option is deemed having a file as parameter

  • arguments called locations are supposed to represent a list of files (or directories).

Note
it shouldn’t be the worry of the completion script’s author to make sure that those assumptions are kept true.

3.4. Locations

There is no reasonable way to help the user with completion if the locations are remote, so we always assume "local" locations and expect files/directories represented by the ::file:: placeholder.

The exact number of possible locations/files isn’t really validated, just cursorily checked.

3.5. Sorted options

The options are sorted by ignoring the hyphens, this means that actions and options are mixed.

4. Adding new completion scripts

It should be pretty simple in most cases, if you know how to create a fork and a pull request against rdiff-backup’s Git repo:

  1. add a subdirectory and script to tools/completions, e.g. bash/rdiff-backup (the script must have its definitive name on the file system, hence the sub-directory)

  2. add an entry of the form "<relative-target-directory>" = ["tools/completions/myshell/myscript"] in the [tool.setuptools.data-files] section of the pyproject.toml file. Take the already existing bash entry "share/bash-completion/completions" = ["tools/completions/bash/rdiff-backup"] as sample.

That’s it, test (e.g. using pip install .), commit and push, a pull request is then just a click or two away.