lordwelch rewrite

This commit is contained in:
Mizaki 2024-05-10 23:51:13 +01:00
parent b761763c4c
commit 3d443e0908
16 changed files with 341 additions and 319 deletions

View File

@ -24,14 +24,12 @@ from __future__ import annotations
import copy
import dataclasses
import logging
import sys
from collections.abc import Sequence
from enum import Enum, auto
from typing import TYPE_CHECKING, Any, TypedDict, Union
from typing import TYPE_CHECKING, Any, TypedDict, Union, overload
from typing_extensions import NamedTuple, Required
from comicapi import utils
from comicapi import merge, utils
from comicapi._url import Url, parse_url
from comicapi.utils import norm_fold
@ -41,50 +39,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
class __remove(Enum):
REMOVE = auto()
REMOVE = __remove.REMOVE
if sys.version_info < (3, 11):
class StrEnum(str, Enum):
"""
Enum where members are also (and must be) strings
"""
def __new__(cls, *values: Any) -> Any:
"values must already be of type `str`"
if len(values) > 3:
raise TypeError(f"too many arguments for str(): {values!r}")
if len(values) == 1:
# it must be a string
if not isinstance(values[0], str):
raise TypeError(f"{values[0]!r} is not a string")
if len(values) >= 2:
# check that encoding argument is a string
if not isinstance(values[1], str):
raise TypeError(f"encoding must be a string, not {values[1]!r}")
if len(values) == 3:
# check that errors argument is a string
if not isinstance(values[2], str):
raise TypeError("errors must be a string, not %r" % (values[2]))
value = str(*values)
member = str.__new__(cls, value)
member._value_ = value
return member
@staticmethod
def _generate_next_value_(name: str, start: int, count: int, last_values: Any) -> str:
"""
Return the lower-cased version of the member name.
"""
return name.lower()
else:
from enum import StrEnum
REMOVE = object()
class PageType:
@ -117,10 +72,7 @@ class ImageMetadata(TypedDict, total=False):
width: str
class Credit(TypedDict):
person: str
role: str
primary: bool
Credit = merge.Credit
@dataclasses.dataclass
@ -145,12 +97,6 @@ class TagOrigin(NamedTuple):
name: str
class OverlayMode(StrEnum):
overlay = auto()
add_missing = auto()
combine = auto()
@dataclasses.dataclass
class GenericMetadata:
writer_synonyms = ("writer", "plotter", "scripter", "script")
@ -261,93 +207,19 @@ class GenericMetadata:
new_md.__post_init__()
return new_md
def credit_dedupe(self, cur: list[Credit], new: list[Credit]) -> list[Credit]:
if len(new) == 0:
return cur
if len(cur) == 0:
return new
# Create dict for deduplication
new_dict: dict[str, Credit] = {norm_fold(f"{n['person']}_{n['role']}"): n for n in new}
cur_dict: dict[str, Credit] = {norm_fold(f"{c['person']}_{c['role']}"): c for c in cur}
# Any duplicates use the 'new' value
cur_dict.update(new_dict)
return list(cur_dict.values())
def assign_dedupe(self, new: list[str] | set[str], cur: list[str] | set[str]) -> list[str] | set[str]:
"""Dedupes normalised (NFKD), casefolded values using 'new' values on collisions"""
if len(new) == 0:
return cur
if len(cur) == 0:
return new
# Create dict values for deduplication
new_dict: dict[str, str] = {norm_fold(n): n for n in new}
cur_dict: dict[str, str] = {norm_fold(c): c for c in cur}
if isinstance(cur, list):
cur_dict.update(new_dict)
return list(cur_dict.values())
if isinstance(cur, set):
cur_dict.update(new_dict)
return set(cur_dict.values())
# All else fails
return cur
def assign_overlay(self, cur: Any, new: Any) -> Any:
"""Overlay - When the 'new' object has non-None values, overwrite 'cur'(rent) with 'new'."""
if new is None:
return cur
if isinstance(new, (list, set)) and len(new) == 0:
return cur
else:
return new
def assign_add_missing(self, cur: Any, new: Any) -> Any:
"""Add Missing - Any 'cur(rent)' values that are None or an empty list/set, add 'new' non-None values"""
if new is None:
return cur
if cur is None:
return new
elif isinstance(cur, (list, set)) and len(cur) == 0:
return new
else:
return cur
def assign_combine(self, cur: Any, new: Any) -> Any:
"""Combine - Combine lists and sets (checking for dupes). All non-None values from new replace cur(rent)"""
if new is None:
return cur
if isinstance(new, (list, set)) and isinstance(cur, (list, set)):
# Check for weblinks (Url is a tuple)
if len(new) > 0 and isinstance(new, list) and isinstance(new[0], Url):
return list(set(new).union(cur))
return self.assign_dedupe(new, cur)
else:
return new
def overlay(self, new_md: GenericMetadata, mode: OverlayMode = OverlayMode.overlay) -> None:
def overlay(self, new_md: GenericMetadata, mode: merge.Mode = merge.Mode.OVERLAY) -> None:
"""Overlay a new metadata object on this one"""
assign_funcs = {
OverlayMode.overlay: self.assign_overlay,
OverlayMode.add_missing: self.assign_add_missing,
OverlayMode.combine: self.assign_combine,
}
merge_function = merge.function[mode]
def assign(cur: Any, new: Any) -> Any:
if new is REMOVE:
if isinstance(cur, (list, set)):
cur.clear()
return cur
else:
return None
return None
assign_func = assign_funcs.get(mode, self.assign_overlay)
return assign_func(cur, new)
return merge_function(cur, new)
if not new_md.is_empty:
self.is_empty = False
@ -390,13 +262,11 @@ class GenericMetadata:
self.scan_info = assign(self.scan_info, new_md.scan_info)
self.tags = assign(self.tags, new_md.tags)
self.pages = assign(self.pages, new_md.pages)
self.page_count = assign(self.page_count, new_md.page_count)
self.characters = assign(self.characters, new_md.characters)
self.teams = assign(self.teams, new_md.teams)
self.locations = assign(self.locations, new_md.locations)
self.credits = self.assign_credits(self.credits, new_md.credits)
[self.add_credit(c) for c in assign(self.credits, new_md.credits)]
self.price = assign(self.price, new_md.price)
self.is_version_of = assign(self.is_version_of, new_md.is_version_of)
@ -406,27 +276,8 @@ class GenericMetadata:
self._cover_image = assign(self._cover_image, new_md._cover_image)
self._alternate_images = assign(self._alternate_images, new_md._alternate_images)
def assign_credits_overlay(self, cur_credits: list[Credit], new_credits: list[Credit]) -> list[Credit]:
return self.credit_dedupe(cur_credits, new_credits)
def assign_credits_add_missing(self, cur_credits: list[Credit], new_credits: list[Credit]) -> list[Credit]:
# Send new_credits as cur_credits and vis-versa to keep cur_credit on duplication
return self.credit_dedupe(new_credits, cur_credits)
def assign_credits(
self, cur_credits: list[Credit], new_credits: list[Credit], mode: OverlayMode = OverlayMode.overlay
) -> list[Credit]:
if new_credits is REMOVE:
return []
assign_credit_funcs = {
OverlayMode.overlay: self.assign_credits_overlay,
OverlayMode.add_missing: self.assign_credits_add_missing,
OverlayMode.combine: self.assign_credits_overlay,
}
assign_credit_func = assign_credit_funcs.get(mode, self.assign_credits_overlay)
return assign_credit_func(cur_credits, new_credits)
self.pages = assign(self.pages, new_md.pages)
self.page_count = assign(self.page_count, new_md.page_count)
def apply_default_page_list(self, page_list: Sequence[str]) -> None:
# generate a default page list, with the first page marked as the cover
@ -471,21 +322,35 @@ class GenericMetadata:
return coverlist
def add_credit(self, person: str, role: str, primary: bool = False) -> None:
if person == "":
@overload
def add_credit(self, person: Credit) -> None: ...
@overload
def add_credit(self, person: str, role: str, primary: bool = False) -> None: ...
def add_credit(self, person: str | Credit, role: str | None = None, primary: bool = False) -> None:
credit: Credit
if isinstance(person, Credit):
credit = person
else:
assert role is not None
credit = Credit(person=person, role=role, primary=primary)
if credit.role is None:
raise TypeError("GenericMetadata.add_credit takes either a Credit object or a person name and role")
if credit.person == "":
return
credit = Credit(person=person, role=role, primary=primary)
person = norm_fold(person)
role = norm_fold(role)
person = norm_fold(credit.person)
role = norm_fold(credit.role)
# look to see if it's not already there...
found = False
for c in self.credits:
if norm_fold(c["person"]) == person and norm_fold(c["role"]) == role:
if norm_fold(c.person) == person and norm_fold(c.role) == role:
# no need to add it. just adjust the "primary" flag as needed
c["primary"] = primary
c.primary = c.primary or primary
found = True
break
@ -495,12 +360,10 @@ class GenericMetadata:
def get_primary_credit(self, role: str) -> str:
primary = ""
for credit in self.credits:
if "role" not in credit or "person" not in credit:
continue
if (primary == "" and credit["role"].casefold() == role.casefold()) or (
credit["role"].casefold() == role.casefold() and "primary" in credit and credit["primary"]
if (primary == "" and credit.role.casefold() == role.casefold()) or (
credit.role.casefold() == role.casefold() and credit.primary
):
primary = credit["person"]
primary = credit.person
return primary
def __str__(self) -> str:
@ -559,9 +422,9 @@ class GenericMetadata:
for c in self.credits:
primary = ""
if "primary" in c and c["primary"]:
if c.primary:
primary = " [P]"
add_string("credit", c["role"] + ": " + c["person"] + primary)
add_string("credit", c.role + ": " + c.person + primary)
# find the longest field name
flen = 0

128
comicapi/merge.py Normal file
View File

@ -0,0 +1,128 @@
from __future__ import annotations
import dataclasses
import sys
from collections import defaultdict
from collections.abc import Collection
from enum import Enum, auto
from typing import Any
from comicapi.utils import norm_fold
if sys.version_info < (3, 11):
class StrEnum(str, Enum):
"""
Enum where members are also (and must be) strings
"""
def __new__(cls, *values: Any) -> Any:
"values must already be of type `str`"
if len(values) > 3:
raise TypeError(f"too many arguments for str(): {values!r}")
if len(values) == 1:
# it must be a string
if not isinstance(values[0], str):
raise TypeError(f"{values[0]!r} is not a string")
if len(values) >= 2:
# check that encoding argument is a string
if not isinstance(values[1], str):
raise TypeError(f"encoding must be a string, not {values[1]!r}")
if len(values) == 3:
# check that errors argument is a string
if not isinstance(values[2], str):
raise TypeError("errors must be a string, not %r" % (values[2]))
value = str(*values)
member = str.__new__(cls, value)
member._value_ = value
return member
@staticmethod
def _generate_next_value_(name: str, start: int, count: int, last_values: Any) -> str:
"""
Return the lower-cased version of the member name.
"""
return name.lower()
else:
from enum import StrEnum
@dataclasses.dataclass
class Credit:
person: str = ""
role: str = ""
primary: bool = False
class Mode(StrEnum):
OVERLAY = auto()
ADD_MISSING = auto()
COMBINE = auto()
def merge_lists(old: Collection[Any], new: Collection[Any]) -> list[Any] | set[Any]:
"""Dedupes normalised (NFKD), casefolded values using 'new' values on collisions"""
if len(new) == 0:
return old if isinstance(old, set) else list(old)
if len(old) == 0:
return new if isinstance(new, set) else list(new)
# Create dict to preserve case
new_dict = {norm_fold(str(n)): n for n in new}
old_dict = {norm_fold(str(c)): c for c in old}
old_dict.update(new_dict)
if isinstance(old, set):
return set(old_dict.values())
return list(old_dict.values())
def combine(old: Any, new: Any) -> Any:
"""combine - Same as `overlay` except lists are merged"""
if new is None:
return old
if (
not (isinstance(new, str) or isinstance(old, str))
and isinstance(new, Collection)
and isinstance(old, Collection)
):
return merge_lists(old, new)
if isinstance(new, str) and len(new) == 0:
return old
return new
def overlay(old: Any, new: Any) -> Any:
"""overlay - When the `new` object is not empty, replace `old` with `new`."""
if new is None:
return old
if (
not (isinstance(new, str) or isinstance(old, str))
and isinstance(new, Collection)
and isinstance(old, Collection)
):
if isinstance(new, list) and len(new) > 0 and isinstance(new[0], Credit):
return merge_lists(old, new)
if len(new) == 0:
return old
return new
def add_missing(old: Any, new: Any) -> Any:
"""add_missing - When the `old` object is empty, replace `old` with `new`"""
return overlay(new, old)
function = defaultdict(
lambda: overlay,
{
Mode.OVERLAY: overlay,
Mode.ADD_MISSING: add_missing,
Mode.COMBINE: combine,
},
)

View File

@ -199,26 +199,26 @@ class CoMet(Metadata):
# loop thru credits, and build a list for each role that CoMet supports
for credit in metadata.credits:
if credit["role"].casefold() in set(GenericMetadata.writer_synonyms):
ET.SubElement(root, "writer").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.writer_synonyms):
ET.SubElement(root, "writer").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.penciller_synonyms):
ET.SubElement(root, "penciller").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.penciller_synonyms):
ET.SubElement(root, "penciller").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.inker_synonyms):
ET.SubElement(root, "inker").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.inker_synonyms):
ET.SubElement(root, "inker").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.colorist_synonyms):
ET.SubElement(root, "colorist").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.colorist_synonyms):
ET.SubElement(root, "colorist").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.letterer_synonyms):
ET.SubElement(root, "letterer").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.letterer_synonyms):
ET.SubElement(root, "letterer").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.cover_synonyms):
ET.SubElement(root, "coverDesigner").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.cover_synonyms):
ET.SubElement(root, "coverDesigner").text = str(credit.person)
if credit["role"].casefold() in set(GenericMetadata.editor_synonyms):
ET.SubElement(root, "editor").text = str(credit["person"])
if credit.role.casefold() in set(GenericMetadata.editor_synonyms):
ET.SubElement(root, "editor").text = str(credit.person)
ET.indent(root)

View File

@ -47,6 +47,12 @@ _CBILiteralType = Literal[
]
class credit(TypedDict):
person: str
role: str
primary: bool
class _ComicBookInfoJson(TypedDict, total=False):
series: str
title: str
@ -61,7 +67,7 @@ class _ComicBookInfoJson(TypedDict, total=False):
genre: str
language: str
country: str
credits: list[Credit]
credits: list[credit]
tags: list[str]
comments: str
@ -217,7 +223,7 @@ class ComicBookInfo(Metadata):
assign("language", utils.xlate(utils.get_language_from_iso(metadata.language)))
assign("country", utils.xlate(metadata.country))
assign("rating", utils.xlate_int(metadata.critical_rating))
assign("credits", metadata.credits)
assign("credits", [credit(person=c.person, role=c.role, primary=c.primary) for c in metadata.credits])
assign("tags", list(metadata.tags))
return cbi_container

View File

@ -187,26 +187,26 @@ class ComicRack(Metadata):
# first, loop thru credits, and build a list for each role that CIX
# supports
for credit in metadata.credits:
if credit["role"].casefold() in set(GenericMetadata.writer_synonyms):
credit_writer_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.writer_synonyms):
credit_writer_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.penciller_synonyms):
credit_penciller_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.penciller_synonyms):
credit_penciller_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.inker_synonyms):
credit_inker_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.inker_synonyms):
credit_inker_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.colorist_synonyms):
credit_colorist_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.colorist_synonyms):
credit_colorist_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.letterer_synonyms):
credit_letterer_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.letterer_synonyms):
credit_letterer_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.cover_synonyms):
credit_cover_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.cover_synonyms):
credit_cover_list.append(credit.person.replace(",", ""))
if credit["role"].casefold() in set(GenericMetadata.editor_synonyms):
credit_editor_list.append(credit["person"].replace(",", ""))
if credit.role.casefold() in set(GenericMetadata.editor_synonyms):
credit_editor_list.append(credit.person.replace(",", ""))
assign("Series", md.series)
assign("Number", md.issue)

View File

@ -36,14 +36,14 @@ class CBLTransformer:
lone_credit: Credit | None = None
count = 0
for c in self.metadata.credits:
if c["role"].casefold() in role_list:
if c.role.casefold() in role_list:
count += 1
lone_credit = c
if count > 1:
lone_credit = None
break
if lone_credit is not None:
lone_credit["primary"] = True
lone_credit.primary = True
return lone_credit, count
# need to loop three times, once for 'writer', 'artist', and then
@ -53,8 +53,8 @@ class CBLTransformer:
if c is None and count == 0:
c, count = set_lone_primary(["penciler", "penciller"])
if c is not None:
c["primary"] = False
self.metadata.add_credit(c["person"], "Artist", True)
c.primary = False
self.metadata.add_credit(c.person, "Artist", True)
if self.config.Metadata_Options__cbl_copy_characters_to_tags:
self.metadata.tags.update(x for x in self.metadata.characters)

View File

@ -26,10 +26,10 @@ import sys
from collections.abc import Collection
from typing import Any, TextIO
from comicapi import utils
from comicapi import merge, utils
from comicapi.comicarchive import ComicArchive
from comicapi.comicarchive import metadata_styles as md_styles
from comicapi.genericmetadata import GenericMetadata, OverlayMode
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.cbltransformer import CBLTransformer
from comictaggerlib.ctsettings import ct_ns
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
@ -259,7 +259,7 @@ class CLI:
logger.error("Failed to load metadata for %s: %s", ca.path, e)
# finally, use explicit stuff (always 'overlay' mode)
md.overlay(self.config.Runtime_Options__metadata, mode=OverlayMode.overlay)
md.overlay(self.config.Runtime_Options__metadata, mode=merge.Mode.OVERLAY)
return md

View File

@ -25,9 +25,9 @@ import subprocess
import settngs
from comicapi import utils
from comicapi import merge, utils
from comicapi.comicarchive import metadata_styles
from comicapi.genericmetadata import GenericMetadata, OverlayMode
from comicapi.genericmetadata import GenericMetadata
from comictaggerlib import ctversion
from comictaggerlib.ctsettings.settngs_namespace import SettngsNS as ct_ns
from comictaggerlib.ctsettings.types import (
@ -179,13 +179,13 @@ def register_runtime(parser: settngs.Manager) -> None:
)
parser.add_setting(
"--read-style-overlay",
type=OverlayMode,
type=merge.Mode,
help="How to overlay new metadata on the current for enabled read styles (CR, CBL, etc.)",
file=False,
)
parser.add_setting(
"--source-overlay",
type=OverlayMode,
type=merge.Mode,
help="How to overlay new metadata from a data source (CV, Metron, GCD, etc.) on to the current",
file=False,
)

View File

@ -5,8 +5,7 @@ import uuid
import settngs
from comicapi import utils
from comicapi.genericmetadata import OverlayMode
from comicapi import merge, utils
from comictaggerlib.ctsettings.settngs_namespace import SettngsNS as ct_ns
from comictaggerlib.defaults import DEFAULT_REPLACEMENTS, Replacement, Replacements
@ -27,8 +26,8 @@ def internal(parser: settngs.Manager) -> None:
parser.add_setting("install_id", default=uuid.uuid4().hex, cmdline=False)
parser.add_setting("save_data_style", default=["cbi"], cmdline=False)
parser.add_setting("load_data_style", default=["cbi"], cmdline=False)
parser.add_setting("load_data_overlay", default=OverlayMode.overlay, cmdline=False, type=OverlayMode)
parser.add_setting("source_data_overlay", default=OverlayMode.overlay, cmdline=False, type=OverlayMode)
parser.add_setting("load_data_overlay", default=merge.Mode.OVERLAY, cmdline=False, type=merge.Mode)
parser.add_setting("source_data_overlay", default=merge.Mode.OVERLAY, cmdline=False, type=merge.Mode)
parser.add_setting("last_opened_folder", default="", cmdline=False)
parser.add_setting("window_width", default=0, cmdline=False)
parser.add_setting("window_height", default=0, cmdline=False)

View File

@ -5,11 +5,11 @@ import typing
import settngs
import comicapi.genericmetadata
import comicapi.merge
import comicapi.utils
import comictaggerlib.ctsettings.types
import comictaggerlib.defaults
import comictaggerlib.resulttypes
from comicapi.genericmetadata import OverlayMode
class SettngsNS(settngs.TypedNS):
@ -37,8 +37,8 @@ class SettngsNS(settngs.TypedNS):
Runtime_Options__json: bool
Runtime_Options__type_modify: list[str]
Runtime_Options__type_read: list[str]
Runtime_Options__read_style_overlay: OverlayMode
Runtime_Options__source_overlay: OverlayMode
Runtime_Options__read_style_overlay: comicapi.merge.Mode
Runtime_Options__source_overlay: comicapi.merge.Mode
Runtime_Options__overwrite: bool
Runtime_Options__no_gui: bool
Runtime_Options__files: list[str]
@ -46,8 +46,8 @@ class SettngsNS(settngs.TypedNS):
internal__install_id: str
internal__save_data_style: list[str]
internal__load_data_style: list[str]
internal__load_data_overlay: OverlayMode
internal__source_data_overlay: OverlayMode
internal__load_data_overlay: comicapi.merge.Mode
internal__source_data_overlay: comicapi.merge.Mode
internal__last_opened_folder: str
internal__window_width: int
internal__window_height: int
@ -147,6 +147,8 @@ class Runtime_Options(typing.TypedDict):
quiet: bool
json: bool
type: list[str]
read_style_overlay: comicapi.merge.Mode
source_overlay: comicapi.merge.Mode
overwrite: bool
no_gui: bool
files: list[str]
@ -156,6 +158,8 @@ class internal(typing.TypedDict):
install_id: str
save_data_style: list[str]
load_data_style: list[str]
load_data_overlay: comicapi.merge.Mode
source_data_overlay: comicapi.merge.Mode
last_opened_folder: str
window_width: int
window_height: int

View File

@ -28,10 +28,9 @@ import settngs
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import comictaggerlib.ui.talkeruigenerator
from comicapi import utils
from comicapi import merge, utils
from comicapi.archivers.archiver import Archiver
# TODO OverlayMode changed to merge
from comicapi.genericmetadata import OverlayMode, md_test
from comicapi.genericmetadata import md_test
from comictaggerlib import ctsettings
from comictaggerlib.ctsettings import ct_ns
from comictaggerlib.ctsettings.plugin import group_for_plugin
@ -195,8 +194,7 @@ class SettingsWindow(QtWidgets.QDialog):
)
self.cbFilenameParser.clear()
self.cbFilenameParser.addItems(utils.Parser)
# TODO check
for mode in OverlayMode:
for mode in merge.Mode:
self.cbxOverlayReadStyle.addItem(mode.name.capitalize().replace("_", " "), mode.value)
self.cbxOverlaySource.addItem(mode.name.capitalize().replace("_", " "), mode.value)
self.connect_signals()
@ -570,8 +568,8 @@ class SettingsWindow(QtWidgets.QDialog):
self.cbxApplyCBLTransformOnBatchOperation.isChecked()
)
self.config[0].internal__load_data_overlay = OverlayMode[self.cbxOverlayReadStyle.currentData()]
self.config[0].internal__source_data_overlay = OverlayMode[self.cbxOverlaySource.currentData()]
self.config[0].internal__load_data_overlay = merge.Mode[self.cbxOverlayReadStyle.currentData()]
self.config[0].internal__source_data_overlay = merge.Mode[self.cbxOverlaySource.currentData()]
self.config[0].Metadata_Options__disable_cr = self.cbxDisableCR.isChecked()
# Update metadata style names if required
if self.config[0].Metadata_Options__use_short_metadata_names != self.cbxShortMetadataNames.isChecked():

View File

@ -939,12 +939,10 @@ class TaggerWindow(QtWidgets.QMainWindow):
for row, credit in enumerate(md.credits):
# if the role-person pair already exists, just skip adding it to the list
if self.is_dupe_credit(credit["role"].title(), credit["person"]):
if self.is_dupe_credit(credit.role.title(), credit.person):
continue
self.add_new_credit_entry(
row, credit["role"].title(), credit["person"], (credit["primary"] if "primary" in credit else False)
)
self.add_new_credit_entry(row, credit.role.title(), credit.person, credit.primary)
self.twCredits.setSortingEnabled(True)
self.update_metadata_credit_colors()

View File

@ -755,7 +755,7 @@
<bool>false</bool>
</property>
<property name="plainText">
<string>Overlays all the none empty values of the read style/data source (e.g. Comic Vine) metadata on top of the current metadata (i.e. file name parsing, other read style(s)).
<string>Overlays all the non-empty values of the new metadata (e.g. Comic Vine) on top of the current metadata.
Example:
(Series=batman, Issue=1, Tags=[batman,joker,robin])
@ -792,7 +792,7 @@ Example:
<item>
<widget class="QPlainTextEdit" name="addTextEdit">
<property name="plainText">
<string>Adds any metadata that is present in the read style/data source (e.g. Comic Vine) but is missing in the current metadata (i.e. file name parsing, other read style(s)).
<string>Adds any metadata that is present in the new metadata (e.g. Comic Vine) but is missing in the current metadata.
Example:
(Series=batman, Issue=1, Tags=[batman,joker,robin])
@ -829,7 +829,7 @@ Example:
<item>
<widget class="QPlainTextEdit" name="combineTextEdit">
<property name="plainText">
<string>Combine lists (tags, characters, genres, credits, etc.) replacing duplicates with the &quot;new&quot; data and all other non-empty values are replaced by the &quot;new&quot; values.
<string>Same as Overlay except lists (tags, characters, genres, credits, etc.) are combined, replacing duplicates with the &quot;new&quot; data and all other non-empty values are replaced by the &quot;new&quot; values.
Example:
(Series=batman, Issue=1, Tags=[batman,joker,robin])

View File

@ -55,23 +55,86 @@ select_details = [
# Used to test GenericMetadata.overlay
metadata = [
(
comicapi.genericmetadata.md_test.copy(),
comicapi.genericmetadata.GenericMetadata(series="test", issue="2", title="never"),
comicapi.genericmetadata.md_test.replace(series="test", issue="2", title="never"),
),
(
comicapi.genericmetadata.md_test.copy(),
comicapi.genericmetadata.GenericMetadata(),
comicapi.genericmetadata.md_test.copy(),
),
(
comicapi.genericmetadata.GenericMetadata(
credits=[
comicapi.genericmetadata.Credit(person="test", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test", role="artist", primary=True),
],
),
comicapi.genericmetadata.GenericMetadata(
credits=[
comicapi.genericmetadata.Credit(person="", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test2", role="inker", primary=False),
]
),
comicapi.genericmetadata.GenericMetadata(
credits=[
comicapi.genericmetadata.Credit(person="test", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test", role="artist", primary=True),
comicapi.genericmetadata.Credit(person="test2", role="inker", primary=False),
]
),
),
]
metadata_add = [
(
comicapi.genericmetadata.GenericMetadata(series="test", issue="1", title="test", genres={"test", "test2"}),
comicapi.genericmetadata.GenericMetadata(
series="test2", issue="2", title="test2", genres={"test3", "test4"}, issue_count=5
series="test",
title="test",
issue="1",
volume_count=1,
page_count=3,
day=3,
genres={"test", "test2"},
story_arcs=["arc"],
characters=set(),
credits=[
comicapi.genericmetadata.Credit(person="test", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test", role="artist", primary=True),
],
),
comicapi.genericmetadata.GenericMetadata(
series="test", issue="1", title="test", genres={"test", "test2"}, issue_count=5
series="test2",
title="",
issue_count=2,
page_count=2,
day=0,
genres={"fake"},
characters={"bob", "fred"},
scan_info="nothing",
credits=[
comicapi.genericmetadata.Credit(person="Bob", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test", role="artist", primary=True),
],
),
comicapi.genericmetadata.GenericMetadata(
series="test",
title="test",
issue="1",
issue_count=2,
volume_count=1,
day=3,
page_count=3,
genres={"test", "test2"},
story_arcs=["arc"],
characters={"bob", "fred"},
scan_info="nothing",
credits=[
comicapi.genericmetadata.Credit(person="test", role="writer", primary=False),
comicapi.genericmetadata.Credit(person="test", role="artist", primary=True),
comicapi.genericmetadata.Credit(person="Bob", role="writer", primary=False),
],
),
),
]
@ -80,33 +143,42 @@ metadata_combine = [
(
comicapi.genericmetadata.GenericMetadata(
series="test",
issue="1",
title="test",
issue="1",
volume_count=1,
page_count=3,
day=3,
genres={"test", "test2"},
story_arcs=["arc1"],
characters={"Bob", "fred"},
story_arcs=["arc"],
characters=set(),
web_links=[comicapi.genericmetadata.parse_url("https://my.comics.here.com")],
),
comicapi.genericmetadata.GenericMetadata(
series="test2",
title="test2",
genres={"test2", "test3", "test4"},
story_arcs=["arc1", "arc2"],
title="",
issue_count=2,
page_count=2,
day=0,
genres={"fake"},
characters={"bob", "fred"},
scan_info="nothing",
web_links=[comicapi.genericmetadata.parse_url("https://my.comics.here.com")],
),
comicapi.genericmetadata.GenericMetadata(
series="test2",
title="test",
issue="1",
title="test2",
genres={"test", "test2", "test3", "test4"},
story_arcs=["arc1", "arc2"],
issue_count=2,
volume_count=1,
day=0,
page_count=2,
genres={"fake", "test", "test2"},
story_arcs=["arc"],
characters={"bob", "fred"},
scan_info="nothing",
web_links=[comicapi.genericmetadata.parse_url("https://my.comics.here.com")],
),
),
]
metadata_dedupe_set = [
(
comicapi.genericmetadata.GenericMetadata(characters={"Macintosh", "Søren Kierkegaard", "Barry"}),
comicapi.genericmetadata.GenericMetadata(characters={"MacIntosh", "Soren Kierkegaard"}),
@ -114,9 +186,6 @@ metadata_dedupe_set = [
characters={"MacIntosh", "Soren Kierkegaard", "Søren Kierkegaard", "Barry"}
),
),
]
metadata_dedupe_list = [
(
comicapi.genericmetadata.GenericMetadata(story_arcs=["arc 1", "arc2", "arc 3"]),
comicapi.genericmetadata.GenericMetadata(story_arcs=["Arc 1", "Arc2"]),
@ -146,7 +215,10 @@ credits = [
(comicapi.genericmetadata.md_test, "writeR", "Dara Naraghi"),
(
comicapi.genericmetadata.md_test.replace(
credits=[{"person": "Dara Naraghi", "role": "writer"}, {"person": "Dara Naraghi", "role": "writer"}]
credits=[
comicapi.genericmetadata.Credit(person="Dara Naraghi", role="writer"),
comicapi.genericmetadata.Credit(person="Dara Naraghi", role="writer"),
]
),
"writeR",
"Dara Naraghi",

View File

@ -5,8 +5,8 @@ import textwrap
import pytest
import comicapi.genericmetadata
import testing.comicdata as cd
from comicapi.genericmetadata import OverlayMode
import comicapi.merge
import testing.comicdata
def test_apply_default_page_list(tmp_path):
@ -18,71 +18,25 @@ def test_apply_default_page_list(tmp_path):
assert isinstance(md.pages[0]["image_index"], int)
@pytest.mark.parametrize("replaced, expected", cd.metadata)
def test_metadata_overlay(md: comicapi.genericmetadata.GenericMetadata, replaced, expected):
md.overlay(replaced)
@pytest.mark.parametrize("md, new, expected", testing.comicdata.metadata)
def test_metadata_overlay(md, new, expected):
md.overlay(new, comicapi.merge.Mode.OVERLAY)
assert md == expected
@pytest.mark.parametrize("md, new, expected", cd.metadata_add)
@pytest.mark.parametrize("md, new, expected", testing.comicdata.metadata_add)
def test_metadata_overlay_add_missing(md, new, expected):
md.overlay(new, OverlayMode.add_missing)
md.overlay(new, comicapi.merge.Mode.ADD_MISSING)
assert md == expected
@pytest.mark.parametrize("md, new, expected", cd.metadata_combine)
@pytest.mark.parametrize("md, new, expected", testing.comicdata.metadata_combine)
def test_metadata_overlay_combine(md, new, expected):
md.overlay(new, OverlayMode.combine)
md.overlay(new, comicapi.merge.Mode.COMBINE)
assert md == expected
@pytest.mark.parametrize("md, new, expected", cd.metadata_dedupe_set)
def test_assign_dedupe_set(md, new, expected):
md.overlay(new, OverlayMode.combine)
assert md == expected
@pytest.mark.parametrize("md, new, expected", cd.metadata_dedupe_list)
def test_assign_dedupe_list(md, new, expected):
md.overlay(new, OverlayMode.combine)
assert md == expected
def test_assign_credits_overlay():
md = comicapi.genericmetadata.GenericMetadata()
md.add_credit(person="test", role="writer", primary=False)
md.add_credit(person="test", role="artist", primary=True)
md_new = comicapi.genericmetadata.GenericMetadata()
md_new.add_credit(person="", role="writer")
md_new.add_credit(person="test2", role="inker")
expected = comicapi.genericmetadata.GenericMetadata()
expected.add_credit(person="test", role="writer", primary=False)
expected.add_credit(person="test", role="artist", primary=True)
expected.add_credit(person="test2", role="inker")
assert md.assign_credits_overlay(md.credits, md_new.credits) == expected.credits
def test_assign_credits_add_missing():
md = comicapi.genericmetadata.GenericMetadata()
md.add_credit(person="test", role="writer", primary=False)
md.add_credit(person="test", role="artist", primary=True)
md_new = comicapi.genericmetadata.GenericMetadata()
md_new.add_credit(person="Bob", role="writer")
md_new.add_credit(person="test", role="artist", primary=True)
expected = comicapi.genericmetadata.GenericMetadata()
expected.add_credit(person="Bob", role="writer")
expected.add_credit(person="test", role="artist", primary=True)
expected.add_credit(person="test", role="writer", primary=False)
assert md.assign_credits_add_missing(md.credits, md_new.credits) == expected.credits
def test_add_credit():
md = comicapi.genericmetadata.GenericMetadata()
@ -98,7 +52,7 @@ def test_add_credit_primary():
assert md.credits == [comicapi.genericmetadata.Credit(person="test", role="writer", primary=True)]
@pytest.mark.parametrize("md, role, expected", cd.credits)
@pytest.mark.parametrize("md, role, expected", testing.comicdata.credits)
def test_get_primary_credit(md, role, expected):
assert md.get_primary_credit(role) == expected

View File

@ -54,11 +54,11 @@ def test_save(
# unrelated to comicvine need to be re-worked
md_saved.credits.insert(
1,
{
"person": "Esteve Polls",
"primary": False,
"role": "Writer",
},
comicapi.genericmetadata.Credit(
person="Esteve Polls",
primary=False,
role="Writer",
),
)
# Validate that we got the correct metadata back