Move build system from setuptools to uv, wire uv into CI/CD
Convert both pyproject.toml files to the uv_build backend and turn the repo into a proper uv workspace (plotstyle moved to src layout, since uv_build requires the module in its own subdirectory). Dev tooling moves from an optional-dependencies extra to a dependency-group, and plotstyle's matplotlib dependency is now its own rather than a gallery "plotting" extra. Gitea Actions workflows and the Dockerfile now use astral-sh/setup-uv, uv sync, uv run, uv build, and uv publish throughout instead of pip, build, and twine. Also fixes .dockerignore, which was excluding uv.lock, deploy/, and README.md and would have broken even the previous Dockerfile's COPY of deploy/entrypoint.sh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,5 @@ build
|
||||
*.egg-info
|
||||
.claude
|
||||
config.yaml
|
||||
deploy/
|
||||
docs/
|
||||
README.md
|
||||
CLAUDE.md
|
||||
uv.lock
|
||||
|
||||
+20
-25
@@ -10,52 +10,48 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- run: pip install -e ".[dev,plotting]"
|
||||
- run: ruff check gallery plotstyle tests
|
||||
enable-cache: true
|
||||
- run: uv sync --all-packages
|
||||
- run: uv run ruff check gallery plotstyle tests
|
||||
|
||||
format:
|
||||
name: format:ruff
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- run: pip install -e ".[dev,plotting]"
|
||||
- run: ruff format --check gallery plotstyle tests
|
||||
enable-cache: true
|
||||
- run: uv sync --all-packages
|
||||
- run: uv run ruff format --check gallery plotstyle tests
|
||||
|
||||
typecheck:
|
||||
name: typecheck:ty
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- run: pip install -e ".[dev,plotting]"
|
||||
- run: ty check gallery plotstyle
|
||||
enable-cache: true
|
||||
- run: uv sync --all-packages
|
||||
- run: uv run ty check gallery plotstyle
|
||||
|
||||
vulnerabilities:
|
||||
name: vulnerabilities:pip-audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- run: pip install -e ".[dev,plotting]"
|
||||
- run: pip-audit --skip-editable
|
||||
enable-cache: true
|
||||
- run: uv sync --all-packages
|
||||
- run: uv run pip-audit --skip-editable
|
||||
|
||||
test:
|
||||
name: test:pytest
|
||||
@@ -63,10 +59,9 @@ jobs:
|
||||
needs: [lint, format, typecheck, vulnerabilities]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- run: pip install -e ".[dev,plotting]"
|
||||
- run: python -m pytest tests/ -v
|
||||
enable-cache: true
|
||||
- run: uv sync --all-packages
|
||||
- run: uv run pytest tests/ -v
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Publish plotstyle
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "plotstyle-v*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: build-and-publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/setup-uv@v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Verify tag matches package version
|
||||
run: |
|
||||
tag_version="${GITHUB_REF_NAME#plotstyle-v}"
|
||||
pkg_version="$(uv version --short --package plotstyle)"
|
||||
if [ "$tag_version" != "$pkg_version" ]; then
|
||||
echo "Tag plotstyle-v$tag_version does not match pyproject.toml version $pkg_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build wheel and sdist
|
||||
run: uv build --package plotstyle -o dist
|
||||
|
||||
- name: Publish to Gitea package registry
|
||||
env:
|
||||
UV_PUBLISH_USERNAME: ${{ github.actor }}
|
||||
UV_PUBLISH_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: uv publish --publish-url "${{ github.server_url }}/api/packages/${{ github.repository_owner }}/pypi" dist/*
|
||||
@@ -8,39 +8,38 @@ A Python package that generates responsive static HTML galleries from scientific
|
||||
|
||||
The repo also ships `plotstyle`, a standalone matplotlib styling toolkit (KIT corporate-design theme + building-block functions) for producing the PDF figures that feed into a `gallery` source directory. `gallery` never imports it — the only connection is the PDF files and `metadata.yaml` on disk. **See `plotstyle/CLAUDE.md` for full agent-facing usage docs, the API reference, and the metadata.yaml workflow.**
|
||||
|
||||
This repo is a **uv workspace** (root `pyproject.toml` has `[tool.uv.workspace] members = ["plotstyle"]`) and both projects build with uv's own `uv_build` backend — there is no setuptools anywhere in this repo. Use `uv`/`uv run` for everything; don't reach for `pip install` here.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Install the package (editable)
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Also install plotstyle's dependency (matplotlib) if working on plot-producing scripts
|
||||
pip install -e ".[dev,plotting]" # or: uv sync --extra dev --extra plotting
|
||||
# Install everything (gallery + plotstyle + dev tools) into the shared workspace venv
|
||||
uv sync --all-packages
|
||||
|
||||
# Run all tests
|
||||
pytest tests/
|
||||
uv run pytest tests/
|
||||
|
||||
# Run a single test file
|
||||
pytest tests/test_generate_gallery.py -v
|
||||
pytest tests/test_plotstyle.py -v
|
||||
uv run pytest tests/test_generate_gallery.py -v
|
||||
uv run pytest tests/test_plotstyle.py -v
|
||||
|
||||
# Run a single test by name
|
||||
pytest tests/test_generate_gallery.py::test_needs_update_missing_target -v
|
||||
uv run pytest tests/test_generate_gallery.py::test_needs_update_missing_target -v
|
||||
|
||||
# Generate gallery
|
||||
gallery generate --verbose
|
||||
uv run gallery generate --verbose
|
||||
|
||||
# Generate with a non-default config
|
||||
gallery --config config.yaml generate --verbose
|
||||
uv run gallery --config config.yaml generate --verbose
|
||||
|
||||
# Incremental update for one source only
|
||||
gallery generate --source /path/to/plots --verbose
|
||||
uv run gallery generate --source /path/to/plots --verbose
|
||||
|
||||
# Clean regeneration
|
||||
gallery generate --clean --verbose
|
||||
uv run gallery generate --clean --verbose
|
||||
|
||||
# Launch TUI
|
||||
gallery tui
|
||||
uv run gallery tui
|
||||
|
||||
# Serve output locally
|
||||
python -m http.server 8000 -d /web/kschmidt/public_html/
|
||||
@@ -49,10 +48,10 @@ python -m http.server 8000 -d /web/kschmidt/public_html/
|
||||
Code style: ruff (lint + format), `line-length = 120`. Type-checked with `ty`.
|
||||
|
||||
```bash
|
||||
ruff check gallery plotstyle tests
|
||||
ruff format gallery plotstyle tests
|
||||
ty check gallery plotstyle
|
||||
pip-audit --skip-editable
|
||||
uv run ruff check gallery plotstyle tests
|
||||
uv run ruff format gallery plotstyle tests
|
||||
uv run ty check gallery plotstyle
|
||||
uv run pip-audit --skip-editable
|
||||
```
|
||||
|
||||
**Before committing**, run the same checks CI (`.gitea/workflows/ci.yml`) runs and make sure they pass — `ruff check`, `ruff format --check`, `ty check`, `pip-audit`, and `pytest tests/`. Catching a failure locally is faster than waiting on the pipeline.
|
||||
@@ -90,7 +89,8 @@ generate() [api.py]
|
||||
| `gallery/assets/js/` | Vanilla JS modules loaded as ES modules; `GalleryApp` in `gallery-app.js` orchestrates all managers |
|
||||
| `gallery/assets/css/` | Modular CSS; `main.css` imports all others via `@import` |
|
||||
| `config.yaml` | Local deployment config (paths are machine-specific) |
|
||||
| `plotstyle/` | Standalone matplotlib styling toolkit for producing plots (see `plotstyle/CLAUDE.md`) — not imported by `gallery/` |
|
||||
| `plotstyle/` | Standalone matplotlib styling toolkit for producing plots (see `plotstyle/CLAUDE.md`) — not imported by `gallery/`; a separate uv workspace member with its own `pyproject.toml`, code under `plotstyle/src/plotstyle/` (src layout) |
|
||||
| `.gitea/workflows/publish-plotstyle.yml` | Builds and publishes `plotstyle` to the Gitea package registry on `plotstyle-v*` tags |
|
||||
| `examples/plotstyle_showcase.ipynb` | Rendered, runnable tour of every `plotstyle` function |
|
||||
|
||||
### Config File Format
|
||||
@@ -125,7 +125,9 @@ Fields are freeform YAML (no fixed schema); `title`, `description`, `plot_type`,
|
||||
|
||||
### Plot-Producing Companion (`plotstyle`)
|
||||
|
||||
`plotstyle` (top-level package, optional `plotting` extra) is how plots destined for a `gallery` source directory should be produced — a KIT corporate-design matplotlib theme plus building blocks (`new_figure`, `colorbar`, `style_legend`, `panel_label`, `savefig`). It has no code dependency on `gallery`; the two only meet on disk, via the PDFs and `metadata.yaml` files a `plotstyle` script writes into a `gallery` source directory. **Full usage docs, API reference, best practices, and the metadata.yaml workflow live in `plotstyle/CLAUDE.md`** — read that file before writing or reviewing any script that `import plotstyle`. `examples/plotstyle_showcase.ipynb` is a rendered, runnable tour of every function.
|
||||
`plotstyle` (a separate uv workspace member, see below) is how plots destined for a `gallery` source directory should be produced — a KIT corporate-design matplotlib theme plus building blocks (`new_figure`, `colorbar`, `style_legend`, `panel_label`, `savefig`). It has no code dependency on `gallery`; the two only meet on disk, via the PDFs and `metadata.yaml` files a `plotstyle` script writes into a `gallery` source directory. **Full usage docs, API reference, best practices, and the metadata.yaml workflow live in `plotstyle/CLAUDE.md`** — read that file before writing or reviewing any script that `import plotstyle`. `examples/plotstyle_showcase.ipynb` is a rendered, runnable tour of every function.
|
||||
|
||||
`plotstyle` has its own `plotstyle/pyproject.toml` (own `uv_build` project, code lives in `plotstyle/src/plotstyle/`) and is a member of this repo's uv workspace (`[tool.uv.workspace] members = ["plotstyle"]` in the root `pyproject.toml`) — `uv sync --all-packages` installs both `gallery` and `plotstyle` (and matplotlib) into one shared venv, which is why `import plotstyle` works from `tests/` and the example notebook without `gallery` ever depending on it. It's also independently publishable: pushing a tag matching `plotstyle-v*` (e.g. `plotstyle-v0.1.0`) runs `.gitea/workflows/publish-plotstyle.yml`, which runs `uv build --package plotstyle` and `uv publish` to this repo's Gitea package registry (`{server}/api/packages/{owner}/pypi`) using the workflow's auto-generated token. The tag's version suffix must match `plotstyle/pyproject.toml`'s `version` field or the workflow fails fast — bump that version before tagging a new release.
|
||||
|
||||
### Frontend (Static JS/CSS)
|
||||
|
||||
@@ -133,6 +135,6 @@ The frontend is vanilla ES modules — no build step. `assets/js/main.js` import
|
||||
|
||||
### Deployment
|
||||
|
||||
The project ships a `Dockerfile` plus `docker-compose.yml` (a `generator` service that runs `gallery generate` on an interval, and an `nginx`-based `web` service serving the output — see `deploy/entrypoint.sh` and `deploy/nginx.conf`). This suits a dedicated VM/server you fully control. CI (`.gitea/workflows/ci.yml`, run via Gitea Actions) runs `ruff check`, `ruff format --check`, `ty check`, `pip-audit`, and pytest on the default runner image with Python 3.11 via `actions/setup-python` — no container build/publish in CI. For local development the `.venv` (or `uv`) is sufficient.
|
||||
The project ships a `Dockerfile` plus `docker-compose.yml` (a `generator` service that runs `gallery generate` on an interval, and an `nginx`-based `web` service serving the output — see `deploy/entrypoint.sh` and `deploy/nginx.conf`). This suits a dedicated VM/server you fully control. The `Dockerfile` is a two-stage build: a builder stage copies in the official `ghcr.io/astral-sh/uv` binary and runs `uv build --package gallery` to produce a wheel, then the final stage `pip install`s just that wheel (no dev deps, no workspace/lockfile needed at runtime). CI (`.gitea/workflows/ci.yml`, run via Gitea Actions) uses `astral-sh/setup-uv` + `uv sync --all-packages` and runs every check (`ruff check`, `ruff format --check`, `ty check`, `pip-audit`, `pytest`) via `uv run` — no plain `pip`/`actions/setup-python` anywhere in CI. For local development, `uv sync --all-packages` is all you need.
|
||||
|
||||
On shared HPC login nodes without a Docker daemon (e.g. KIT ETP, where `public_html` is already auto-served) use a plain venv install plus a `systemd --user` timer instead — see `deploy/systemd/README.md`.
|
||||
|
||||
+14
-4
@@ -1,3 +1,15 @@
|
||||
FROM python:3.11-slim AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock README.md ./
|
||||
COPY plotstyle/pyproject.toml plotstyle/README.md plotstyle/
|
||||
COPY gallery/ gallery/
|
||||
|
||||
RUN uv build --package gallery -o dist
|
||||
|
||||
FROM python:3.11-slim
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -6,12 +18,10 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml .
|
||||
COPY gallery/ gallery/
|
||||
COPY tests/ tests/
|
||||
COPY --from=builder /app/dist/*.whl /tmp/
|
||||
COPY deploy/entrypoint.sh deploy/entrypoint.sh
|
||||
|
||||
RUN pip install --no-cache-dir ".[dev]"
|
||||
RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl
|
||||
|
||||
RUN useradd -r -s /bin/false gallery
|
||||
USER gallery
|
||||
|
||||
@@ -60,13 +60,17 @@ gallery --help
|
||||
|
||||
### Optional: `plotstyle` (for producing plots)
|
||||
|
||||
`plotstyle` is a separate package (its own `plotstyle/pyproject.toml`, a member of this repo's uv workspace) — installing `gallery` alone never pulls in `matplotlib`.
|
||||
|
||||
```bash
|
||||
pip install -e ".[plotting]"
|
||||
# or
|
||||
uv sync --extra plotting
|
||||
# Working in this repo (installs gallery + plotstyle + matplotlib together):
|
||||
uv sync --all-packages
|
||||
|
||||
# Just the plotstyle library, standalone:
|
||||
pip install "plotstyle @ git+https://git.larsbogner.de/lars/ETPlot#subdirectory=plotstyle"
|
||||
```
|
||||
|
||||
This only installs `matplotlib` — it's kept out of the core dependencies so a plain gallery install stays lightweight. `plotstyle` also requires a local LaTeX toolchain (`latex` + `dvipng`) to be installed separately; see [Producing Plots with `plotstyle`](#producing-plots-with-plotstyle).
|
||||
`plotstyle` also requires a local LaTeX toolchain (`latex` + `dvipng`) to be installed separately; see [Producing Plots with `plotstyle`](#producing-plots-with-plotstyle).
|
||||
|
||||
### Shell Completion (optional)
|
||||
|
||||
|
||||
+9
-6
@@ -21,15 +21,18 @@ produces. Everything below is the reference; the notebook is the demo.
|
||||
|
||||
## Install
|
||||
|
||||
`plotstyle` is its own project (`plotstyle/pyproject.toml`, `uv_build`
|
||||
backend, code under `plotstyle/src/plotstyle/`) and a member of this repo's
|
||||
uv workspace (`[tool.uv.workspace]` in the root `pyproject.toml`) — not a
|
||||
`gallery` extra.
|
||||
|
||||
```bash
|
||||
uv sync --extra plotting # this repo's uv workflow
|
||||
# or
|
||||
pip install -e ".[plotting]"
|
||||
uv sync --all-packages # this repo's uv workflow — installs gallery + plotstyle together
|
||||
```
|
||||
|
||||
`plotstyle` is an optional extra (`matplotlib>=3.7`) so core `gallery`
|
||||
installs stay lightweight — don't add matplotlib to `gallery`'s own
|
||||
unconditional dependencies to support this package.
|
||||
`matplotlib>=3.7` is `plotstyle`'s own direct dependency — don't add it to
|
||||
`gallery`'s dependencies to support this package; `gallery` should stay
|
||||
installable without ever pulling in matplotlib.
|
||||
|
||||
**Hard requirement: a working local LaTeX toolchain (`latex` + `dvipng`).**
|
||||
`plotstyle.use()` sets `text.usetex = True` unconditionally — there is no
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# plotstyle
|
||||
|
||||
A KIT (Karlsruhe Institute of Technology) corporate-design matplotlib
|
||||
styling toolkit — a validated color palette, consistent spines/ticks/
|
||||
gridlines, LaTeX text in a modern sans font, and a few building-block
|
||||
functions (figure titles with a parameters subtitle, a same-size colorbar
|
||||
helper, an outside-axes legend, panel labels) for producing figures that
|
||||
look consistent across a thesis and a slide deck.
|
||||
|
||||
Requires a local LaTeX toolchain (`latex` + `dvipng`) — `plotstyle` always
|
||||
renders text through real LaTeX, there's no fallback.
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import plotstyle as ps
|
||||
|
||||
ps.use() # once, before creating any figure
|
||||
|
||||
fig, ax = ps.new_figure(
|
||||
"thesis-single",
|
||||
title="Measured signal",
|
||||
params={"N": 512, "seed": 42},
|
||||
)
|
||||
x = np.linspace(0, 10, 200)
|
||||
ax.plot(x, np.sin(x), label="signal")
|
||||
ax.set_xlabel("Time (s)")
|
||||
ax.set_ylabel(r"Amplitude $A(t)$")
|
||||
ps.style_legend(ax, title="Series")
|
||||
|
||||
ps.savefig(fig, "plots/measured_signal", formats=("pdf",))
|
||||
```
|
||||
|
||||
This package is developed as part of the
|
||||
[ETPlot](https://git.larsbogner.de/lars/ETPlot) monorepo, where it is paired
|
||||
with `gallery`, a static HTML gallery generator that turns directories of
|
||||
plot PDFs into a browsable website — `plotstyle` has no code dependency on
|
||||
`gallery`, the two only meet on disk via the PDF (and optional
|
||||
`metadata.yaml`) files a `plotstyle` script writes out. See that repo's
|
||||
`examples/plotstyle_showcase.ipynb` for a fully rendered tour of every
|
||||
function, and `plotstyle/CLAUDE.md` for the full API reference.
|
||||
@@ -0,0 +1,37 @@
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.11.19,<0.12.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[project]
|
||||
name = "plotstyle"
|
||||
version = "0.1.0"
|
||||
description = "KIT corporate-design matplotlib styling toolkit for consistent scientific figures"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "K. Schmidt"},
|
||||
]
|
||||
keywords = [
|
||||
"matplotlib",
|
||||
"plotting",
|
||||
"scientific-computing",
|
||||
"styling",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Science/Research",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"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",
|
||||
"Topic :: Scientific/Engineering :: Visualization",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"matplotlib>=3.7",
|
||||
]
|
||||
+10
-13
@@ -1,6 +1,6 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=83.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = ["uv_build>=0.11.19,<0.12.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[project]
|
||||
name = "gallery"
|
||||
@@ -43,25 +43,22 @@ dependencies = [
|
||||
"textual>=0.50",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
[project.scripts]
|
||||
gallery = "gallery.cli:main"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=7.0",
|
||||
"ruff>=0.6",
|
||||
"ty>=0.0.1",
|
||||
"pip-audit>=2.7",
|
||||
"setuptools>=83.0.0",
|
||||
]
|
||||
plotting = [
|
||||
"matplotlib>=3.7",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
gallery = "gallery.cli:main"
|
||||
[tool.uv.workspace]
|
||||
members = ["plotstyle"]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["gallery", "gallery.utils", "gallery.config", "plotstyle"]
|
||||
package-data = {gallery = ["templates/*", "assets/css/*", "assets/js/*", "config/*"], plotstyle = ["assets/*.mplstyle"]}
|
||||
include-package-data = true
|
||||
[tool.uv.build-backend]
|
||||
module-root = ""
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
||||
Reference in New Issue
Block a user