Source code for django_typer.shells.bash
import typing as t
from functools import cached_property
from pathlib import Path
from click.shell_completion import CompletionItem
from . import DjangoTyperShellCompleter
[docs]
class BashComplete(DjangoTyperShellCompleter):
"""
This completer class supports the bash_ shell. Completion scripts are
installed in the ``~/.bash_completions`` directory and are sourced in
``~/.bashrc``.
Bash_ does not support help text in completions so these are not returned.
The format of the completion is ``type,value`` and different suggestions
are separated by newlines.
See also: https://github.com/scop/bash-completion#faq
"""
name = "bash"
"""
shell executable.
"""
template = "shell_complete/bash.sh"
"""
The template used to render the bash completion script.
"""
supports_scripts = True
"""
The bash completer supports script invocations.
"""
color = False
"""
Zsh_ does support ansi control codes in completion suggestions, but we disable them
by default.
"""
def get_user_profile(self) -> Path:
"""
Get .bashrc it is always located in the user's home directory.
"""
return Path.home() / ".bashrc"
@cached_property
def install_dir(self) -> Path:
install_dir = self.get_user_profile().parent / ".bash_completions"
install_dir.mkdir(parents=True, exist_ok=True)
return install_dir
@staticmethod
def _check_version() -> t.Optional[t.Tuple[int, int]]:
import re
import subprocess # nosec B404
output = subprocess.run( # nosec B603 B607
["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE
)
match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
return (int(match.groups()[0]), int(match.groups()[1])) if match else None
def source_vars(self) -> t.Dict[str, t.Any]:
"""
When the bash version is 4.4 or higher, the ``nosort`` option is
available and can be used to disable sorting of completions. We
add an additional context variable to the template to include this:
* ``complete_opts``: The options to pass to bash's ``complete`` command.
"""
complete_opts = ""
version = self._check_version()
if version and version >= (4, 4):
complete_opts = "-o nosort"
return {
**super().source_vars(),
"complete_opts": complete_opts,
"bash_version": version,
"use_compopt": version and version >= (4, 4),
}
def format_completion(self, item: CompletionItem) -> str:
return f"{item.type},{item.value}"
def install(self, prompt: bool = True) -> t.List[Path]:
assert self.prog_name
edited = []
script = self.install_dir / f"{self.prog_name}.sh"
bashrc = self.get_user_profile()
bashrc_source = bashrc.read_text() if bashrc.is_file() else ""
source_line = f"source {script}"
if source_line not in bashrc_source:
bashrc_source += f"\n{source_line}\n"
if self.prompt(
prompt=prompt,
source=source_line,
file=bashrc,
start_line=bashrc_source.count("\n") + 1,
):
Path.home().mkdir(parents=True, exist_ok=True)
with open(bashrc, "a") as f:
f.write(f"\n{source_line}\n")
edited.append(bashrc)
source = self.source()
if self.prompt(prompt=prompt, source=source, file=script):
script.parent.mkdir(parents=True, exist_ok=True)
script.write_text(self.source())
edited.append(script)
return edited
def uninstall(self):
assert self.prog_name
script = self.install_dir / f"{self.prog_name}.sh"
if script.is_file():
script.unlink()
bashrc = self.get_user_profile()
if bashrc.is_file():
bashrc_source = bashrc.read_text()
bashrc.write_text(bashrc_source.replace(f"source {script}\n", ""))