diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 869ce66..b0bac72 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,19 +27,23 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python3 -m pip install --upgrade pip setuptools_scm wheel + python3 -m pip install -r requirements_dev.txt python3 -m pip install -r requirements.txt + for requirement in requirements-*.txt; do + python3 -m pip install -r "$requirement" + done + shell: bash - name: Install Windows dependencies run: | choco install -y zip if: runner.os == 'Windows' - name: build run: | + make pydist make dist - # TODO: Add a step to comment on a pull request with links to the artifacts - name: Archive production artifacts uses: actions/upload-artifact@v2 - if: "runner.os != 'linux'" # linux currently has a segfault when running on latest fedora + if: runner.os != 'Linux' # linux binary currently has a segfault when running on latest fedora with: name: "${{ format('ComicTagger-{0}', runner.os) }}" path: dist/*.zip @@ -51,3 +55,9 @@ jobs: prerelease: "${{ contains(github.ref, '-') }}" # alpha-releases should be 1.3.0-alpha.x full releases should be 1.3.0 draft: true files: dist/*.zip + - name: "Publish distribution 📦 to PyPI" + if: startsWith(github.ref, 'refs/tags/') && runner.os == 'Linux' + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: piprelease diff --git a/.gitignore b/.gitignore index 65fb80f..9840800 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,157 @@ -/.idea/ -/nbproject/ -/dist -*.pyc -/.vscode -venv -*.o -*.a -*.so -build/ +# generated by setuptools_scm ctversion.py -.eggs \ No newline at end of file + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion + +*.iml + +## Directory-based project format: +.idea/ + +### Other editors +.*.swp +nbproject/ +.vscode + +comictaggerlib/_version.py +*.exe +*.zip + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/.travis.yml b/.travis.yml index c5dec61..99a9ce0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,9 @@ matrix: before_install: - if [ "$TRAVIS_OS_NAME" = "windows" ]; then choco install -y python --version 3.7.9; choco install -y mingw zip; fi install: - - $PIP install --upgrade setuptools - - $PIP install -r requirements.txt + - $PIP install -r requirements_dev.txt + - $PIP install -r requirements-GUI.txt + - $PIP install -r requirements-CBR.txt script: - if [ "$TRAVIS_OS_NAME" != "linux" ]; then $MAKE dist ; fi diff --git a/Makefile b/Makefile index b170d0b..4c456b1 100644 --- a/Makefile +++ b/Makefile @@ -29,19 +29,19 @@ clean: $(MAKE) -C mac clean rm -rf build rm -rf comictaggerlib/ui/__pycache__ - rm comitaggerlib/ctversion.py + rm comictaggerlib/ctversion.py pydist: make clean mkdir -p piprelease rm -f comictagger-$(VERSION_STR).zip - $(PYTHON) setup.py sdist --formats=zip #,gztar - mv dist/comictagger-$(VERSION_STR).zip piprelease + $(PYTHON) setup.py sdist --formats=gztar + mv dist/comictagger-$(VERSION_STR).tar.gz piprelease rm -rf comictagger.egg-info dist upload: $(PYTHON) setup.py register - $(PYTHON) setup.py sdist --formats=zip upload + $(PYTHON) setup.py sdist --formats=gztar upload dist: $(PIP) install . diff --git a/README.md b/README.md index 574175f..cf41c2c 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,14 @@ Just unzip the archive in any folder and run, no additional installation steps a A pip package is provided, you can install it with: ``` - $ pip install comictagger + $ pip3 install comictagger[GUI] ``` ### From source - 1. ensure you have a recent version of python3 and setuptools installed - 2. clone this repository `git clone https://github.com/comictagger/comictagger.git` - 3. `pip install -r requirements.txt` - 4. `python comictagger.py` \ No newline at end of file + 1. Ensure you have a recent version of python3 installed + 2. Clone this repository `git clone https://github.com/comictagger/comictagger.git` + 3. `pip3 install -r requirements_dev.txt` + 4. Optionally install the GUI `pip3 install -r requirements-GUI.txt` + 5. Optionally install CBR support `pip3 install -r requirements-CBR.txt` + 6. `python3 comictagger.py` diff --git a/comicapi/comicarchive.py b/comicapi/comicarchive.py index 11ecd86..d5a39c5 100644 --- a/comicapi/comicarchive.py +++ b/comicapi/comicarchive.py @@ -24,7 +24,7 @@ import platform import time import io -from natsort import natsorted +import natsort from PyPDF2 import PdfFileReader from unrar.cffi import rarfile try: @@ -809,13 +809,9 @@ class ComicArchive: # about case-sensitivity! if sort_list: def keyfunc(k): - # hack to account for some weird scanner ID pages - # basename=os.path.split(k)[1] - # if basename < '0': - # k = os.path.join(os.path.split(k)[0], "z" + basename) return k.lower() - files = natsorted(files, key=keyfunc, signed=False) + files = natsort.natsorted(files, alg=natsort.ns.IC | natsort.ns.I) # make a sub-list of image files self.page_list = [] diff --git a/mac/Makefile b/mac/Makefile index 6a1d7fb..0ea166b 100644 --- a/mac/Makefile +++ b/mac/Makefile @@ -5,7 +5,7 @@ TAGGER_BASE ?= ../ TAGGER_SRC := $(TAGGER_BASE)/comictaggerlib APP_NAME := ComicTagger -VERSION_STR := $(shell python setup.py --version) +VERSION_STR := $(shell cd .. && python setup.py --version) MAC_BASE := $(TAGGER_BASE)/mac DIST_DIR := $(MAC_BASE)/dist diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..91b7476 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 150 + +[tool.isort] +line_length = 150 + +[build-system] +requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +write_to = "comictaggerlib/ctversion.py" +local_scheme = "no-local-version" diff --git a/requirements-CBR.txt b/requirements-CBR.txt new file mode 100644 index 0000000..0e3a2cd --- /dev/null +++ b/requirements-CBR.txt @@ -0,0 +1 @@ +unrar-cffi>=0.2.2 diff --git a/requirements-GUI.txt b/requirements-GUI.txt new file mode 100644 index 0000000..362ae7b --- /dev/null +++ b/requirements-GUI.txt @@ -0,0 +1 @@ +PyQt5<=5.15.3 diff --git a/requirements.txt b/requirements.txt index b7d1d2e..792c868 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,6 @@ -configparser -requests beautifulsoup4 >= 4.1 -natsort==3.5.2 PyPDF2==1.24 +configparser +natsort pillow>=4.3.0 -PyQt5>=5.12.2 -pyinstaller==4.3 -unrar-cffi==0.2.2 +requests diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..6d4e0a3 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,4 @@ +pyinstaller==4.3 +setuptools>=42 +setuptools_scm[toml]>=3.4 +wheel diff --git a/setup.py b/setup.py index 813e0ff..50f1ac2 100644 --- a/setup.py +++ b/setup.py @@ -6,174 +6,76 @@ # It seems that post installation tweaks are broken by wheel files. # Kept here for further research -from __future__ import print_function -from setuptools import setup -from setuptools import dist -from setuptools import Command -import setuptools.command.build_py -import setuptools.command.install -import subprocess +import glob import os -import sys -import shutil -import platform -import tempfile -python_requires='>=3', +from setuptools import setup -with open('requirements.txt') as f: - required = f.read().splitlines() -with open('README.md') as f: - long_description = f.read() +def read(fname): + """ + Read the contents of a file. + Parameters + ---------- + fname : str + Path to file. + Returns + ------- + str + File contents. + """ + with open(os.path.join(os.path.dirname(__file__), fname)) as f: + return f.read() -platform_data_files = [] -""" -if platform.system() in [ "Windows" ]: - required.append("winshell") +install_requires = read("requirements.txt").splitlines() -# Some files to install on different platforms +# Dynamically determine extra dependencies +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) + extras_require[name] = read(extra_req_file).splitlines() -if platform.system() == "Linux": - linux_desktop_shortcut = "/usr/local/share/applications/ComicTagger.desktop" - platform_data_files = [("/usr/local/share/applications", - ["desktop-integration/linux/ComicTagger.desktop"]), - ("/usr/local/share/comictagger", - ["comictaggerlib/graphics/app.png"]), - ] - -if platform.system() == "Windows": - win_desktop_folder = os.path.join(os.environ["USERPROFILE"], "Desktop") - win_appdata_folder = os.path.join(os.environ["APPDATA"], "comictagger") - win_desktop_shortcut = os.path.join(win_desktop_folder, "ComicTagger-pip.lnk") - platform_data_files = [(win_desktop_folder, - ["desktop-integration/windows/ComicTagger-pip.lnk"]), - (win_appdata_folder, - ["windows/app.ico"]), - ] +# If there are any extras, add a catch-all case that includes everything. +# This assumes that entries in extras_require are lists (not single strings), +# and that there are no duplicated packages across the extras. +if extras_require: + extras_require["all"] = sorted({x for v in extras_require.values() for x in v}) -if platform.system() == "Darwin": - mac_app_folder = "/Applications" - ct_app_name = "ComicTagger-pip.app" - mac_app_infoplist = os.path.join(mac_app_folder, ct_app_name, "Contents", "Info.plist") - mac_app_main = os.path.join(mac_app_folder, ct_app_name, "MacOS", "main.sh") - mac_python_link = os.path.join(mac_app_folder, ct_app_name, "MacOS", "ComicTagger") - platform_data_files = [(os.path.join(mac_app_folder, ct_app_name, "Contents"), - ["desktop-integration/mac/Info.plist"]), - (os.path.join(mac_app_folder, ct_app_name, "Contents/Resources"), - ["mac/app.icns"]), - (os.path.join(mac_app_folder, ct_app_name, "Contents/MacOS"), - ["desktop-integration/mac/main.sh", - "desktop-integration/mac/ComicTagger"]), - ] -def fileTokenReplace(filename, token, replacement): - with open(filename, "rt") as fin: - fd, tmpfile = tempfile.mkstemp() - with open(tmpfile, "wt") as fout: - for line in fin: - fout.write(line.replace('%%{}%%'.format(token), replacement)) - os.close(fd) - # fix permissions of temp file - os.chmod(tmpfile, 420) #Octal 0o644 - os.rename(tmpfile, filename) - -def postInstall(scripts_folder): - entry_point_script = os.path.join(scripts_folder, "comictagger") - - if platform.system() == "Windows": - # doctor the shortcut for this windows system after deployment - import winshell - winshell.CreateShortcut( - Path=os.path.abspath(win_desktop_shortcut), - Target=entry_point_script + ".exe", - Icon=(os.path.join(win_appdata_folder, 'app.ico'), 0), - Description="Launch ComicTagger as installed by PIP" - ) - - if platform.system() == "Linux": - # doctor the script path in the desktop file - fileTokenReplace(linux_desktop_shortcut, - "CTSCRIPT", - entry_point_script) - - if platform.system() == "Darwin": - # doctor the plist app version - fileTokenReplace(mac_app_infoplist, - "CTVERSION", - comictaggerlib.ctversion.version) - # doctor the script path in main.sh - fileTokenReplace(mac_app_main, - "CTSCRIPT", - entry_point_script) - # Make the launcher script executable - os.chmod(mac_app_main, 509) #Octal 0o775 - - # Final install step: create a symlink to Python OS X application - punt = False - pythonpath,top = os.path.split(os.path.realpath(sys.executable)) - while top: - if 'Resources' in pythonpath: - pass - elif os.path.exists(os.path.join(pythonpath,'Resources')): - break - pythonpath,top = os.path.split(pythonpath) - else: - print("Failed to find a Resources directory associated with ", str(sys.executable)) - punt = True - - if not punt: - pythonapp = os.path.join(pythonpath, 'Resources','Python.app','Contents','MacOS','Python') - if not os.path.exists(pythonapp): - print("Failed to find a Python app in ", str(pythonapp)) - punt = True - - # remove the placeholder - os.remove(mac_python_link) - if not punt: - os.symlink(pythonapp, mac_python_link) - else: - # We failed, but we can still be functional - os.symlink(sys.executable, mac_python_link) -""" - -setup(name="comictagger", - install_requires=required, - description="A cross-platform GUI/CLI app for writing metadata to comic archives", - author="ComicTagger team", - author_email="comictagger@gmail.com", - url="https://github.com/comictagger/comictagger", - packages=["comictaggerlib", "comicapi"], - package_data={ - 'comictaggerlib': ['ui/*', 'graphics/*'], - }, - entry_points=dict(console_scripts=['comictagger=comictaggerlib.main:ctmain']), - data_files=platform_data_files, - setup_requires=[ - "setuptools_scm" - ], - use_scm_version={ - 'write_to': 'comictaggerlib/ctversion.py' - }, - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Environment :: Win32 (MS Windows)", - "Environment :: MacOS X", - "Environment :: X11 Applications :: Qt", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Topic :: Utilities", - "Topic :: Other/Nonlisted Topic", - "Topic :: Multimedia :: Graphics" - ], - keywords=['comictagger', 'comics', 'comic', 'metadata', 'tagging', 'tagger'], - license="Apache License 2.0", - long_description=long_description, - long_description_content_type="text/markdown" +setup( + name="comictagger", + install_requires=install_requires, + extras_require=extras_require, + python_requires=">=3", + description="A cross-platform GUI/CLI app for writing metadata to comic archives", + author="ComicTagger team", + author_email="comictagger@gmail.com", + url="https://github.com/comictagger/comictagger", + packages=["comictaggerlib", "comicapi"], + package_data={ + "comictaggerlib": ["ui/*", "graphics/*"], + }, + entry_points=dict(console_scripts=["comictagger=comictaggerlib.main:ctmain"]), + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Environment :: Win32 (MS Windows)", + "Environment :: MacOS X", + "Environment :: X11 Applications :: Qt", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Topic :: Utilities", + "Topic :: Other/Nonlisted Topic", + "Topic :: Multimedia :: Graphics", + ], + keywords=["comictagger", "comics", "comic", "metadata", "tagging", "tagger"], + license="Apache License 2.0", + long_description=read("README.md"), + long_description_content_type='text/markdown' )