Add Bash completion script so you can tab-complete the borgmatic command-line.

This commit is contained in:
Dan Helfman 2022-05-26 10:27:53 -07:00
parent 691ec96909
commit 77b84f8a48
7 changed files with 123 additions and 5 deletions

3
NEWS
View file

@ -1,6 +1,9 @@
1.6.2.dev0 1.6.2.dev0
* #536: Fix generate-borgmatic-config with "--source" flag to support more complex schema changes * #536: Fix generate-borgmatic-config with "--source" flag to support more complex schema changes
like the new Healthchecks configuration options. like the new Healthchecks configuration options.
* Add Bash completion script so you can tab-complete the borgmatic command-line. See the
documentation for more information:
https://torsion.org/borgmatic/docs/how-to/set-up-backups/#shell-completion
1.6.1 1.6.1
* #294: Add Healthchecks monitoring hook "ping_body_limit" option to configure how many bytes of * #294: Add Healthchecks monitoring hook "ping_body_limit" option to configure how many bytes of

View file

@ -109,10 +109,9 @@ class Extend_action(Action):
setattr(namespace, self.dest, list(values)) setattr(namespace, self.dest, list(values))
def parse_arguments(*unparsed_arguments): def make_parsers():
''' '''
Given command-line arguments with which this script was invoked, parse the arguments and return Build a top-level parser and its subparsers and return them as a tuple.
them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
''' '''
config_paths = collect.get_default_config_paths(expand_home=True) config_paths = collect.get_default_config_paths(expand_home=True)
unexpanded_config_paths = collect.get_default_config_paths(expand_home=False) unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)
@ -189,6 +188,12 @@ def parse_arguments(*unparsed_arguments):
action='extend', action='extend',
help='One or more configuration file options to override with specified values', help='One or more configuration file options to override with specified values',
) )
global_group.add_argument(
'--bash-completion',
default=False,
action='store_true',
help='Show bash completion script and exit',
)
global_group.add_argument( global_group.add_argument(
'--version', '--version',
dest='version', dest='version',
@ -647,6 +652,16 @@ def parse_arguments(*unparsed_arguments):
) )
borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') borg_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
return top_level_parser, subparsers
def parse_arguments(*unparsed_arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
'''
top_level_parser, subparsers = make_parsers()
arguments, remaining_arguments = parse_subparser_arguments( arguments, remaining_arguments = parse_subparser_arguments(
unparsed_arguments, subparsers.choices unparsed_arguments, subparsers.choices
) )

View file

@ -11,6 +11,7 @@ from subprocess import CalledProcessError
import colorama import colorama
import pkg_resources import pkg_resources
import borgmatic.commands.completion
from borgmatic.borg import borg as borg_borg from borgmatic.borg import borg as borg_borg
from borgmatic.borg import check as borg_check from borgmatic.borg import check as borg_check
from borgmatic.borg import compact as borg_compact from borgmatic.borg import compact as borg_compact
@ -884,6 +885,9 @@ def main(): # pragma: no cover
if global_arguments.version: if global_arguments.version:
print(pkg_resources.require('borgmatic')[0].version) print(pkg_resources.require('borgmatic')[0].version)
sys.exit(0) sys.exit(0)
if global_arguments.bash_completion:
print(borgmatic.commands.completion.bash_completion())
sys.exit(0)
config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths)) config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths))
configs, parse_logs = load_configurations(config_filenames, global_arguments.overrides) configs, parse_logs = load_configurations(config_filenames, global_arguments.overrides)

View file

@ -0,0 +1,60 @@
import pkg_resources
from borgmatic.commands import arguments
UPGRADE_MESSAGE = '''
Your bash completions script is from a different version of borgmatic than is
currently installed. Please upgrade your script so your completions match the
command-line flags in your installed borgmatic! Try this to upgrade:
sudo sh -c "borgmatic --bash-completions > $BASH_SOURCE"
source $BASH_SOURCE
'''
def parser_flags(parser):
'''
Given an argparse.ArgumentParser instance, return its argument flags in a space-separated
string.
'''
return ' '.join(option for action in parser._actions for option in action.option_strings)
def bash_completion():
'''
Return a bash completion script for the borgmatic command. Produce this by introspecting
borgmatic's command-line argument parsers.
'''
top_level_parser, subparsers = arguments.make_parsers()
global_flags = parser_flags(top_level_parser)
actions = ' '.join(subparsers.choices.keys())
borgmatic_version = pkg_resources.require('borgmatic')[0].version
# Avert your eyes.
return '\n'.join(
(
'check_version() {',
' local installed_version="$(borgmatic --version 2> /dev/null)"',
' if [ "$installed_version" != "%s" ] && [ "$installed_version" != "" ];'
% borgmatic_version,
' then cat << EOF\n%s\nEOF' % UPGRADE_MESSAGE,
' fi',
'}',
'complete_borgmatic() {',
)
+ tuple(
''' if [[ " ${COMP_WORDS[*]} " =~ " %s " ]]; then
COMPREPLY=($(compgen -W "%s %s %s" -- "${COMP_WORDS[COMP_CWORD]}"))
return 0
fi'''
% (action, parser_flags(subparser), actions, global_flags)
for action, subparser in subparsers.choices.items()
)
+ (
' COMPREPLY=($(compgen -W "%s %s" -- "${COMP_WORDS[COMP_CWORD]}"))'
% (actions, global_flags),
' (check_version &)',
'}',
'\ncomplete -F complete_borgmatic borgmatic',
)
)

View file

@ -111,6 +111,7 @@ Additionally, [rsync.net](https://www.rsync.net/products/borg.html) and
[Hetzner](https://www.hetzner.com/storage/storage-box) have compatible storage [Hetzner](https://www.hetzner.com/storage/storage-box) have compatible storage
offerings, but do not currently fund borgmatic development or hosting. offerings, but do not currently fund borgmatic development or hosting.
## Configuration ## Configuration
After you install borgmatic, generate a sample configuration file: After you install borgmatic, generate a sample configuration file:
@ -302,9 +303,34 @@ interested in an [unofficial work-around for Full Disk
Access](https://projects.torsion.org/borgmatic-collective/borgmatic/issues/293). Access](https://projects.torsion.org/borgmatic-collective/borgmatic/issues/293).
## Colored output ## Niceties
Borgmatic produces colored terminal output by default. It is disabled when a
### Shell completion
borgmatic includes a shell completion script (currently only for Bash) to
support tab-completing borgmatic command-line actions and flags. Depending on
how you installed borgmatic, this may be enabled by default. But if it's not,
you can install the shell completion script globally:
```bash
sudo su -c "borgmatic --bash-completion > /usr/share/bash-completion/completions/borgmatic"
```
Alternatively, if you'd like to install the script for just the current user:
```bash
mkdir --parents ~/.local/share/bash-completion/completions
borgmatic --bash-completion > ~/.local/share/bash-completion/completions/borgmatic
```
In either case, you may also need to install the `bash-completion` Linux
package and restart your shell (`exit` and open a new shell).
### Colored output
borgmatic produces colored terminal output by default. It is disabled when a
non-interactive terminal is detected (like a cron job), or when you use the non-interactive terminal is detected (like a cron job), or when you use the
`--json` flag. Otherwise, you can disable it by passing the `--no-color` flag, `--json` flag. Otherwise, you can disable it by passing the `--no-color` flag,
setting the environment variable `PY_COLORS=False`, or setting the `color` setting the environment variable `PY_COLORS=False`, or setting the `color`

View file

@ -0,0 +1,5 @@
import subprocess
def test_bash_completion_runs_without_error():
subprocess.check_call('eval "$(borgmatic --bash-completion)"', shell=True)

View file

@ -0,0 +1,5 @@
from borgmatic.commands import completion as module
def test_bash_completion_does_not_raise():
assert module.bash_completion()