docs: extract version for the manpage

This commit is contained in:
2025-06-23 18:51:25 +03:00
parent 7769a4a6e0
commit 7f223ecc0a
11 changed files with 479 additions and 140 deletions

View File

@ -1,10 +1,10 @@
[tool.pylint.main]
init-hook = "sys.path.append('pylint_plugins')"
init-hook = "sys.path.append('tools')"
load-plugins = [
"pylint.extensions.docparams",
"pylint.extensions.bad_builtin",
"definition_order",
"import_order",
"pylint_plugins.definition_order",
"pylint_plugins.import_order",
]
[tool.pylint.classes]

5
.pytest.ini Normal file
View File

@ -0,0 +1,5 @@
[pytest]
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
asyncio_default_fixture_loop_scope = function
asyncio_mode = auto
spec_test_format = {result} {docstring_summary}

View File

@ -1,6 +1,6 @@
.TH AHRIMAN "1" "2025\-06\-16" "ahriman" "Generated Python Manual"
.TH AHRIMAN "1" "2025\-06\-23" "ahriman 2.18.3" "ArcH linux ReposItory MANager"
.SH NAME
ahriman
ahriman \- ArcH linux ReposItory MANager
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ...

View File

@ -58,23 +58,23 @@ web = [
"aiohttp_cors",
"aiohttp_jinja2",
]
web_api-docs = [
"ahriman[web]",
"aiohttp-apispec",
"setuptools", # required by aiohttp-apispec
]
web_auth = [
web-auth = [
"ahriman[web]",
"aiohttp_session",
"aiohttp_security",
"cryptography",
]
web_metrics = [
web-docs = [
"ahriman[web]",
"aiohttp-apispec",
"setuptools", # required by aiohttp-apispec
]
web-metrics = [
"ahriman[web]",
"aiohttp-openmetrics",
]
web_oauth2 = [
"ahriman[web_auth]",
web-oauth2 = [
"ahriman[web-auth]",
"aioauth-client",
]

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2024 ahriman team.
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

19
tools/__init__.py Normal file
View File

@ -0,0 +1,19 @@
#
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2023 ahriman team.
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021-2023 ahriman team.
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

123
tox.ini
View File

@ -1,123 +0,0 @@
[tox]
envlist = check, tests
isolated_build = true
labels =
release = version, docs, publish
dependencies = -e .[journald,pacman,reports,s3,shell,stats,unixsocket,validator,web,web_api-docs,web_auth,web_oauth2,web_metrics]
project_name = ahriman
[flags]
autopep8 = --max-line-length 120 -aa --in-place
bandit = --configfile .bandit.yml
manpage = --author "ahriman team" --author-email "" --description "ArcH linux ReposItory MANager" --manual-title "ArcH linux ReposItory MANager" --project-name ahriman --url https://github.com/arcan1s/ahriman
mypy = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any
pydeps = --no-config --cluster
pylint = --rcfile .pylint.toml
shtab = --prefix ahriman --prog ahriman ahriman.application.ahriman._parser
[pytest]
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
asyncio_default_fixture_loop_scope = function
asyncio_mode = auto
spec_test_format = {result} {docstring_summary}
[testenv:archive]
description = Create source files tarball
deps =
build
commands =
python -m build --sdist
[testenv:check]
description = Run common checks like linter, mypy, etc
dependency_groups =
check
deps =
{[tox]dependencies}
pip_pre = true
setenv =
CFLAGS="-Wno-unterminated-string-initialization"
MYPYPATH=src
commands =
autopep8 {[flags]autopep8} --exit-code --jobs 0 --recursive "src/{[tox]project_name}" "tests/{[tox]project_name}"
pylint {[flags]pylint} "src/{[tox]project_name}"
bandit {[flags]bandit} --recursive "src/{[tox]project_name}"
bandit {[flags]bandit} --skip B101,B105,B106 --recursive "tests/{[tox]project_name}"
mypy {[flags]mypy} --install-types --non-interactive --package "{[tox]project_name}"
[testenv:docs]
description = Generate source files for documentation
allowlist_externals =
bash
find
dependency_groups =
docs
depends =
version
deps =
{[tox]dependencies}
uv
pip_pre = true
setenv =
PYTHONPATH=src
SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance
commands =
bash -c 'shtab {[flags]shtab} --shell bash > package/share/bash-completion/completions/_ahriman'
bash -c 'shtab {[flags]shtab} --shell zsh > package/share/zsh/site-functions/_ahriman'
argparse-manpage {[flags]manpage} --module ahriman.application.ahriman --function _parser --output ../package/share/man/man1/ahriman.1
pydeps {[flags]pydeps} --no-output --show-dot --dot-output {tox_root}{/}docs/_static/architecture.dot src/ahriman
# remove autogenerated modules rst files
find docs -type f -name "{[tox]project_name}*.rst" -delete
sphinx-apidoc --output-dir docs src
# compile list of dependencies for rtd.io
uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt --quiet pyproject.toml
[testenv:html]
description = Generate html documentation
dependency_groups =
docs
deps =
{[tox]dependencies}
pip_pre = true
recreate = true
commands =
sphinx-build --builder html --write-all --jobs auto --fail-on-warning docs {envtmpdir}{/}html
[testenv:publish]
description = Create and publish release to GitHub
allowlist_externals =
git
depends =
docs
passenv =
SSH_AUTH_SOCK
commands =
git add package/archlinux/PKGBUILD src/ahriman/__init__.py docs/_static/architecture.dot package/share/man/man1/ahriman.1 package/share/bash-completion/completions/_ahriman package/share/zsh/site-functions/_ahriman
git commit -m "Release {posargs}"
git tag "{posargs}"
git push
git push --tags
[testenv:tests]
description = Run tests
dependency_groups =
tests
deps =
{[tox]dependencies}
pip_pre = true
setenv =
CFLAGS="-Wno-unterminated-string-initialization"
commands =
pytest {posargs}
[testenv:version]
description = Bump package version
allowlist_externals =
sed
deps =
packaging
commands =
# check if version is set and validate it
{envpython} -c 'from packaging.version import Version; Version("{posargs}")'
sed -i 's/^__version__ = .*/__version__ = "{posargs}"/' src/ahriman/__init__.py
sed -i "s/pkgver=.*/pkgver={posargs}/" package/archlinux/PKGBUILD

310
tox.toml Normal file
View File

@ -0,0 +1,310 @@
env_list = [
"check",
"tests",
]
isolated_build = true
labels.release = [
"version",
"docs",
"publish",
]
[flags]
autopep8 = [
"--max-line-length", "120",
"-aa",
]
bandit = [
"--configfile", ".bandit.yml",
]
manpage = [
"--author", "{[project]name} team",
"--author-email", "",
"--description", "ArcH linux ReposItory MANager",
"--manual-title", "ArcH linux ReposItory MANager",
"--project-name", "{[project]name}",
"--version", "{env:VERSION}",
"--url", "https://github.com/arcan1s/ahriman",
]
mypy = [
"--implicit-reexport",
"--strict",
"--allow-untyped-decorators",
"--allow-subclassing-any",
]
pydeps = [
"--no-config",
"--cluster",
]
pylint = [
"--rcfile", ".pylint.toml",
]
shtab = [
"--prefix", "{[project]name}",
"--prog", "{[project]name}",
"ahriman.application.ahriman._parser",
]
[project]
extras = [
"journald",
"pacman",
"reports",
"s3",
"shell",
"stats",
"unixsocket",
"validator",
"web",
"web-auth",
"web-docs",
"web-oauth2",
"web-metrics",
]
name = "ahriman"
[env.archive]
description = "Create source files tarball"
deps = [
"build",
]
commands = [
[
"{envpython}",
"-m", "build",
"--sdist",
],
]
[env.check]
description = "Run common checks like linter, mypy, etc"
dependency_groups = [
"check",
]
extras = [
{ replace = "ref", of = ["project", "extras"], extend = true },
]
pip_pre = true
set_env.CFLAGS = "-Wno-unterminated-string-initialization"
set_env.MYPYPATH = "src"
commands = [
[
"autopep8",
{ replace = "ref", of = ["flags", "autopep8"], extend = true },
"--exit-code",
"--in-place",
"--jobs", "0",
"--recursive",
"src/{[project]name}",
"tests/{[project]name}",
],
[
"pylint",
{ replace = "ref", of = ["flags", "pylint"], extend = true },
"src/{[project]name}",
],
[
"bandit",
{ replace = "ref", of = ["flags", "bandit"], extend = true },
"--recursive",
"src/{[project]name}",
],
[
"bandit",
{ replace = "ref", of = ["flags", "bandit"], extend = true },
"--skip", "B101,B105,B106",
"--recursive",
"src/{[project]name}",
],
[
"mypy",
{ replace = "ref", of = ["flags", "mypy"], extend = true },
"--install-types",
"--non-interactive",
"--package", "{[project]name}",
],
]
[env.docs]
description = "Generate source files for documentation"
dependency_groups = [
"docs",
]
depends = [
"version",
]
deps = [
"uv",
]
dynamic_version = "{[project]name}.__version__"
extras = [
{ replace = "ref", of = ["project", "extras"], extend = true },
]
# TODO: steamline shlex usage after https://github.com/iterative/shtab/pull/192 merge
handle_redirect = true
pip_pre = true
set_env.PYTHONPATH = "src"
set_env.SPHINX_APIDOC_OPTIONS = "members,no-undoc-members,show-inheritance"
commands = [
[
"shtab",
{ replace = "ref", of = ["flags", "shtab"], extend = true },
"--shell",
"bash",
">",
"package/share/bash-completion/completions/_ahriman",
],
[
"shtab",
{ replace = "ref", of = ["flags", "shtab"], extend = true },
"--shell",
"zsh",
">",
"package/share/zsh/site-functions/_ahriman",
],
[
"argparse-manpage",
{ replace = "ref", of = ["flags", "manpage"], extend = true },
"--module", "ahriman.application.ahriman",
"--function", "_parser",
"--output", "package/share/man/man1/ahriman.1",
],
[
"pydeps",
{ replace = "ref", of = ["flags", "pydeps"], extend = true },
"--dot-output", "{tox_root}/docs/_static/architecture.dot",
"--no-output",
"--show-dot",
"src/ahriman",
],
[
"sphinx-apidoc",
"--force",
"--no-toc",
"--output-dir", "docs",
"src",
],
# compile list of dependencies for rtd.io
[
"uv",
"pip",
"compile",
"--group", "pyproject.toml:docs",
"--extra", "s3",
"--extra", "validator",
"--extra", "web",
"--output-file", "docs/requirements.txt",
"--quiet",
"pyproject.toml",
],
]
[env.html]
description = "Generate html documentation"
dependency_groups = [
"docs",
]
extras = [
{ replace = "ref", of = ["project", "extras"], extend = true },
]
pip_pre = true
recreate = true
commands = [
[
"sphinx-build",
"--builder", "html",
"--fail-on-warning",
"--jobs", "auto",
"--write-all",
"docs",
"{envtmpdir}/html",
],
]
[env.publish]
description = "Create and publish release to GitHub"
allowlist_externals = [
"git",
]
depends = [
"docs",
]
pass_env = [
"SSH_AUTH_SOCK",
]
commands = [
[
"git",
"add",
"package/archlinux/PKGBUILD",
"src/ahriman/__init__.py",
"docs/_static/architecture.dot",
"package/share/man/man1/ahriman.1",
"package/share/bash-completion/completions/_ahriman",
"package/share/zsh/site-functions/_ahriman",
],
[
"git",
"commit",
"--message", "Release {posargs}",
],
[
"git",
"tag",
"{posargs}",
],
[
"git",
"push",
],
[
"git",
"push",
"--tags",
],
]
[env.tests]
description = "Run tests"
dependency_groups = [
"tests",
]
extras = [
{ replace = "ref", of = ["project", "extras"], extend = true },
]
pip_pre = true
set_env.CFLAGS = "-Wno-unterminated-string-initialization"
commands = [
[
"pytest",
{ replace = "posargs", extend = true },
],
]
[env.version]
description = "Bump package version"
allowlist_externals = [
"sed",
]
deps = [
"packaging",
]
commands = [
# check if version is set and validate it
[
"{envpython}",
"-c", "from packaging.version import Version; Version('{posargs}')",
],
[
"sed",
"--in-place",
"s/^__version__ = .*/__version__ = \"{posargs}\"/",
"src/ahriman/__init__.py",
],
[
"sed",
"--in-place",
"s/pkgver=.*/pkgver={posargs}/",
"package/archlinux/PKGBUILD",
],
]

128
toxfile.py Normal file
View File

@ -0,0 +1,128 @@
#
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import importlib
import shlex
import sys
from tox.config.sets import EnvConfigSet
from tox.config.types import Command
from tox.plugin import impl
from tox.session.state import State
from tox.tox_env.api import ToxEnv
def _extract_version(env_conf: EnvConfigSet, python_path: str | None = None) -> dict[str, str]:
"""
extract version dynamically and set VERSION environment variable
Args:
env_conf(EnvConfigSet): the core configuration object
python_path(str | None): python path variable if available
Returns:
dict[str, str]: environment variables which must be inserted
"""
import_path = env_conf["dynamic_version"]
if not import_path:
return {}
if python_path is not None:
sys.path.append(python_path)
module_name, variable_name = import_path.rsplit(".", maxsplit=1)
module = importlib.import_module(module_name)
version = getattr(module, variable_name)
# reset import paths
sys.path.pop()
return {"VERSION": version}
def _wrap_commands(env_conf: EnvConfigSet, shell: str = "bash") -> None:
"""
wrap commands into shell if there is redirect
Args:
env_conf(EnvConfigSet): the core configuration object
shell(str, optional): shell command to use (Default value = "bash")
"""
if not env_conf["handle_redirect"]:
return
# append shell just in case
env_conf["allowlist_externals"].append(shell)
for command in env_conf["commands"]:
if len(command.args) < 3: # command itself, redirect and output
continue
redirect, output = command.args[-2:]
if redirect not in (">", "2>", "&>"):
continue
command.args = [
shell,
"-c",
f"{Command(command.args[:-2]).shell} {redirect} {shlex.quote(output)}",
]
@impl
def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None:
"""
add a command line argument. This is the first hook to be called,
right after the logging setup and config source discovery.
Args:
env_conf(EnvConfigSet): the core configuration object
state(State): the global tox state object
"""
del state
env_conf.add_config(
keys=["dynamic_version"],
of_type=str,
default="",
desc="import path for the version variable",
)
env_conf.add_config(
keys=["handle_redirect"],
of_type=bool,
default=False,
desc="wrap commands to handle redirects if any",
)
@impl
def tox_before_run_commands(tox_env: ToxEnv) -> None:
"""
called before the commands set is executed
Args:
tox_env(ToxEnv): the tox environment being executed
"""
env_conf = tox_env.conf
set_env = env_conf["set_env"]
python_path = set_env.load("PYTHONPATH") if "PYTHONPATH" in set_env else None
set_env.update(_extract_version(env_conf, python_path))
_wrap_commands(env_conf)