From e70c47d12af8f0bb5b5469ac50b993ea4b20ed24 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Sat, 22 Apr 2023 22:00:26 -0700 Subject: [PATCH] Make PyICU optional Update README.md --- comicapi/comicarchive.py | 3 +-- comicapi/utils.py | 30 ++++++++++++++++++++--- comictagger.py | 10 -------- comictagger.spec | 2 +- comictaggerlib/__main__.py | 5 ++++ comictaggerlib/gui.py | 4 +-- comictaggerlib/main.py | 48 ++++++++++++++++++++++++++++++++++++ localefix.py | 50 -------------------------------------- setup.py | 2 +- tests/utils_test.py | 40 ++++++++++++++++++++++++++++++ 10 files changed, 125 insertions(+), 69 deletions(-) delete mode 100755 comictagger.py create mode 100644 comictaggerlib/__main__.py delete mode 100644 localefix.py 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..699e6d4 100644 --- a/comicapi/utils.py +++ b/comicapi/utils.py @@ -18,22 +18,46 @@ import json import logging import os import pathlib +import platform 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 = ("a", *x[1:]) + + lst.append(ret) + return tuple(lst) + + +def os_sorted(lst: Iterable) -> Iterable: + key = _custom_key + if icu_available or platform.system() == "Windows": + key = natsort.os_sort_keygen() + return sorted(lst, key=key) def combine_notes(existing_notes: str | None, new_notes: str | None, split: str) -> str: diff --git a/comictagger.py b/comictagger.py deleted file mode 100755 index 17c515d..0000000 --- a/comictagger.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import localefix -from comictaggerlib.main import App - -if __name__ == "__main__": - localefix.configure_locale() - - App().run() diff --git a/comictagger.spec b/comictagger.spec index 413c6e4..fa199b9 100644 --- a/comictagger.spec +++ b/comictagger.spec @@ -9,7 +9,7 @@ block_cipher = None a = Analysis( - ["comictagger.py"], + ["comictaggerlib/__main__.py"], pathex=[], binaries=[], datas=[], diff --git a/comictaggerlib/__main__.py b/comictaggerlib/__main__.py new file mode 100644 index 0000000..9b7d9ab --- /dev/null +++ b/comictaggerlib/__main__.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from comictaggerlib.main import main + +main() diff --git a/comictaggerlib/gui.py b/comictaggerlib/gui.py index 6f41e88..541c647 100644 --- a/comictaggerlib/gui.py +++ b/comictaggerlib/gui.py @@ -73,12 +73,12 @@ try: return True return super().event(event) -except ImportError as e: +except ImportError: def show_exception_box(log_msg: str) -> None: ... - logger.error(str(e)) + logger.exception("Qt unavailable") qt_available = False diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 3b0a6f0..ea8b070 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -17,8 +17,12 @@ from __future__ import annotations import argparse import json +import locale +import logging import logging.handlers +import os import signal +import subprocess import sys import settngs @@ -48,6 +52,49 @@ except Exception: logger.setLevel(logging.DEBUG) +def _lang_code_mac() -> str: + """ + stolen from https://github.com/mu-editor/mu + Returns the user's language preference as defined in the Language & Region + preference pane in macOS's System Preferences. + """ + + # Uses the shell command `defaults read -g AppleLocale` that prints out a + # language code to standard output. Assumptions about the command: + # - It exists and is in the shell's PATH. + # - It accepts those arguments. + # - It returns a usable language code. + # + # Reference documentation: + # - The man page for the `defaults` command on macOS. + # - The macOS underlying API: + # https://developer.apple.com/documentation/foundation/nsuserdefaults. + + lang_detect_command = "defaults read -g AppleLocale" + + status, output = subprocess.getstatusoutput(lang_detect_command) + if status == 0: + # Command was successful. + lang_code = output + else: + logging.warning("Language detection command failed: %r", output) + lang_code = "" + + return lang_code + + +def configure_locale() -> None: + if sys.platform == "darwin" and "LANG" not in os.environ: + code = _lang_code_mac() + if code != "": + os.environ["LANG"] = f"{code}.utf-8" + + locale.setlocale(locale.LC_ALL, "") + sys.stdout.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] + sys.stderr.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] + sys.stdin.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] + + def update_publishers(config: settngs.Config[settngs.Namespace]) -> None: json_file = config[0].runtime_config.user_config_dir / "publishers.json" if json_file.exists(): @@ -66,6 +113,7 @@ class App: self.config_load_success = False def run(self) -> None: + configure_locale() conf = self.initialize() self.initialize_dirs(conf.config) self.load_plugins(conf) diff --git a/localefix.py b/localefix.py deleted file mode 100644 index b7bf897..0000000 --- a/localefix.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -import locale -import logging -import os -import subprocess -import sys - - -def _lang_code_mac() -> str: - """ - stolen from https://github.com/mu-editor/mu - Returns the user's language preference as defined in the Language & Region - preference pane in macOS's System Preferences. - """ - - # Uses the shell command `defaults read -g AppleLocale` that prints out a - # language code to standard output. Assumptions about the command: - # - It exists and is in the shell's PATH. - # - It accepts those arguments. - # - It returns a usable language code. - # - # Reference documentation: - # - The man page for the `defaults` command on macOS. - # - The macOS underlying API: - # https://developer.apple.com/documentation/foundation/nsuserdefaults. - - lang_detect_command = "defaults read -g AppleLocale" - - status, output = subprocess.getstatusoutput(lang_detect_command) - if status == 0: - # Command was successful. - lang_code = output - else: - logging.warning("Language detection command failed: %r", output) - lang_code = "" - - return lang_code - - -def configure_locale() -> None: - if sys.platform == "darwin" and "LANG" not in os.environ: - code = _lang_code_mac() - if code != "": - os.environ["LANG"] = f"{code}.utf-8" - - locale.setlocale(locale.LC_ALL, "") - sys.stdout.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] - sys.stderr.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] - sys.stdin.reconfigure(encoding=sys.getdefaultencoding()) # type: ignore[attr-defined] diff --git a/setup.py b/setup.py index b2cb90a..5ff775b 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ install_requires = read("requirements.txt").splitlines() extras_require = {} extra_req_files = glob.glob("requirements-*.txt") for extra_req_file in extra_req_files: - name = os.path.splitext(extra_req_file)[0].replace("requirements-", "", 1) + name = os.path.splitext(extra_req_file)[0].removeprefix("requirements-") extras_require[name] = read(extra_req_file).splitlines() # If there are any extras, add a catch-all case that includes everything. 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")