Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-specfile for openSUSE:Factory checked in at 2022-11-12 17:40:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-specfile (Old) and /work/SRC/openSUSE:Factory/.python-specfile.new.1597 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-specfile" Sat Nov 12 17:40:56 2022 rev:2 rq:1035243 version:0.9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-specfile/python-specfile.changes 2022-10-26 12:31:31.432278077 +0200 +++ /work/SRC/openSUSE:Factory/.python-specfile.new.1597/python-specfile.changes 2022-11-12 17:41:13.062185332 +0100 @@ -1,0 +2,7 @@ +Wed Nov 9 19:02:12 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to version 0.9.0 + * Added utility classes for working with (N)EVR. (#113) + * Fixed an issue with multiple instances of Specfile not expanding macros in the right context. (#117) + +------------------------------------------------------------------- Old: ---- specfile-0.8.0.tar.gz New: ---- specfile-0.9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-specfile.spec ++++++ --- /var/tmp/diff_new_pack.iQkmU7/_old 2022-11-12 17:41:13.858190071 +0100 +++ /var/tmp/diff_new_pack.iQkmU7/_new 2022-11-12 17:41:13.866190119 +0100 @@ -15,23 +15,24 @@ # Please submit bugfixes or comments via https://bugs.opensuse.org/ # + %define skip_python38 1 Name: python-specfile -Version: 0.8.0 +Version: 0.9.0 Release: 0 Summary: A library for parsing and manipulating RPM spec files License: MIT URL: https://github.com/packit/specfile Source: https://files.pythonhosted.org/packages/source/s/specfile/specfile-%{version}.tar.gz -BuildRequires: python-rpm-macros -BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module setuptools_scm} BuildRequires: %{python_module setuptools_scm_git_archive} +BuildRequires: %{python_module setuptools_scm} +BuildRequires: %{python_module setuptools} +BuildRequires: python-rpm-macros # SECTION test requirements BuildRequires: %{python_module rpm} -BuildRequires: %{python_module typing-extensions} -BuildRequires: %{python_module pytest} BuildRequires: %{python_module flexmock} +BuildRequires: %{python_module pytest} +BuildRequires: %{python_module typing-extensions} # /SECTION BuildRequires: fdupes Requires: python-rpm ++++++ specfile-0.8.0.tar.gz -> specfile-0.9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/.packit.yaml new/specfile-0.9.0/.packit.yaml --- old/specfile-0.8.0/.packit.yaml 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/.packit.yaml 2022-10-25 19:06:38.000000000 +0200 @@ -54,6 +54,12 @@ - fedora-all - epel-9 + - job: tests + trigger: pull_request + targets: + - fedora-all + - epel-9 + - job: copr_build trigger: commit branch: main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/.pre-commit-config.yaml new/specfile-0.9.0/.pre-commit-config.yaml --- old/specfile-0.8.0/.pre-commit-config.yaml 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/.pre-commit-config.yaml 2022-10-25 19:06:38.000000000 +0200 @@ -12,7 +12,7 @@ hooks: - id: black - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.1 + rev: v3.0.0-alpha.3 hooks: - id: prettier - repo: https://github.com/pre-commit/pre-commit-hooks diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/CHANGELOG.md new/specfile-0.9.0/CHANGELOG.md --- old/specfile-0.8.0/CHANGELOG.md 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/CHANGELOG.md 2022-10-25 19:06:38.000000000 +0200 @@ -1,3 +1,8 @@ +# 0.9.0 + +- Added utility classes for working with (N)EVR. (#113) +- Fixed an issue with multiple instances of `Specfile` not expanding macros in the right context. (#117) + # 0.8.0 - Added `Specfile.update_tag()` method that allows updating tag values while trying to preserve macro expansions. You can watch a demo on [YouTube](https://youtu.be/yzMfBPdFXZY). (#101) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/PKG-INFO new/specfile-0.9.0/PKG-INFO --- old/specfile-0.8.0/PKG-INFO 2022-10-14 13:31:18.927929400 +0200 +++ new/specfile-0.9.0/PKG-INFO 2022-10-25 19:06:48.976166000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: specfile -Version: 0.8.0 +Version: 0.9.0 Summary: A library for parsing and manipulating RPM spec files. Home-page: https://github.com/packit/specfile Author: Red Hat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/fedora/python-specfile.spec new/specfile-0.9.0/fedora/python-specfile.spec --- old/specfile-0.8.0/fedora/python-specfile.spec 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/fedora/python-specfile.spec 2022-10-25 19:06:38.000000000 +0200 @@ -13,7 +13,7 @@ Name: python-specfile -Version: 0.8.0 +Version: 0.9.0 Release: 1%{?dist} Summary: A library for parsing and manipulating RPM spec files @@ -69,6 +69,9 @@ %changelog +* Tue Oct 25 2022 Packit Team <he...@packit.dev> - 0.9.0-1 +- New upstream release 0.9.0 + * Fri Oct 14 2022 Packit Team <he...@packit.dev> - 0.8.0-1 - New upstream release 0.8.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/changelog.py new/specfile-0.9.0/specfile/changelog.py --- old/specfile-0.8.0/specfile/changelog.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/changelog.py 2022-10-25 19:06:38.000000000 +0200 @@ -8,7 +8,9 @@ import rpm +from specfile.exceptions import SpecfileException from specfile.sections import Section +from specfile.utils import EVR WEEKDAYS = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") MONTHS = ( @@ -137,8 +139,9 @@ return "" return m.group("wsp") + (m.group("zp") or "") - @staticmethod + @classmethod def assemble( + cls, timestamp: Union[datetime.date, datetime.datetime], author: str, content: List[str], @@ -176,7 +179,7 @@ header += f" {timestamp:%Y} {author}" if evr is not None: header += f" - {evr}" - return ChangelogEntry(header, content, [""] if append_newline else None) + return cls(header, content, [""] if append_newline else None) class Changelog(collections.UserList): @@ -262,12 +265,11 @@ """ def parse_evr(s): - if not s: + try: + evr = EVR.from_string(s) + except SpecfileException: return "0", "0", "" - m = re.match(r"^(?:(\d+):)?(.*?)(?:-([^-]*))?$", s) - if not m: - return "0", "0", "" - return m.group(1) or "0", m.group(2), m.group(3) or "" + return str(evr.epoch), evr.version or "0", evr.release if since is None: start_index = 0 @@ -293,8 +295,8 @@ ) return self[start_index:end_index] - @staticmethod - def parse(section: Section) -> "Changelog": + @classmethod + def parse(cls, section: Section) -> "Changelog": """ Parses a %changelog section. @@ -325,7 +327,7 @@ predecessor.append(line) if header: data.insert(0, ChangelogEntry(header, content, following_lines)) - return Changelog(data, predecessor) + return cls(data, predecessor) def get_raw_section_data(self) -> List[str]: """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/macro_definitions.py new/specfile-0.9.0/specfile/macro_definitions.py --- old/specfile-0.8.0/specfile/macro_definitions.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/macro_definitions.py 2022-10-25 19:06:38.000000000 +0200 @@ -162,8 +162,8 @@ return i raise ValueError - @staticmethod - def parse(lines: List[str]) -> "MacroDefinitions": + @classmethod + def parse(cls, lines: List[str]) -> "MacroDefinitions": """ Parses given lines into macro defintions. @@ -213,7 +213,7 @@ buffer = [] else: buffer.append(line) - return MacroDefinitions(data, buffer) + return cls(data, buffer) def get_raw_data(self) -> List[str]: result = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/prep.py new/specfile-0.9.0/specfile/prep.py --- old/specfile-0.8.0/specfile/prep.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/prep.py 2022-10-25 19:06:38.000000000 +0200 @@ -319,8 +319,8 @@ if index: del self.macros[index] - @staticmethod - def parse(section: Section) -> "Prep": + @classmethod + def parse(cls, section: Section) -> "Prep": """ Parses a section into a `Prep` object. @@ -350,25 +350,27 @@ m.group("o"), ) prefix, suffix = line[: m.start("m")], line[m.end("o") :] - cls = next( + klass = next( ( - cls - for cls in PrepMacro.__subclasses__() - if name.startswith(cls.CANONICAL_NAME) + klass + for klass in PrepMacro.__subclasses__() + if name.startswith(klass.CANONICAL_NAME) ), None, ) - if not cls: + if not klass: buffer.append(line) continue options = MacroOptions( - MacroOptions.tokenize(option_string), cls.OPTSTRING, cls.DEFAULTS + MacroOptions.tokenize(option_string), + klass.OPTSTRING, + klass.DEFAULTS, ) - data.append(cls(name, options, delimiter, prefix, suffix, buffer)) + data.append(klass(name, options, delimiter, prefix, suffix, buffer)) buffer = [] else: buffer.append(line) - return Prep(PrepMacros(data, buffer)) + return cls(PrepMacros(data, buffer)) def get_raw_section_data(self) -> List[str]: """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/sections.py new/specfile-0.9.0/specfile/sections.py --- old/specfile-0.8.0/specfile/sections.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/sections.py 2022-10-25 19:06:38.000000000 +0200 @@ -172,8 +172,8 @@ return i raise ValueError - @staticmethod - def parse(lines: List[str]) -> "Sections": + @classmethod + def parse(cls, lines: List[str]) -> "Sections": """ Parses given lines into sections. @@ -197,7 +197,7 @@ data = [Section(PREAMBLE, lines[: section_starts[0]])] for start, end in zip(section_starts, section_starts[1:]): data.append(Section(lines[start][1:], lines[start + 1 : end])) - return Sections(data) + return cls(data) def get_raw_data(self) -> List[str]: result = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/sourcelist.py new/specfile-0.9.0/specfile/sourcelist.py --- old/specfile-0.8.0/specfile/sourcelist.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/sourcelist.py 2022-10-25 19:06:38.000000000 +0200 @@ -2,12 +2,15 @@ # SPDX-License-Identifier: MIT import collections -from typing import List, Optional, SupportsIndex, overload +from typing import TYPE_CHECKING, List, Optional, SupportsIndex, overload from specfile.rpm import Macros from specfile.sections import Section from specfile.tags import Comments +if TYPE_CHECKING: + from specfile.specfile import Specfile + class SourcelistEntry: """ @@ -18,9 +21,23 @@ comments: List of comments associated with the source/patch. """ - def __init__(self, location: str, comments: Comments) -> None: + def __init__( + self, location: str, comments: Comments, context: Optional["Specfile"] = None + ) -> None: + """ + Constructs a `SourceListEntry` object. + + Args: + location: Literal location of the source/patch as stored in the spec file. + comments: List of comments associated with the source/patch. + context: `Specfile` instance that defines the context for macro expansions. + + Returns: + Constructed instance of `SourceListEntry` class. + """ self.location = location self.comments = comments.copy() + self._context = context def __eq__(self, other: object) -> bool: if not isinstance(other, SourcelistEntry): @@ -34,6 +51,8 @@ @property def expanded_location(self) -> str: """URL of the source/patch after expanding macros.""" + if self._context: + return self._context.expand(self.location) return Macros.expand(self.location) @@ -87,13 +106,16 @@ def copy(self) -> "Sourcelist": return Sourcelist(self.data, self._remainder) - @staticmethod - def parse(section: Section) -> "Sourcelist": + @classmethod + def parse( + cls, section: Section, context: Optional["Specfile"] = None + ) -> "Sourcelist": """ Parses a section into sources/patches. Args: section: %sourcelist/%patchlist section. + context: `Specfile` instance that defines the context for macro expansions. Returns: Constructed instance of `Sourcelist` class. @@ -102,11 +124,11 @@ buffer: List[str] = [] for line in section: if line and not line.lstrip().startswith("#"): - data.append(SourcelistEntry(line, Comments.parse(buffer))) + data.append(SourcelistEntry(line, Comments.parse(buffer), context)) buffer = [] else: buffer.append(line) - return Sourcelist(data, buffer) + return cls(data, buffer) def get_raw_section_data(self) -> List[str]: """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/sources.py new/specfile-0.9.0/specfile/sources.py --- old/specfile-0.8.0/specfile/sources.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/sources.py 2022-10-25 19:06:38.000000000 +0200 @@ -5,7 +5,7 @@ import re import urllib.parse from abc import ABC, abstractmethod -from typing import Iterable, List, Optional, Tuple, Union, cast, overload +from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union, cast, overload from specfile.exceptions import DuplicateSourceException from specfile.rpm import Macros @@ -13,6 +13,9 @@ from specfile.tags import Comments, Tag, Tags from specfile.utils import get_filename_from_location +if TYPE_CHECKING: + from specfile.specfile import Specfile + class Source(ABC): """Class that represents a source.""" @@ -218,6 +221,7 @@ allow_duplicates: bool = False, default_to_implicit_numbering: bool = False, default_source_number_digits: int = 1, + context: Optional["Specfile"] = None, ) -> None: """ Constructs a `Sources` object. @@ -228,6 +232,7 @@ allow_duplicates: Whether to allow duplicate entries when adding new sources. default_to_implicit_numbering: Use implicit numbering (no source numbers) by default. default_source_number_digits: Default number of digits in a source number. + context: `Specfile` instance that defines the context for macro expansions. Returns: Constructed instance of `Sources` class. @@ -237,6 +242,7 @@ self._allow_duplicates = allow_duplicates self._default_to_implicit_numbering = default_to_implicit_numbering self._default_source_number_digits = default_source_number_digits + self._context = context def __repr__(self) -> str: tags = repr(self._tags) @@ -306,6 +312,11 @@ _, container, index = items[i] del container[index] + def _expand(self, s: str) -> str: + if self._context: + return self._context.expand(s) + return Macros.expand(s) + def _get_tags(self) -> List[Tuple[TagSource, Tags, int]]: """ Gets all tag sources. @@ -474,7 +485,7 @@ name, separator = self._get_tag_format(source, number) container.insert( index, - Tag(name, location, Macros.expand(location), separator, Comments()), + Tag(name, location, self._expand(location), separator, Comments()), ) self._deduplicate_tag_names(i) else: @@ -488,7 +499,7 @@ index, name, separator = self._get_initial_tag_setup() self._tags.insert( index, - Tag(name, location, Macros.expand(location), separator, Comments()), + Tag(name, location, self._expand(location), separator, Comments()), ) def insert_numbered(self, number: int, location: str) -> int: @@ -522,7 +533,7 @@ i = 0 index, name, separator = self._get_initial_tag_setup(number) self._tags.insert( - index, Tag(name, location, Macros.expand(location), separator, Comments()) + index, Tag(name, location, self._expand(location), separator, Comments()) ) self._deduplicate_tag_names(i) return i diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/specfile.py new/specfile-0.9.0/specfile/specfile.py --- old/specfile-0.8.0/specfile/specfile.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/specfile.py 2022-10-25 19:06:38.000000000 +0200 @@ -233,7 +233,9 @@ """ with self.sections() as sections, self.tags(sections.package) as tags: sourcelists = [ - (s, Sourcelist.parse(s)) for s in sections if s.name == "sourcelist" + (s, Sourcelist.parse(s, context=self)) + for s in sections + if s.name == "sourcelist" ] try: yield Sources( @@ -242,6 +244,7 @@ allow_duplicates, default_to_implicit_numbering, default_source_number_digits, + context=self, ) finally: for section, sourcelist in sourcelists: @@ -267,7 +270,9 @@ """ with self.sections() as sections, self.tags(sections.package) as tags: patchlists = [ - (s, Sourcelist.parse(s)) for s in sections if s.name == "patchlist" + (s, Sourcelist.parse(s, context=self)) + for s in sections + if s.name == "patchlist" ] try: yield Patches( @@ -276,6 +281,7 @@ allow_duplicates, default_to_implicit_numbering, default_source_number_digits, + context=self, ) finally: for section, patchlist in patchlists: @@ -572,7 +578,9 @@ entities.update({k.upper(): v for k, v in entities.items() if v.type == Tag}) def update(value, requested_value): - regex, template = ValueParser.construct_regex(value, entities.keys()) + regex, template = ValueParser.construct_regex( + value, entities.keys(), context=self + ) m = regex.match(requested_value) if m: d = m.groupdict() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/tags.py new/specfile-0.9.0/specfile/tags.py --- old/specfile-0.8.0/specfile/tags.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/tags.py 2022-10-25 19:06:38.000000000 +0200 @@ -190,8 +190,8 @@ item = Comment(item) self.data.append(item) - @staticmethod - def parse(lines: List[str]) -> "Comments": + @classmethod + def parse(cls, lines: List[str]) -> "Comments": """ Parses list of lines into comments. @@ -210,7 +210,7 @@ preceding_lines.insert(0, line) continue comments.insert(0, Comment(*reversed(m.groups()))) - return Comments(comments, preceding_lines) + return cls(comments, preceding_lines) def get_raw_data(self) -> List[str]: return self._preceding_lines + [str(i) for i in self.data] @@ -427,8 +427,10 @@ item.comments._preceding_lines[0:0] = lines[: index + 1] del lines[: index + 1] - @staticmethod - def parse(raw_section: Section, parsed_section: Optional[Section] = None) -> "Tags": + @classmethod + def parse( + cls, raw_section: Section, parsed_section: Optional[Section] = None + ) -> "Tags": """ Parses a section into tags. @@ -475,7 +477,7 @@ buffer = [] else: buffer.append(line) - return Tags(data, buffer) + return cls(data, buffer) def get_raw_section_data(self) -> List[str]: """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/utils.py new/specfile-0.9.0/specfile/utils.py --- old/specfile-0.8.0/specfile/utils.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/utils.py 2022-10-25 19:06:38.000000000 +0200 @@ -1,9 +1,79 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT +import collections +import re import urllib.parse from pathlib import Path +from specfile.exceptions import SpecfileException + + +class EVR(collections.abc.Hashable): + """Class representing Epoch-Version-Release combination.""" + + def __init__(self, *, version: str, release: str = "", epoch: int = 0) -> None: + self.epoch = epoch + self.version = version + self.release = release + + def _key(self) -> tuple: + return self.epoch, self.version, self.release + + def __hash__(self) -> int: + return hash(self._key()) + + def __eq__(self, other: object) -> bool: + if type(other) != self.__class__: + return NotImplemented + return self._key() == other._key() + + def __repr__(self) -> str: + return f"EVR(epoch={self.epoch}, version='{self.version}', release='{self.release}')" + + def __str__(self) -> str: + epoch = f"{self.epoch}:" if self.epoch > 0 else "" + release = f"-{self.release}" if self.release else "" + return f"{epoch}{self.version}{release}" + + @classmethod + def from_string(cls, evr: str) -> "EVR": + m = re.match(r"^(?:(\d+):)?([^-]+?)(?:-([^-]+))?$", evr) + if not m: + raise SpecfileException("Invalid EVR string.") + e, v, r = m.groups() + return cls(epoch=int(e) if e else 0, version=v, release=r or "") + + +class NEVR(EVR): + """Class representing Name-Epoch-Version-Release combination.""" + + def __init__( + self, *, name: str, version: str, release: str = "", epoch: int = 0 + ) -> None: + self.name = name + super().__init__(epoch=epoch, version=version, release=release) + + def _key(self) -> tuple: + return self.name, self.epoch, self.version, self.release + + def __repr__(self) -> str: + return ( + f"NEVR(name='{self.name}', epoch={self.epoch}, " + f"version='{self.version}', release='{self.release}')" + ) + + def __str__(self) -> str: + return f"{self.name}-" + super().__str__() + + @classmethod + def from_string(cls, nevr: str) -> "NEVR": + m = re.match(r"^(.+?)-(?:(\d+):)?([^-]+?)(?:-([^-]+))?$", nevr) + if not m: + raise SpecfileException("Invalid NEVR string.") + n, e, v, r = m.groups() + return cls(name=n, epoch=int(e) if e else 0, version=v, release=r or "") + def get_filename_from_location(location: str) -> str: """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile/value_parser.py new/specfile-0.9.0/specfile/value_parser.py --- old/specfile-0.8.0/specfile/value_parser.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/specfile/value_parser.py 2022-10-25 19:06:38.000000000 +0200 @@ -5,12 +5,15 @@ import re from abc import ABC from string import Template -from typing import List, Tuple +from typing import TYPE_CHECKING, List, Optional, Tuple from typing.re import Pattern from specfile.exceptions import UnterminatedMacroException from specfile.rpm import Macros +if TYPE_CHECKING: + from specfile.specfile import Specfile + SUBSTITUTION_GROUP_PREFIX = "sub_" @@ -227,7 +230,10 @@ @classmethod def construct_regex( - cls, value: str, modifiable_entities: List[str] + cls, + value: str, + modifiable_entities: List[str], + context: Optional["Specfile"] = None, ) -> Tuple[Pattern, Template]: """ Parses the given value and constructs a regex that allows matching @@ -250,18 +256,24 @@ value: Value string to parse. modifiable_entities: Names of modifiable entities, i.e. local macro definitions and tags. + context: `Specfile` instance that defines the context for macro expansions. Returns: Tuple in the form of (constructed regex, corresponding template). """ + def expand(s): + if context: + return context.expand(s) + return Macros.expand(s) + def flatten(nodes): # get rid of conditional macro expansions result = [] for node in nodes: if isinstance(node, ConditionalMacroExpansion): # evaluate the condition - if Macros.expand(f"%{node.prefix}{node.name}"): + if expand(f"%{node.prefix}{node.name}"): result.append(f"%{{{node.prefix}{node.name}:") result.extend(flatten(node.body)) result.append("}") @@ -281,13 +293,13 @@ elif isinstance(node, StringLiteral): tokens.append(("v", node.value, "")) elif isinstance(node, (ShellExpansion, ExpressionExpansion)): - const = Macros.expand(str(node)) + const = expand(str(node)) tokens.append(("c", const, str(node))) elif isinstance(node, MacroSubstitution): if node.prefix.count("!") % 2 == 0 and node.name in modifiable_entities: tokens.append(("g", node.name, str(node))) else: - const = Macros.expand(str(node)) + const = expand(str(node)) tokens.append(("c", const, str(node))) elif isinstance(node, EnclosedMacroSubstitution): if ( @@ -297,7 +309,7 @@ ): tokens.append(("g", node.name, str(node))) else: - const = Macros.expand(str(node)) + const = expand(str(node)) tokens.append(("c", const, str(node))) def escape(s): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/specfile.egg-info/PKG-INFO new/specfile-0.9.0/specfile.egg-info/PKG-INFO --- old/specfile-0.8.0/specfile.egg-info/PKG-INFO 2022-10-14 13:31:18.000000000 +0200 +++ new/specfile-0.9.0/specfile.egg-info/PKG-INFO 2022-10-25 19:06:48.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: specfile -Version: 0.8.0 +Version: 0.9.0 Summary: A library for parsing and manipulating RPM spec files. Home-page: https://github.com/packit/specfile Author: Red Hat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/tests/integration/test_specfile.py new/specfile-0.9.0/tests/integration/test_specfile.py --- old/specfile-0.8.0/tests/integration/test_specfile.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/tests/integration/test_specfile.py 2022-10-25 19:06:38.000000000 +0200 @@ -333,3 +333,14 @@ assert md.minorver.body == "2" with spec.sources() as sources: assert sources[1].location == "tests-86.tar.xz" + + +def test_multiple_instances(spec_minimal, spec_autosetup): + spec1 = Specfile(spec_minimal) + spec2 = Specfile(spec_autosetup) + spec1.version = "14.2" + assert spec2.expanded_version == "0.1" + with spec2.sources() as sources: + assert sources[0].expanded_location == "test-0.1.tar.xz" + sources.append("tests-%{version}.tar.xz") + assert sources[1].expanded_location == "tests-0.1.tar.xz" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/specfile-0.8.0/tests/unit/test_utils.py new/specfile-0.9.0/tests/unit/test_utils.py --- old/specfile-0.8.0/tests/unit/test_utils.py 2022-10-14 13:31:08.000000000 +0200 +++ new/specfile-0.9.0/tests/unit/test_utils.py 2022-10-25 19:06:38.000000000 +0200 @@ -3,7 +3,7 @@ import pytest -from specfile.utils import get_filename_from_location +from specfile.utils import EVR, NEVR, get_filename_from_location @pytest.mark.parametrize( @@ -29,3 +29,55 @@ ) def test_get_filename_from_location(location, filename): assert get_filename_from_location(location) == filename + + +@pytest.mark.parametrize( + "evr, result", + [ + ("0", EVR(version="0")), + ("12.0-1", EVR(version="12.0", release="1")), + ("2:56.8-5", EVR(epoch=2, version="56.8", release="5")), + ("0.8.0-1.fc37", EVR(version="0.8.0", release="1.fc37")), + ("0.5.0~rc2-1.el9", EVR(version="0.5.0~rc2", release="1.el9")), + ("7.3-0.2.rc1.fc38", EVR(version="7.3", release="0.2.rc1.fc38")), + ( + "7.3~rc1^20200701gdeadf00f-12.fc38", + EVR(version="7.3~rc1^20200701gdeadf00f", release="12.fc38"), + ), + ], +) +def test_EVR_from_string(evr, result): + assert EVR.from_string(evr) == result + + +@pytest.mark.parametrize( + "nevr, result", + [ + ("package-0", NEVR(name="package", version="0")), + ("package-12.0-1", NEVR(name="package", version="12.0", release="1")), + ( + "package-2:56.8-5", + NEVR(name="package", epoch=2, version="56.8", release="5"), + ), + ( + "package-0.8.0-1.fc37", + NEVR(name="package", version="0.8.0", release="1.fc37"), + ), + ( + "package-0.5.0~rc2-1.el9", + NEVR(name="package", version="0.5.0~rc2", release="1.el9"), + ), + ( + "package-devel-7.3-0.2.rc1.fc38", + NEVR(name="package-devel", version="7.3", release="0.2.rc1.fc38"), + ), + ( + "package-7.3~rc1^20200701gdeadf00f-12.fc38", + NEVR( + name="package", version="7.3~rc1^20200701gdeadf00f", release="12.fc38" + ), + ), + ], +) +def test_NEVR_from_string(nevr, result): + assert NEVR.from_string(nevr) == result