mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-08-23 01:49:55 +00:00
Compare commits
2 Commits
2.18.0
...
3f642ebcf1
Author | SHA1 | Date | |
---|---|---|---|
3f642ebcf1 | |||
38571eba04 |
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@ -8,10 +8,6 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
- '!*rc*'
|
- '!*rc*'
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-image:
|
docker-image:
|
||||||
|
|
||||||
|
3
.github/workflows/regress.yml
vendored
3
.github/workflows/regress.yml
vendored
@ -2,9 +2,6 @@ name: Regress
|
|||||||
|
|
||||||
on: workflow_dispatch
|
on: workflow_dispatch
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-regress-tests:
|
run-regress-tests:
|
||||||
|
|
||||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -5,9 +5,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
make-release:
|
make-release:
|
||||||
|
|
||||||
|
3
.github/workflows/setup.yml
vendored
3
.github/workflows/setup.yml
vendored
@ -8,9 +8,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-setup-minimal:
|
run-setup-minimal:
|
||||||
|
|
||||||
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -10,9 +10,6 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: 1 0 * * *
|
- cron: 1 0 * * *
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests:
|
run-tests:
|
||||||
|
|
||||||
|
@ -9,7 +9,13 @@ build:
|
|||||||
|
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: docs/requirements.txt
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
- s3
|
||||||
|
- validator
|
||||||
|
- web
|
||||||
|
|
||||||
formats:
|
formats:
|
||||||
- pdf
|
- pdf
|
||||||
|
1145
docs/_static/architecture.dot
vendored
1145
docs/_static/architecture.dot
vendored
File diff suppressed because it is too large
Load Diff
12
docs/conf.py
12
docs/conf.py
@ -15,8 +15,9 @@ import sys
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ahriman import __version__
|
||||||
|
|
||||||
|
|
||||||
# support package imports
|
|
||||||
basedir = Path(__file__).resolve().parent.parent / "src"
|
basedir = Path(__file__).resolve().parent.parent / "src"
|
||||||
sys.path.insert(0, str(basedir))
|
sys.path.insert(0, str(basedir))
|
||||||
|
|
||||||
@ -28,7 +29,6 @@ copyright = f"2021-{datetime.date.today().year}, ahriman team"
|
|||||||
author = "ahriman team"
|
author = "ahriman team"
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
from ahriman import __version__
|
|
||||||
release = __version__
|
release = __version__
|
||||||
|
|
||||||
|
|
||||||
@ -91,13 +91,7 @@ autoclass_content = "both"
|
|||||||
|
|
||||||
autodoc_member_order = "groupwise"
|
autodoc_member_order = "groupwise"
|
||||||
|
|
||||||
autodoc_mock_imports = [
|
autodoc_mock_imports = ["cryptography", "pyalpm"]
|
||||||
"aioauth_client",
|
|
||||||
"aiohttp_security",
|
|
||||||
"aiohttp_session",
|
|
||||||
"cryptography",
|
|
||||||
"pyalpm",
|
|
||||||
]
|
|
||||||
|
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
"no-undoc-members": True,
|
"no-undoc-members": True,
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
# This file was autogenerated by uv via the following command:
|
|
||||||
# uv pip compile --group ../pyproject.toml:docs --extra s3 --extra validator --extra web --output-file ../docs/requirements.txt ../pyproject.toml
|
|
||||||
aiohappyeyeballs==2.6.1
|
|
||||||
# via aiohttp
|
|
||||||
aiohttp==3.11.18
|
|
||||||
# via
|
|
||||||
# ahriman (../pyproject.toml)
|
|
||||||
# aiohttp-cors
|
|
||||||
# aiohttp-jinja2
|
|
||||||
aiohttp-cors==0.8.1
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
aiohttp-jinja2==1.6
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
aiosignal==1.3.2
|
|
||||||
# via aiohttp
|
|
||||||
alabaster==1.0.0
|
|
||||||
# via sphinx
|
|
||||||
argparse-manpage==4.6
|
|
||||||
# via ahriman (../pyproject.toml:docs)
|
|
||||||
attrs==25.3.0
|
|
||||||
# via aiohttp
|
|
||||||
babel==2.17.0
|
|
||||||
# via sphinx
|
|
||||||
bcrypt==4.3.0
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
boto3==1.38.11
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
botocore==1.38.11
|
|
||||||
# via
|
|
||||||
# boto3
|
|
||||||
# s3transfer
|
|
||||||
cerberus==1.3.7
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
certifi==2025.4.26
|
|
||||||
# via requests
|
|
||||||
charset-normalizer==3.4.2
|
|
||||||
# via requests
|
|
||||||
docutils==0.21.2
|
|
||||||
# via
|
|
||||||
# sphinx
|
|
||||||
# sphinx-argparse
|
|
||||||
# sphinx-rtd-theme
|
|
||||||
frozenlist==1.6.0
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# aiosignal
|
|
||||||
idna==3.10
|
|
||||||
# via
|
|
||||||
# requests
|
|
||||||
# yarl
|
|
||||||
imagesize==1.4.1
|
|
||||||
# via sphinx
|
|
||||||
inflection==0.5.1
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
jinja2==3.1.6
|
|
||||||
# via
|
|
||||||
# aiohttp-jinja2
|
|
||||||
# sphinx
|
|
||||||
jmespath==1.0.1
|
|
||||||
# via
|
|
||||||
# boto3
|
|
||||||
# botocore
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
multidict==6.4.3
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# yarl
|
|
||||||
packaging==25.0
|
|
||||||
# via sphinx
|
|
||||||
propcache==0.3.1
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# yarl
|
|
||||||
pydeps==3.0.1
|
|
||||||
# via ahriman (../pyproject.toml:docs)
|
|
||||||
pyelftools==0.32
|
|
||||||
# via ahriman (../pyproject.toml)
|
|
||||||
pygments==2.19.1
|
|
||||||
# via sphinx
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
# via botocore
|
|
||||||
requests==2.32.3
|
|
||||||
# via
|
|
||||||
# ahriman (../pyproject.toml)
|
|
||||||
# sphinx
|
|
||||||
roman-numerals-py==3.1.0
|
|
||||||
# via sphinx
|
|
||||||
s3transfer==0.12.0
|
|
||||||
# via boto3
|
|
||||||
shtab==1.7.2
|
|
||||||
# via ahriman (../pyproject.toml:docs)
|
|
||||||
six==1.17.0
|
|
||||||
# via python-dateutil
|
|
||||||
snowballstemmer==3.0.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinx==8.2.3
|
|
||||||
# via
|
|
||||||
# ahriman (../pyproject.toml:docs)
|
|
||||||
# sphinx-argparse
|
|
||||||
# sphinx-rtd-theme
|
|
||||||
# sphinxcontrib-jquery
|
|
||||||
sphinx-argparse==0.5.2
|
|
||||||
# via ahriman (../pyproject.toml:docs)
|
|
||||||
sphinx-rtd-theme==3.0.2
|
|
||||||
# via ahriman (../pyproject.toml:docs)
|
|
||||||
sphinxcontrib-applehelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-devhelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.1.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-jquery==4.1
|
|
||||||
# via sphinx-rtd-theme
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-qthelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-serializinghtml==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
stdlib-list==0.11.1
|
|
||||||
# via pydeps
|
|
||||||
urllib3==2.4.0
|
|
||||||
# via
|
|
||||||
# botocore
|
|
||||||
# requests
|
|
||||||
yarl==1.20.0
|
|
||||||
# via aiohttp
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
pkgbase='ahriman'
|
pkgbase='ahriman'
|
||||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||||
pkgver=2.18.0
|
pkgver=2.17.1
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
@ -635,7 +635,6 @@ _set_new_action() {
|
|||||||
# ${!x} -> ${hello} -> "world"
|
# ${!x} -> ${hello} -> "world"
|
||||||
_shtab_ahriman() {
|
_shtab_ahriman() {
|
||||||
local completing_word="${COMP_WORDS[COMP_CWORD]}"
|
local completing_word="${COMP_WORDS[COMP_CWORD]}"
|
||||||
local previous_word="${COMP_WORDS[COMP_CWORD-1]}"
|
|
||||||
local completed_positional_actions
|
local completed_positional_actions
|
||||||
local current_action
|
local current_action
|
||||||
local current_action_args_start_index
|
local current_action_args_start_index
|
||||||
@ -692,10 +691,6 @@ _shtab_ahriman() {
|
|||||||
if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then
|
if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then
|
||||||
# optional argument started: use option strings
|
# optional argument started: use option strings
|
||||||
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
|
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
|
||||||
elif [[ "${previous_word}" == ">" || "${previous_word}" == ">>" ||
|
|
||||||
"${previous_word}" =~ ^[12]">" || "${previous_word}" =~ ^[12]">>" ]]; then
|
|
||||||
# handle redirection operators
|
|
||||||
COMPREPLY=( $(compgen -f -- "${completing_word}") )
|
|
||||||
else
|
else
|
||||||
# use choices & compgen
|
# use choices & compgen
|
||||||
local IFS=$'\n' # items may contain spaces, so delimit using newline
|
local IFS=$'\n' # items may contain spaces, so delimit using newline
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.TH AHRIMAN "1" "2025\-06\-13" "ahriman" "Generated Python Manual"
|
.TH AHRIMAN "1" "2025\-01\-05" "ahriman" "Generated Python Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ahriman
|
ahriman
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
@ -25,64 +25,15 @@ dependencies = [
|
|||||||
|
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
journald = [
|
|
||||||
"systemd-python",
|
|
||||||
]
|
|
||||||
# FIXME technically this dependency is required, but in some cases we do not have access to
|
|
||||||
# the libalpm which is required in order to install the package. Thus in case if we do not
|
|
||||||
# really need to run the application we can move it to "optional" dependencies
|
|
||||||
pacman = [
|
|
||||||
"pyalpm",
|
|
||||||
]
|
|
||||||
reports = [
|
|
||||||
"Jinja2",
|
|
||||||
]
|
|
||||||
s3 = [
|
|
||||||
"boto3",
|
|
||||||
]
|
|
||||||
shell = [
|
|
||||||
"IPython"
|
|
||||||
]
|
|
||||||
stats = [
|
|
||||||
"matplotlib",
|
|
||||||
]
|
|
||||||
unixsocket = [
|
|
||||||
"requests-unixsocket2", # required by unix socket support
|
|
||||||
]
|
|
||||||
validator = [
|
|
||||||
"cerberus",
|
|
||||||
]
|
|
||||||
web = [
|
|
||||||
"aiohttp",
|
|
||||||
"aiohttp_cors",
|
|
||||||
"aiohttp_jinja2",
|
|
||||||
]
|
|
||||||
web_api-docs = [
|
|
||||||
"ahriman[web]",
|
|
||||||
"aiohttp-apispec",
|
|
||||||
"setuptools", # required by aiohttp-apispec
|
|
||||||
]
|
|
||||||
web_auth = [
|
|
||||||
"ahriman[web]",
|
|
||||||
"aiohttp_session",
|
|
||||||
"aiohttp_security",
|
|
||||||
"cryptography",
|
|
||||||
]
|
|
||||||
web_oauth2 = [
|
|
||||||
"ahriman[web_auth]",
|
|
||||||
"aioauth-client",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
ahriman = "ahriman.application.ahriman:run"
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Documentation = "https://ahriman.readthedocs.io/"
|
Documentation = "https://ahriman.readthedocs.io/"
|
||||||
Repository = "https://github.com/arcan1s/ahriman"
|
Repository = "https://github.com/arcan1s/ahriman"
|
||||||
Changelog = "https://github.com/arcan1s/ahriman/releases"
|
Changelog = "https://github.com/arcan1s/ahriman/releases"
|
||||||
|
|
||||||
[dependency-groups]
|
[project.scripts]
|
||||||
|
ahriman = "ahriman.application.ahriman:run"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
check = [
|
check = [
|
||||||
"autopep8",
|
"autopep8",
|
||||||
"bandit",
|
"bandit",
|
||||||
@ -96,6 +47,24 @@ docs = [
|
|||||||
"shtab",
|
"shtab",
|
||||||
"sphinx-argparse",
|
"sphinx-argparse",
|
||||||
"sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734
|
"sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734
|
||||||
|
]
|
||||||
|
journald = [
|
||||||
|
"systemd-python",
|
||||||
|
]
|
||||||
|
# FIXME technically this dependency is required, but in some cases we do not have access to
|
||||||
|
# the libalpm which is required in order to install the package. Thus in case if we do not
|
||||||
|
# really need to run the application we can move it to "optional" dependencies
|
||||||
|
pacman = [
|
||||||
|
"pyalpm",
|
||||||
|
]
|
||||||
|
s3 = [
|
||||||
|
"boto3",
|
||||||
|
]
|
||||||
|
shell = [
|
||||||
|
"IPython"
|
||||||
|
]
|
||||||
|
stats = [
|
||||||
|
"matplotlib",
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
"pytest",
|
"pytest",
|
||||||
@ -106,6 +75,22 @@ tests = [
|
|||||||
"pytest-resource-path",
|
"pytest-resource-path",
|
||||||
"pytest-spec",
|
"pytest-spec",
|
||||||
]
|
]
|
||||||
|
validator = [
|
||||||
|
"cerberus",
|
||||||
|
]
|
||||||
|
web = [
|
||||||
|
"Jinja2",
|
||||||
|
"aioauth-client",
|
||||||
|
"aiohttp",
|
||||||
|
"aiohttp-apispec",
|
||||||
|
"aiohttp_cors",
|
||||||
|
"aiohttp_jinja2",
|
||||||
|
"aiohttp_session",
|
||||||
|
"aiohttp_security",
|
||||||
|
"cryptography",
|
||||||
|
"requests-unixsocket2", # required by unix socket support
|
||||||
|
"setuptools", # required by aiohttp-apispec
|
||||||
|
]
|
||||||
|
|
||||||
[tool.flit.sdist]
|
[tool.flit.sdist]
|
||||||
include = [
|
include = [
|
||||||
|
@ -17,4 +17,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2.18.0"
|
__version__ = "2.17.1"
|
||||||
|
183
src/ahriman/core/alpm/bytes_pkgbuild_parser.py
Normal file
183
src/ahriman/core/alpm/bytes_pkgbuild_parser.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import ReprEnum
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from typing import Generator, IO, Self
|
||||||
|
|
||||||
|
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||||
|
|
||||||
|
|
||||||
|
class PkgbuildToken(bytes, ReprEnum):
|
||||||
|
|
||||||
|
Comment = b"#"
|
||||||
|
Assignment = b"="
|
||||||
|
SingleQuote = b"'"
|
||||||
|
DoubleQuote = b"\""
|
||||||
|
Space = b" "
|
||||||
|
NewLine = b"\n"
|
||||||
|
|
||||||
|
ParenthesisOpen = b"("
|
||||||
|
ParenthesisClose = b")"
|
||||||
|
|
||||||
|
FunctionStarts = b"function"
|
||||||
|
FunctionDeclaration = b"()"
|
||||||
|
BraceOpen = b"{"
|
||||||
|
BraceClose = b"}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PkgbuildWord:
|
||||||
|
|
||||||
|
word: bytes
|
||||||
|
quote: bytes | None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closing(self) -> PkgbuildToken | None:
|
||||||
|
if self.quote:
|
||||||
|
return None
|
||||||
|
match self.word:
|
||||||
|
case PkgbuildToken.ParenthesisOpen:
|
||||||
|
return PkgbuildToken.ParenthesisClose
|
||||||
|
case PkgbuildToken.BraceOpen:
|
||||||
|
return PkgbuildToken.BraceClose
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def original(self) -> bytes:
|
||||||
|
quote = self.quote or b""
|
||||||
|
return quote + self.word + quote
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self.original)
|
||||||
|
|
||||||
|
|
||||||
|
class BytesPkgbuildParser(Iterator[PkgbuildPatch]):
|
||||||
|
|
||||||
|
def __init__(self, stream: IO[bytes]) -> None:
|
||||||
|
self._io = stream
|
||||||
|
|
||||||
|
def _next(self, *, declaration: bool) -> bytes:
|
||||||
|
while not (token := self._next_token(declaration=declaration)):
|
||||||
|
continue
|
||||||
|
return token
|
||||||
|
|
||||||
|
def _next_token(self, *, declaration: bool) -> bytes:
|
||||||
|
buffer = b""
|
||||||
|
while word := self._next_word():
|
||||||
|
match word:
|
||||||
|
case PkgbuildWord(PkgbuildToken.Comment, None):
|
||||||
|
self._io.readline()
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.NewLine, None):
|
||||||
|
if declaration:
|
||||||
|
buffer = b""
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.Assignment, None) if declaration:
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.Space, None) if declaration:
|
||||||
|
if buffer.endswith(PkgbuildToken.FunctionDeclaration):
|
||||||
|
return buffer
|
||||||
|
buffer = b""
|
||||||
|
continue
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.Space, None):
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.ParenthesisOpen, None):
|
||||||
|
buffer += PkgbuildToken.ParenthesisOpen
|
||||||
|
buffer += b"".join(self._next_words_until(PkgbuildWord(PkgbuildToken.ParenthesisClose, None)))
|
||||||
|
|
||||||
|
case PkgbuildWord(PkgbuildToken.BraceOpen, None):
|
||||||
|
buffer += PkgbuildToken.BraceOpen
|
||||||
|
buffer += b"".join(self._next_words_until(PkgbuildWord(PkgbuildToken.BraceClose, None)))
|
||||||
|
|
||||||
|
case PkgbuildWord(token, _):
|
||||||
|
buffer += token
|
||||||
|
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
def _next_word(self) -> PkgbuildWord:
|
||||||
|
# pass SimpleNamespace as an argument to implement side effects
|
||||||
|
def generator(quote: SimpleNamespace) -> Generator[bytes, None, None]:
|
||||||
|
while token := self._io.read(1):
|
||||||
|
match token:
|
||||||
|
case (PkgbuildToken.SingleQuote | PkgbuildToken.DoubleQuote) if quote.open is None:
|
||||||
|
quote.open = token
|
||||||
|
case closing_quote if closing_quote == quote.open:
|
||||||
|
return
|
||||||
|
case part:
|
||||||
|
yield part
|
||||||
|
if quote.open is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if quote.open is not None:
|
||||||
|
raise ValueError("No closing quotation")
|
||||||
|
|
||||||
|
open_quote = SimpleNamespace(open=None)
|
||||||
|
value = b"".join(generator(open_quote))
|
||||||
|
|
||||||
|
return PkgbuildWord(value, open_quote.open)
|
||||||
|
|
||||||
|
def _next_words_until(self, ending: PkgbuildWord) -> Generator[bytes, None, None]:
|
||||||
|
braces = defaultdict(int)
|
||||||
|
while element := self._next_word():
|
||||||
|
yield element.original
|
||||||
|
match element:
|
||||||
|
case PkgbuildWord(token, None) if braces[token] > 0:
|
||||||
|
braces[token] -= 1
|
||||||
|
case with_closure if (closing := with_closure.closing) is not None:
|
||||||
|
braces[closing] += 1
|
||||||
|
case _ if element == ending:
|
||||||
|
return
|
||||||
|
|
||||||
|
if any(brace for brace in braces.values() if brace > 0):
|
||||||
|
raise ValueError("Unclosed parenthesis and/or braces found")
|
||||||
|
raise ValueError(f"No matching ending element {ending.word} found")
|
||||||
|
|
||||||
|
def parse(self) -> Generator[PkgbuildPatch, None, None]:
|
||||||
|
"""
|
||||||
|
parse source stream and yield parsed entries
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
PkgbuildPatch: extracted a PKGBUILD node
|
||||||
|
"""
|
||||||
|
yield from self
|
||||||
|
|
||||||
|
def __iter__(self) -> Self:
|
||||||
|
"""
|
||||||
|
base iterator method
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self: iterator instance
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> PkgbuildPatch:
|
||||||
|
key = self._next(declaration=True)
|
||||||
|
value = self._next(declaration=False)
|
||||||
|
|
||||||
|
return PkgbuildPatch(key.decode(encoding="utf8"), value.decode(encoding="utf8"))
|
@ -23,7 +23,7 @@ import sys
|
|||||||
|
|
||||||
from collections.abc import Generator, Mapping, MutableMapping
|
from collections.abc import Generator, Mapping, MutableMapping
|
||||||
from string import Template
|
from string import Template
|
||||||
from typing import Any, ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.core.configuration.shell_template import ShellTemplate
|
from ahriman.core.configuration.shell_template import ShellTemplate
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class ShellInterpolator(configparser.Interpolation):
|
|||||||
"prefix": sys.prefix,
|
"prefix": sys.prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
def before_get(self, parser: MutableMapping[str, Mapping[str, str]], section: Any, option: Any, value: str,
|
def before_get(self, parser: MutableMapping[str, Mapping[str, str]], section: str, option: str, value: str,
|
||||||
defaults: Mapping[str, str]) -> str:
|
defaults: Mapping[str, str]) -> str:
|
||||||
"""
|
"""
|
||||||
interpolate option value
|
interpolate option value
|
||||||
@ -100,8 +100,8 @@ class ShellInterpolator(configparser.Interpolation):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
parser(MutableMapping[str, Mapping[str, str]]): option parser
|
parser(MutableMapping[str, Mapping[str, str]]): option parser
|
||||||
section(Any): section name
|
section(str): section name
|
||||||
option(Any): option name
|
option(str): option name
|
||||||
value(str): source (not-converted) value
|
value(str): source (not-converted) value
|
||||||
defaults(Mapping[str, str]): default values
|
defaults(Mapping[str, str]): default values
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from aiohttp.web import Application, Resource, UrlMappingMatchInfo
|
from aiohttp.web import Application, Resource, UrlMappingMatchInfo
|
||||||
|
from asyncio import BaseEventLoop
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from marshmallow import Schema
|
from marshmallow import Schema
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
@ -164,13 +164,15 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
|
|||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest.fixture
|
||||||
async def client(application: Application, aiohttp_client: Any, mocker: MockerFixture) -> TestClient:
|
def client(application: Application, event_loop: BaseEventLoop, aiohttp_client: Any,
|
||||||
|
mocker: MockerFixture) -> TestClient:
|
||||||
"""
|
"""
|
||||||
web client fixture
|
web client fixture
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
application(Application): application fixture
|
application(Application): application fixture
|
||||||
|
event_loop(BaseEventLoop): context event loop
|
||||||
aiohttp_client(Any): aiohttp client fixture
|
aiohttp_client(Any): aiohttp client fixture
|
||||||
mocker(MockerFixture): mocker object
|
mocker(MockerFixture): mocker object
|
||||||
|
|
||||||
@ -178,35 +180,37 @@ async def client(application: Application, aiohttp_client: Any, mocker: MockerFi
|
|||||||
TestClient: web client test instance
|
TestClient: web client test instance
|
||||||
"""
|
"""
|
||||||
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
||||||
return await aiohttp_client(application)
|
return event_loop.run_until_complete(aiohttp_client(application))
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest.fixture
|
||||||
async def client_with_auth(application_with_auth: Application, aiohttp_client: Any,
|
def client_with_auth(application_with_auth: Application, event_loop: BaseEventLoop, aiohttp_client: Any,
|
||||||
|
mocker: MockerFixture) -> TestClient:
|
||||||
|
"""
|
||||||
|
web client fixture with full authorization functions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
application_with_auth(Application): application fixture
|
||||||
|
event_loop(BaseEventLoop): context event loop
|
||||||
|
aiohttp_client(Any): aiohttp client fixture
|
||||||
|
mocker(MockerFixture): mocker object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TestClient: web client test instance
|
||||||
|
"""
|
||||||
|
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
||||||
|
return event_loop.run_until_complete(aiohttp_client(application_with_auth))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client_with_oauth_auth(application_with_auth: Application, event_loop: BaseEventLoop, aiohttp_client: Any,
|
||||||
mocker: MockerFixture) -> TestClient:
|
mocker: MockerFixture) -> TestClient:
|
||||||
"""
|
"""
|
||||||
web client fixture with full authorization functions
|
web client fixture with full authorization functions
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
application_with_auth(Application): application fixture
|
application_with_auth(Application): application fixture
|
||||||
aiohttp_client(Any): aiohttp client fixture
|
event_loop(BaseEventLoop): context event loop
|
||||||
mocker(MockerFixture): mocker object
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
TestClient: web client test instance
|
|
||||||
"""
|
|
||||||
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
|
||||||
return await aiohttp_client(application_with_auth)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
|
||||||
async def client_with_oauth_auth(application_with_auth: Application, aiohttp_client: Any,
|
|
||||||
mocker: MockerFixture) -> TestClient:
|
|
||||||
"""
|
|
||||||
web client fixture with full authorization functions
|
|
||||||
|
|
||||||
Args:
|
|
||||||
application_with_auth(Application): application fixture
|
|
||||||
aiohttp_client(Any): aiohttp client fixture
|
aiohttp_client(Any): aiohttp client fixture
|
||||||
mocker(MockerFixture): mocker object
|
mocker(MockerFixture): mocker object
|
||||||
|
|
||||||
@ -215,4 +219,4 @@ async def client_with_oauth_auth(application_with_auth: Application, aiohttp_cli
|
|||||||
"""
|
"""
|
||||||
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
||||||
application_with_auth[AuthKey] = MagicMock(spec=OAuth)
|
application_with_auth[AuthKey] = MagicMock(spec=OAuth)
|
||||||
return await aiohttp_client(application_with_auth)
|
return event_loop.run_until_complete(aiohttp_client(application_with_auth))
|
||||||
|
39
tox.ini
39
tox.ini
@ -3,7 +3,7 @@ envlist = check, tests
|
|||||||
isolated_build = true
|
isolated_build = true
|
||||||
labels =
|
labels =
|
||||||
release = version, docs, publish
|
release = version, docs, publish
|
||||||
dependencies = -e .[journald,pacman,reports,s3,shell,stats,unixsocket,validator,web,web_api-docs,web_auth,web_oauth2]
|
dependencies = -e .[journald,pacman,s3,shell,stats,validator,web]
|
||||||
project_name = ahriman
|
project_name = ahriman
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
@ -24,10 +24,9 @@ commands =
|
|||||||
|
|
||||||
[testenv:check]
|
[testenv:check]
|
||||||
description = Run common checks like linter, mypy, etc
|
description = Run common checks like linter, mypy, etc
|
||||||
dependency_groups =
|
|
||||||
check
|
|
||||||
deps =
|
deps =
|
||||||
{[tox]dependencies}
|
{[tox]dependencies}
|
||||||
|
-e .[check]
|
||||||
pip_pre = true
|
pip_pre = true
|
||||||
setenv =
|
setenv =
|
||||||
CFLAGS="-Wno-unterminated-string-initialization"
|
CFLAGS="-Wno-unterminated-string-initialization"
|
||||||
@ -41,19 +40,16 @@ commands =
|
|||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
description = Generate source files for documentation
|
description = Generate source files for documentation
|
||||||
allowlist_externals =
|
|
||||||
bash
|
|
||||||
find
|
|
||||||
mv
|
|
||||||
changedir = src
|
|
||||||
dependency_groups =
|
|
||||||
docs
|
|
||||||
depends =
|
depends =
|
||||||
version
|
version
|
||||||
deps =
|
deps =
|
||||||
{[tox]dependencies}
|
{[tox]dependencies}
|
||||||
uv
|
-e .[docs]
|
||||||
pip_pre = true
|
changedir = src
|
||||||
|
allowlist_externals =
|
||||||
|
bash
|
||||||
|
find
|
||||||
|
mv
|
||||||
setenv =
|
setenv =
|
||||||
SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance
|
SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance
|
||||||
commands =
|
commands =
|
||||||
@ -65,26 +61,22 @@ commands =
|
|||||||
# remove autogenerated modules rst files
|
# remove autogenerated modules rst files
|
||||||
find ../docs -type f -name "{[tox]project_name}*.rst" -delete
|
find ../docs -type f -name "{[tox]project_name}*.rst" -delete
|
||||||
sphinx-apidoc -o ../docs .
|
sphinx-apidoc -o ../docs .
|
||||||
# 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]
|
[testenv:html]
|
||||||
description = Generate html documentation
|
description = Generate html documentation
|
||||||
dependency_groups =
|
|
||||||
docs
|
|
||||||
deps =
|
deps =
|
||||||
{[tox]dependencies}
|
{[tox]dependencies}
|
||||||
pip_pre = true
|
-e .[docs]
|
||||||
recreate = true
|
recreate = true
|
||||||
commands =
|
commands =
|
||||||
sphinx-build -b html -a -j auto -W docs {envtmpdir}{/}html
|
sphinx-build -b html -a -j auto -W docs {envtmpdir}{/}html
|
||||||
|
|
||||||
[testenv:publish]
|
[testenv:publish]
|
||||||
description = Create and publish release to GitHub
|
description = Create and publish release to GitHub
|
||||||
allowlist_externals =
|
|
||||||
git
|
|
||||||
depends =
|
depends =
|
||||||
docs
|
docs
|
||||||
|
allowlist_externals =
|
||||||
|
git
|
||||||
passenv =
|
passenv =
|
||||||
SSH_AUTH_SOCK
|
SSH_AUTH_SOCK
|
||||||
commands =
|
commands =
|
||||||
@ -96,22 +88,19 @@ commands =
|
|||||||
|
|
||||||
[testenv:tests]
|
[testenv:tests]
|
||||||
description = Run tests
|
description = Run tests
|
||||||
dependency_groups =
|
|
||||||
tests
|
|
||||||
deps =
|
deps =
|
||||||
{[tox]dependencies}
|
{[tox]dependencies}
|
||||||
|
-e .[tests]
|
||||||
pip_pre = true
|
pip_pre = true
|
||||||
setenv =
|
|
||||||
CFLAGS="-Wno-unterminated-string-initialization"
|
|
||||||
commands =
|
commands =
|
||||||
pytest {posargs}
|
pytest {posargs}
|
||||||
|
|
||||||
[testenv:version]
|
[testenv:version]
|
||||||
description = Bump package version
|
description = Bump package version
|
||||||
allowlist_externals =
|
|
||||||
sed
|
|
||||||
deps =
|
deps =
|
||||||
packaging
|
packaging
|
||||||
|
allowlist_externals =
|
||||||
|
sed
|
||||||
commands =
|
commands =
|
||||||
# check if version is set and validate it
|
# check if version is set and validate it
|
||||||
{envpython} -c 'from packaging.version import Version; Version("{posargs}")'
|
{envpython} -c 'from packaging.version import Version; Version("{posargs}")'
|
||||||
|
Reference in New Issue
Block a user