Skip to content

Commit

Permalink
Refactoring (#124)
Browse files Browse the repository at this point in the history
* Restructure and simplify code
* Convert for loop into list comprehension, Inline variable that is immediately returned
* Remove unnecessary casts to bool
* Use __future__ annotations
* Update project deps
* Quite lint warning
  • Loading branch information
bpepple authored Jun 15, 2024
1 parent 7b107a9 commit 9d1cdc0
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 218 deletions.
16 changes: 9 additions & 7 deletions metrontagger/duplicates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Module to find and remove any duplicate pages from a directory of comics."""

# Copyright 2023 Brian Pepple
from __future__ import annotations

import io
from dataclasses import dataclass
Expand All @@ -20,18 +22,18 @@ class DuplicateIssue:
path_: str
pages_index: list[int]

def __repr__(self: "DuplicateIssue") -> str:
def __repr__(self: DuplicateIssue) -> str:
return f"{self.__class__.__name__}(name={Path(self.path_).name})"


class Duplicates:
"""Class to find and remove any duplicate pages."""

def __init__(self: "Duplicates", file_lst: list[Path]) -> None:
def __init__(self: Duplicates, file_lst: list[Path]) -> None:
self._file_lst = file_lst
self._data_frame: pd.DataFrame | None = None

def _image_hashes(self: "Duplicates") -> list[dict[str, any]]:
def _image_hashes(self: Duplicates) -> list[dict[str, any]]:
"""Method to get a list of dicts containing the file path, page index, and page hashes."""
hashes_lst = []
for item in self._file_lst:
Expand Down Expand Up @@ -68,25 +70,25 @@ def _image_hashes(self: "Duplicates") -> list[dict[str, any]]:
continue
return hashes_lst

def _get_page_hashes(self: "Duplicates") -> pd.DataFrame:
def _get_page_hashes(self: Duplicates) -> pd.DataFrame:
"""Method to get a dataframe of comics with duplicate pages."""
comic_hashes = self._image_hashes()
self._data_frame = pd.DataFrame(comic_hashes)
hashes = self._data_frame["hash"]
return self._data_frame[hashes.isin(hashes[hashes.duplicated()])].sort_values("hash")

def get_distinct_hashes(self: "Duplicates") -> list[str]:
def get_distinct_hashes(self: Duplicates) -> list[str]:
"""Method to get distinct hash values."""
page_hashes = self._get_page_hashes()
return [key for key, _group in groupby(page_hashes["hash"])]

def get_comic_info_for_distinct_hash(self: "Duplicates", img_hash: str) -> DuplicateIssue:
def get_comic_info_for_distinct_hash(self: Duplicates, img_hash: str) -> DuplicateIssue:
"""Method to retrieve first comic instance's path and page index from a hash value."""
idx = self._data_frame.loc[self._data_frame["hash"] == img_hash].index[0]
row = self._data_frame.iloc[idx]
return DuplicateIssue(row["path"], row["index"])

def get_comic_list_from_hash(self: "Duplicates", img_hash: str) -> list[DuplicateIssue]:
def get_comic_list_from_hash(self: Duplicates, img_hash: str) -> list[DuplicateIssue]:
comic_lst = []
for i in self._data_frame.loc[self._data_frame["hash"] == img_hash].index:
row = self._data_frame.iloc[i]
Expand Down
27 changes: 16 additions & 11 deletions metrontagger/filerenamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

# Copyright 2012-2014 Anthony Beville
# Copyright 2020 Brian Pepple
from __future__ import annotations

import datetime
import re
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pathlib import Path

from darkseid.metadata import Metadata

import questionary
from darkseid.issue_string import IssueString
from darkseid.metadata import Metadata
from darkseid.utils import unique_file

from metrontagger.styles import Styles
Expand All @@ -19,29 +24,29 @@
class FileRenamer:
"""Class to rename a comic archive based on its metadata tag"""

def __init__(self: "FileRenamer", metadata: Metadata | None = None) -> None:
def __init__(self: FileRenamer, metadata: Metadata | None = None) -> None:
self.metadata: Metadata | None = metadata
self.template: str = "%series% v%volume% #%issue% (of %issuecount%) (%year%)"
self.smart_cleanup: bool = True
self.issue_zero_padding: int = 3

def set_smart_cleanup(self: "FileRenamer", on: bool) -> None:
def set_smart_cleanup(self: FileRenamer, on: bool) -> None:
self.smart_cleanup = on

def set_metadata(self: "FileRenamer", metadata: Metadata) -> None:
def set_metadata(self: FileRenamer, metadata: Metadata) -> None:
"""Method to set the metadata"""
self.metadata = metadata

def set_issue_zero_padding(self: "FileRenamer", count: int) -> None:
def set_issue_zero_padding(self: FileRenamer, count: int) -> None:
"""Method to set the padding for the issue's number"""
self.issue_zero_padding = count

def set_template(self: "FileRenamer", template: str) -> None:
def set_template(self: FileRenamer, template: str) -> None:
"""Method to use a user's custom file naming template."""
self.template = template

def replace_token(
self: "FileRenamer", text: str, value: int | str | None, token: str
self: FileRenamer, text: str, value: int | str | None, token: str
) -> str:
"""Method to replace a value with another value"""

Expand Down Expand Up @@ -81,7 +86,7 @@ def _remove_duplicate_hyphen_underscore(value: str) -> str:
value = re.sub(r"(\s--)+", " --", value)
return re.sub(r"(\s-)+", " -", value)

def smart_cleanup_string(self: "FileRenamer", new_name: str) -> str:
def smart_cleanup_string(self: FileRenamer, new_name: str) -> str:
# remove empty braces,brackets, parentheses
new_name = self._remove_empty_separators(new_name)

Expand All @@ -97,7 +102,7 @@ def smart_cleanup_string(self: "FileRenamer", new_name: str) -> str:
# remove duplicate spaces (again!)
return " ".join(new_name.split())

def determine_name(self: "FileRenamer", filename: Path) -> str | None:
def determine_name(self: FileRenamer, filename: Path) -> str | None: # noqa: C901 RUF100
"""Method to create the new filename based on the files metadata"""
if not self.metadata:
return None
Expand Down Expand Up @@ -181,7 +186,7 @@ def determine_name(self: "FileRenamer", filename: Path) -> str | None:

return cleanup_string(new_name)

def rename_file(self: "FileRenamer", comic: Path) -> Path | None:
def rename_file(self: FileRenamer, comic: Path) -> Path | None:
# This shouldn't happen, but just in case let's make sure there is metadata.
if self.metadata is None:
questionary.print(
Expand Down
13 changes: 9 additions & 4 deletions metrontagger/filesorter.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"""Class to sort comic file based on its metadata tags"""

from __future__ import annotations

from pathlib import Path
from shutil import Error, move
from typing import TYPE_CHECKING

import questionary
from darkseid.comic import Comic
from darkseid.metadata import Metadata

if TYPE_CHECKING:
from darkseid.metadata import Metadata

from metrontagger.styles import Styles
from metrontagger.utils import cleanup_string
Expand All @@ -19,7 +24,7 @@ def get_file_size(fn: Path) -> float:
class FileSorter:
"""Class to move comic files based on its metadata tags"""

def __init__(self: "FileSorter", directory: str) -> None:
def __init__(self: FileSorter, directory: str) -> None:
self.sort_directory = directory

@staticmethod
Expand Down Expand Up @@ -62,7 +67,7 @@ def _overwrite_existing(new_path: Path, old_comic: Path) -> None:
existing_comic.unlink()

def _get_new_path(
self: "FileSorter",
self: FileSorter,
meta_data: Metadata,
publisher: str,
series: str,
Expand Down Expand Up @@ -90,7 +95,7 @@ def _create_new_path(new_sort_path: Path) -> bool:
return False
return True

def sort_comics(self: "FileSorter", comic: Path) -> bool:
def sort_comics(self: FileSorter, comic: Path) -> bool:
"""Method to move the comic file based on its metadata tag"""
try:
comic_archive = Comic(str(comic))
Expand Down
25 changes: 15 additions & 10 deletions metrontagger/run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

import sys
from pathlib import Path
from typing import TYPE_CHECKING

import questionary
from darkseid.comic import Comic
Expand All @@ -10,7 +13,9 @@
from metrontagger.filerenamer import FileRenamer
from metrontagger.filesorter import FileSorter
from metrontagger.logging import init_logging
from metrontagger.settings import MetronTaggerSettings

if TYPE_CHECKING:
from metrontagger.settings import MetronTaggerSettings
from metrontagger.styles import Styles
from metrontagger.talker import Talker
from metrontagger.validate_ci import SchemaVersion, ValidateComicInfo
Expand All @@ -19,10 +24,10 @@
class Runner:
"""Main runner"""

def __init__(self: "Runner", config: MetronTaggerSettings) -> None:
def __init__(self: Runner, config: MetronTaggerSettings) -> None:
self.config = config

def rename_comics(self: "Runner", file_list: list[Path]) -> list[Path]:
def rename_comics(self: Runner, file_list: list[Path]) -> list[Path]:
questionary.print(
"\nStarting comic archive renaming:\n-------------------------------",
style=Styles.TITLE,
Expand Down Expand Up @@ -68,7 +73,7 @@ def rename_comics(self: "Runner", file_list: list[Path]) -> list[Path]:
file_list.extend(iter(new_file_names))
return file_list

def _export_to_zip(self: "Runner", file_list: list[Path]) -> None:
def _export_to_zip(self: Runner, file_list: list[Path]) -> None:
questionary.print("\nExporting to cbz:\n-----------------", style=Styles.TITLE)
for comic in file_list:
ca = Comic(str(comic))
Expand Down Expand Up @@ -121,7 +126,7 @@ def _validate_comic_info(file_list: list[Path], remove_ci: bool = False) -> None
style=Styles.WARNING,
)

def _sort_comic_list(self: "Runner", file_list: list[Path]) -> None:
def _sort_comic_list(self: Runner, file_list: list[Path]) -> None:
if not self.config.sort_dir:
questionary.print(
"\nUnable to sort files. No destination directory was provided.",
Expand Down Expand Up @@ -165,10 +170,10 @@ def _delete_metadata(file_list: list[Path]) -> None:
else:
questionary.print(f"no metadata in '{comic.name}'", style=Styles.WARNING)

def _has_credentials(self: "Runner") -> bool:
def _has_credentials(self: Runner) -> bool:
return bool(self.config.metron_user and self.config.metron_pass)

def _get_credentials(self: "Runner") -> bool:
def _get_credentials(self: Runner) -> bool:
answers = questionary.form(
user=questionary.text("What is your Metron username?"),
passwd=questionary.text("What is your Metron password?"),
Expand All @@ -182,7 +187,7 @@ def _get_credentials(self: "Runner") -> bool:
return True
return False

def _get_sort_dir(self: "Runner") -> bool:
def _get_sort_dir(self: Runner) -> bool:
answers = questionary.form(
dir=questionary.path("What directory should comics be sorted to?"),
save=questionary.confirm("Would you like to save this location for future use?"),
Expand Down Expand Up @@ -227,7 +232,7 @@ def _update_ci_xml(file_list: list[DuplicateIssue]) -> None:
# No metadata
questionary.print(f"No metadata in {comic}. Skipping...")

def _remove_duplicates(self: "Runner", file_list: list[Path]) -> None:
def _remove_duplicates(self: Runner, file_list: list[Path]) -> None:
dups_obj = Duplicates(file_list)
distinct_hashes = dups_obj.get_distinct_hashes()
if not questionary.confirm(
Expand Down Expand Up @@ -288,7 +293,7 @@ def _remove_duplicates(self: "Runner", file_list: list[Path]) -> None:
).ask():
self._update_ci_xml(duplicates_lst)

def run(self: "Runner") -> None: # noqa: C901, PLR0912
def run(self: Runner) -> None: # noqa: C901, PLR0912
if not (file_list := get_recursive_filelist(self.config.path)):
questionary.print("No files to process. Exiting.", style=Styles.WARNING)
sys.exit(0)
Expand Down
8 changes: 5 additions & 3 deletions metrontagger/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Class to handle project settings"""

from __future__ import annotations

import configparser
import platform
from os import environ
Expand All @@ -11,7 +13,7 @@
class MetronTaggerSettings:
"""Class to handle project settings"""

def __init__(self: "MetronTaggerSettings", config_dir: str | None = None) -> None:
def __init__(self: MetronTaggerSettings, config_dir: str | None = None) -> None:
# Metron credentials
self.metron_user: str = ""
self.metron_pass: str = ""
Expand Down Expand Up @@ -59,7 +61,7 @@ def get_settings_folder() -> Path:
windows_path = PurePath(environ["APPDATA"]).joinpath("MetronTagger")
return Path(windows_path)

def load(self: "MetronTaggerSettings") -> None:
def load(self: MetronTaggerSettings) -> None:
"""Method to retrieve a users settings"""
self.config.read(self.settings_file)

Expand Down Expand Up @@ -87,7 +89,7 @@ def load(self: "MetronTaggerSettings") -> None:
"rename_use_smart_string_cleanup",
)

def save(self: "MetronTaggerSettings") -> None:
def save(self: MetronTaggerSettings) -> None:
"""Method to save a users settings"""
if not self.config.has_section("metron"):
self.config.add_section("metron")
Expand Down
Loading

0 comments on commit 9d1cdc0

Please sign in to comment.