-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Integrate click for cli and config-parsing
- Loading branch information
Showing
10 changed files
with
542 additions
and
357 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import pathlib | ||
from typing import Any | ||
|
||
import rich_click as click | ||
from pydantic import ValidationError | ||
|
||
from podcast_archiver import __version__ as version | ||
from podcast_archiver.base import PodcastArchiver | ||
from podcast_archiver.config import DEFAULT_SETTINGS, Settings | ||
from podcast_archiver.constants import ENVVAR_PREFIX, PROG_NAME | ||
|
||
click.rich_click.USE_RICH_MARKUP = True | ||
click.rich_click.USE_MARKDOWN = True | ||
click.rich_click.OPTIONS_PANEL_TITLE = "Miscellaneous Options" | ||
click.rich_click.OPTION_GROUPS = { | ||
PROG_NAME: [ | ||
{ | ||
"name": "Basic parameters", | ||
"options": [ | ||
"--feed", | ||
"--opml", | ||
"--dir", | ||
"--config", | ||
], | ||
}, | ||
{ | ||
"name": "Processing parameters", | ||
"options": [ | ||
"--subdirs", | ||
"--update", | ||
"--slugify", | ||
"--max-episodes", | ||
"--date-prefix", | ||
], | ||
}, | ||
] | ||
} | ||
|
||
|
||
@click.command( | ||
context_settings={ | ||
"auto_envvar_prefix": ENVVAR_PREFIX, | ||
}, | ||
help="Archive all of your favorite podcasts", | ||
) | ||
@click.help_option("-h", "--help") | ||
@click.option( | ||
"-f", | ||
"--feed", | ||
multiple=True, | ||
show_envvar=True, | ||
help="Feed URLs to archive. Use repeatedly for multiple feeds.", | ||
) | ||
@click.option( | ||
"-o", | ||
"--opml", | ||
multiple=True, | ||
show_envvar=True, | ||
help=( | ||
"OPML files (as exported by many other podcatchers) containing feed URLs to archive. " | ||
"Use repeatedly for multiple files." | ||
), | ||
) | ||
@click.option( | ||
"-d", | ||
"--dir", | ||
type=click.Path( | ||
exists=False, | ||
writable=True, | ||
file_okay=False, | ||
dir_okay=True, | ||
resolve_path=True, | ||
path_type=pathlib.Path, | ||
), | ||
show_default=True, | ||
required=False, | ||
default=DEFAULT_SETTINGS.archive_directory, | ||
show_envvar=True, | ||
help="Directory to which to download the podcast archive", | ||
) | ||
@click.option( | ||
"-s", | ||
"--subdirs", | ||
type=bool, | ||
default=DEFAULT_SETTINGS.create_subdirectories, | ||
is_flag=True, | ||
show_envvar=True, | ||
help="Place downloaded podcasts in separate subdirectories per podcast (named with their title).", | ||
) | ||
@click.option( | ||
"-u", | ||
"--update", | ||
type=bool, | ||
default=DEFAULT_SETTINGS.update_archive, | ||
is_flag=True, | ||
show_envvar=True, | ||
help=( | ||
"Update the feeds with newly added episodes only. " | ||
"Adding episodes ends with the first episode already present in the download directory." | ||
), | ||
) | ||
@click.option( | ||
"-p", | ||
"--progress", | ||
type=bool, | ||
default=DEFAULT_SETTINGS.show_progress_bars, | ||
is_flag=True, | ||
show_envvar=True, | ||
help="Show progress bars while downloading episodes.", | ||
) | ||
@click.option( | ||
"-v", | ||
"--verbose", | ||
count=True, | ||
default=DEFAULT_SETTINGS.verbose, | ||
help="Increase the level of verbosity while downloading.", | ||
) | ||
@click.option( | ||
"-S", | ||
"--slugify", | ||
type=bool, | ||
default=DEFAULT_SETTINGS.slugify_paths, | ||
is_flag=True, | ||
show_envvar=True, | ||
help="Format filenames in the most compatible way, replacing all special characters.", | ||
) | ||
@click.option( | ||
"-m", | ||
"--max-episodes", | ||
type=int, | ||
default=DEFAULT_SETTINGS.maximum_episode_count, | ||
help=( | ||
"Only download the given number of episodes per podcast feed. " | ||
"Useful if you don't really need the entire backlog." | ||
), | ||
) | ||
@click.option( | ||
"--date-prefix", | ||
type=bool, | ||
default=DEFAULT_SETTINGS.add_date_prefix, | ||
is_flag=True, | ||
show_envvar=True, | ||
help="Prefix episodes with their publishing date. Useful to ensure chronological ordering.", | ||
) | ||
@click.version_option( | ||
version, | ||
"-V", | ||
"--version", | ||
prog_name=PROG_NAME, | ||
) | ||
@click.option( | ||
"--config-generate", | ||
type=bool, | ||
expose_value=False, | ||
is_flag=True, | ||
callback=Settings.click_callback_generate, | ||
is_eager=True, | ||
help="Emit an example YAML config file to stdout and exit.", | ||
) | ||
@click.option( | ||
"-c", | ||
"--config", | ||
type=click.Path( | ||
readable=True, | ||
file_okay=True, | ||
dir_okay=False, | ||
resolve_path=True, | ||
path_type=pathlib.Path, | ||
), | ||
expose_value=False, | ||
default=pathlib.Path(click.get_app_dir(PROG_NAME)) / "config.yaml", | ||
show_default=True, | ||
callback=Settings.click_callback_load, | ||
is_eager=True, | ||
show_envvar=True, | ||
help="Path to a config file. Command line arguments will take precedence.", | ||
) | ||
@click.pass_context | ||
def main(ctx: click.RichContext, **kwargs: Any) -> int: | ||
try: | ||
config = Settings.model_validate(kwargs) | ||
|
||
# Replicate click's `no_args_is_help` behavior but only when config file does not contain feeds/OPMLs | ||
if not (config.feeds or config.opml_files): | ||
click.echo(ctx.command.get_help(ctx)) | ||
return 0 | ||
|
||
pa = PodcastArchiver(config) | ||
pa.run() | ||
except KeyboardInterrupt as exc: | ||
raise click.Abort("Interrupted by user") from exc | ||
except FileNotFoundError as exc: | ||
raise click.Abort(exc) from exc | ||
except ValidationError as exc: | ||
raise click.Abort(f"Invalid settings: {exc}") from exc | ||
return 0 | ||
|
||
|
||
if __name__ == "__main__": | ||
main.main(prog_name=PROG_NAME) |
Oops, something went wrong.