diff --git a/README.md b/README.md index c276be0..3f3d51e 100644 --- a/README.md +++ b/README.md @@ -33,21 +33,13 @@ For details, screen-shots, and more, visit [the Wiki](https://github.com/comicta ## Installation +It is reccomended to either use the Binaries or a package manager for the best experience + ### Binaries -Windows and macOS binaries are provided in the [Releases Page](https://github.com/comictagger/comictagger/releases). +Windows, Linux and MacOS binaries are provided in the [Releases Page](https://github.com/comictagger/comictagger/releases). -Just unzip the archive in any folder and run, no additional installation steps are required. - -### PIP installation - -A pip package is provided, you can install it with: - -``` - $ pip3 install comictagger[GUI] -``` - -There are two optional dependencies GUI and CBR. You can install the optional dependencies by specifying one or more of `GUI`,`CBR` or `all` in braces e.g. `comictagger[CBR,GUI]` +Unzip the archive in any folder and run, no additional installation steps are required. ### Chocolatey installation (Windows only) @@ -55,12 +47,26 @@ A [Chocolatey package](https://community.chocolatey.org/packages/comictagger), m ```powershell choco install comictagger ``` + +### PyPi installation + +! Please note that pages may not be sorted correctly if ICU is not installed on Linux and MacOS. +If you have issues installing ICU please see the PyICU documentation [here](https://pyicu.org) + +A PyPi package is provided, you can install it with: + +``` + $ pip3 install comictagger[GUI,ICU] +``` + +There are several optional dependencies GUI, CBR, 7Z and ICU. You can install the optional dependencies by specifying one or more of `GUI`,`CBR` or `all` in braces e.g. `comictagger[CBR,GUI,ICU]` + ### From source 1. Ensure you have python 3.9 installed 2. Clone this repository `git clone https://github.com/comictagger/comictagger.git` 3. `pip3 install -r requirements_dev.txt` - 7. `pip3 install .` or `pip3 install .[GUI]` + 7. `pip3 install .` or `pip3 install .[GUI,ICU]` ## Contributors diff --git a/comicapi/comicarchive.py b/comicapi/comicarchive.py index 57bc0a7..b225bba 100644 --- a/comicapi/comicarchive.py +++ b/comicapi/comicarchive.py @@ -22,7 +22,6 @@ import shutil import sys from typing import cast -import natsort import wordninja from comicapi import filenamelexer, filenameparser, utils @@ -280,7 +279,7 @@ class ComicArchive: # seems like some archive creators are on Windows, and don't know about case-sensitivity! if sort_list: - files = cast(list[str], natsort.os_sorted(files)) + files = cast(list[str], utils.os_sorted(files)) # make a sub-list of image files self.page_list = [] diff --git a/comicapi/utils.py b/comicapi/utils.py index 797b969..f44b765 100644 --- a/comicapi/utils.py +++ b/comicapi/utils.py @@ -15,25 +15,69 @@ from __future__ import annotations import json +import locale import logging import os import pathlib import unicodedata from collections import defaultdict -from collections.abc import Mapping +from collections.abc import Iterable, Mapping from shutil import which # noqa: F401 from typing import Any +import natsort import pycountry import rapidfuzz.fuzz import comicapi.data +try: + import icu + + del icu + icu_available = True +except ImportError: + icu_available = False + logger = logging.getLogger(__name__) -class UtilsVars: - already_fixed_encoding = False +def _custom_key(tup): + lst = [] + for x in natsort.os_sort_keygen()(tup): + ret = x + if len(x) > 1 and isinstance(x[1], int) and isinstance(x[0], str) and x[0] == "": + ret = (str(x[1]), *x[1:]) + + lst.append(ret) + return tuple(lst) + + +def os_sorted(lst: Iterable) -> Iterable: + if icu_available: + raise Exception("fuck off") + return natsort.os_sorted(lst) + return sorted(lst, key=_custom_key) + + +def save_locale() -> dict[int, tuple[str | None, str | None]]: + locales: dict[int, tuple[str | None, str | None]] = { + locale.LC_ALL: (None, None), + locale.LC_COLLATE: (None, None), + locale.LC_CTYPE: (None, None), + locale.LC_MESSAGES: (None, None), + locale.LC_MONETARY: (None, None), + locale.LC_NUMERIC: (None, None), + locale.LC_TIME: (None, None), + } + for x in locales: + locales[x] = locale.getlocale(x) + return locales + + +def set_locale(locales: dict[int, tuple[str | None, str | None]]) -> None: + for x, value in locales.items(): + locale.setlocale(x, value) def combine_notes(existing_notes: str | None, new_notes: str | None, split: str) -> str: diff --git a/comictaggerlib/gui.py b/comictaggerlib/gui.py index 6f41e88..3d45285 100644 --- a/comictaggerlib/gui.py +++ b/comictaggerlib/gui.py @@ -9,14 +9,20 @@ import types import settngs +from comicapi.utils import save_locale, set_locale from comictaggerlib.graphics import graphics_path from comictalker.comictalker import ComicTalker logger = logging.getLogger("comictagger") + try: - qt_available = True + loc = save_locale() from PyQt5 import QtCore, QtGui, QtWidgets + qt_available = True + + set_locale(loc) + def show_exception_box(log_msg: str) -> None: """Checks if a QApplication instance is available and shows a messagebox with the exception message. If unavailable (non-console application), log an additional notice. diff --git a/comictaggerlib/imagefetcher.py b/comictaggerlib/imagefetcher.py index 779340f..6f23bdd 100644 --- a/comictaggerlib/imagefetcher.py +++ b/comictaggerlib/imagefetcher.py @@ -25,11 +25,15 @@ import tempfile import requests +from comicapi.utils import save_locale, set_locale from comictaggerlib import ctversion try: + loc = save_locale() from PyQt5 import QtCore, QtNetwork + set_locale(loc) + qt_available = True except ImportError: qt_available = False diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 3b0a6f0..28a4da8 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -25,6 +25,7 @@ import settngs import comicapi import comictalker +from comicapi.utils import save_locale, set_locale from comictaggerlib import cli, ctsettings from comictaggerlib.ctversion import version from comictaggerlib.log import setup_logging @@ -36,9 +37,13 @@ else: logger = logging.getLogger("comictagger") + try: + loc = save_locale() from comictaggerlib import gui + set_locale(loc) + qt_available = gui.qt_available except Exception: logger.exception("Qt unavailable") diff --git a/comictaggerlib/ui/qtutils.py b/comictaggerlib/ui/qtutils.py index c3ce3dc..dbef9f9 100644 --- a/comictaggerlib/ui/qtutils.py +++ b/comictaggerlib/ui/qtutils.py @@ -6,13 +6,17 @@ import io import logging import traceback +from comicapi.utils import save_locale, set_locale from comictaggerlib.graphics import graphics_path logger = logging.getLogger(__name__) try: + loc = save_locale() from PyQt5 import QtGui, QtWidgets + set_locale(loc) + qt_available = True except ImportError: qt_available = False diff --git a/requirements-ICU.txt b/requirements-ICU.txt new file mode 100644 index 0000000..80c4759 --- /dev/null +++ b/requirements-ICU.txt @@ -0,0 +1 @@ +pyicu; sys_platform == 'linux' or sys_platform == 'darwin' diff --git a/requirements.txt b/requirements.txt index 7a936ec..dde80d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ natsort>=8.1.0 pathvalidate pillow>=9.1.0, <10 pycountry -#pyicu; sys_platform == 'linux' or sys_platform == 'darwin' rapidfuzz>=2.12.0 requests==2.* settngs==0.6.2 diff --git a/tests/comicarchive_test.py b/tests/comicarchive_test.py index cec116a..bad70f0 100644 --- a/tests/comicarchive_test.py +++ b/tests/comicarchive_test.py @@ -12,7 +12,7 @@ from testing.filenames import datadir @pytest.mark.xfail(not comicapi.archivers.rar.rar_support, reason="rar support") -def test_getPageNameList(): +def test_get_page_name_list(): c = comicapi.comicarchive.ComicArchive(datadir / "fake_cbr.cbr") assert c.seems_to_be_a_comic_archive() pageNameList = c.get_page_name_list() diff --git a/tests/utils_test.py b/tests/utils_test.py index b5d00d7..a9bcda4 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -7,6 +7,46 @@ import pytest import comicapi.utils +def test_os_sorted(): + page_name_list = [ + "cover.jpg", + "Page1.jpeg", + "!cover.jpg", + "page4.webp", + "test/!cover.tar.gz", + "!cover.tar.gz", + "00.jpg", + "ignored.txt", + "page0.jpg", + "test/00.tar.gz", + ".ignored.jpg", + "Page3.gif", + "!cover.tar.gz", + "Page2.png", + "page10.jpg", + "!cover", + ] + + assert comicapi.utils.os_sorted(page_name_list) == [ + "!cover", + "!cover.jpg", + "!cover.tar.gz", + "!cover.tar.gz", # Depending on locale punctuation or numbers might come first (Linux, MacOS) + ".ignored.jpg", + "00.jpg", + "cover.jpg", + "ignored.txt", + "page0.jpg", + "Page1.jpeg", + "Page2.png", + "Page3.gif", + "page4.webp", + "page10.jpg", + "test/!cover.tar.gz", + "test/00.tar.gz", + ] + + def test_recursive_list_with_file(tmp_path) -> None: foo_png = tmp_path / "foo.png" foo_png.write_text("not a png")