Skip to content

Commit

Permalink
chore: Template upgrade
Browse files Browse the repository at this point in the history
pawamoy committed Nov 25, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent d18d300 commit 86b6f8f
Showing 30 changed files with 532 additions and 421 deletions.
2 changes: 1 addition & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
_commit: 1.1.0
_commit: 1.5.4
_src_path: gh:pawamoy/copier-uv
author_email: [email protected]
author_fullname: Timothée Mazzucotelli
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ PASTE TRACEBACK HERE
git-changelog --debug-info # | xclip -selection clipboard
```

PASTE OUTPUT HERE
PASTE MARKDOWN OUTPUT HERE

### Additional context
<!-- Add any other relevant context about the problem here,
File renamed without changes.
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE/3-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: Documentation update
about: Point at unclear, missing or outdated documentation.
title: "docs: "
labels: docs
assignees: pawamoy
---

### Is something unclear, missing or outdated in our documentation?
<!-- A clear and concise description of what the documentation issue is. Ex. I can't find an explanation on feature [...]. -->

### Relevant code snippets
<!-- If the documentation issue is related to code, please provide relevant code snippets. -->

### Link to the relevant documentation section
<!-- Add a link to the relevant section of our documentation, or any addition context. -->
18 changes: 18 additions & 0 deletions .github/ISSUE_TEMPLATE/4-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: Change request
about: Suggest any other kind of change for this project.
title: "change: "
assignees: pawamoy
---

### Is your change request related to a problem? Please describe.
<!-- A clear and concise description of what the problem is. -->

### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->

### Describe alternatives you've considered
<!-- A clear and concise description of any alternative solutions you've considered. -->

### Additional context
<!-- Add any other context or screenshots about the change request here. -->
48 changes: 33 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -25,17 +25,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Fetch all tags
run: git fetch --depth=1 --tags

- name: Set up Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"

- name: Install uv
run: pip install uv
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: pyproject.toml

- name: Install dependencies
run: make setup
@@ -49,9 +52,6 @@ jobs:
- name: Check if the code is correctly typed
run: make check-types

- name: Check for vulnerabilities in dependencies
run: make check-dependencies

- name: Check for breaking changes in the API
run: make check-api

@@ -64,28 +64,46 @@ jobs:
- macos-latest
- windows-latest
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
resolution:
- highest
- lowest-direct
exclude:
- os: macos-latest
resolution: lowest-direct
- os: windows-latest
resolution: lowest-direct
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.python-version == '3.12' }}
continue-on-error: ${{ matrix.python-version == '3.14' }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Set up Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install uv
run: pip install uv
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: pyproject.toml
cache-suffix: py${{ matrix.python-version }}

- name: Install dependencies
env:
UV_RESOLUTION: ${{ matrix.resolution }}
run: make setup

- name: Run the test suite
17 changes: 10 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -11,15 +11,18 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fetch all tags
run: git fetch --depth=1 --tags
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Python
uses: actions/setup-python@v4
- name: Install git-changelog
run: pip install git-changelog
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Setup uv
uses: astral-sh/setup-uv@v3
- name: Prepare release notes
run: git-changelog --release-notes > release-notes.md
run: uv tool run git-changelog --release-notes > release-notes.md
- name: Create release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body_path: release-notes.md
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
/.pdm-build/
/htmlcov/
/site/
uv.lock

# cache
.cache/
6 changes: 0 additions & 6 deletions .gitpod.dockerfile

This file was deleted.

13 changes: 0 additions & 13 deletions .gitpod.yml

This file was deleted.

17 changes: 7 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -23,12 +23,11 @@ make setup
> You can install it with:
>
> ```bash
> python3 -m pip install --user pipx
> pipx install uv
> curl -LsSf https://astral.sh/uv/install.sh | sh
> ```
>
> Now you can try running `make setup` again,
> or simply `uv install`.
> or simply `uv sync`.
You now have the dependencies installed.
@@ -38,13 +37,11 @@ Run `make help` to see all the available actions!
## Tasks
This project uses [duty](https://github.com/pawamoy/duty) to run tasks.
A Makefile is also provided. The Makefile will try to run certain tasks
on multiple Python versions. If for some reason you don't want to run the task
on multiple Python versions, you run the task directly with `make run duty TASK`.
The Makefile detects if a virtual environment is activated,
so `make` will work the same with the virtualenv activated or not.
The entry-point to run commands and tasks is the `make` Python script,
located in the `scripts` directory. Try running `make` to show the available commands and tasks.
The *commands* do not need the Python dependencies to be installed,
while the *tasks* do.
The cross-platform tasks are written in Python, thanks to [duty](https://github.com/pawamoy/duty).
If you work in VSCode, we provide
[an action to configure VSCode](https://pawamoy.github.io/copier-uv/work/#vscode-setup)
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@
# This Makefile is just here to allow auto-completion in the terminal.

actions = \
allrun \
changelog \
check \
check-api \
check-dependencies \
check-docs \
check-quality \
check-types \
@@ -16,6 +16,7 @@ actions = \
docs-deploy \
format \
help \
multirun \
profile \
release \
run \
@@ -25,4 +26,4 @@ actions = \

.PHONY: $(actions)
$(actions):
@bash scripts/make "$@"
@python scripts/make "$@"
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# git-changelog

[![ci](https://github.com/pawamoy/git-changelog/workflows/ci/badge.svg)](https://github.com/pawamoy/git-changelog/actions?query=workflow%3Aci)
[![documentation](https://img.shields.io/badge/docs-mkdocs%20material-blue.svg?style=flat)](https://pawamoy.github.io/git-changelog/)
[![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/git-changelog/)
[![pypi version](https://img.shields.io/pypi/v/git-changelog.svg)](https://pypi.org/project/git-changelog/)
[![gitpod](https://img.shields.io/badge/gitpod-workspace-blue.svg?style=flat)](https://gitpod.io/#https://github.com/pawamoy/git-changelog)
[![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#git-changelog:gitter.im)

Automatic Changelog generator using Jinja2 templates. From git logs to change logs.
@@ -50,17 +49,14 @@ Automatic Changelog generator using Jinja2 templates. From git logs to change lo

## Installation

With `pip`:

```bash
pip install git-changelog
```

With [`pipx`](https://github.com/pipxproject/pipx):
With [`uv`](https://docs.astral.sh/uv/):

```bash
python3.8 -m pip install --user pipx
pipx install git-changelog
uv tool install git-changelog
```

## Usage
2 changes: 0 additions & 2 deletions config/pytest.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
[pytest]
python_files =
test_*.py
*_test.py
tests.py
addopts =
--cov
--cov-config config/coverage.ini
2 changes: 1 addition & 1 deletion config/ruff.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
target-version = "py38"
target-version = "py39"
line-length = 120

[lint]
6 changes: 0 additions & 6 deletions config/vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -31,12 +31,6 @@
"command": "scripts/make",
"args": ["check-docs"]
},
{
"label": "check-dependencies",
"type": "process",
"command": "scripts/make",
"args": ["check-dependencies"]
},
{
"label": "check-api",
"type": "process",
27 changes: 0 additions & 27 deletions devdeps.txt

This file was deleted.

4 changes: 3 additions & 1 deletion docs/.overrides/main.html
Original file line number Diff line number Diff line change
@@ -2,11 +2,13 @@

{% block announce %}

For updates follow <strong>@pawamoy</strong> on
Follow
<strong>@pawamoy</strong> on
<a rel="me" href="https://fosstodon.org/@pawamoy">
<span class="twemoji mastodon">
{% include ".icons/fontawesome/brands/mastodon.svg" %}
</span>
<strong>Fosstodon</strong>
</a>
for updates
{% endblock %}
57 changes: 57 additions & 0 deletions docs/.overrides/partials/comments.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!-- Giscus -->
<!-- https://squidfunk.github.io/mkdocs-material/setup/adding-a-comment-system/#giscus-integration -->
<div id="feedback" style="display: none;">
<h2 id="__comments">Feedback</h2>
<script src="https://giscus.app/client.js"
data-repo="pawamoy/git-changelog"
data-repo-id="MDEwOlJlcG9zaXRvcnkxMzgwNjk3MjQ="
data-category="Documentation"
data-category-id="DIC_kwDOCDrG3M4CkmXD"
data-mapping="pathname"
data-strict="1"
data-reactions-enabled="0"
data-emit-metadata="0"
data-input-position="top"
data-theme="preferred_color_scheme"
data-lang="en"
data-loading="lazy"
crossorigin="anonymous"
async>
</script>

<!-- Synchronize Giscus theme with palette -->
<script>
var giscus = document.querySelector("script[src*=giscus]")

// Set palette on initial load
var palette = __md_get("__palette")
if (palette && typeof palette.color === "object") {
var theme = palette.color.scheme === "slate"
? "transparent_dark"
: "light"

// Instruct Giscus to set theme
giscus.setAttribute("data-theme", theme)
}

// Register event handlers after documented loaded
document.addEventListener("DOMContentLoaded", function() {
var ref = document.querySelector("[data-md-component=palette]")
ref.addEventListener("change", function() {
var palette = __md_get("__palette")
if (palette && typeof palette.color === "object") {
var theme = palette.color.scheme === "slate"
? "transparent_dark"
: "light"

// Instruct Giscus to change theme
var frame = document.querySelector(".giscus-frame")
frame.contentWindow.postMessage(
{ giscus: { setConfig: { theme } } },
"https://giscus.app"
)
}
})
})
</script>
</div>
2 changes: 1 addition & 1 deletion docs/css/mkdocstrings.css
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ a.autorefs-external::after {

height: 1em;
width: 1em;
background-color: var(--md-typeset-a-color);
background-color: currentColor;
}

a.external:hover::after,
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
---
hide:
- feedback
---

--8<-- "README.md"
14 changes: 14 additions & 0 deletions docs/js/feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const feedback = document.forms.feedback;
feedback.hidden = false;

feedback.addEventListener("submit", function(ev) {
ev.preventDefault();
const commentElement = document.getElementById("feedback");
commentElement.style.display = "block";
feedback.firstElementChild.disabled = true;
const data = ev.submitter.getAttribute("data-md-value");
const note = feedback.querySelector(".md-feedback__note [data-md-value='" + data + "']");
if (note) {
note.hidden = false;
}
})
5 changes: 5 additions & 0 deletions docs/license.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
hide:
- feedback
---

# License

```
215 changes: 67 additions & 148 deletions duties.py
Original file line number Diff line number Diff line change
@@ -4,18 +4,20 @@

import os
import sys
from collections.abc import Iterator
from contextlib import contextmanager
from cProfile import Profile
from importlib.metadata import version as pkgversion
from pathlib import Path
from pstats import SortKey, Stats
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Iterator
from typing import TYPE_CHECKING

from duty import duty
from duty.callables import coverage, lazy, mkdocs, mypy, pytest, ruff, safety
from duty import duty, tools

if TYPE_CHECKING:
from collections.abc import Iterator

from duty.context import Context


@@ -48,246 +50,163 @@ def material_insiders() -> Iterator[bool]: # noqa: D103


@duty
def changelog(ctx: Context) -> None:
def changelog(ctx: Context, bump: str = "") -> None:
"""Update the changelog in-place with latest commits.
Parameters:
ctx: The context instance (passed automatically).
bump: Bump option passed to git-changelog.
"""
from git_changelog.cli import main as git_changelog

ctx.run(git_changelog, args=[[]], title="Updating changelog")

ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog")

@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"])
def check(ctx: Context) -> None: # noqa: ARG001
"""Check it all!

Parameters:
ctx: The context instance (passed automatically).
"""
@duty(pre=["check-quality", "check-types", "check-docs", "check-api"])
def check(ctx: Context) -> None:
"""Check it all!"""


@duty
def check_quality(ctx: Context) -> None:
"""Check the code quality.
Parameters:
ctx: The context instance (passed automatically).
"""
"""Check the code quality."""
ctx.run(
ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml"),
title=pyprefix("Checking code quality"),
command=f"ruff check --config config/ruff.toml {PY_SRC}",
)


@duty
def check_dependencies(ctx: Context) -> None:
"""Check for vulnerabilities in dependencies.
Parameters:
ctx: The context instance (passed automatically).
"""
# retrieve the list of dependencies
requirements = ctx.run(
["uv", "pip", "freeze"],
silent=True,
allow_overrides=False,
)

ctx.run(
safety.check(requirements),
title="Checking dependencies",
command="uv pip freeze | safety check --stdin",
)


@duty
def check_docs(ctx: Context) -> None:
"""Check if the documentation builds correctly.
Parameters:
ctx: The context instance (passed automatically).
"""
"""Check if the documentation builds correctly."""
Path("htmlcov").mkdir(parents=True, exist_ok=True)
Path("htmlcov/index.html").touch(exist_ok=True)
with material_insiders():
ctx.run(
mkdocs.build(strict=True, verbose=True),
tools.mkdocs.build(strict=True, verbose=True),
title=pyprefix("Building documentation"),
command="mkdocs build -vs",
)


@duty
def check_types(ctx: Context) -> None:
"""Check that the code is correctly typed.
Parameters:
ctx: The context instance (passed automatically).
"""
"""Check that the code is correctly typed."""
ctx.run(
mypy.run(*PY_SRC_LIST, config_file="config/mypy.ini"),
tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"),
title=pyprefix("Type-checking"),
command=f"mypy --config-file config/mypy.ini {PY_SRC}",
)


@duty
def check_api(ctx: Context) -> None:
"""Check for API breaking changes.
Parameters:
ctx: The context instance (passed automatically).
"""
from griffe.cli import check as g_check

griffe_check = lazy(g_check, name="griffe.check")
def check_api(ctx: Context, *cli_args: str) -> None:
"""Check for API breaking changes."""
ctx.run(
griffe_check("git_changelog", search_paths=["src"], color=True),
tools.griffe.check("git_changelog", search=["src"], color=True).add_args(*cli_args),
title="Checking for API breaking changes",
command="griffe check -ssrc git_changelog",
nofail=True,
)


@duty(silent=True)
def clean(ctx: Context) -> None:
"""Delete temporary files.
Parameters:
ctx: The context instance (passed automatically).
"""

def _rm(*targets: str) -> None:
for target in targets:
ctx.run(f"rm -rf {target}")

def _find_rm(*targets: str) -> None:
for target in targets:
ctx.run(f"find . -type d -name '{target}' | xargs rm -rf")

_rm("build", "dist", ".coverage*", "htmlcov", "site", ".pdm-build")
_find_rm(".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__")


@duty
def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None:
def docs(ctx: Context, *cli_args: str, host: str = "127.0.0.1", port: int = 8000) -> None:
"""Serve the documentation (localhost:8000).
Parameters:
ctx: The context instance (passed automatically).
host: The host to serve the docs from.
port: The port to serve the docs on.
"""
with material_insiders():
ctx.run(
mkdocs.serve(dev_addr=f"{host}:{port}"),
tools.mkdocs.serve(dev_addr=f"{host}:{port}").add_args(*cli_args),
title="Serving documentation",
capture=False,
)


@duty
def docs_deploy(ctx: Context) -> None:
"""Deploy the documentation on GitHub pages.
Parameters:
ctx: The context instance (passed automatically).
"""
"""Deploy the documentation to GitHub pages."""
os.environ["DEPLOY"] = "true"
with material_insiders() as insiders:
if not insiders:
ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!")
ctx.run(mkdocs.gh_deploy(), title="Deploying documentation")
ctx.run(tools.mkdocs.gh_deploy(), title="Deploying documentation")


@duty
def format(ctx: Context) -> None:
"""Run formatting tools on the code.
Parameters:
ctx: The context instance (passed automatically).
"""
"""Run formatting tools on the code."""
ctx.run(
ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True),
tools.ruff.check(*PY_SRC_LIST, config="config/ruff.toml", fix_only=True, exit_zero=True),
title="Auto-fixing code",
)
ctx.run(ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code")
ctx.run(tools.ruff.format(*PY_SRC_LIST, config="config/ruff.toml"), title="Formatting code")


@duty
def build(ctx: Context) -> None:
"""Build source and wheel distributions."""
ctx.run(
tools.build(),
title="Building source and wheel distributions",
pty=PTY,
)


@duty
def publish(ctx: Context) -> None:
"""Publish source and wheel distributions to PyPI."""
if not Path("dist").exists():
ctx.run("false", title="No distribution files found")
dists = [str(dist) for dist in Path("dist").iterdir()]
ctx.run(
tools.twine.upload(*dists, skip_existing=True),
title="Publishing source and wheel distributions to PyPI",
pty=PTY,
)


@duty(post=["docs-deploy"])
def release(ctx: Context, version: str) -> None:
@duty(post=["build", "publish", "docs-deploy"])
def release(ctx: Context, version: str = "") -> None:
"""Release a new Python package.
Parameters:
ctx: The context instance (passed automatically).
version: The new version number to use.
"""
if not (version := (version or input("> Version to release: ")).strip()):
ctx.run("false", title="A version must be provided")
ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY)
ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY)
ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY)
ctx.run("git push", title="Pushing commits", pty=False)
ctx.run("git push --tags", title="Pushing tags", pty=False)
ctx.run("pyproject-build", title="Building dist/wheel", pty=PTY)
ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY)


@duty(silent=True, aliases=["coverage"])
def cov(ctx: Context) -> None:
"""Report coverage as text and HTML.

Parameters:
ctx: The context instance (passed automatically).
"""
ctx.run(coverage.combine, nofail=True)
ctx.run(coverage.report(rcfile="config/coverage.ini"), capture=False)
ctx.run(coverage.html(rcfile="config/coverage.ini"))
@duty(silent=True, aliases=["cov"])
def coverage(ctx: Context) -> None:
"""Report coverage as text and HTML."""
ctx.run(tools.coverage.combine(), nofail=True)
ctx.run(tools.coverage.report(rcfile="config/coverage.ini"), capture=False)
ctx.run(tools.coverage.html(rcfile="config/coverage.ini"))


@duty
def test(ctx: Context, match: str = "") -> None:
def test(ctx: Context, *cli_args: str, match: str = "") -> None:
"""Run the test suite.
Parameters:
ctx: The context instance (passed automatically).
match: A pytest expression to filter selected tests.
"""
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
os.environ["COVERAGE_FILE"] = f".coverage.{py_version}"
ctx.run(
pytest.run("-n", "auto", "tests", config_file="config/pytest.ini", select=match, color="yes"),
tools.pytest(
"tests",
config_file="config/pytest.ini",
select=match,
color="yes",
).add_args("-n", "auto", *cli_args),
title=pyprefix("Running tests"),
command=f"pytest -c config/pytest.ini -n auto -k{match!r} --color=yes tests",
)


@duty
def vscode(ctx: Context) -> None:
"""Configure VSCode.
This task will overwrite the following files,
so make sure to back them up:
- `.vscode/launch.json`
- `.vscode/settings.json`
- `.vscode/tasks.json`
Parameters:
ctx: The context instance (passed automatically).
"""

def update_config(filename: str) -> None:
source_file = Path("config", "vscode", filename)
target_file = Path(".vscode", filename)
target_file.parent.mkdir(exist_ok=True)
target_file.write_text(source_file.read_text())

for filename in ("launch.json", "settings.json", "tasks.json"):
ctx.run(update_config, args=[filename], title=f"Update .vscode/{filename}")


@duty
def profile(ctx: Context, merge: int = 15) -> None:
"""Profile the parsing and grouping of commits.
21 changes: 19 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -75,6 +75,9 @@ extra_css:
- css/material.css
- css/mkdocstrings.css

extra_javascript:
- js/feedback.js

markdown_extensions:
- attr_list
- admonition
@@ -126,13 +129,15 @@ plugins:
show_root_heading: true
show_root_full_path: false
show_signature_annotations: true
show_source: true
show_symbol_type_heading: true
show_symbol_type_toc: true
signature_crossrefs: true
summary: true
- git-committers:
- git-revision-date-localized:
enabled: !ENV [DEPLOY, false]
repository: pawamoy/git-changelog
enable_creation_date: true
type: timeago
- minify:
minify_html: !ENV [DEPLOY, false]
- group:
@@ -152,3 +157,15 @@ extra:
link: https://gitter.im/git-changelog/community
- icon: fontawesome/brands/python
link: https://pypi.org/project/git-changelog/
analytics:
feedback:
title: Was this page helpful?
ratings:
- icon: material/emoticon-happy-outline
name: This page was helpful
data: 1
note: Thanks for your feedback!
- icon: material/emoticon-sad-outline
name: This page could be improved
data: 0
note: Let us know how we can improve this page.
65 changes: 63 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ description = "Automatic Changelog generator using Jinja2 templates."
authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}]
license = {text = "ISC"}
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.9"
keywords = [
"git",
"changelog",
@@ -23,10 +23,12 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"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.14",
"Topic :: Documentation",
"Topic :: Software Development",
"Topic :: Software Development :: Documentation",
@@ -60,3 +62,62 @@ version = {source = "scm"}
[tool.pdm.build]
package-dir = "src"
editable-backend = "editables"

# Include as much as possible in the source distribution, to help redistributors.
excludes = ["**/.pytest_cache"]
source-includes = [
"config",
"docs",
"scripts",
"share",
"tests",
"duties.py",
"mkdocs.yml",
"*.md",
"LICENSE",
]

[tool.pdm.build.wheel-data]
# Manual pages can be included in the wheel.
# Depending on the installation tool, they will be accessible to users.
# pipx supports it, uv does not yet, see https://github.com/astral-sh/uv/issues/4731.
data = [
{path = "share/**/*", relative-to = "."},
]

[dependency-groups]
dev = [
# dev
"editables>=0.5",

# maintenance
"build>=1.2",
"git-changelog>=2.5",
"twine>=5.1",

# ci
"duty>=1.4",
"ruff>=0.4",
"pytest>=8.2",
"pytest-cov>=5.0",
"pytest-randomly>=3.15",
"pytest-xdist>=3.6",
"mypy>=1.10",
"types-markdown>=3.6",
"types-pyyaml>=6.0",

# docs
"black>=24.4",
"markdown-callouts>=0.4",
"markdown-exec>=1.8",
"mkdocs>=1.6",
"mkdocs-coverage>=1.0",
"mkdocs-gen-files>=0.5",
"mkdocs-git-revision-date-localized-plugin>=1.2",
"mkdocs-literate-nav>=0.6",
"mkdocs-material>=9.5",
"mkdocs-minify-plugin>=0.8",
"mkdocstrings[python]>=0.25",
# YORE: EOL 3.10: Remove line.
"tomli>=2.0; python_version < '3.11'",
]
18 changes: 9 additions & 9 deletions scripts/gen_credits.py
Original file line number Diff line number Diff line change
@@ -5,17 +5,18 @@
import os
import sys
from collections import defaultdict
from collections.abc import Iterable
from importlib.metadata import distributions
from itertools import chain
from pathlib import Path
from textwrap import dedent
from typing import Dict, Iterable, Union
from typing import Union

from jinja2 import StrictUndefined
from jinja2.sandbox import SandboxedEnvironment
from packaging.requirements import Requirement

# TODO: Remove once support for Python 3.10 is dropped.
# YORE: EOL 3.10: Replace block with line 2.
if sys.version_info >= (3, 11):
import tomllib
else:
@@ -26,11 +27,10 @@
pyproject = tomllib.load(pyproject_file)
project = pyproject["project"]
project_name = project["name"]
with project_dir.joinpath("devdeps.txt").open() as devdeps_file:
devdeps = [line.strip() for line in devdeps_file if not line.startswith("-e")]
devdeps = [dep for dep in pyproject["dependency-groups"]["dev"] if not dep.startswith("-e")]

PackageMetadata = Dict[str, Union[str, Iterable[str]]]
Metadata = Dict[str, PackageMetadata]
PackageMetadata = dict[str, Union[str, Iterable[str]]]
Metadata = dict[str, PackageMetadata]


def _merge_fields(metadata: dict) -> PackageMetadata:
@@ -88,7 +88,7 @@ def _set_license(metadata: PackageMetadata) -> None:
def _get_deps(base_deps: dict[str, Requirement], metadata: Metadata) -> Metadata:
deps = {}
for dep_name, dep_req in base_deps.items():
if dep_name not in metadata:
if dep_name not in metadata or dep_name == "git-changelog":
continue
metadata[dep_name]["spec"] |= {str(spec) for spec in dep_req.specifier} # type: ignore[operator]
metadata[dep_name]["extras"] |= dep_req.extras # type: ignore[operator]
@@ -131,8 +131,8 @@ def _render_credits() -> str:

template_data = {
"project_name": project_name,
"prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"])),
"dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"])),
"prod_dependencies": sorted(prod_dependencies.values(), key=lambda dep: str(dep["name"]).lower()),
"dev_dependencies": sorted(dev_dependencies.values(), key=lambda dep: str(dep["name"]).lower()),
"more_credits": "http://pawamoy.github.io/credits/",
}
template_text = dedent(
2 changes: 1 addition & 1 deletion scripts/gen_ref_nav.py
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@

with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
fd.write(f"::: {ident}")
fd.write(f"---\ntitle: {ident}\n---\n\n::: {ident}")

mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path.relative_to(root))

159 changes: 0 additions & 159 deletions scripts/make

This file was deleted.

1 change: 1 addition & 0 deletions scripts/make
193 changes: 193 additions & 0 deletions scripts/make.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Management commands."""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from collections.abc import Iterator


PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split()


def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None:
"""Run a shell command."""
if capture_output:
return subprocess.check_output(cmd, shell=True, text=True, **kwargs) # noqa: S602
subprocess.run(cmd, shell=True, check=True, stderr=subprocess.STDOUT, **kwargs) # noqa: S602
return None


@contextmanager
def environ(**kwargs: str) -> Iterator[None]:
"""Temporarily set environment variables."""
original = dict(os.environ)
os.environ.update(kwargs)
try:
yield
finally:
os.environ.clear()
os.environ.update(original)


def uv_install(venv: Path) -> None:
"""Install dependencies using uv."""
with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"):
if "CI" in os.environ:
shell("uv sync --no-editable")
else:
shell("uv sync")


def setup() -> None:
"""Setup the project."""
if not shutil.which("uv"):
raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv")

print("Installing dependencies (default environment)")
default_venv = Path(".venv")
if not default_venv.exists():
shell("uv venv")
uv_install(default_venv)

if PYTHON_VERSIONS:
for version in PYTHON_VERSIONS:
print(f"\nInstalling dependencies (python{version})")
venv_path = Path(f".venvs/{version}")
if not venv_path.exists():
shell(f"uv venv --python {version} {venv_path}")
with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
uv_install(venv_path)


def run(version: str, cmd: str, *args: str, no_sync: bool = False, **kwargs: Any) -> None:
"""Run a command in a virtual environment."""
kwargs = {"check": True, **kwargs}
uv_run = ["uv", "run"]
if no_sync:
uv_run.append("--no-sync")
if version == "default":
with environ(UV_PROJECT_ENVIRONMENT=".venv"):
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
else:
with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510


def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
"""Run a command for all configured Python versions."""
if PYTHON_VERSIONS:
for version in PYTHON_VERSIONS:
run(version, cmd, *args, **kwargs)
else:
run("default", cmd, *args, **kwargs)


def allrun(cmd: str, *args: str, **kwargs: Any) -> None:
"""Run a command in all virtual environments."""
run("default", cmd, *args, **kwargs)
if PYTHON_VERSIONS:
multirun(cmd, *args, **kwargs)


def clean() -> None:
"""Delete build artifacts and cache files."""
paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"]
for path in paths_to_clean:
shutil.rmtree(path, ignore_errors=True)

cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"}
for dirpath in Path(".").rglob("*/"):
if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs:
shutil.rmtree(dirpath, ignore_errors=True)


def vscode() -> None:
"""Configure VSCode to work on this project."""
shutil.copytree("config/vscode", ".vscode", dirs_exist_ok=True)


def main() -> int:
"""Main entry point."""
args = list(sys.argv[1:])
if not args or args[0] == "help":
if len(args) > 1:
run("default", "duty", "--help", args[1])
else:
print(
dedent(
"""
Available commands
help Print this help. Add task name to print help.
setup Setup all virtual environments (install dependencies).
run Run a command in the default virtual environment.
multirun Run a command for all configured Python versions.
allrun Run a command in all virtual environments.
3.x Run a command in the virtual environment for Python 3.x.
clean Delete build artifacts and cache files.
vscode Configure VSCode to work on this project.
""",
),
flush=True,
)
if os.path.exists(".venv"):
print("\nAvailable tasks", flush=True)
run("default", "duty", "--list", no_sync=True)
return 0

while args:
cmd = args.pop(0)

if cmd == "run":
run("default", *args)
return 0

if cmd == "multirun":
multirun(*args)
return 0

if cmd == "allrun":
allrun(*args)
return 0

if cmd.startswith("3."):
run(cmd, *args)
return 0

opts = []
while args and (args[0].startswith("-") or "=" in args[0]):
opts.append(args.pop(0))

if cmd == "clean":
clean()
elif cmd == "setup":
setup()
elif cmd == "vscode":
vscode()
elif cmd == "check":
multirun("duty", "check-quality", "check-types", "check-docs")
run("default", "duty", "check-api")
elif cmd in {"check-quality", "check-docs", "check-types", "test"}:
multirun("duty", cmd, *opts)
else:
run("default", "duty", cmd, *opts)

return 0


if __name__ == "__main__":
try:
sys.exit(main())
except subprocess.CalledProcessError as process:
if process.output:
print(process.output, file=sys.stderr)
sys.exit(process.returncode)

0 comments on commit 86b6f8f

Please sign in to comment.