diff options
| author | Dennis Fink | 2025-10-04 21:49:58 +0200 |
|---|---|---|
| committer | Dennis Fink | 2025-10-04 21:49:58 +0200 |
| commit | 5917aac231c76bcabdad9d084ec0efc02bcb2bb9 (patch) | |
| tree | 24a83da8ee1cd508d7228afaf64770d480300eea | |
| parent | 6dfeef92268623efa541e2838b74369859342eaf (diff) | |
| download | patternutils-5917aac231c76bcabdad9d084ec0efc02bcb2bb9.tar.gz patternutils-5917aac231c76bcabdad9d084ec0efc02bcb2bb9.zip | |
Continue dev here
| -rw-r--r-- | patternutils/commands/__init__.py | 3 | ||||
| -rw-r--r-- | patternutils/commands/patterncfg.py | 149 | ||||
| -rw-r--r-- | patternutils/commands/pjson.py | 57 | ||||
| -rw-r--r-- | patternutils/commands/pmatch.py | 222 | ||||
| -rw-r--r-- | patternutils/commands/pmediainfo.py | 38 | ||||
| -rw-r--r-- | patternutils/commands/prss.py | 47 | ||||
| -rw-r--r-- | patternutils/commands/targs.py | 204 | ||||
| -rw-r--r-- | patternutils/commands/tln.py | 44 | ||||
| -rw-r--r-- | patternutils/commands/tmv.py | 209 | ||||
| -rw-r--r-- | patternutils/config.py | 193 | ||||
| -rw-r--r-- | patternutils/edit.py | 69 | ||||
| -rw-r--r-- | patternutils/types.py | 39 | ||||
| -rw-r--r-- | patternutils/utils.py | 2 | ||||
| -rw-r--r-- | pyproject.toml | 17 | ||||
| -rw-r--r-- | uv.lock | 267 |
15 files changed, 904 insertions, 656 deletions
diff --git a/patternutils/commands/__init__.py b/patternutils/commands/__init__.py index e69de29..4d29bb5 100644 --- a/patternutils/commands/__init__.py +++ b/patternutils/commands/__init__.py @@ -0,0 +1,3 @@ +from cloup import HelpFormatter, HelpTheme + +FORMATTER_SETTINGS = HelpFormatter.settings(theme=HelpTheme.light()) diff --git a/patternutils/commands/patterncfg.py b/patternutils/commands/patterncfg.py index 07c815a..1f18179 100644 --- a/patternutils/commands/patterncfg.py +++ b/patternutils/commands/patterncfg.py @@ -1,10 +1,8 @@ import itertools import os -import os.path -from typing import Literal, get_args import click -from click_aliases import ClickAliasedGroup +import cloup try: from pygments import highlight # type: ignore @@ -15,27 +13,35 @@ except ImportError: else: has_pygments = True - from .. import config from ..types import ( - PredefinedCommands, + RegexCommands, + RegexExtensions, + TemplateCommands, TemplateExtensions, ) +from . import FORMATTER_SETTINGS -CURRENT_SUPPORTED_PROGRAMS: tuple[PredefinedCommands] = get_args(PredefinedCommands) +SUPPORTED_COMMANDS = tuple( + x.value for x in itertools.chain(RegexCommands, TemplateCommands) +) -@click.group( +@cloup.group( context_settings={"help_option_names": ("-h", "--help", "-?")}, - cls=ClickAliasedGroup, + formatter_settings=FORMATTER_SETTINGS, + show_subcommand_aliases=True, ) def patterncfg() -> None: """CLI tool for managing patternutils templates.""" pass -@patterncfg.command(name="init", help="Initialize config folders") -@click.option( +@patterncfg.command( + name="init", + help="Initialize config folders", +) +@cloup.option( "-v", "--verbose", is_flag=True, @@ -44,25 +50,38 @@ def patterncfg() -> None: ) def init(verbose: bool) -> None: """Creates necessary config directories if they do not exist.""" - for directory in itertools.chain(CURRENT_SUPPORTED_PROGRAMS, ("globals",)): + for directory in itertools.chain(SUPPORTED_COMMANDS, ("globals",)): path = config.CONFIG_PATH / directory try: path.mkdir(parents=True) except FileExistsError: if verbose: - click.secho(f"{path} already exists!", fg="red", err=True) + click.secho( + f"{path} already exists!", + fg="red", + err=True, + ) else: if verbose: click.echo(f"Created {path}") - click.secho("Initialized patternutils!", fg="green") + click.secho( + "Initialized patternutils!", + fg="green", + ) -@patterncfg.command(name="list", help="List all patternutils template files") +@patterncfg.command( + name="list", + help="List all patternutils template files", +) def list() -> None: """Lists all available template files in the config directory.""" try: for directory in config.CONFIG_PATH.iterdir(): - click.secho(directory.name, fg="blue") + click.secho( + directory.name, + fg="blue", + ) for filename in directory.iterdir(): click.echo(f"└── {filename.name}") except FileNotFoundError: @@ -74,41 +93,52 @@ def list() -> None: raise SystemExit(1) -@patterncfg.command(name="add", help="Add a new template file") -@click.option( +@patterncfg.command( + name="add", + help="Add a new template file", +) +@cloup.option( "-t", "--template-engine", - default=None, + default="python", type=click.Choice(("python", "jinja2")), help="Select the template engine (only applicable for tmv, targs, tln).", ) -@click.argument("command", required=True, type=click.Choice(CURRENT_SUPPORTED_PROGRAMS)) -@click.argument("name") +@cloup.argument( + "command", + required=True, + type=click.Choice(SUPPORTED_COMMANDS), +) +@cloup.argument("name") def add( - template_engine: TemplateExtensions | None, + template_engine: TemplateExtensions, command: str, name: str, ) -> None: """Creates a new template file for a given command.""" - fileext: TemplateExtensions | Literal["regex"] | None = template_engine - if command == "pmatch": - fileext = "regex" + fileext = template_engine + if command in RegexCommands: + fileext = RegexExtensions.REGEX.value filename = config.CONFIG_PATH / command / f"{name}.{fileext}" click.edit(filename=str(filename)) -@patterncfg.command(name="remove", help="Remove a template file") -@click.option( +@patterncfg.command( + name="remove", + help="Remove a template file", + aliases=["rm"], +) +@cloup.option( "-c", "--command", + type=click.Choice(SUPPORTED_COMMANDS), default=None, - type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), help="Specify the command from which to remove the file.", ) -@click.argument("name") -def remove(command: PredefinedCommands | None, name: str) -> None: +@cloup.argument("name") +def remove(command: None, name: str) -> None: """Removes a specified template file.""" files = config.find_templates(name, command) @@ -126,35 +156,45 @@ def remove(command: PredefinedCommands | None, name: str) -> None: err=True, ) for f in files: - click.secho(f, fg="red", err=True) + click.secho( + f, + fg="red", + err=True, + ) raise SystemExit(1) os.remove(files[0]) -@patterncfg.command(name="view", help="View a template file", aliases=["show"]) -@click.option( +@patterncfg.command( + name="view", + help="View a template file", + aliases=["show", "cat"], +) +@cloup.option( "-c", "--command", + type=click.Choice(SUPPORTED_COMMANDS), default=None, - type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), help="Specify the command from which to view the file.", ) -@click.option( +@cloup.option( "--color/--no-color", is_flag=True, default=has_pygments, help="Colorize output using pygments if available.", ) -@click.argument("name") -def view(name: str, command: PredefinedCommands, color: bool) -> None: +@cloup.argument("name") +def view(name: str, command: RegexCommands | TemplateCommands, color: bool) -> None: """Displays the contents of a template file.""" files = config.find_templates(name, command) if not files: click.secho( - f"No matching template files found for '{name}'.", fg="red", err=True + f"No matching template files found for '{name}'.", + fg="red", + err=True, ) raise SystemExit(1) @@ -165,7 +205,11 @@ def view(name: str, command: PredefinedCommands, color: bool) -> None: err=True, ) for f in files: - click.secho(f, fg="red", err=True) + click.secho( + f, + fg="red", + err=True, + ) raise SystemExit(1) filename = files[0] @@ -174,7 +218,11 @@ def view(name: str, command: PredefinedCommands, color: bool) -> None: with open(filename, encoding="utf-8") as fd: text = fd.read() except FileNotFoundError: - click.secho(f"File '{filename}' not found!", fg="red", err=True) + click.secho( + f"File '{filename}' not found!", + fg="red", + err=True, + ) raise SystemExit(1) if color: @@ -192,22 +240,27 @@ def view(name: str, command: PredefinedCommands, color: bool) -> None: click.echo(text[:-1]) -@patterncfg.command(name="edit", help="Edit a template file") -@click.option( +@patterncfg.command( + name="edit", + help="Edit a template file", +) +@cloup.option( "-c", "--command", + type=click.Choice(SUPPORTED_COMMANDS), default=None, - type=click.Choice(CURRENT_SUPPORTED_PROGRAMS), help="Specify the command from which to edit the file.", ) -@click.argument("name") -def edit(name: str, command: PredefinedCommands) -> None: +@cloup.argument("name") +def edit(name: str, command: RegexCommands | TemplateCommands) -> None: """Opens a template file in an editor.""" files = config.find_templates(name, command) if not files: click.secho( - f"No matching template files found for '{name}'.", fg="red", err=True + f"No matching template files found for '{name}'.", + fg="red", + err=True, ) raise SystemExit(1) @@ -218,7 +271,11 @@ def edit(name: str, command: PredefinedCommands) -> None: err=True, ) for file in files: - click.secho(file, fg="red", err=True) + click.secho( + file, + fg="red", + err=True, + ) raise SystemExit(1) click.edit(filename=files[0]) diff --git a/patternutils/commands/pjson.py b/patternutils/commands/pjson.py index de300cb..8abf952 100644 --- a/patternutils/commands/pjson.py +++ b/patternutils/commands/pjson.py @@ -3,9 +3,11 @@ from collections.abc import Iterable from typing import Any, Pattern, TextIO import click +import cloup from .. import match as mat from .. import utils +from . import FORMATTER_SETTINGS def apply_regex( @@ -14,56 +16,67 @@ def apply_regex( try: subject = data[key] except KeyError: - click.secho(f"{key} can not be found in data!", fg="red", err=True) + click.secho( + f"{key} can not be found in data!", + fg="red", + err=True, + ) raise try: match = mat.apply(subject, regex_pattern) except ValueError: - click.secho(f"{subject} did not match", fg="red", err=True) + click.secho( + f"{subject} did not match", + fg="red", + err=True, + ) raise else: return match -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option("--read-from", default="-", type=click.File("r")) -@click.option( +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, +) +@cloup.option("--read-from", default="-", type=click.File("r")) +@cloup.option( "--stream", is_flag=True, default=False, help="Input is a stream of JSON objects and output will also be a stream of JSON objects", ) -@click.option( +@cloup.option( "--abort-on-no-key", is_flag=True, default=False, help="exit if a key does not exist", ) -@click.option( +@cloup.option( "--abort-on-no-match", is_flag=True, default=False, help="exit if a value does not match the REGEX_PATTERN", ) -@click.option( +@cloup.option( "--abort-on-empty-data", is_flag=True, default=False, help="exit if the data is empty", ) -@click.option( +@cloup.option( "--human-readable", is_flag=True, default=False, help="output the JSON in a human readable format", ) -@click.option( +@cloup.option( "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" ) -@click.version_option() -@click.argument("key") -@click.argument("regex-pattern") +@cloup.version_option() +@cloup.argument("key") +@cloup.argument("regex-pattern") def pjson( key: str, regex_pattern: str, @@ -79,8 +92,16 @@ def pjson( regex_pattern_c = re.compile(regex_pattern) except re.error as e: if verbose: - click.secho("Regular expression could not be compiled!", fg="red", err=True) - click.secho(e.msg, fg="red", err=True) + click.secho( + "Regular expression could not be compiled!", + fg="red", + err=True, + ) + click.secho( + e.msg, + fg="red", + err=True, + ) raise SystemExit data: Iterable[dict[str, Any]] @@ -97,7 +118,11 @@ def pjson( for line in data: if not line: has_empty_data = True - click.secho(f"{line} has no input.", fg="red", err=True) + click.secho( + f"{line} has no input.", + fg="red", + err=True, + ) if stream and abort_on_empty_data: raise SystemExit continue diff --git a/patternutils/commands/pmatch.py b/patternutils/commands/pmatch.py index 68b166a..a6eed77 100644 --- a/patternutils/commands/pmatch.py +++ b/patternutils/commands/pmatch.py @@ -6,12 +6,12 @@ import re from typing import Generator, Iterator, Pattern import click -from click_option_group import MutuallyExclusiveOptionGroup, optgroup - -from patternutils.types import RegexFlags, TemplatePrefer +import cloup from .. import config, utils from .. import match as mat +from ..types import RegexCommands, RegexFlags, TemplatePrefer +from . import FORMATTER_SETTINGS def apply_regex( @@ -35,7 +35,11 @@ def apply_regex( try: match = mat.apply(subject, regex_pattern) except ValueError: - click.secho(f"'{subject}' did not match!", fg="red", err=True) + click.secho( + f"'{subject}' did not match!", + fg="red", + err=True, + ) yield None continue @@ -51,105 +55,113 @@ def apply_regex( yield match -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@optgroup.group("General options") -@optgroup.option( - "-v", - "--verbose", - is_flag=True, - default=False, - help="Explain what is being done.", -) -@optgroup.group("Output Options") -@optgroup.option( - "--stream", - is_flag=True, - default=False, - help="Output a stream of objects instead a list.", -) -@optgroup.option( - "--human-readable", - is_flag=True, - default=False, - help="Output the JSON in a human readable format.", +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, ) -@optgroup.group("Match options") -@optgroup.option( - "-d", - "--directory", - multiple=True, - default=["./"], - help="directories to search. Can be specified multiple times. Default: ./", -) -@optgroup.option( - "-m", - "--match-directories", - is_flag=True, - default=False, - help="Match on directories as well.", -) -@optgroup.option( - "-r", - "--recursive", - is_flag=True, - default=False, - help="Search directories recursively.", -) -@optgroup.option( - "--abort-on-no-match", - is_flag=True, - default=False, - help="Exit if a name does not match the INPUT_PATTERN.", -) -@optgroup.option( - "--match-full-path", - is_flag=True, - default=False, - help="Match on full absolute path instead of only the filename.", +@cloup.option_group( + "General options", + cloup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", + ), ) -@optgroup.group("Regex Options") -@optgroup.option( - "-p", - "--use-predefined-pattern", - is_flag=True, - default=False, - help="Use predefined regex pattern.", +@cloup.option_group( + "Output Options", + cloup.option( + "--stream", + is_flag=True, + default=False, + help="Output a stream of objects instead a list.", + ), + cloup.option( + "--human-readable", + is_flag=True, + default=False, + help="Output the JSON in a human readable format.", + ), ) -@optgroup.option( - "--flag", - type=click.Choice( - [ - "ascii", - "a", - "ignorecase", - "i", - "multiline", - "m", - "dotall", - "s", - "verbose", - "x", - ], - case_sensitive=False, +@cloup.option_group( + "Match options", + cloup.option( + "-d", + "--directory", + multiple=True, + default=["./"], + help="directories to search. Can be specified multiple times. Default: ./", + ), + cloup.option( + "-m", + "--match-directories", + is_flag=True, + default=False, + help="Match on directories as well.", + ), + cloup.option( + "-r", + "--recursive", + is_flag=True, + default=False, + help="Search directories recursively.", + ), + cloup.option( + "--abort-on-no-match", + is_flag=True, + default=False, + help="Exit if a name does not match the INPUT_PATTERN.", + ), + cloup.option( + "--match-full-path", + is_flag=True, + default=False, + help="Match on full absolute path instead of only the filename.", ), - multiple=True, - help="Specify the flags for the regex", ) -@optgroup.group("Pattern Loading Options", cls=MutuallyExclusiveOptionGroup) -@optgroup.option( - "--prefer-global", - "prefer", - flag_value="global", - help="Prefer loading predefined pattern from the globals directory", +@cloup.option_group( + "Regex Options", + cloup.option( + "-p", + "--use-predefined-pattern", + is_flag=True, + default=False, + help="Use predefined regex pattern.", + ), + cloup.option( + "--flag", + "flags", + type=click.Choice( + [ + "ascii", + "a", + "ignorecase", + "i", + "multiline", + "m", + "dotall", + "s", + "verbose", + "x", + ], + case_sensitive=False, + ), + multiple=True, + help="Specify the flags for the regex", + ), + cloup.option( + "--prefer", + type=click.Choice(TemplatePrefer, case_sensitive=False), + help="Select which directory to prefer, to load the template from, in case of duplicates.", + ), ) -@optgroup.option( - "--prefer-command", - "prefer", - flag_value="command", - help="Prefer loading predefined pattern from the pmatch directory", +@cloup.constraint( + cloup.constraints.If("prefer", then=cloup.constraints.require_all), + ["use_predefined_pattern", "prefer"], ) -@click.version_option() -@click.argument("regex-pattern") +@cloup.version_option() +@cloup.argument("regex-pattern") def pmatch( regex_pattern: str, directory: list[str], @@ -170,12 +182,14 @@ def pmatch( try: regex_pattern, _ = config.load_template( regex_pattern, - "pmatch", + RegexCommands.PMATCH, prefer, ) except FileNotFoundError: click.secho( - f"Regex pattern '{regex_pattern}' not found!", fg="red", err=True + f"Regex pattern '{regex_pattern}' not found!", + fg="red", + err=True, ) raise SystemExit(1) except RuntimeError: @@ -192,8 +206,16 @@ def pmatch( regex_pattern_c = re.compile(regex_pattern, flags=concrete_flags) except re.error as e: if verbose: - click.secho("Regular expression could not be compiled!", fg="red", err=True) - click.secho(e.msg, fg="red", err=True) + click.secho( + "Regular expression could not be compiled!", + fg="red", + err=True, + ) + click.secho( + e.msg, + fg="red", + err=True, + ) raise SystemExit(1) # Monkey patch walk function to include commandline options diff --git a/patternutils/commands/pmediainfo.py b/patternutils/commands/pmediainfo.py index b782ab5..52be979 100644 --- a/patternutils/commands/pmediainfo.py +++ b/patternutils/commands/pmediainfo.py @@ -4,9 +4,20 @@ import os.path from typing import Any, Generator, Iterator import click -from pymediainfo import MediaInfo +import cloup + +try: + from pymediainfo import MediaInfo +except ImportError: + click.secho( + "pymediainfo not installed!", + fg="red", + err=True, + ) + raise SystemExit from .. import utils +from . import FORMATTER_SETTINGS def apply_mediainfo( @@ -15,7 +26,11 @@ def apply_mediainfo( for f in walk_function: media_info_raw = MediaInfo.parse(os.path.abspath(f)) if len(media_info_raw.tracks) <= 1: - click.secho(f"{f.name} cannot be parsed by mediainfo", fg="red", err=True) + click.secho( + f"{f.name} cannot be parsed by mediainfo", + fg="red", + err=True, + ) yield None else: media_info = media_info_raw.to_data() @@ -27,41 +42,44 @@ def apply_mediainfo( yield media_info -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option("-d", "--directory", multiple=True, default=["./"]) -@click.option( +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, +) +@cloup.option("-d", "--directory", multiple=True, default=["./"]) +@cloup.option( "-r", "--recursive", is_flag=True, default=False, help="search directories recursively", ) -@click.option( +@cloup.option( "--abort-on-unparseable", is_flag=True, default=False, help="exit if a file cannot be parsed by mediainfo", ) -@click.option( +@cloup.option( "--stream", is_flag=True, default=False, help="Output a stream of objects instead a list of objects", ) -@click.option( +@cloup.option( "--human-readable", is_flag=True, default=False, help="output the JSON in a human readable format", ) -@click.option( +@cloup.option( "-v", "--verbose", is_flag=True, default=False, help="explain what is being done", ) -@click.version_option() +@cloup.version_option() def pmediainfo( directory: list[str], recursive: bool, diff --git a/patternutils/commands/prss.py b/patternutils/commands/prss.py index 9385a7f..ed43c37 100644 --- a/patternutils/commands/prss.py +++ b/patternutils/commands/prss.py @@ -5,14 +5,20 @@ import os.path from typing import Any, Generator import click +import cloup try: import feedparser # type: ignore except ImportError: - click.secho("feedparser not installed!", fg="red", err=True) + click.secho( + "feedparser not installed!", + fg="red", + err=True, + ) raise SystemExit from .. import utils +from . import FORMATTER_SETTINGS def match( @@ -34,7 +40,11 @@ def match( try: file_index = filenames.index(file_to_search) except ValueError: - click.secho(f"{file_to_search} not found!", fg="red", err=True) + click.secho( + f"{file_to_search} not found!", + fg="red", + err=True, + ) yield None else: filename = filenames[file_index] @@ -61,42 +71,45 @@ def match( yield entry_values -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option("-d", "--directory", multiple=True, default=["./"]) -@click.option( +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, +) +@cloup.option("-d", "--directory", multiple=True, default=["./"]) +@cloup.option( "-r", "--recursive", is_flag=True, default=False, help="search directories recursively", ) -@click.option( +@cloup.option( "--abort-on-no-file", is_flag=True, default=False, help="exit if a file can not be found", ) -@click.option( +@cloup.option( "--stream", is_flag=True, default=False, help="Output a stream of objects instead a list of objects", ) -@click.option( +@cloup.option( "--human-readable", is_flag=True, default=False, help="output the JSON in a human readable format", ) -@click.option( +@cloup.option( "-v", "--verbose", is_flag=True, default=False, help="explain what is being done", ) -@click.version_option() -@click.argument("url") +@cloup.version_option() +@cloup.argument("url") def prss( url: str, directory: list[str], @@ -109,11 +122,19 @@ def prss( try: parsed_feed = feedparser.parse(url) except Exception: - click.secho("Reading feed failed!", fg="red", err=True) + click.secho( + "Reading feed failed!", + fg="red", + err=True, + ) raise SystemExit else: if not parsed_feed.version: - click.secho("Unsupported feed!", fg="red", err=True) + click.secho( + "Unsupported feed!", + fg="red", + err=True, + ) raise SystemExit # Mokey patch walk function to include commandline options diff --git a/patternutils/commands/targs.py b/patternutils/commands/targs.py index e7c85cd..b09e6ca 100644 --- a/patternutils/commands/targs.py +++ b/patternutils/commands/targs.py @@ -6,10 +6,11 @@ from collections.abc import Iterable from typing import Any, Callable, TextIO import click -from click_option_group import optgroup +import cloup -from .. import config, template, utils -from ..types import TemplateExtensions +from .. import config, utils +from ..types import TemplateCommands, TemplateExtensions +from . import FORMATTER_SETTINGS def run_command( @@ -27,90 +28,101 @@ def run_command( return run(shlex.split(command) if not shell else command) -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@optgroup.group("General options") -@optgroup.option( - "-i", - "--interactive", - is_flag=True, - default=False, - help="Prompt before running command.", +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, ) -@optgroup.option( - "-e", - "--editor", - is_flag=True, - default=False, - help="Open an editor before running commands to allow manual changes.", +@cloup.option_group( + "General options", + cloup.option( + "-i", + "--interactive", + is_flag=True, + default=False, + help="Prompt before running command.", + ), + cloup.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before running commands to allow manual changes.", + ), + cloup.option( + "-n", + "--dry-run", + is_flag=True, + default=False, + help="Perform a trial run without executing commands.", + ), + cloup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", + ), ) -@optgroup.option( - "-n", - "--dry-run", - is_flag=True, - default=False, - help="Perform a trial run without executing commands.", +@cloup.option_group( + "Input options", + cloup.option( + "--read-from", + default="-", + type=click.File("r"), + help="Input file (default: stdin)", + ), + cloup.option( + "-s", + "--stream", + is_flag=True, + default=False, + help="Read input as stream of JSON objects.", + ), ) -@optgroup.option( - "-v", - "--verbose", - is_flag=True, - default=False, - help="Explain what is being done.", +@cloup.option_group( + "Template options", + cloup.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(TemplateExtensions, case_sensitive=False), + help="Select the template engine to use.", + ), + cloup.option( + "-p", + "--use-predefined-template", + is_flag=True, + default=False, + help="Use a predefined template.", + ), ) -@optgroup.group("Input options") -@optgroup.option( - "--read-from", - default="-", - type=click.File("r"), - help="Input file (default: stdin)", -) -@optgroup.option( - "-s", - "--stream", - is_flag=True, - default=False, - help="Read input as stream of JSON objects.", -) -@optgroup.group("Template options") -@optgroup.option( - "-t", - "--template-engine", - default="python", - type=click.Choice(["python", "jinja2"]), - help="Select the template engine to use.", -) -@optgroup.option( - "-p", - "--use-predefined-template", - is_flag=True, - default=False, - help="Use a predefined template.", -) -@optgroup.group("Command options") -@optgroup.option( - "--shell", - is_flag=True, - default=False, - help="Run command in a shell.", -) -@optgroup.option( - "--redirect-stdout", - is_flag=True, - default=False, - help="Redirect stdout to /dev/null.", -) -@optgroup.option( - "--redirect-stderr", - is_flag=True, - default=False, - help="Redirect stderr to /dev/null.", -) -@optgroup.option( - "-P", - "--max-procs", - default=1, - type=click.IntRange(min=0, clamp=True), - help="Max parallel processes.", +@cloup.option_group( + "Command options", + cloup.option( + "--shell", + is_flag=True, + default=False, + help="Run command in a shell.", + ), + cloup.option( + "--redirect-stdout", + is_flag=True, + default=False, + help="Redirect stdout to /dev/null.", + ), + cloup.option( + "--redirect-stderr", + is_flag=True, + default=False, + help="Redirect stderr to /dev/null.", + ), + cloup.option( + "-P", + "--max-procs", + default=1, + type=click.IntRange(min=0, clamp=True), + help="Max parallel processes.", + ), ) @click.version_option() @click.argument("command") @@ -146,9 +158,16 @@ def targs( if use_predefined_template: try: - command, template_engine = config.load_template(command, "targs") + command, template_engine = config.load_template( + command, + TemplateCommands.TARGS, + ) except FileNotFoundError: - click.secho(f"Command template '{command}' not found!", fg="red", err=True) + click.secho( + f"Command template '{command}' not found!", + fg="red", + err=True, + ) raise SystemExit(1) except RuntimeError: click.secho( @@ -157,11 +176,12 @@ def targs( err=True, ) raise SystemExit(1) - - render = template.get_render_function( + else: + template_engine = TemplateExtensions(template_engine) + render = config.get_render_function( command, - "targs", - engine=template_engine, + template_engine, + TemplateCommands.TARGS, ) commands = [render(r) for r in data] @@ -170,7 +190,11 @@ def targs( if (new_commands := utils.edit(commands)) is not None: commands = new_commands except RuntimeError: - click.secho("Your edit is not parseable!", fg="red", err=True) + click.secho( + "Your edit is not parseable!", + fg="red", + err=True, + ) raise SystemExit(1) stdout = subprocess.DEVNULL if redirect_stdout else None diff --git a/patternutils/commands/tln.py b/patternutils/commands/tln.py index cd2d4fe..2b6d679 100644 --- a/patternutils/commands/tln.py +++ b/patternutils/commands/tln.py @@ -4,9 +4,11 @@ from collections.abc import Iterable from typing import Any, TextIO import click +import cloup from .. import config, template, utils -from ..types import TemplateExtensions +from ..types import TemplateCommands, TemplateExtensions +from . import FORMATTER_SETTINGS def link_file(source: str, destination: str, symbolic: bool) -> None: @@ -19,94 +21,97 @@ def link_file(source: str, destination: str, symbolic: bool) -> None: os.link(source, destination) -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@click.option( +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, +) +@cloup.option( "-i", "--interactive", is_flag=True, default=False, help="prompt wether to remove destinations", ) -@click.option( +@cloup.option( "-e", "--editor", is_flag=True, default=False, help="Open an editor before renaming files to allow manual changes", ) -@click.option( +@cloup.option( "-f", "--force", is_flag=True, default=False, help="remove existing destination files", ) -@click.option( +@cloup.option( "-s", "--symbolic", is_flag=True, default=False, help="make symbolic links instead of hard links", ) -@click.option( +@cloup.option( "-t", "--template-engine", default="python", type=click.Choice(["python", "jinja2"]), help="select the template engine to use", ) -@click.option( +@cloup.option( "-k", "--key", default="_abspath", help="select the key to use as the source file", ) -@click.option( +@cloup.option( "--stream", is_flag=True, default=False, help="Input is a stream of JSON objects", ) -@click.option( +@cloup.option( "-p", "--use-predefined-template", is_flag=True, default=False, help="use predefined template", ) -@click.option( +@cloup.option( "--read-from", default="-", type=click.File("r"), ) -@click.option( +@cloup.option( "--abort-on-path-exist", is_flag=True, default=False, help="exit if a renamed path already exists", ) -@click.option( +@cloup.option( "--abort-on-key-not-found", is_flag=True, default=False, help="Exit if the specified key (specified by the --key option) is not found in the input JSON.", ) -@click.option( +@cloup.option( "-n", "--dry-run", is_flag=True, default=False, help="perform a trial run with no changes made", ) -@click.option( +@cloup.option( "-v", "--verbose", is_flag=True, default=False, help="explain what is being done", ) -@click.version_option() -@click.argument("output-pattern") +@cloup.version_option() +@cloup.argument("output-pattern") def tln( output_pattern: str, interactive: bool, @@ -132,7 +137,8 @@ def tln( if use_predefined_template: try: output_pattern, template_engine = config.load_template( - output_pattern, "tln" + output_pattern, + TemplateCommands.TLN, ) except FileNotFoundError: click.secho(f"Template {output_pattern} not found!", fg="red", err=True) @@ -143,7 +149,7 @@ def tln( render = template.get_render_function( output_pattern, - "tln", + TemplateCommands.TLN, engine=template_engine, ) new_filenames = dict() diff --git a/patternutils/commands/tmv.py b/patternutils/commands/tmv.py index ecc680b..989c94e 100644 --- a/patternutils/commands/tmv.py +++ b/patternutils/commands/tmv.py @@ -4,10 +4,11 @@ from collections.abc import Iterable from typing import Any, TextIO import click -from click_option_group import optgroup +import cloup from .. import config, template, utils -from ..types import TemplateExtensions +from ..types import TemplateCommands, TemplateExtensions +from . import FORMATTER_SETTINGS def move_file(old_filename: str, new_filename: str) -> None: @@ -19,93 +20,104 @@ def move_file(old_filename: str, new_filename: str) -> None: shutil.move(old_filename, new_filename) -@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")}) -@optgroup.group("General options") -@optgroup.option( - "-i", - "--interactive", - is_flag=True, - default=False, - help="Prompt before renaming files.", +@cloup.command( + context_settings={"help_option_names": ("-h", "--help", "-?")}, + formatter_settings=FORMATTER_SETTINGS, ) -@optgroup.option( - "-e", - "--editor", - is_flag=True, - default=False, - help="Open an editor before renaming files to allow manual changes.", +@cloup.option_group( + "General options", + cloup.option( + "-i", + "--interactive", + is_flag=True, + default=False, + help="Prompt before renaming files.", + ), + cloup.option( + "-e", + "--editor", + is_flag=True, + default=False, + help="Open an editor before renaming files to allow manual changes.", + ), + cloup.option( + "-n", + "--dry-run", + is_flag=True, + default=False, + help="Perform a trial run without making changes.", + ), + cloup.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", + ), ) -@optgroup.option( - "-n", - "--dry-run", - is_flag=True, - default=False, - help="Perform a trial run without making changes.", +@cloup.option_group( + "Input options", + cloup.option( + "--read-from", + default="-", + type=click.File("r"), + help="Input file (default: stdin)", + ), + cloup.option( + "-s", + "--stream", + is_flag=True, + default=False, + help="Read input as stream of JSON objects.", + ), ) -@optgroup.option( - "-v", - "--verbose", - is_flag=True, - default=False, - help="Explain what is being done.", +@cloup.option_group( + "Template options", + cloup.option( + "-t", + "--template-engine", + default="python", + type=click.Choice(["python", "jinja2"]), + help="Select the template engine to use.", + ), + cloup.option( + "-p", + "--use-predefined-template", + is_flag=True, + default=False, + help="Use a predefined template.", + ), ) -@optgroup.group("Input options") -@optgroup.option( - "--read-from", - default="-", - type=click.File("r"), - help="Input file (default: stdin)", +@cloup.option_group( + "Renaming options", + cloup.option( + "-k", + "--key", + default="_abspath", + help="Select the key to use as the source file.", + ), + cloup.option( + "-f", + "--force", + is_flag=True, + default=False, + help="Force rename, even if a file exists.", + ), + cloup.option( + "--abort-on-path-exist", + is_flag=True, + default=False, + help="Exit if a renamed path already exists.", + ), + cloup.option( + "--abort-on-key-not-found", + is_flag=True, + default=False, + help="Exit if the specified key (specified by the --key option) is not found in the input JSON.", + ), ) -@optgroup.option( - "-s", - "--stream", - is_flag=True, - default=False, - help="Read input as stream of JSON objects.", -) -@optgroup.group("Template options") -@optgroup.option( - "-t", - "--template-engine", - default="python", - type=click.Choice(["python", "jinja2"]), - help="Select the template engine to use.", -) -@optgroup.option( - "-p", - "--use-predefined-template", - is_flag=True, - default=False, - help="Use a predefined template.", -) -@optgroup.group("Renaming options") -@optgroup.option( - "-k", - "--key", - default="_abspath", - help="Select the key to use as the source file.", -) -@optgroup.option( - "-f", - "--force", - is_flag=True, - default=False, - help="Force rename, even if a file exists.", -) -@optgroup.option( - "--abort-on-path-exist", - is_flag=True, - default=False, - help="Exit if a renamed path already exists.", -) -@optgroup.option( - "--abort-on-key-not-found", - is_flag=True, - default=False, - help="Exit if the specified key (specified by the --key option) is not found in the input JSON.", -) -@click.version_option() -@click.argument("output-pattern") +@cloup.version_option() +@cloup.argument("output-pattern") def tmv( output_pattern: str, interactive: bool, @@ -139,10 +151,15 @@ def tmv( if use_predefined_template: try: output_pattern, template_engine = config.load_template( - output_pattern, "tmv" + output_pattern, + TemplateCommands.TMV, ) except FileNotFoundError: - click.secho(f"Template '{output_pattern}' not found!", fg="red", err=True) + click.secho( + f"Template '{output_pattern}' not found!", + fg="red", + err=True, + ) raise SystemExit(1) except RuntimeError: click.secho( @@ -154,7 +171,7 @@ def tmv( render = template.get_render_function( output_pattern, - "tmv", + TemplateCommands.TMV, engine=template_engine, ) new_filenames = dict() @@ -162,7 +179,11 @@ def tmv( for entry in data: new_filenames[entry[key]] = render(**entry) except KeyError: - click.secho(f"Key '{key}' not found in input data.", fg="red", err=True) + click.secho( + f"Key '{key}' not found in input data.", + fg="red", + err=True, + ) if abort_on_key_not_found: raise SystemExit(1) @@ -171,14 +192,22 @@ def tmv( if (temporary_filenames := utils.edit(new_filenames)) is not None: new_filenames = temporary_filenames except RuntimeError: - click.secho("Your edit is not parseable!", fg="red", err=True) + click.secho( + "Your edit is not parseable!", + fg="red", + err=True, + ) raise SystemExit(1) has_existing = False non_existing = {} for old, new in new_filenames.items(): if os.path.exists(new): - click.secho(f"File '{new}' already exists!", fg="red", err=True) + click.secho( + f"File '{new}' already exists!", + fg="red", + err=True, + ) has_existing = True else: non_existing[old] = new diff --git a/patternutils/config.py b/patternutils/config.py index 88a86ed..3392ede 100644 --- a/patternutils/config.py +++ b/patternutils/config.py @@ -1,14 +1,13 @@ -import glob import os.path -import re +import shlex +from datetime import datetime from pathlib import Path -from typing import cast, get_args, overload +from typing import Callable, overload import click +import jinja2 from .types import ( - PredefinedCommands, - PredefinedExtensions, RegexCommands, RegexExtensions, TemplateCommands, @@ -17,13 +16,72 @@ from .types import ( ) CONFIG_PATH = Path(click.get_app_dir("patternutils")) +JINJA_ENV = jinja2.Environment() + + +def to_datetime(value: str, format: str = "%Y-%m-%d") -> datetime: + """Converts a string to a datetime object.""" + return datetime.strptime(value, format) + + +def format_datetime(value: datetime, format: str = "%Y-%m-%d") -> str: + """Formats a datetime object as a string.""" + return value.strftime(format) + + +def to_int(value: str, base: int = 10) -> int: + """Converts a string to an integer.""" + return int(value, base=base) + + +def to_float(value: str) -> float: + """Converts a string to a float.""" + return float(value) + + +def quote(value: str) -> str: + """Quotes a string safely for shell execution.""" + return shlex.quote(value) + + +def splitpath(value: str) -> tuple[str, str]: + """Splits a path into (directory, filename).""" + return os.path.split(value) + + +def splitext(value: str) -> tuple[str, str]: + """Splits a file path into (root, extension).""" + return os.path.splitext(value) + + +JINJA_ENV.filters["datetime"] = to_datetime +JINJA_ENV.filters["datetimeformat"] = format_datetime +JINJA_ENV.filters["int"] = to_int +JINJA_ENV.filters["float"] = to_float +JINJA_ENV.filters["quote"] = quote +JINJA_ENV.filters["splitpath"] = splitpath +JINJA_ENV.filters["splitext"] = splitext + + +def get_loader( + command_name: RegexCommands | TemplateCommands, + prefer: TemplatePrefer | None = None, +) -> jinja2.ChoiceLoader: + loaders = [ + jinja2.FileSystemLoader(CONFIG_PATH / command_name.value), + jinja2.FileSystemLoader(CONFIG_PATH / "globals"), + ] + if prefer == TemplatePrefer.GLOBAL: + loaders[0], loaders[1] = loaders[1], loaders[0] + + return jinja2.ChoiceLoader(loaders) @overload def load_template( template_name: str, command_name: RegexCommands, - prefer: TemplatePrefer = None, + prefer: TemplatePrefer | None = None, ) -> tuple[str, RegexExtensions]: ... @@ -31,15 +89,15 @@ def load_template( def load_template( template_name: str, command_name: TemplateCommands, - prefer: TemplatePrefer = None, + prefer: TemplatePrefer | None = None, ) -> tuple[str, TemplateExtensions]: ... def load_template( template_name: str, - command_name: PredefinedCommands | None, - prefer: TemplatePrefer = None, -) -> tuple[str, PredefinedExtensions]: + command_name: RegexCommands | TemplateCommands, + prefer: TemplatePrefer | None = None, +) -> tuple[str, RegexExtensions | TemplateExtensions]: """Load a template from the config directory. Args: @@ -55,84 +113,51 @@ def load_template( RuntimeError: If multiple templates are found for the given name. ValueError: If the extracted template extension is invalid. """ + loader = get_loader(command_name, prefer) - files = find_templates(template_name, command_name, prefer) - - if not files: - raise FileNotFoundError(f"No template found for '{template_name}'.") - - if len(files) > 1: - raise RuntimeError(f"Multiple template files found for '{template_name}'.") + if command_name in RegexCommands: + extensions = RegexExtensions + elif command_name in TemplateCommands: + extensions = TemplateExtensions + else: + raise ValueError(f"{command_name.value} not supported!") - template_file = files[0] - with open(template_file, encoding="utf-8") as f: - template_lines = f.read() + files: list[tuple[str, str]] = [] - output_pattern = template_lines.replace("\n", "") - extension = os.path.splitext(template_file)[1][1:].lower() - - if extension not in get_args(PredefinedExtensions): - raise ValueError( - f"Invalid template extension '{extension}' in '{template_file}'." - ) - - template_engine = cast(TemplateExtensions, extension) - return output_pattern, template_engine - - -def find_templates( - template_name: str, - command_name: PredefinedCommands | None = None, - prefer: TemplatePrefer = None, -) -> list[str]: - """Finds matching template files based on the template name and command. - - Args: - template_name: The name of the template file (without extension). - command_name: The command requesting the template. - - Returns: - A list of matching template file paths. - - Raises: - ValueError: If `command_name` is invalid. - """ - - extension = ".*" - path_sub_regex = "globals" if prefer == "global" else ".*" - - if command_name is not None: - if prefer == "command": - path_sub_regex = f"{command_name}" - elif prefer is None: - path_sub_regex = f"(globals|{command_name})" + for extension in extensions: + try: + template, filename, _ = loader.get_source( + None, + f"{template_name}.{extension.value}", + ) + files.append((filename, template)) + except jinja2.TemplateNotFound: + continue - if command_name in get_args(RegexCommands): - extensions = get_args(RegexExtensions) - elif command_name in get_args(TemplateCommands): - # CONFIG_PATH/globals/TEMPLATE_NAME.jinja2 - # CONFIG_PATH/globals/TEMPLATE_NAME.python - # CONFIG_PATH/COMMAND_NAME/TEMPLATE_NAME.jinja2 - # CONFIG_PATH/COMMAND_NAME/TEMPLATE_NAME.python - extensions = get_args(TemplateExtensions) + if not files: + raise FileNotFoundError(f"No template with the name {template_name} found.") + elif len(files) > 1: + raise RuntimeError("Multiple files found.") + else: + filename, template = files[0] + if filename.endswith("regex"): + template_engine = RegexExtensions.REGEX else: - raise ValueError(f"Unknown command '{command_name}'.") + filename, fileext = os.path.splitext(filename) + template_engine = TemplateExtensions(fileext[1:]) + return template, template_engine - if len(extensions) > 0: - if len(extensions) > 1: - extension = f"({'|'.join(extensions)})" - else: - extension = extensions[0] - else: - raise ValueError( - f"Could not determine file extension for command '{command_name}'." - ) - pattern = re.compile( - str(CONFIG_PATH / path_sub_regex / rf"{template_name}\.{extension}") - ) - return [ - file - for file in glob.iglob(os.path.join(CONFIG_PATH, "*", "*")) - if pattern.search(file) - ] +def get_render_function( + template: str, + template_engine: TemplateExtensions, + command_name: TemplateCommands, + prefer: TemplatePrefer | None = None, +) -> Callable[..., str]: + if template_engine == TemplateExtensions.JINJA2: + loader = get_loader(command_name, prefer) + jinja_env_overlay = JINJA_ENV.overlay(loader=loader) + env_template = jinja_env_overlay.from_string(template) + return env_template.render + else: + return template.format diff --git a/patternutils/edit.py b/patternutils/edit.py new file mode 100644 index 0000000..62ae646 --- /dev/null +++ b/patternutils/edit.py @@ -0,0 +1,69 @@ +from enum import Flag, StrEnum, auto +from itertools import batched, chain +from typing import cast + + +class EditFormat(StrEnum): + SINGLE_COLUMN = "sc" + DUAL_COLUMN = "dc" + DESTINATION_ONLY = "do" + + +class EditFormatOption(Flag): + SWAP = auto() + SEPARATE = auto() + + +def generate( + data: dict[str, str], + edit_format: EditFormat = EditFormat.DUAL_COLUMN, + edit_option: EditFormatOption | None = None, +) -> str: + join_character = ( + "\n\n" if edit_option and EditFormatOption.SEPARATE in edit_option else "\n" + ) + + if edit_format == EditFormat.SINGLE_COLUMN: + if ( + edit_option is None + or EditFormatOption.SWAP | EditFormatOption.SEPARATE not in edit_option + ): + text = "\n".join(line for line in chain.from_iterable(data.items())) + else: + lines = [] + for old, new in data.items(): + if EditFormatOption.SWAP in edit_option: + new, old = old, new + lines.append(f"{old}\n{new}") + text = join_character.join(lines) + + elif edit_format == EditFormat.DUAL_COLUMN: + longest_prefix = len(max(data, key=len)) + text = join_character.join( + f"{key.ljust(longest_prefix)}\t{value}" for key, value in data.items() + ) + elif edit_format == EditFormat.DESTINATION_ONLY: + text = join_character.join(data.values()) + + return text + + +def parse( + text: str, + data: dict[str, str], + edit_format: EditFormat = EditFormat.DUAL_COLUMN, +) -> dict[str, str]: + lines = text.splitlines() + + if edit_format == EditFormat.SINGLE_COLUMN: + new_data = dict(batched(lines, 2)) # type: ignore + elif edit_format == EditFormat.DUAL_COLUMN: + new_data = {} + for line in lines: + if line and "\t" in line: + old, new = line.split("\t") + new_data[old.split()] = new + elif edit_format == EditFormat.DESTINATION_ONLY: + new_data = dict(zip(data.keys(), lines)) + + return cast(dict[str, str], new_data) diff --git a/patternutils/types.py b/patternutils/types.py index a81eb34..19571b0 100644 --- a/patternutils/types.py +++ b/patternutils/types.py @@ -1,19 +1,36 @@ -from typing import Literal +from enum import StrEnum, auto +from typing import Literal, TypeAlias -type PlainCommands = Literal["pmediainfo"] -type RegexCommands = Literal["pmatch", "pjson"] -type RegexExtensions = Literal["regex"] +class PlainCommands(StrEnum): + PMEDIAINFO = auto() -type TemplateCommands = Literal["tmv", "targs", "tln"] -type TemplateExtensions = Literal["python", "jinja2"] -type PredefinedCommands = Literal[RegexCommands, TemplateCommands] -type PredefinedExtensions = Literal[TemplateExtensions, RegexExtensions] +class RegexCommands(StrEnum): + PMATCH = auto() + PJSON = auto() -type CommandNames = Literal[PlainCommands, TemplateCommands, RegexCommands] -type RegexFlags = Literal[ +class TemplateCommands(StrEnum): + TMV = auto() + TARGS = auto() + TLN = auto() + + +class RegexExtensions(StrEnum): + REGEX = auto() + + +class TemplateExtensions(StrEnum): + PYTHON = auto() + JINJA2 = auto() + + +class TemplatePrefer(StrEnum): + GLOBAL = auto() + COMMAND = auto() + + +RegexFlags: TypeAlias = Literal[ "ascii", "a", "ignorecase", "i", "multiline", "m", "dotall", "s" ] -type TemplatePrefer = Literal["global", "command"] | None diff --git a/patternutils/utils.py b/patternutils/utils.py index 6a57457..cfb999b 100644 --- a/patternutils/utils.py +++ b/patternutils/utils.py @@ -6,7 +6,7 @@ from typing import Any, Generator, overload import click -from patternutils.types import RegexFlags +from .types import RegexFlags def max_procs() -> int: diff --git a/pyproject.toml b/pyproject.toml index c64dcad..d960cda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,9 @@ authors = [ license = {text = "GPL-3.0-or-later"} requires-python = ">=3.12, <4" dependencies = [ - "click >=8.1.8, <9.0.0", - "click-aliases>=1.0.5", - "click-option-group>=0.5.7", - "Jinja2 >=3.1.5, <4.0.0", + "click >=8.3.0, <9.0.0", + "cloup>=3.0.8", + "Jinja2 >=3.1.6, <4.0.0", ] [project.urls] @@ -19,10 +18,10 @@ repository = "https://projects.metalgamer.eu/patternutils" [project.optional-dependencies] pygments = [ - "Pygments >=2.19.1, <3.0.0", + "Pygments >=2.19.2, <3.0.0", ] prss = [ - "feedparser >=6.0.11, <7.0.0", + "feedparser >=6.0.12, <7.0.0", ] pmediainfo = [ "pymediainfo >=7.0.1, <8.0.0", @@ -40,10 +39,8 @@ pmediainfo = "patternutils.commands.pmediainfo:pmediainfo" [dependency-groups] dev = [ - "mypy >=1.15.0, <2.0.0", - "black >=25.1.0, <26.0.0", - "isort >=6.0.1, <7.0.0", "vermin >=1.6.0, <2.0.0", + "pyright>=1.1.405", ] [build-system] @@ -55,4 +52,4 @@ build-backend = "pdm.backend" includes = [] [tool.uv] -default-groups = [] +default-groups = "all" @@ -3,63 +3,27 @@ revision = 3 requires-python = ">=3.12, <4" [[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - -[[package]] name = "click" -version = "8.1.8" +version = "8.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[package]] -name = "click-aliases" -version = "1.0.5" +name = "cloup" +version = "3.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/d8/adbaeadc13c9686b9bda8b4c50e5a3983f504faae2ffbea5165d5beb1cdb/click_aliases-1.0.5.tar.gz", hash = "sha256:e37d4cabbaad68e1c48ec0f063a59dfa15f0e7450ec901bd1ce4f4b954bc881d", size = 3105, upload-time = "2024-10-17T15:44:19.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/cf/09a31f0f51b5c8ef2343baf37c35a5feb4f6dfdcbd0592a014baf837f2e4/cloup-3.0.8.tar.gz", hash = "sha256:f91c080a725196ddf74feabd6250266f466e97fc16dfe21a762cf6bc6beb3ecb", size = 229657, upload-time = "2025-08-05T02:25:02.83Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/1a/d5e29a6f896293e32ab3e63201df5d396599e57a726575adaafbcd9d70a6/click_aliases-1.0.5-py3-none-any.whl", hash = "sha256:cbb83a348acc00809fe18b6da13a7f6307bc71b3c5f69cc730e012dfb4bbfdc3", size = 3524, upload-time = "2024-10-17T15:44:17.389Z" }, -] - -[[package]] -name = "click-option-group" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/9f/1f917934da4e07ae7715a982347e3c2179556d8a58d1108c5da3e8f09c76/click_option_group-0.5.7.tar.gz", hash = "sha256:8dc780be038712fc12c9fecb3db4fe49e0d0723f9c171d7cda85c20369be693c", size = 22110, upload-time = "2025-03-24T13:24:55.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/27/bf74dc1494625c3b14dbcdb93740defd7b8c58dae3736be8d264f2a643fb/click_option_group-0.5.7-py3-none-any.whl", hash = "sha256:96b9f52f397ef4d916f81929bd6c1f85e89046c7a401a64e72a61ae74ad35c24", size = 11483, upload-time = "2025-03-24T13:24:54.611Z" }, + { url = "https://files.pythonhosted.org/packages/45/0a/494a923f90cd97cdf4fb989cfd06ac0c6745f6dfb8adcef1b7f99d3c7834/cloup-3.0.8-py2.py3-none-any.whl", hash = "sha256:6fe9474dc44fa06f8870e9c797f005de1e3ef891ddc1a9612d9b58598a038323", size = 54647, upload-time = "2025-08-05T02:25:01.536Z" }, ] [[package]] @@ -73,125 +37,98 @@ wheels = [ [[package]] name = "feedparser" -version = "6.0.11" +version = "6.0.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sgmllib3k" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197, upload-time = "2023-12-10T16:03:20.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343, upload-time = "2023-12-10T16:03:19.484Z" }, -] - -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/79/db7edb5e77d6dfbc54d7d9df72828be4318275b2e580549ff45a962f6461/feedparser-6.0.12.tar.gz", hash = "sha256:64f76ce90ae3e8ef5d1ede0f8d3b50ce26bcce71dd8ae5e82b1cd2d4a5f94228", size = 286579, upload-time = "2025-09-10T13:33:59.486Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, + { url = "https://files.pythonhosted.org/packages/4e/eb/c96d64137e29ae17d83ad2552470bafe3a7a915e85434d9942077d7fd011/feedparser-6.0.12-py3-none-any.whl", hash = "sha256:6bbff10f5a52662c00a2e3f86a38928c37c48f77b3c511aedcd51de933549324", size = 81480, upload-time = "2025-09-10T13:33:58.022Z" }, ] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674, upload-time = "2024-12-21T18:30:22.828Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596, upload-time = "2024-12-21T18:30:19.133Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] -name = "mypy" -version = "1.15.0" +name = "nodeenv" +version = "1.9.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] @@ -200,8 +137,7 @@ version = "0.0.13" source = { editable = "." } dependencies = [ { name = "click" }, - { name = "click-aliases" }, - { name = "click-option-group" }, + { name = "cloup" }, { name = "jinja2" }, ] @@ -218,48 +154,34 @@ pygments = [ [package.dev-dependencies] dev = [ - { name = "black" }, - { name = "isort" }, - { name = "mypy" }, + { name = "pyright" }, { name = "vermin" }, ] [package.metadata] requires-dist = [ - { name = "click", specifier = ">=8.1.8,<9.0.0" }, - { name = "click-aliases", specifier = ">=1.0.5" }, - { name = "click-option-group", specifier = ">=0.5.7" }, - { name = "feedparser", marker = "extra == 'prss'", specifier = ">=6.0.11,<7.0.0" }, - { name = "jinja2", specifier = ">=3.1.5,<4.0.0" }, - { name = "pygments", marker = "extra == 'pygments'", specifier = ">=2.19.1,<3.0.0" }, + { name = "click", specifier = ">=8.3.0,<9.0.0" }, + { name = "cloup", specifier = ">=3.0.8" }, + { name = "feedparser", marker = "extra == 'prss'", specifier = ">=6.0.12,<7.0.0" }, + { name = "jinja2", specifier = ">=3.1.6,<4.0.0" }, + { name = "pygments", marker = "extra == 'pygments'", specifier = ">=2.19.2,<3.0.0" }, { name = "pymediainfo", marker = "extra == 'pmediainfo'", specifier = ">=7.0.1,<8.0.0" }, ] provides-extras = ["pygments", "prss", "pmediainfo"] [package.metadata.requires-dev] dev = [ - { name = "black", specifier = ">=25.1.0,<26.0.0" }, - { name = "isort", specifier = ">=6.0.1,<7.0.0" }, - { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, + { name = "pyright", specifier = ">=1.1.405" }, { name = "vermin", specifier = ">=1.6.0,<2.0.0" }, ] [[package]] -name = "platformdirs" -version = "4.3.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, -] - -[[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -276,6 +198,19 @@ wheels = [ ] [[package]] +name = "pyright" +version = "1.1.405" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, +] + +[[package]] name = "sgmllib3k" version = "1.0.0" source = { registry = "https://pypi.org/simple" } @@ -283,11 +218,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711 [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] |
