fix: correctly serialize patches from database (#137)

If value is stored as array in database it is serialized as json, but
read as normal string, which lead to innability to use list patches

This fix also removes any postprocessing (unquoting) for functions
This commit is contained in:
Evgenii Alekseev 2024-11-18 17:45:43 +02:00
parent 0cc35e70e3
commit f5d7085325
5 changed files with 50 additions and 16 deletions

View File

@ -198,8 +198,7 @@ class Patch(Handler):
else: else:
patch = patch_path.read_text(encoding="utf8") patch = patch_path.read_text(encoding="utf8")
# remove spaces around the patch and parse to correct type # remove spaces around the patch and parse to correct type
parsed = PkgbuildPatch.parse(patch.strip()) return PkgbuildPatch.parse(variable, patch.strip())
return PkgbuildPatch(variable, parsed)
@staticmethod @staticmethod
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None: def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:

View File

@ -83,7 +83,7 @@ class PatchOperations(Operations):
""" """
def run(connection: Connection) -> list[tuple[str, PkgbuildPatch]]: def run(connection: Connection) -> list[tuple[str, PkgbuildPatch]]:
return [ return [
(row["package_base"], PkgbuildPatch(row["variable"], row["patch"])) (row["package_base"], PkgbuildPatch.parse(row["variable"], row["patch"]))
for row in connection.execute( for row in connection.execute(
"""select * from patches where :package_base is null or package_base = :package_base""", """select * from patches where :package_base is null or package_base = :package_base""",
{"package_base": package_base}) {"package_base": package_base})

View File

@ -17,6 +17,7 @@
# 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/>.
# #
import json
import shlex import shlex
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
@ -79,11 +80,11 @@ class PkgbuildPatch:
variable(str): variable in bash form, i.e. KEY=VALUE variable(str): variable in bash form, i.e. KEY=VALUE
Returns: Returns:
Self: package properties Self: patch object
""" """
key, *value_parts = variable.split("=", maxsplit=1) key, *value_parts = variable.split("=", maxsplit=1)
raw_value = next(iter(value_parts), "") # extract raw value raw_value = next(iter(value_parts), "") # extract raw value
return cls(key, cls.parse(raw_value)) return cls.parse(key, raw_value)
@classmethod @classmethod
def from_json(cls, dump: dict[str, Any]) -> Self: def from_json(cls, dump: dict[str, Any]) -> Self:
@ -100,21 +101,36 @@ class PkgbuildPatch:
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
return cls(**filter_json(dump, known_fields)) return cls(**filter_json(dump, known_fields))
@staticmethod @classmethod
def parse(source: str) -> str | list[str]: def parse(cls, key: str | None, source: str) -> Self:
""" """
parse string value to the PKGBUILD patch value. This method simply takes string, tries to identify it as array 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 or just string and return the respective value. Functions are returned as is. Shell arrays and single values
are returned without quotes
Args: Args:
source(str): source string to parse key(str | None): variable key
source(str): source value string to parse
Returns: Returns:
str | list[str]: parsed value either string or list of strings Self: parsed patch object
""" """
if source.startswith("(") and source.endswith(")"): def value() -> str | list[str]:
return shlex.split(source[1:-1]) # arrays for poor match source:
return PkgbuildPatch.unquote(source) case function if key is not None and key.endswith("()"):
# the key looks like a function, no further processing should be applied here
return function
case shell_array if shell_array.startswith("(") and shell_array.endswith(")"):
# the source value looks like shell array, remove brackets and parse with shlex
return shlex.split(shell_array[1:-1])
case json_array if json_array.startswith("[") and json_array.endswith("]"):
# json (aka python) array, parse with json parser instead
parsed: list[str] = json.loads(json_array)
return parsed
case variable:
return cls.unquote(variable)
return cls(key, value())
@staticmethod @staticmethod
def unquote(source: str) -> str: def unquote(source: str) -> str:

View File

@ -15,6 +15,22 @@ def test_patches_get_insert(database: SQLite, package_ahriman: Package, package_
] ]
def test_patches_get_insert_array(database: SQLite, package_ahriman: Package) -> None:
"""
must insert array patch to database
"""
database.patches_insert(package_ahriman.base, [PkgbuildPatch("array", ["array", "value"])])
assert database.patches_get(package_ahriman.base) == [PkgbuildPatch("array", ["array", "value"])]
def test_patches_get_insert_function(database: SQLite, package_ahriman: Package) -> None:
"""
must insert function patch to database
"""
database.patches_insert(package_ahriman.base, [PkgbuildPatch("function()", "{ function body' }")])
assert database.patches_get(package_ahriman.base) == [PkgbuildPatch("function()", "{ function body' }")]
def test_patches_list(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None: def test_patches_list(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
""" """
must list all patches must list all patches

View File

@ -1,3 +1,4 @@
import json
import pytest import pytest
import shlex import shlex
@ -63,9 +64,11 @@ def test_parse() -> None:
""" """
must parse string correctly must parse string correctly
""" """
assert PkgbuildPatch.parse("VALUE") == "VALUE" assert PkgbuildPatch.parse("key", "VALUE").value == "VALUE"
assert PkgbuildPatch.parse("(ARRAY VALUE)") == ["ARRAY", "VALUE"] assert PkgbuildPatch.parse("key", "(ARRAY VALUE)").value == ["ARRAY", "VALUE"]
assert PkgbuildPatch.parse("""("QU'OUTED" ARRAY VALUE)""") == ["QU'OUTED", "ARRAY", "VALUE"] assert PkgbuildPatch.parse("key", """("QU'OUTED" ARRAY VALUE)""").value == ["QU'OUTED", "ARRAY", "VALUE"]
assert PkgbuildPatch.parse("key()", """{ function with " quotes }""").value == """{ function with " quotes }"""
assert PkgbuildPatch.parse("key", json.dumps(["array", "value"])).value == ["array", "value"]
def test_unquote() -> None: def test_unquote() -> None: