Skip to content

Commit

Permalink
Handle multi-line tag values (#412)
Browse files Browse the repository at this point in the history
Handle multi-line tag values

Fixes #410.
RELEASE NOTES BEGIN
specfile can now handle multi-line tag values (enclosed in a macro body, e.g. %shrink).
RELEASE NOTES END

Reviewed-by: Maja Massarini
Reviewed-by: Nikola Forró
  • Loading branch information
softwarefactory-project-zuul[bot] committed Sep 26, 2024
2 parents 514fbde + b20d072 commit 89cd1a4
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 37 deletions.
31 changes: 1 addition & 30 deletions specfile/macro_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from specfile.conditions import process_conditions
from specfile.formatter import formatted
from specfile.types import SupportsIndex
from specfile.utils import UserList
from specfile.utils import UserList, count_brackets

if TYPE_CHECKING:
from specfile.specfile import Specfile
Expand Down Expand Up @@ -303,35 +303,6 @@ def pop(lines):
else:
return line

def count_brackets(s):
bc = pc = 0
chars = list(s)
while chars:
c = chars.pop(0)
if c == "\\" and chars:
chars.pop(0)
continue
if c == "%" and chars:
c = chars.pop(0)
if c == "{":
bc += 1
elif c == "(":
pc += 1
continue
if c == "{" and bc > 0:
bc += 1
continue
if c == "}" and bc > 0:
bc -= 1
continue
if c == "(" and pc > 0:
pc += 1
continue
if c == ")" and pc > 0:
pc -= 1
continue
return bc, pc

md_regex = re.compile(
r"""
^
Expand Down
31 changes: 26 additions & 5 deletions specfile/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from specfile.macros import Macros
from specfile.sections import Section
from specfile.types import SupportsIndex
from specfile.utils import UserList, split_conditional_macro_expansion
from specfile.utils import UserList, count_brackets, split_conditional_macro_expansion

if TYPE_CHECKING:
from specfile.specfile import Specfile
Expand Down Expand Up @@ -489,6 +489,13 @@ def parse(cls, section: Section, context: Optional["Specfile"] = None) -> "Tags"
New instance of `Tags` class.
"""

def pop(lines):
line = lines.pop(0)
if isinstance(line, str):
return line, True
else:
return line

def regex_pattern(tag):
name_regex = get_tag_name_regex(tag)
return rf"^(?P<n>{name_regex})(?P<s>\s*:\s*)(?P<v>.+)"
Expand All @@ -498,7 +505,8 @@ def regex_pattern(tag):
tag_regexes = [re.compile(regex_pattern(t), re.IGNORECASE) for t in TAG_NAMES]
data = []
buffer: List[str] = []
for line, valid in lines:
while lines:
line, valid = pop(lines)
ws = ""
tokens = re.split(r"([^\S\n]+)$", line, maxsplit=1)
if len(tokens) > 1:
Expand All @@ -507,10 +515,21 @@ def regex_pattern(tag):
# find out if there is a match for one of the tag regexes
m = next((m for m in (r.match(line) for r in tag_regexes) if m), None)
if m:
value = m.group("v")
if not suffix:
bc, pc = count_brackets(value)
while (bc > 0 or pc > 0) and lines:
value += ws
line, _ = pop(lines)
tokens = re.split(r"([^\S\n]+)$", line, maxsplit=1)
if len(tokens) > 1:
line, ws, _ = tokens
value += "\n" + line
bc, pc = count_brackets(value)
data.append(
Tag(
m.group("n"),
m.group("v"),
value,
m.group("s"),
Comments.parse(buffer),
valid,
Expand All @@ -534,8 +553,10 @@ def get_raw_section_data(self) -> List[str]:
result = []
for tag in self.data:
result.extend(tag.comments.get_raw_data())
result.append(
f"{tag._prefix}{tag.name}{tag._separator}{tag.value}{tag._suffix}"
result.extend(
f"{tag._prefix}{tag.name}{tag._separator}{tag.value}{tag._suffix}".split(
"\n"
)
)
result.extend(self._remainder)
return result
39 changes: 39 additions & 0 deletions specfile/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,45 @@ def get_filename_from_location(location: str) -> str:
return location[slash + 1 :].split("=")[-1]


def count_brackets(string: str) -> Tuple[int, int]:
"""
Counts non-pair brackets in %{...} and %(...) expressions appearing in the given string.
Args:
string: Input string.
Returns:
The count of non-pair curly braces and the count of non-pair parentheses.
"""
bc = pc = 0
chars = list(string)
while chars:
c = chars.pop(0)
if c == "\\" and chars:
chars.pop(0)
continue
if c == "%" and chars:
c = chars.pop(0)
if c == "{":
bc += 1
elif c == "(":
pc += 1
continue
if c == "{" and bc > 0:
bc += 1
continue
if c == "}" and bc > 0:
bc -= 1
continue
if c == "(" and pc > 0:
pc += 1
continue
if c == ")" and pc > 0:
pc -= 1
continue
return bc, pc


def split_conditional_macro_expansion(value: str) -> Tuple[str, str, str]:
"""
Splits conditional macro expansion into its body and prefix and suffix of it.
Expand Down
28 changes: 27 additions & 1 deletion tests/unit/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def test_parse():
"Epoch: 1",
"%endif",
"",
"License: %{shrink:",
" MIT AND",
" (MIT OR Apache-2.0)",
" }",
"",
"Requires: make ",
"Requires(post): bash",
"",
Expand All @@ -62,6 +67,13 @@ def test_parse():
assert not tags[1].comments
assert tags.release.comments[0].prefix == " # "
assert tags.epoch.name == "Epoch"
assert tags[-6].name == "License"
assert (
tags[-6].value == "%{shrink:\n"
" MIT AND\n"
" (MIT OR Apache-2.0)\n"
" }"
)
assert tags.requires.value == "make"
assert "requires(post)" in tags
assert tags[-4].name == "Requires(post)"
Expand Down Expand Up @@ -102,11 +114,20 @@ def test_get_raw_section_data():
Comments([Comment("this is a valid comment", " # ")]),
),
Tag("Epoch", "1", ": ", Comments([], ["", "%if 0"])),
Tag(
"License",
"%{shrink:\n"
" MIT AND\n"
" (MIT OR Apache-2.0)\n"
" }",
": ",
Comments([], ["%endif", ""]),
),
Tag(
"Requires",
"make",
": ",
Comments([], ["%endif", ""]),
Comments([], [""]),
True,
"",
" ",
Expand Down Expand Up @@ -141,6 +162,11 @@ def test_get_raw_section_data():
"Epoch: 1",
"%endif",
"",
"License: %{shrink:",
" MIT AND",
" (MIT OR Apache-2.0)",
" }",
"",
"Requires: make ",
"Requires(post): bash",
"",
Expand Down
19 changes: 18 additions & 1 deletion tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from specfile.utils import EVR, NEVR, NEVRA, get_filename_from_location
from specfile.utils import EVR, NEVR, NEVRA, count_brackets, get_filename_from_location


@pytest.mark.parametrize(
Expand Down Expand Up @@ -32,6 +32,23 @@ def test_get_filename_from_location(location, filename):
assert get_filename_from_location(location) == filename


@pytest.mark.parametrize(
"string, count",
[
("", (0, 0)),
("%macro", (0, 0)),
("%{macro}", (0, 0)),
("%{{macro}}", (0, 0)),
("%{{macro}", (1, 0)),
("%{macro:", (1, 0)),
("%(echo %{v}", (0, 1)),
("%(echo %{v} | cut -d. -f3)", (0, 0)),
],
)
def test_count_brackets(string, count):
assert count_brackets(string) == count


def test_EVR_compare():
assert EVR(version="0") == EVR(version="0")
assert EVR(version="0", release="1") != EVR(version="0", release="2")
Expand Down

0 comments on commit 89cd1a4

Please sign in to comment.