diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9196d6a..13c3ad7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: args: - --number - --wrap=keep + exclude: ".github\/ISSUE_TEMPLATE\/.*.md" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -38,6 +39,10 @@ repos: args: - --fix=auto - id: name-tests-test + - id: pretty-format-json + args: + - --autofix + - --indent=2 - id: trailing-whitespace args: - --markdown-linebreak-ext=md diff --git a/README.md b/README.md index db9fbe2..a5693b6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ [![Github Action - Testing](https://img.shields.io/github/actions/workflow/status/Buried-In-Code/Perdoo/testing.yaml?branch=main&logo=Github&label=Testing&style=flat-square)](https://github.com/Buried-In-Code/Perdoo/actions/workflows/testing.yaml) [![Github Action - Publishing](https://img.shields.io/github/actions/workflow/status/Buried-In-Code/Perdoo/publishing.yaml?branch=main&logo=Github&label=Publishing&style=flat-square)](https://github.com/Buried-In-Code/Perdoo/actions/workflows/publishing.yaml) - Perdoo is designed to assist in sorting and organizing your comic collection by utilizing metadata files stored within comic archives.\ Perdoo standardizes all your digital comics into a unified format (cbz).\ It adds and/or updates metadata files using supported services.\ @@ -136,6 +135,92 @@ Naming is done based on the Comic Format, set the value to `""` and it will fall | `{upc}` | The issue's UPC. | | `{volume}` | The volume of the series. | +## Settings + +To set Perdoo setting details, update the file: `~/.config/perdoo/settings.toml`. +File will be created on first run. + +### Example File + +```toml +[output] +folder = "~/.local/share/perdoo" +format = "cbz" + +[output.comic_info] +create = true +handle_pages = true + +[output.metron_info] +create = true + +[output.naming] +seperator = "-" +default = "{publisher-name}/{series-name}-v{volume}/{series-name}-v{volume}_#{number:3}" +annual = "" +digital_chapter = "" +graphic_novel = "" +hardcover = "" +limited_series = "" +omnibus = "" +one_shot = "" +single_issue = "" +trade_paperback = "" + +[services] +order = ["Metron", "Marvel", "Comicvine"] + +[services.comicvine] +api_key = "" + +[services.marvel] +public_key = "" +private_key = "" + +[services.metron] +username = "" +password = "" + +``` + +### Details + +- `output.folder` + The folder where the output files will be stored. + Defaults to `~/.local/share/perdoo`. + +- `output.format` + The output file format for the comic archives. + Defaults to `cbz`. + +- `output.comic_info.create` + Whether to create a ComicInfo.xml file in the output archive. + Defaults to `true`. + +- `output.comic_info.handle_pages` + Whether to handle page data in the ComicInfo.xml file. + Defaults to `true`. + +- `output.metron_info.create` + Whether to create a MetronInfo.xml file in the output archive. + Defaults to `true`. + +- `output.naming.seperator` + The word separator used in the output file names. + Defaults to `-`. + Options are `-`, `_`, `.`, or ` ` (space). + +- `output.naming.*` + The naming patterns for different comic formats. + Each pattern can be customized or left empty to use the default setting. + The patterns support various metadata fields as described in the above "File Renaming and Organization" section. + +- `services.order` + The order in which the services will be used for metadata retrieval. + Metadata will be fetched from the first service that returns a result. + Don't include the service name in the list if you don't want to use it. + Defaults to `["Metron", "Marvel", "Comicvine"]`, options are `Metron`, `Marvel` and `Comicvine`. + ## Socials [![Social - Fosstodon](https://img.shields.io/badge/%40BuriedInCode-teal?label=Fosstodon&logo=mastodon&style=for-the-badge)](https://fosstodon.org/@BuriedInCode)\ diff --git a/docs/img/perdoo-archive-view.svg b/docs/img/perdoo-archive-view.svg index 7a351b5..d20e909 100644 --- a/docs/img/perdoo-archive-view.svg +++ b/docs/img/perdoo-archive-view.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + - + - + - - Traceback (most recent call last): -  File "/home/runner/work/Perdoo/Perdoo/.venv/bin/Perdoo", line 4, in <module> -    from perdoo.__main__ import app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/__main__.py", line 12, in <module> -    from perdoo.cli import archive_app, settings_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/__init__.py", line 3, in <module> -    from perdoo.cli.archive import app as archive_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/archive.py", line 8, in <module> -    from perdoo.comic import Comic -  File "/home/runner/work/Perdoo/Perdoo/perdoo/comic.py", line 14, in <module> -    from darkseid.archivers import ( -    ...<6 lines>... -    ) -ImportError: cannot import name 'SevenZipArchiver' from 'darkseid.archivers' (/home/runner/work/Perdoo/Perdoo/.venv/lib/python3.13/site-packages/darkseid/archivers/__init__.py) + + +Usage: Perdoo archive view [OPTIONS] TARGET + + View the ComicInfo/MetronInfo inside a Comic archive.                           + + +╭─ Arguments ──────────────────────────────────────────────────────────────────╮ +*    target      FILE  Comic to view details of. [required] +╰──────────────────────────────────────────────────────────────────────────────╯ +╭─ Options ────────────────────────────────────────────────────────────────────╮ +--hide-comic-info           Don't show the ComicInfo details.                 +--hide-metron-info          Don't show the MetronInfo details.                +--help                      Show this message and exit.                       +╰──────────────────────────────────────────────────────────────────────────────╯ + diff --git a/docs/img/perdoo-commands.svg b/docs/img/perdoo-commands.svg index 7a351b5..deb317d 100644 --- a/docs/img/perdoo-commands.svg +++ b/docs/img/perdoo-commands.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + - + - + - - Traceback (most recent call last): -  File "/home/runner/work/Perdoo/Perdoo/.venv/bin/Perdoo", line 4, in <module> -    from perdoo.__main__ import app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/__main__.py", line 12, in <module> -    from perdoo.cli import archive_app, settings_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/__init__.py", line 3, in <module> -    from perdoo.cli.archive import app as archive_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/archive.py", line 8, in <module> -    from perdoo.comic import Comic -  File "/home/runner/work/Perdoo/Perdoo/perdoo/comic.py", line 14, in <module> -    from darkseid.archivers import ( -    ...<6 lines>... -    ) -ImportError: cannot import name 'SevenZipArchiver' from 'darkseid.archivers' (/home/runner/work/Perdoo/Perdoo/.venv/lib/python3.13/site-packages/darkseid/archivers/__init__.py) + + +Usage: Perdoo [OPTIONS] COMMAND [ARGS]... + + CLI tool for managing comic collections and settings.                           + + +╭─ Options ────────────────────────────────────────────────────────────────────╮ +--version                     Show the version and exit.                      +--install-completion          Install completion for the current shell.       +--show-completion             Show completion for the current shell, to copy  +                               it or customize the installation.               +--help                        Show this message and exit.                     +╰──────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ───────────────────────────────────────────────────────────────────╮ +import    Import comics into your collection using Perdoo.                   +archive   Commands for inspecting and managing comic archive metadata.       +settings  Commands for managing and configuring application settings.        +╰──────────────────────────────────────────────────────────────────────────────╯ + diff --git a/docs/img/perdoo-import.svg b/docs/img/perdoo-import.svg index 7a351b5..c7da7e4 100644 --- a/docs/img/perdoo-import.svg +++ b/docs/img/perdoo-import.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - Traceback (most recent call last): -  File "/home/runner/work/Perdoo/Perdoo/.venv/bin/Perdoo", line 4, in <module> -    from perdoo.__main__ import app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/__main__.py", line 12, in <module> -    from perdoo.cli import archive_app, settings_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/__init__.py", line 3, in <module> -    from perdoo.cli.archive import app as archive_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/archive.py", line 8, in <module> -    from perdoo.comic import Comic -  File "/home/runner/work/Perdoo/Perdoo/perdoo/comic.py", line 14, in <module> -    from darkseid.archivers import ( -    ...<6 lines>... -    ) -ImportError: cannot import name 'SevenZipArchiver' from 'darkseid.archivers' (/home/runner/work/Perdoo/Perdoo/.venv/lib/python3.13/site-packages/darkseid/archivers/__init__.py) + + +Usage: Perdoo import [OPTIONS] TARGET + + Import comics into your collection using Perdoo.                                + + +╭─ Arguments ──────────────────────────────────────────────────────────────────╮ +*    target      PATH  Import comics from the specified file/folder.          +[required]                                    +╰──────────────────────────────────────────────────────────────────────────────╯ +╭─ Options ────────────────────────────────────────────────────────────────────╮ +--skip-convert  Skip converting comics to the  +                                                configured format.             +--sync-s[force|outdated|skip]  Sync ComicInfo/MetronInfo      +                                                with online services.          +[default: Outdated]           +--skip-clean  Skip removing any files not    +                                                listed in the                  +                                                'image_extensions' setting.    +--skip-rename  Skip organizing and renaming   +                                                comics based on their          +                                                MetronInfo/ComicInfo.          +--clean-c  Clean the cache before         +                                                starting the synchronization   +                                                process. Removes all cached    +                                                files.                         +--debug  Enable debug mode to show      +                                                extra information.             +--help  Show this message and exit.    +╰──────────────────────────────────────────────────────────────────────────────╯ + diff --git a/docs/img/perdoo-settings-locate.svg b/docs/img/perdoo-settings-locate.svg index 7a351b5..19cad73 100644 --- a/docs/img/perdoo-settings-locate.svg +++ b/docs/img/perdoo-settings-locate.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - + + - + - + - - Traceback (most recent call last): -  File "/home/runner/work/Perdoo/Perdoo/.venv/bin/Perdoo", line 4, in <module> -    from perdoo.__main__ import app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/__main__.py", line 12, in <module> -    from perdoo.cli import archive_app, settings_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/__init__.py", line 3, in <module> -    from perdoo.cli.archive import app as archive_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/archive.py", line 8, in <module> -    from perdoo.comic import Comic -  File "/home/runner/work/Perdoo/Perdoo/perdoo/comic.py", line 14, in <module> -    from darkseid.archivers import ( -    ...<6 lines>... -    ) -ImportError: cannot import name 'SevenZipArchiver' from 'darkseid.archivers' (/home/runner/work/Perdoo/Perdoo/.venv/lib/python3.13/site-packages/darkseid/archivers/__init__.py) + + +Usage: Perdoo settings locate [OPTIONS] + + Display the path to the settings file.                                          + + +╭─ Options ────────────────────────────────────────────────────────────────────╮ +--help          Show this message and exit.                                   +╰──────────────────────────────────────────────────────────────────────────────╯ + diff --git a/docs/img/perdoo-settings-view.svg b/docs/img/perdoo-settings-view.svg index 7a351b5..15de690 100644 --- a/docs/img/perdoo-settings-view.svg +++ b/docs/img/perdoo-settings-view.svg @@ -1,4 +1,4 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - + + - + - + - - Traceback (most recent call last): -  File "/home/runner/work/Perdoo/Perdoo/.venv/bin/Perdoo", line 4, in <module> -    from perdoo.__main__ import app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/__main__.py", line 12, in <module> -    from perdoo.cli import archive_app, settings_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/__init__.py", line 3, in <module> -    from perdoo.cli.archive import app as archive_app -  File "/home/runner/work/Perdoo/Perdoo/perdoo/cli/archive.py", line 8, in <module> -    from perdoo.comic import Comic -  File "/home/runner/work/Perdoo/Perdoo/perdoo/comic.py", line 14, in <module> -    from darkseid.archivers import ( -    ...<6 lines>... -    ) -ImportError: cannot import name 'SevenZipArchiver' from 'darkseid.archivers' (/home/runner/work/Perdoo/Perdoo/.venv/lib/python3.13/site-packages/darkseid/archivers/__init__.py) + + +Usage: Perdoo settings view [OPTIONS] + + Display the current and default settings.                                       + + +╭─ Options ────────────────────────────────────────────────────────────────────╮ +--help          Show this message and exit.                                   +╰──────────────────────────────────────────────────────────────────────────────╯ + diff --git a/perdoo/comic.py b/perdoo/comic.py index 7d09f1d..30f59f1 100644 --- a/perdoo/comic.py +++ b/perdoo/comic.py @@ -156,9 +156,9 @@ def _rename_images(self, base_name: str) -> None: pad_count = len(str(len(files))) if files else 1 for idx, filename in enumerate(files): img_file = Path(filename) - new_file = img_file.with_name(f"{base_name}_{str(idx).zfill(pad_count)}") + new_file = img_file.with_stem(f"{base_name}_{str(idx).zfill(pad_count)}") if new_file.stem != img_file.stem: - LOGGER.info("Renaming '%s' to '%s'", img_file.stem, new_file.stem) + LOGGER.info("Renaming '%s' to '%s'", img_file.name, new_file.name) file_contents = source.read_file(archive_file=filename) source.remove_files(filename_list=[filename]) source.write_file(archive_file=new_file.name, data=file_contents) diff --git a/perdoo/metadata/_base.py b/perdoo/metadata/_base.py index 172cb48..9bde05a 100644 --- a/perdoo/metadata/_base.py +++ b/perdoo/metadata/_base.py @@ -4,6 +4,7 @@ import re from collections.abc import Callable from pathlib import Path +from typing import Literal from pydantic.alias_generators import to_pascal from pydantic_xml import BaseXmlModel @@ -20,13 +21,13 @@ LOGGER = logging.getLogger(__name__) -def sanitize(value: str | None) -> str | None: +def sanitize(value: str | None, seperator: Literal["-", "_", ".", " "]) -> str | None: if not value: return value value = str(value) - value = re.sub(r"[^0-9a-zA-Z&! ]+", "", value.replace("-", " ")) + value = re.sub(r"[^0-9a-zA-Z&! ]+", "", value.replace(seperator, " ")) value = " ".join(value.split()) - return value.replace(" ", "-") + return value.replace(" ", seperator) class PascalModel( @@ -61,7 +62,12 @@ def display(self) -> None: CONSOLE.print(Panel.fit("\n".join(content_vals), title=type(self).__name__)) - def evaluate_pattern(self, pattern_map: dict[str, Callable[[Self], str]], pattern: str) -> str: + def evaluate_pattern( + self, + pattern_map: dict[str, Callable[[Self], str]], + pattern: str, + seperator: Literal["-", "_", ".", " "], + ) -> str: def replace_match(match: re.Match) -> str: key = match.group("key") padding = match.group("padding") @@ -73,7 +79,7 @@ def replace_match(match: re.Match) -> str: if padding and (isinstance(value, int) or (isinstance(value, str) and value.isdigit())): return f"{int(value):0{padding}}" - return sanitize(value=value) or "" + return sanitize(value=value, seperator=seperator) or "" pattern_regex = re.compile(r"{(?P[a-zA-Z-]+)(?::(?P\d+))?}") return pattern_regex.sub(replace_match, pattern) diff --git a/perdoo/metadata/comic_info.py b/perdoo/metadata/comic_info.py index b28cb4c..52f715d 100644 --- a/perdoo/metadata/comic_info.py +++ b/perdoo/metadata/comic_info.py @@ -322,6 +322,7 @@ def get_filename(self, settings: Naming) -> str: Format.SINGLE_ISSUE.value: settings.single_issue or settings.default, Format.TRADE_PAPERBACK.value: settings.trade_paperback or settings.default, }.get(self.format, settings.default), + seperator=settings.seperator, ) diff --git a/perdoo/metadata/metron_info.py b/perdoo/metadata/metron_info.py index 03e70ea..2a27bc9 100644 --- a/perdoo/metadata/metron_info.py +++ b/perdoo/metadata/metron_info.py @@ -386,6 +386,7 @@ def get_filename(self, settings: Naming) -> str: Format.SINGLE_ISSUE: settings.single_issue or settings.default, Format.TRADE_PAPERBACK: settings.trade_paperback or settings.default, }.get(self.series.format, settings.default), + seperator=settings.seperator, ) diff --git a/perdoo/settings.py b/perdoo/settings.py index b2d4c56..d8dfd40 100644 --- a/perdoo/settings.py +++ b/perdoo/settings.py @@ -47,6 +47,7 @@ class MetronInfo(SettingsModel): class Naming(SettingsModel): + seperator: Literal["-", "_", ".", " "] = "-" default: str = "{publisher-name}/{series-name}-v{volume}/{series-name}-v{volume}_#{number:3}" annual: Annotated[str | None, BeforeValidator(blank_is_none)] = ( "{publisher-name}/{series-name}-v{volume}/{series-name}-v{volume}_Annual_#{number:2}" diff --git a/pyproject.toml b/pyproject.toml index fa540c6..4d592e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "simyan >= 1.5.1", "tomli >= 2.2.1 ; python_version < '3.11'", "tomli-w >= 1.2.0", - "typer >= 0.16.0", + "typer >= 0.16.0" ] description = "Unify and organize your comic collection." dynamic = ["version"] diff --git a/tests/naming_test.py b/tests/naming_test.py index d3a8263..74e9bc7 100644 --- a/tests/naming_test.py +++ b/tests/naming_test.py @@ -5,10 +5,10 @@ def test_sanitize() -> None: - assert sanitize("Example Title!") == "Example-Title!" - assert sanitize("Example/Title: 123") == "ExampleTitle-123" - assert sanitize("!@#$%^&*()[]{};':,.<>?/") == "!&" - assert sanitize(None) is None + assert sanitize("Example Title!", seperator="-") == "Example-Title!" + assert sanitize("Example/Title: 123", seperator="-") == "ExampleTitle-123" + assert sanitize("!@#$%^&*()[]{};':,.<>?/", seperator="-") == "!&" + assert sanitize(None, seperator="-") is None def test_metron_info_default_naming() -> None: