diff --git a/README.md b/README.md index dd65753..4b634ec 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ Included in this README are a few example Bash tricks to get certain behaviour. ## Installation -*Bulk Downloader for Reddit* needs Python version 3.9 or above. Please update Python before installation to meet the requirement. Then, you can install it via pip with: +*Bulk Downloader for Reddit* needs Python version 3.9 or above. Please update Python before installation to meet the requirement. + +Then, you can install it via pip with: ```bash python3 -m pip install bdfr --upgrade @@ -21,10 +23,12 @@ python3 -m pip install bdfr --upgrade or via [pipx](https://pypa.github.io/pipx) with: ```bash -python3 -m pipx install bdfr --upgrade +python3 -m pipx install bdfr ``` -**To update BDFR**, run the above command again after the installation. +**To update BDFR**, run the above command again for pip or `pipx upgrade bdfr` for pipx installations. + +**To install shell completions**, run `bdfr completions` ### AUR Package diff --git a/bdfr/__main__.py b/bdfr/__main__.py index c26f577..d0c6664 100644 --- a/bdfr/__main__.py +++ b/bdfr/__main__.py @@ -7,6 +7,7 @@ import click from bdfr.archiver import Archiver from bdfr.cloner import RedditCloner +from bdfr.completion import Completion from bdfr.configuration import Configuration from bdfr.downloader import RedditDownloader @@ -74,15 +75,19 @@ def _add_options(opts: list): @click.group() +@click.help_option("-h", "--help") def cli(): + """BDFR is used to download and archive content from Reddit.""" pass @cli.command("download") @_add_options(_common_options) @_add_options(_downloader_options) +@click.help_option("-h", "--help") @click.pass_context def cli_download(context: click.Context, **_): + """Used to download content posted to Reddit.""" config = Configuration() config.process_click_arguments(context) setup_logging(config.verbose) @@ -99,8 +104,10 @@ def cli_download(context: click.Context, **_): @cli.command("archive") @_add_options(_common_options) @_add_options(_archiver_options) +@click.help_option("-h", "--help") @click.pass_context def cli_archive(context: click.Context, **_): + """Used to archive post data from Reddit.""" config = Configuration() config.process_click_arguments(context) setup_logging(config.verbose) @@ -118,8 +125,10 @@ def cli_archive(context: click.Context, **_): @_add_options(_common_options) @_add_options(_archiver_options) @_add_options(_downloader_options) +@click.help_option("-h", "--help") @click.pass_context def cli_clone(context: click.Context, **_): + """Combines archive and download commands.""" config = Configuration() config.process_click_arguments(context) setup_logging(config.verbose) @@ -133,6 +142,30 @@ def cli_clone(context: click.Context, **_): logger.info("Program complete") +@cli.command("completion") +@click.argument("shell", type=click.Choice(("all", "bash", "fish", "zsh"), case_sensitive=False), default="all") +@click.help_option("-h", "--help") +@click.option("-u", "--uninstall", is_flag=True, default=False, help="Uninstall completion") +def cli_completion(shell: str, uninstall: bool): + """\b + Installs shell completions for BDFR. + Options: all, bash, fish, zsh + Default: all""" + shell = shell.lower() + if sys.platform == "win32": + print("Completions are not currently supported on Windows.") + return + if uninstall and click.confirm(f"Would you like to uninstall {shell} completions for BDFR"): + Completion(shell).uninstall() + return + if shell not in ("all", "bash", "fish", "zsh"): + print(f"{shell} is not a valid option.") + print("Options: all, bash, fish, zsh") + return + if click.confirm(f"Would you like to install {shell} completions for BDFR"): + Completion(shell).install() + + def setup_logging(verbosity: int): class StreamExceptionFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: diff --git a/bdfr/completion.py b/bdfr/completion.py new file mode 100644 index 0000000..1902319 --- /dev/null +++ b/bdfr/completion.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# coding=utf-8 + +import os +import subprocess + +import appdirs + + +class Completion: + def __init__(self, shell: str): + self.shell = shell + self.env = os.environ.copy() + self.share_dir = appdirs.user_data_dir() + self.entry_points = ["bdfr"] + + def install(self): + if self.shell in ("all", "bash"): + comp_dir = self.share_dir + "/bash-completion/completions/" + for point in self.entry_points: + self.env[f"_{point.upper().replace('-', '_')}_COMPLETE"] = "bash_source" + with open(comp_dir + point, "w") as file: + file.write(subprocess.run([point], env=self.env, capture_output=True, text=True).stdout) + print(f"Bash completion for {point} written to {comp_dir}{point}") + if self.shell in ("all", "fish"): + comp_dir = self.share_dir + "/fish/vendor_completions.d/" + for point in self.entry_points: + self.env[f"_{point.upper().replace('-', '_')}_COMPLETE"] = "fish_source" + with open(comp_dir + point, "w") as file: + file.write(subprocess.run([point], env=self.env, capture_output=True, text=True).stdout) + print(f"Fish completion for {point} written to {comp_dir}{point}") + if self.shell in ("all", "zsh"): + comp_dir = self.share_dir + "/zsh/site-functions/" + for point in self.entry_points: + self.env[f"_{point.upper().replace('-', '_')}_COMPLETE"] = "zsh_source" + with open(comp_dir + point, "w") as file: + file.write(subprocess.run([point], env=self.env, capture_output=True, text=True).stdout) + print(f"Zsh completion for {point} written to {comp_dir}{point}") + + def uninstall(self): + if self.shell in ("all", "bash"): + comp_dir = self.share_dir + "/bash-completion/completions/" + for point in self.entry_points: + if os.path.exists(comp_dir + point): + os.remove(comp_dir + point) + print(f"Bash completion for {point} removed from {comp_dir}{point}") + if self.shell in ("all", "fish"): + comp_dir = self.share_dir + "/fish/vendor_completions.d/" + for point in self.entry_points: + if os.path.exists(comp_dir + point): + os.remove(comp_dir + point) + print(f"Fish completion for {point} removed from {comp_dir}{point}") + if self.shell in ("all", "zsh"): + comp_dir = self.share_dir + "/zsh/site-functions/" + for point in self.entry_points: + if os.path.exists(comp_dir + point): + os.remove(comp_dir + point) + print(f"Zsh completion for {point} removed from {comp_dir}{point}")