diff --git a/.github/workflows/comment-bot.yml b/.github/workflows/comment-bot.yml index 420c49b..0989a29 100644 --- a/.github/workflows/comment-bot.yml +++ b/.github/workflows/comment-bot.yml @@ -1,48 +1,17 @@ +# runs on any comment matching the format `/tag ` name: Comment Bot on: issue_comment: {types: [created]} pull_request_review_comment: {types: [created]} jobs: - tag: # /tag - if: startsWith(github.event.comment.body, '/tag ') + tag: runs-on: ubuntu-latest + permissions: {contents: write, pull-requests: write, issues: write} steps: - - uses: actions/checkout@v3 - - name: React Seen - uses: actions/github-script@v6 + - uses: actions/checkout@v4 with: - script: | - const perm = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, repo: context.repo.repo, - username: context.payload.comment.user.login}) - post = (context.eventName == "issue_comment" - ? github.rest.reactions.createForIssueComment - : github.rest.reactions.createForPullRequestReviewComment) - if (!["admin", "write"].includes(perm.data.permission)){ - post({ - owner: context.repo.owner, repo: context.repo.repo, - comment_id: context.payload.comment.id, content: "laugh"}) - throw "Permission denied for user " + context.payload.comment.user.login - } - post({ - owner: context.repo.owner, repo: context.repo.repo, - comment_id: context.payload.comment.id, content: "eyes"}) - - name: Tag Commit - run: | - git clone https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} repo - git -C repo tag $(echo "$BODY" | awk '{print $2" "$3}') - git -C repo push --tags - rm -rf repo - env: - BODY: ${{ github.event.comment.body }} - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - - name: React Success - uses: actions/github-script@v6 + fetch-depth: 0 + token: ${{ secrets.GH_TOKEN || github.token }} + - uses: casperdcl/comment-bot@v1 with: - script: | - post = (context.eventName == "issue_comment" - ? github.rest.reactions.createForIssueComment - : github.rest.reactions.createForPullRequestReviewComment) - post({ - owner: context.repo.owner, repo: context.repo.repo, - comment_id: context.payload.comment.id, content: "rocket"}) + token: ${{ secrets.GH_TOKEN || github.token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d07f639..32e346f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,60 +4,22 @@ on: pull_request: schedule: [{cron: '3 2 1 * *'}] # M H d m w (monthly at 2:03) jobs: - check: - if: github.event_name != 'pull_request' || !contains('OWNER,MEMBER,COLLABORATOR', github.event.pull_request.author_association) - name: Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Prepare cache - run: echo "PYSHA=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - - uses: actions/cache@v3 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ env.PYSHA }}|${{ hashFiles('.pre-commit-config.yaml') }} - - name: Dependencies - run: pip install -U pre-commit - - uses: reviewdog/action-setup@v1 - - if: github.event_name == 'push' || github.event_name == 'pull_request' - name: Comment - run: | - if [[ $EVENT == pull_request ]]; then - REPORTER=github-pr-review - else - REPORTER=github-check - fi - pre-commit run -a todo | reviewdog -efm="%f:%l: %m" -name=TODO -tee -reporter=$REPORTER -filter-mode nofilter - pre-commit run -a flake8 | reviewdog -f=pep8 -name=flake8 -tee -reporter=$REPORTER -filter-mode nofilter - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - EVENT: ${{ github.event_name }} - - name: Lint - run: pre-commit run -a --show-diff-on-failure test: if: github.event_name != 'pull_request' || !contains('OWNER,MEMBER,COLLABORATOR', github.event.pull_request.author_association) name: py${{ matrix.python }}-${{ matrix.os }} strategy: matrix: os: [ubuntu] - python: [3.7, 3.8, 3.9, '3.10', 3.11] + python: [3.7, 3.8, 3.9, '3.10', 3.11, 3.12] include: - - os: macos - python: 3.11 - - os: windows - python: 3.11 + - {os: macos, python: 3.12} + - {os: windows, python: 3.12} runs-on: ${{ matrix.os }}-latest - defaults: - run: - shell: bash + defaults: {run: {shell: bash}} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + with: {fetch-depth: 0} + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install @@ -79,7 +41,7 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Coveralls Finished @@ -90,13 +52,14 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} deploy: name: Deploy - needs: [check, test] + needs: test runs-on: ubuntu-latest + environment: pypi + permissions: {contents: write, id-token: write, packages: write} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + with: {fetch-depth: 0} + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install @@ -107,7 +70,6 @@ jobs: - id: dist uses: casperdcl/deploy-pypi@v2 with: - password: ${{ secrets.TWINE_PASSWORD }} gpg_key: ${{ secrets.GPG_KEY }} upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - id: collect_assets diff --git a/.meta/requirements-build.txt b/.meta/requirements-build.txt index 5e3fc70..13da5bd 100644 --- a/.meta/requirements-build.txt +++ b/.meta/requirements-build.txt @@ -1,3 +1,3 @@ py-make>=0.1.0 twine -wheel +build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9a2654e..3a548bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -35,10 +35,11 @@ repos: additional_dependencies: - pytest-timeout - argopt + - pyyaml - tabulate - tqdm - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 7.1.1 hooks: - id: flake8 args: [-j8] @@ -48,14 +49,15 @@ repos: - flake8-comprehensions - flake8-debugger - flake8-isort + - flake8-pyproject - flake8-string-format - repo: https://github.com/google/yapf - rev: v0.32.0 + rev: v0.40.2 hooks: - id: yapf args: [-i] additional_dependencies: [toml] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort diff --git a/Dockerfile b/Dockerfile index d2f333a..a581151 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-alpine +FROM python:3.12-alpine RUN apk update && apk add --no-cache git COPY dist/*.whl . RUN pip install -U $(ls *.whl)[full] && rm *.whl diff --git a/LICENCE b/LICENCE index 7315c38..7c5a645 100644 --- a/LICENCE +++ b/LICENCE @@ -1,5 +1,5 @@ * files: * - MPLv2.0 2016-2023 (c) Casper da Costa-Luis + MPLv2.0 2016-2024 (c) Casper da Costa-Luis [casperdcl](https://github.com/casperdcl). diff --git a/Makefile b/Makefile index 7b2f06a..1aa960e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# IMPORTANT: for compatibility with `python setup.py make [alias]`, ensure: +# IMPORTANT: for compatibility with `python -m pymake [alias]`, ensure: # 1. Every alias is preceded by @[+]make (eg: @make alias) # 2. A maximum of one @make alias or command per line # see: https://github.com/tqdm/py-make/issues/1 @@ -29,7 +29,7 @@ run help: - @python setup.py make -p + @python -m pymake -p alltests: @+make testcoverage @@ -51,8 +51,7 @@ pytest: testsetup: @make gitfame/git-fame.1 - python setup.py check --metadata --restructuredtext --strict - python setup.py make none + @make help testcoverage: @make coverclean @@ -91,29 +90,26 @@ coverclean: @+python -c "import shutil; shutil.rmtree('gitfame/__pycache__', True)" clean: @+python -c "import os, glob; [os.remove(i) for i in glob.glob('*.py[co]')]" - @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.py[co]')]" - @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.c')]" - @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.so')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tests/*.py[co]')]" + @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.py[co]')]" toxclean: @+python -c "import shutil; shutil.rmtree('.tox', True)" install: - python setup.py install + python -m pip install . install_dev: - python setup.py develop --uninstall - python setup.py develop + python -m pip install -e . install_build: python -m pip install -r .meta/requirements-build.txt build: @make prebuildclean @make testsetup - python setup.py sdist bdist_wheel - # python setup.py bdist_wininst + python -m build + python -m twine check dist/* pypi: - twine upload dist/* + python -m twine upload dist/* buildupload: @make build diff --git a/gitfame/_gitfame.py b/gitfame/_gitfame.py index 1ca3fb1..490b4b8 100755 --- a/gitfame/_gitfame.py +++ b/gitfame/_gitfame.py @@ -480,13 +480,15 @@ def main(args=None): log.debug(args) if args.manpath is not None: import sys - from os import path - from shutil import copyfile + from pathlib import Path - from pkg_resources import resource_filename - fi = resource_filename(__name__, 'git-fame.1') - fo = path.join(args.manpath, 'git-fame.1') - copyfile(fi, fo) + try: # py<3.9 + import importlib_resources as resources + except ImportError: + from importlib import resources + fi = resources.files('gitfame') / 'git-fame.1' + fo = Path(args.manpath) / 'git-fame.1' + fo.write_bytes(fi.read_bytes()) log.info("written:%s", fo) sys.exit(0) diff --git a/gitfame/_utils.py b/gitfame/_utils.py index 81508e1..61cd5d2 100644 --- a/gitfame/_utils.py +++ b/gitfame/_utils.py @@ -113,7 +113,7 @@ def Max(it, empty_default=0): try: return max(it) except ValueError as e: - if 'empty sequence' in str(e): + if 'empty' in str(e): return empty_default raise # pragma: no cover diff --git a/pyproject.toml b/pyproject.toml index ca661d3..18e2613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,128 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "gitfame/_dist_ver.py" write_to_template = "__version__ = '{version}'\n" +# py>3.7: +# version_file = "gitfame/_dist_ver.py" +# version_file_template = "__version__ = '{version}'\n" + +[tool.setuptools.packages.find] +exclude = ["tests"] + +[project.urls] +repository = "https://github.com/casperdcl/git-fame" +changelog = "https://github.com/casperdcl/git-fame/releases" + +[project] +name = "git-fame" +dynamic = ["version"] +authors = [{name = "Casper da Costa-Luis", email = "casper.dcl@physics.org"}] +description = "Pretty-print `git` repository collaborators sorted by contributions" +readme = "README.rst" +requires-python = ">=3.7" +keywords = ["git", "blame", "git-blame", "git-log", "code-analysis", "cost", "loc", "author", "commit", "shortlog", "ls-files"] +license = {text = "MPL-2.0"} +# Trove classifiers (https://pypi.org/pypi?%3Aaction=list_classifiers) +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: MacOS X", + "Environment :: Other Environment", + "Environment :: Win32 (MS Windows)", + "Environment :: X11 Applications", + "Framework :: IPython", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Other Audience", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Operating System :: MacOS", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft", + "Operating System :: Microsoft :: MS-DOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: POSIX :: BSD", + "Operating System :: POSIX :: BSD :: FreeBSD", + "Operating System :: POSIX :: Linux", + "Operating System :: POSIX :: SunOS/Solaris", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Implementation", + "Programming Language :: Python :: Implementation :: IronPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Unix Shell", + "Topic :: Desktop Environment", + "Topic :: Education :: Computer Aided Instruction (CAI)", + "Topic :: Education :: Testing", + "Topic :: Office/Business", + "Topic :: Other/Nonlisted Topic", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Pre-processors", + "Topic :: Software Development :: User Interfaces", + "Topic :: System :: Installation/Setup", + "Topic :: System :: Logging", + "Topic :: System :: Monitoring", + "Topic :: System :: Shells", + "Topic :: Terminals", + "Topic :: Utilities"] +dependencies = [ + "argopt>=0.3.5", + 'importlib_resources; python_version < "3.9"', + "tabulate", + "tqdm>=4.44.0"] + +[project.optional-dependencies] +dev = ["pytest>=6", "pytest-cov", "pytest-timeout", "pytest-xdist"] +yaml = ["pyyaml"] +tabulate = [] +full = ["pyyaml"] + +[project.scripts] +git-fame = "gitfame:main" + +[tool.flake8] +max_line_length = 99 +extend_ignore = ["E261"] +exclude = [".git", "__pycache__", "build", "dist", ".eggs", ".tox"] + +[tool.yapf] +spaces_before_comment = [15, 20] +arithmetic_precedence_indication = true +allow_split_before_dict_value = false +coalesce_brackets = true +column_limit = 99 +each_dict_entry_on_separate_line = false +space_between_ending_comma_and_closing_bracket = false +split_before_named_assigns = false +split_before_closing_bracket = false +blank_line_before_nested_class_or_def = false + +[tool.isort] +line_length = 99 +known_first_party = ["gitfame", "tests"] + +[tool.pytest.ini_options] +timeout = 30 +log_level = "INFO" +python_files = ["tests_*.py"] +testpaths = ["tests"] +addopts = "-v --tb=short -rxs -W=error --durations=0 --durations-min=0.1" + +[tool.coverage.run] +branch = true +omit = ["tests/*"] +relative_files = true +[tool.coverage.report] +show_missing = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2e7a8f1..0000000 --- a/setup.cfg +++ /dev/null @@ -1,125 +0,0 @@ -[metadata] -name=git-fame -url=https://github.com/casperdcl/git-fame -project_urls= - Changelog=https://github.com/casperdcl/git-fame/releases - Documentation=https://github.com/casperdcl/git-fame/#git-fame -licence=MPL 2.0 -license_file=LICENCE -description=Pretty-print `git` repository collaborators sorted by contributions -long_description=file: README.rst -long_description_content_type=text/x-rst -author=Casper da Costa-Luis -author_email=casper.dcl@physics.org -keywords=git, blame, git-blame, git-log, code-analysis, cost, loc, author, commit, shortlog, ls-files -platforms=any -provides=gitfame -# Trove classifiers (https://pypi.org/pypi?%3Aaction=list_classifiers) -classifiers= - Development Status :: 5 - Production/Stable - Environment :: Console - Environment :: MacOS X - Environment :: Other Environment - Environment :: Win32 (MS Windows) - Environment :: X11 Applications - Framework :: IPython - Intended Audience :: Developers - Intended Audience :: Education - Intended Audience :: End Users/Desktop - Intended Audience :: Other Audience - Intended Audience :: System Administrators - License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) - Operating System :: MacOS - Operating System :: MacOS :: MacOS X - Operating System :: Microsoft - Operating System :: Microsoft :: MS-DOS - Operating System :: Microsoft :: Windows - Operating System :: POSIX - Operating System :: POSIX :: BSD - Operating System :: POSIX :: BSD :: FreeBSD - Operating System :: POSIX :: Linux - Operating System :: POSIX :: SunOS/Solaris - Operating System :: Unix - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: Implementation - Programming Language :: Python :: Implementation :: IronPython - Programming Language :: Python :: Implementation :: PyPy - Programming Language :: Unix Shell - Topic :: Desktop Environment - Topic :: Education :: Computer Aided Instruction (CAI) - Topic :: Education :: Testing - Topic :: Office/Business - Topic :: Other/Nonlisted Topic - Topic :: Software Development :: Build Tools - Topic :: Software Development :: Libraries - Topic :: Software Development :: Libraries :: Python Modules - Topic :: Software Development :: Pre-processors - Topic :: Software Development :: User Interfaces - Topic :: System :: Installation/Setup - Topic :: System :: Logging - Topic :: System :: Monitoring - Topic :: System :: Shells - Topic :: Terminals - Topic :: Utilities -[options] -setup_requires=setuptools>=42; setuptools_scm[toml]>=3.4 -python_requires=>=3.7 -install_requires=argopt>=0.3.5; setuptools; tabulate; tqdm>=4.44.0 -tests_require=tox -include_package_data=True -packages=find: -[options.extras_require] -yaml=pyyaml -tabulate= -full=pyyaml -dev=pyyaml; py-make>=0.1.0; twine; wheel -[options.entry_points] -console_scripts= - git-fame=gitfame:main -[options.packages.find] -exclude=tests -[options.package_data] -gitfame=git-fame.1 - -[flake8] -max_line_length=99 -extend_ignore=E261 -exclude=.eggs,.tox,build,dist,.git,__pycache__ - -[yapf] -spaces_before_comment=15, 20 -arithmetic_precedence_indication=true -allow_split_before_dict_value=false -coalesce_brackets=True -column_limit=99 -each_dict_entry_on_separate_line=False -space_between_ending_comma_and_closing_bracket=False -split_before_named_assigns=False -split_before_closing_bracket=False -blank_line_before_nested_class_or_def=False - -[isort] -line_length=99 -known_first_party=gitfame,tests - -[tool:pytest] -timeout=30 -log_level=INFO -python_files=tests_*.py -testpaths=tests -addopts=-v --tb=short -rxs -W=error --durations=0 --durations-min=0.1 - -[coverage:run] -branch=True -omit= - tests/* -relative_files=True -[coverage:report] -show_missing=True diff --git a/setup.py b/setup.py deleted file mode 100755 index f0d7302..0000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import sys -from os import path - -from setuptools import setup - -src_dir = path.abspath(path.dirname(__file__)) -if sys.argv[1].lower().strip() == 'make': # exec Makefile commands - import pymake - fpath = path.join(src_dir, 'Makefile') - pymake.main(['-f', fpath] + sys.argv[2:]) - - # Stop to avoid setup.py raising non-standard command error - sys.exit(0) - -ext_modules = [] -if '--cython' in sys.argv: - sys.argv.remove('--cython') - try: - from Cython.Build import cythonize - ext_modules = cythonize(["gitfame/_gitfame.py", "gitfame/_utils.py"], nthreads=2) - except ImportError: - pass - -setup(use_scm_version=True, ext_modules=ext_modules) diff --git a/tox.ini b/tox.ini index 66b8143..058aa7f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist=py{37,38,39,310,311,py3}, setup.py, nodeps +envlist=py{37,38,39,310,311,312,313,py3}, check isolated_build=True [gh-actions] @@ -14,6 +14,8 @@ python= 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 + 3.13: py313 pypy-3.7: pypy3 [core] @@ -32,20 +34,20 @@ commands= passenv=TOXENV,CI,GITHUB_*,CODECOV_*,COVERALLS_*,HOME deps= {[core]deps} + argopt + tabulate tqdm pyyaml commands= - pytest --cov=argopt --cov-report=xml --cov-report=term + pytest --cov=gitfame --cov-report=xml --cov-report=term {[core]commands} -[testenv:setup.py] +[testenv:check] deps= - docutils - pygments + build + twine py-make>=0.1.0 commands= - {envpython} setup.py check --restructuredtext --metadata --strict - {envpython} setup.py make none - -[testenv:nodeps] -deps={[core]deps} + {envpython} -m build + {envpython} -m twine check dist/* + {envpython} -m pymake -h