Compare commits

..

4 Commits

Author SHA1 Message Date
15a13ea283 implement local reporter mode 2024-05-09 15:08:12 +03:00
04e10bd9b7 feat: add abillity to check broken dependencies (#122)
* implement elf dynamic linking check

* load local database too in pacman wrapper
2024-05-09 13:27:06 +03:00
07b77be6b8 Release 2.13.7 2024-05-09 13:26:40 +03:00
2b33510ada fix: parse array variable from command 2024-05-09 13:21:42 +03:00
13 changed files with 131 additions and 79 deletions

View File

@ -208,6 +208,18 @@ This command will prompt for new value of the PKGBUILD variable ``version``. You
sudo -u ahriman ahriman patch-add ahriman version version.patch
The command also supports arrays, but in this case you need to specify full array, e.g.
.. code-block:: shell
sudo -u ahriman ahriman patch-add ahriman depends
Post new function or variable value below. Press Ctrl-D to finish:
(python python-aiohttp)
^D
will set depends PKGBUILD variable (exactly) to array ``["python", "python-aiohttp"]``.
Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch:
#.

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev
pkgname='ahriman'
pkgver=2.13.6
pkgver=2.13.7
pkgrel=1
pkgdesc="ArcH linux ReposItory MANager"
arch=('any')

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2024\-05\-05" "ahriman" "Generated Python Manual"
.TH AHRIMAN "1" "2024\-05\-09" "ahriman" "Generated Python Manual"
.SH NAME
ahriman
.SH SYNOPSIS

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__version__ = "2.13.6"
__version__ = "2.13.7"

View File

@ -446,7 +446,7 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
parser = root.add_parser("patch-list", help="list patch sets",
description="list available patches for the package", formatter_class=_formatter)
parser.add_argument("package", help="package base", nargs="?")
parser.add_argument("package", help="package base")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
action="append")

View File

@ -50,7 +50,8 @@ class Add(Handler):
application.add(args.package, args.source, args.username)
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
for package in args.package: # for each requested package insert patch
application.database.patches_insert(package, patches)
for patch in patches:
application.reporter.package_patches_add(package, patch)
if not args.now:
return

View File

@ -102,8 +102,9 @@ class Patch(Handler):
patch = "".join(list(sys.stdin))
else:
patch = patch_path.read_text(encoding="utf8")
patch = patch.strip() # remove spaces around the patch
return PkgbuildPatch(variable, patch)
# remove spaces around the patch and parse to correct type
parsed = PkgbuildPatch.parse(patch.strip())
return PkgbuildPatch(variable, parsed)
@staticmethod
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:
@ -115,25 +116,29 @@ class Patch(Handler):
package_base(str): package base
patch(PkgbuildPatch): patch descriptor
"""
application.database.patches_insert(package_base, [patch])
application.reporter.package_patches_add(package_base, patch)
@staticmethod
def patch_set_list(application: Application, package_base: str | None, variables: list[str] | None,
def patch_set_list(application: Application, package_base: str, variables: list[str] | None,
exit_code: bool) -> None:
"""
list patches available for the package base
Args:
application(Application): application instance
package_base(str | None): package base
package_base(str): package base
variables(list[str] | None): extract patches only for specified PKGBUILD variables
exit_code(bool): exit with error on empty search result
"""
patches = application.database.patches_list(package_base, variables)
patches = []
if variables is not None:
for variable in variables:
patches.extend(application.reporter.package_patches_get(package_base, variable))
else:
patches = application.reporter.package_patches_get(package_base, variables)
Patch.check_if_empty(exit_code, not patches)
for base, patch in patches.items():
PatchPrinter(base, patch)(verbose=True, separator=" = ")
PatchPrinter(package_base, patches)(verbose=True, separator=" = ")
@staticmethod
def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None:
@ -145,4 +150,8 @@ class Patch(Handler):
package_base(str): package base
variables(list[str] | None): remove patches only for specified PKGBUILD variables
"""
application.database.patches_remove(package_base, variables)
if variables is not None:
for variable in variables: # iterate over single variable
application.reporter.package_patches_remove(package_base, variable)
else:
application.reporter.package_patches_remove(package_base, variables) # just pass as is

View File

@ -76,7 +76,7 @@ class Rebuild(Handler):
if from_database:
return [
package
for (package, last_status) in application.database.packages_get()
for (package, last_status) in application.reporter.package_get(None)
if status is None or last_status.status == status
]

View File

@ -56,7 +56,6 @@ __all__ = [
"srcinfo_property",
"srcinfo_property_list",
"trim_package",
"unquote",
"utcnow",
"walk",
]
@ -466,38 +465,6 @@ def trim_package(package_name: str) -> str:
return package_name
def unquote(source: str) -> str:
"""
like :func:`shlex.quote()`, but opposite
Args:
source(str): source string to remove quotes
Returns:
str: string with quotes removed
Raises:
ValueError: if no closing quotation
"""
def generator() -> Generator[str, None, None]:
token = None
for char in source:
if token is not None:
if char == token:
token = None # closed quote
else:
yield char # character inside quotes
elif char in ("'", "\""):
token = char # first quote found
else:
yield char # normal character
if token is not None:
raise ValueError("No closing quotation")
return "".join(generator())
def utcnow() -> datetime.datetime:
"""
get current time

View File

@ -21,9 +21,9 @@ import shlex
from dataclasses import dataclass, fields
from pathlib import Path
from typing import Any, Self
from typing import Any, Generator, Self
from ahriman.core.util import dataclass_view, filter_json, unquote
from ahriman.core.util import dataclass_view, filter_json
@dataclass(frozen=True)
@ -81,14 +81,57 @@ class PkgbuildPatch:
Self: package properties
"""
key, *value_parts = variable.split("=", maxsplit=1)
raw_value = next(iter(value_parts), "") # extract raw value
if raw_value.startswith("(") and raw_value.endswith(")"):
value: str | list[str] = shlex.split(raw_value[1:-1]) # arrays for poor
else:
value = unquote(raw_value)
return cls(key, cls.parse(raw_value))
return cls(key, value)
@staticmethod
def parse(source: str) -> str | list[str]:
"""
parse string value to the PKGBUILD patch value. This method simply takes string, tries to identify it as array
or just string and return the respective value. Functions should be processed correctly, however, not guaranteed
Args:
source(str): source string to parse
Returns:
str | list[str]: parsed value either string or list of strings
"""
if source.startswith("(") and source.endswith(")"):
return shlex.split(source[1:-1]) # arrays for poor
return PkgbuildPatch.unquote(source)
@staticmethod
def unquote(source: str) -> str:
"""
like :func:`shlex.quote()`, but opposite
Args:
source(str): source string to remove quotes
Returns:
str: string with quotes removed
Raises:
ValueError: if no closing quotation
"""
def generator() -> Generator[str, None, None]:
token = None
for char in source:
if token is not None:
if char == token:
token = None # closed quote
else:
yield char # character inside quotes
elif char in ("'", "\""):
token = char # first quote found
else:
yield char # normal character
if token is not None:
raise ValueError("No closing quotation")
return "".join(generator())
@classmethod
def from_json(cls, dump: dict[str, Any]) -> Self:

View File

@ -122,11 +122,10 @@ def test_patch_create_from_function(mocker: MockerFixture) -> None:
"""
must create function patch from file
"""
path = Path("local")
patch = PkgbuildPatch("version", "patch")
read_mock = mocker.patch("pathlib.Path.read_text", return_value=patch.value)
assert Patch.patch_create_from_function(patch.key, path) == patch
assert Patch.patch_create_from_function(patch.key, Path("local")) == patch
read_mock.assert_called_once_with(encoding="utf8")
@ -148,6 +147,15 @@ def test_patch_create_from_function_strip(mocker: MockerFixture) -> None:
assert Patch.patch_create_from_function(patch.key, None) == patch
def test_patch_create_from_function_array(mocker: MockerFixture) -> None:
"""
must correctly read array variable
"""
patch = PkgbuildPatch("version", ["array", "patch"])
mocker.patch("pathlib.Path.read_text", return_value=f"({" ".join(patch.value)})")
assert Patch.patch_create_from_function(patch.key, Path("local")) == patch
def test_patch_set_list(application: Application, mocker: MockerFixture) -> None:
"""
must list available patches for the command

View File

@ -12,7 +12,7 @@ from unittest.mock import call as MockCall
from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError
from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, extract_user, filter_json, \
full_version, minmax, package_like, parse_version, partition, pretty_datetime, pretty_size, safe_filename, \
srcinfo_property, srcinfo_property_list, trim_package, unquote, utcnow, walk
srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.repository_id import RepositoryId
@ -445,26 +445,6 @@ def test_trim_package() -> None:
assert trim_package("package: a description") == "package"
def test_unquote() -> None:
"""
must remove quotation marks
"""
for source in (
"abc",
"ab'c",
"ab\"c",
):
assert unquote(shlex.quote(source)) == source
def test_unquote_error() -> None:
"""
must raise value error on invalid quotation
"""
with pytest.raises(ValueError):
unquote("ab'c")
def test_utcnow() -> None:
"""
must generate correct timestamp

View File

@ -1,3 +1,6 @@
import pytest
import shlex
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call
@ -48,6 +51,35 @@ def test_from_env() -> None:
assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "")
def test_parse() -> None:
"""
must parse string correctly
"""
assert PkgbuildPatch.parse("VALUE") == "VALUE"
assert PkgbuildPatch.parse("(ARRAY VALUE)") == ["ARRAY", "VALUE"]
assert PkgbuildPatch.parse("""("QU'OUTED" ARRAY VALUE)""") == ["QU'OUTED", "ARRAY", "VALUE"]
def test_unquote() -> None:
"""
must remove quotation marks
"""
for source in (
"abc",
"ab'c",
"ab\"c",
):
assert PkgbuildPatch.unquote(shlex.quote(source)) == source
def test_unquote_error() -> None:
"""
must raise value error on invalid quotation
"""
with pytest.raises(ValueError):
PkgbuildPatch.unquote("ab'c")
def test_serialize() -> None:
"""
must correctly serialize string values