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 2023-07-19 19:10:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-specfile (Old)
and /work/SRC/openSUSE:Factory/.python-specfile.new.5570 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-specfile"
Wed Jul 19 19:10:44 2023 rev:13 rq:1099363 version:0.20.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-specfile/python-specfile.changes
2023-05-30 22:03:21.047331514 +0200
+++
/work/SRC/openSUSE:Factory/.python-specfile.new.5570/python-specfile.changes
2023-07-19 19:10:46.380614922 +0200
@@ -1,0 +2,16 @@
+Wed Jul 19 04:53:41 UTC 2023 - Steve Kowalik <[email protected]>
+
+- Update to 0.20.0:
+ * Fixed infinite loop when removing macros with `%` in the name. (#244)
+ * Added a possibility to undefine system macros by setting a macro value
+ to `None` in the `macros` argument of the `Specfile` constructor. (#244)
+ * Fixed a bug in processing options of `%prep` macros. For instance, when
+ a quoted string appeared inside an expression expansion, it could lead
+ to improper parsing, rendering the spec file invalid after accessing
+ the options. (#253)
+ * Parsing has been optimized so that even spec files with hundreds of
+ thousands of lines can be processed in reasonable time. (#240)
+- Drop setuptools_scm_git_archive BuildRequires.
+- Don't need to skip Python 3.8.
+
+-------------------------------------------------------------------
Old:
----
specfile-0.18.0.tar.gz
New:
----
specfile-0.20.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-specfile.spec ++++++
--- /var/tmp/diff_new_pack.bY4P3j/_old 2023-07-19 19:10:47.044618806 +0200
+++ /var/tmp/diff_new_pack.bY4P3j/_new 2023-07-19 19:10:47.052618852 +0200
@@ -16,16 +16,14 @@
#
-%define skip_python38 1
Name: python-specfile
-Version: 0.18.0
+Version: 0.20.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_module pip}
-BuildRequires: %{python_module setuptools_scm_git_archive}
BuildRequires: %{python_module setuptools_scm}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
++++++ specfile-0.18.0.tar.gz -> specfile-0.20.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/specfile-0.18.0/.github/workflows/prepare-release.yml
new/specfile-0.20.0/.github/workflows/prepare-release.yml
--- old/specfile-0.18.0/.github/workflows/prepare-release.yml 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/.github/workflows/prepare-release.yml 2023-07-13
17:26:17.000000000 +0200
@@ -11,6 +11,9 @@
# To not run in forks
if: github.repository_owner == 'packit'
runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ contents: write
steps:
- uses: actions/checkout@v3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/.pre-commit-config.yaml
new/specfile-0.20.0/.pre-commit-config.yaml
--- old/specfile-0.18.0/.pre-commit-config.yaml 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/.pre-commit-config.yaml 2023-07-13 17:26:17.000000000
+0200
@@ -4,15 +4,15 @@
repos:
- repo: https://github.com/asottile/pyupgrade
- rev: v3.4.0
+ rev: v3.9.0
hooks:
- id: pyupgrade
- repo: https://github.com/psf/black
- rev: 23.3.0
+ rev: 23.7.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v3.0.0-alpha.9-for-vscode
+ rev: v3.0.0
hooks:
- id: prettier
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -44,7 +44,7 @@
- id: isort
args: [--profile, black]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.3.0
+ rev: v1.4.1
hooks:
- id: mypy
args: [--show-error-codes, --ignore-missing-imports]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/CHANGELOG.md
new/specfile-0.20.0/CHANGELOG.md
--- old/specfile-0.18.0/CHANGELOG.md 2023-05-26 11:50:44.000000000 +0200
+++ new/specfile-0.20.0/CHANGELOG.md 2023-07-13 17:26:17.000000000 +0200
@@ -1,3 +1,13 @@
+# 0.20.0
+
+- Fixed infinite loop when removing macros with `%` in the name. (#244)
+- Added a possibility to undefine system macros by setting a macro value to
`None` in the `macros` argument of the `Specfile` constructor. (#244)
+- Fixed a bug in processing options of `%prep` macros. For instance, when a
quoted string appeared inside an expression expansion, it could lead to
improper parsing, rendering the spec file invalid after accessing the options.
(#253)
+
+# 0.19.0
+
+- Parsing has been optimized so that even spec files with hundreds of
thousands of lines can be processed in reasonable time. (#240)
+
# 0.18.0
- Specfile library now handles multiple `%changelog` sections. (#230)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/Containerfile.tests
new/specfile-0.20.0/Containerfile.tests
--- old/specfile-0.18.0/Containerfile.tests 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/Containerfile.tests 2023-07-13 17:26:17.000000000
+0200
@@ -1,4 +1,4 @@
-FROM quay.io/packit/base
+FROM quay.io/packit/base:c9s
COPY files/tasks/*.yaml /files/tasks/
COPY files/*.yaml /files/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/PKG-INFO new/specfile-0.20.0/PKG-INFO
--- old/specfile-0.18.0/PKG-INFO 2023-05-26 11:50:53.421457300 +0200
+++ new/specfile-0.20.0/PKG-INFO 2023-07-13 17:26:27.064338700 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: specfile
-Version: 0.18.0
+Version: 0.20.0
Summary: A library for parsing and manipulating RPM spec files.
Home-page: https://github.com/packit/specfile
Author: Red Hat
@@ -105,6 +105,16 @@
...
```
+### Defining and undefining macros
+
+```python
+# override macros loaded from system macro files
+specfile = Specfile('test.spec', macros=[('fedora', '38'), ('dist', '.fc38')])
+
+# undefine a system macro (in case it's defined)
+specfile = Specfile('test.spec', macros=[('rhel', None)])
+```
+
### Low-level manipulation
```python
@@ -243,8 +253,40 @@
specfile.url = 'https://example.com'
```
+Note that if you want to access multiple tag values, it may be noticeably
faster to do it using the `tags` context manager:
+
+```python
+# same as above, but roughly 4x times faster (parsing/saving happens only once)
+with specfile.tags() as tags:
+ print(tags.name.value)
+ print(tags.license.value)
+ print(tags.summary.value)
+ tags.url.value = 'https://example.com'
+```
+
+### Read-only access
+
+If you don't need write access, you can use the `content` property of context
managers and avoid the `with` statement:
+
+```python
+# no changes done to the tags object will be saved
+tags = specfile.tags().content
+
+print(tags.version.expanded_value)
+print(tags.release.expanded_value)
+
+# number of sources
+print(len(specfile.sources().content))
+```
+
## Caveats
### RPM macros
specfile uses RPM for parsing spec files and macro expansion. Unfortunately,
macros are always stored in a global context, which poses a problem for
multiple instances of Specfile.
+
+## Videos
+
+Here is a demo showcasing the `Specfile.update_tag()` method and its use cases:
+
+[](https://www.youtube.com/watch?v=yzMfBPdFXZY)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/README.md
new/specfile-0.20.0/README.md
--- old/specfile-0.18.0/README.md 2023-05-26 11:50:44.000000000 +0200
+++ new/specfile-0.20.0/README.md 2023-07-13 17:26:17.000000000 +0200
@@ -78,6 +78,16 @@
...
```
+### Defining and undefining macros
+
+```python
+# override macros loaded from system macro files
+specfile = Specfile('test.spec', macros=[('fedora', '38'), ('dist', '.fc38')])
+
+# undefine a system macro (in case it's defined)
+specfile = Specfile('test.spec', macros=[('rhel', None)])
+```
+
### Low-level manipulation
```python
@@ -216,8 +226,40 @@
specfile.url = 'https://example.com'
```
+Note that if you want to access multiple tag values, it may be noticeably
faster to do it using the `tags` context manager:
+
+```python
+# same as above, but roughly 4x times faster (parsing/saving happens only once)
+with specfile.tags() as tags:
+ print(tags.name.value)
+ print(tags.license.value)
+ print(tags.summary.value)
+ tags.url.value = 'https://example.com'
+```
+
+### Read-only access
+
+If you don't need write access, you can use the `content` property of context
managers and avoid the `with` statement:
+
+```python
+# no changes done to the tags object will be saved
+tags = specfile.tags().content
+
+print(tags.version.expanded_value)
+print(tags.release.expanded_value)
+
+# number of sources
+print(len(specfile.sources().content))
+```
+
## Caveats
### RPM macros
specfile uses RPM for parsing spec files and macro expansion. Unfortunately,
macros are always stored in a global context, which poses a problem for
multiple instances of Specfile.
+
+## Videos
+
+Here is a demo showcasing the `Specfile.update_tag()` method and its use cases:
+
+[](https://www.youtube.com/watch?v=yzMfBPdFXZY)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/fedora/python-specfile.spec
new/specfile-0.20.0/fedora/python-specfile.spec
--- old/specfile-0.18.0/fedora/python-specfile.spec 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/fedora/python-specfile.spec 2023-07-13
17:26:17.000000000 +0200
@@ -13,7 +13,7 @@
Name: python-specfile
-Version: 0.18.0
+Version: 0.20.0
Release: 1%{?dist}
Summary: A library for parsing and manipulating RPM spec files
@@ -24,7 +24,7 @@
BuildArch: noarch
-BuildRequires: python%{python3_pkgversion}-devel
+BuildRequires: python3-devel
%if %{with tests}
# tests/unit/test_guess_packager.py
BuildRequires: git-core
@@ -71,6 +71,12 @@
%changelog
+* Thu Jul 13 2023 Packit Team <[email protected]> - 0.20.0-1
+- New upstream release 0.20.0
+
+* Thu Jun 22 2023 Packit Team <[email protected]> - 0.19.0-1
+- New upstream release 0.19.0
+
* Fri May 26 2023 Packit Team <[email protected]> - 0.18.0-1
- New upstream release 0.18.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/files/tasks/rpm-test-deps.yaml
new/specfile-0.20.0/files/tasks/rpm-test-deps.yaml
--- old/specfile-0.18.0/files/tasks/rpm-test-deps.yaml 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/files/tasks/rpm-test-deps.yaml 2023-07-13
17:26:17.000000000 +0200
@@ -2,7 +2,10 @@
- name: Install test RPM dependencies
dnf:
name:
- - python3-flexmock
- python3-pytest
- python3-pytest-cov
become: true
+- name: Pip install test dependencies
+ ansible.builtin.pip:
+ name:
+ - flexmock # RHBZ#2120251
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/macros.py
new/specfile-0.20.0/specfile/macros.py
--- old/specfile-0.18.0/specfile/macros.py 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/specfile/macros.py 2023-07-13 17:26:17.000000000
+0200
@@ -169,7 +169,7 @@
while retry < MAX_REMOVAL_RETRIES:
rpm.delMacro(macro)
try:
- if cls.expand(f"%{macro}") == f"%{macro}":
+ if cls.expand(f"%{{{macro}}}") == f"%{{{macro.replace('%%',
'%')}}}":
break
except RPMException:
# the macro can't be expanded, but it still exists
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/options.py
new/specfile-0.20.0/specfile/options.py
--- old/specfile-0.18.0/specfile/options.py 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/specfile/options.py 2023-07-13 17:26:17.000000000
+0200
@@ -9,6 +9,7 @@
from specfile.exceptions import OptionsException
from specfile.formatter import formatted
+from specfile.value_parser import Node, StringLiteral, ValueParser
class TokenType(Enum):
@@ -474,63 +475,80 @@
Raises:
OptionsException if the option string is untokenizable.
"""
- result = []
- token = ""
- quote = None
- inp = list(option_string)
- while inp:
- c = inp.pop(0)
- if c == quote:
- if token:
- result.append(
- Token(
- TokenType.QUOTED
- if quote == "'"
- else TokenType.DOUBLE_QUOTED,
- token,
- )
- )
- token = ""
+ result: List[Token] = []
+
+ def append_default(s):
+ if result and result[-1].type == TokenType.DEFAULT:
+ result[-1].value += s
+ else:
+ result.append(Token(TokenType.DEFAULT, s))
+
+ token_nodes: List[Node] = []
+ for node in ValueParser.parse(option_string):
+ if isinstance(node, StringLiteral):
+ if token_nodes:
+ append_default("".join(str(n) for n in token_nodes))
+ token_nodes = []
+ token = ""
quote = None
- continue
- if quote:
- if c == "\\":
- if not inp:
- raise OptionsException("No escaped character")
- c = inp.pop(0)
- if c != quote:
- token += "\\"
- token += c
- continue
- if c.isspace():
- if token:
- result.append(Token(TokenType.DEFAULT, token))
- token = ""
- whitespace = c
+ inp = list(str(node))
while inp:
c = inp.pop(0)
- if not c.isspace():
- break
- whitespace += c
- else:
- result.append(Token(TokenType.WHITESPACE, whitespace))
- break
- inp.insert(0, c)
- result.append(Token(TokenType.WHITESPACE, whitespace))
- continue
- if c in ('"', "'"):
+ if c == quote:
+ if token:
+ result.append(
+ Token(
+ TokenType.QUOTED
+ if quote == "'"
+ else TokenType.DOUBLE_QUOTED,
+ token,
+ )
+ )
+ token = ""
+ quote = None
+ continue
+ if quote:
+ if c == "\\":
+ if not inp:
+ raise OptionsException("No escaped character")
+ c = inp.pop(0)
+ if c != quote:
+ token += "\\"
+ token += c
+ continue
+ if c.isspace():
+ if token:
+ append_default(token)
+ token = ""
+ whitespace = c
+ while inp:
+ c = inp.pop(0)
+ if not c.isspace():
+ break
+ whitespace += c
+ else:
+ result.append(Token(TokenType.WHITESPACE,
whitespace))
+ break
+ inp.insert(0, c)
+ result.append(Token(TokenType.WHITESPACE, whitespace))
+ continue
+ if c in ('"', "'"):
+ if token:
+ append_default(token)
+ token = ""
+ quote = c
+ continue
+ if c == "\\":
+ if not inp:
+ raise OptionsException("No escaped character")
+ c = inp.pop(0)
+ token += c
+ if quote:
+ raise OptionsException("No closing quotation")
if token:
- result.append(Token(TokenType.DEFAULT, token))
- token = ""
- quote = c
- continue
- if c == "\\":
- if not inp:
- raise OptionsException("No escaped character")
- c = inp.pop(0)
- token += c
- if quote:
- raise OptionsException("No closing quotation")
- if token:
- result.append(Token(TokenType.DEFAULT, token))
+ append_default(token)
+ else:
+ token_nodes.append(node)
+ if token_nodes:
+ append_default("".join(str(n) for n in token_nodes))
return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/sections.py
new/specfile-0.20.0/specfile/sections.py
--- old/specfile-0.18.0/specfile/sections.py 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/specfile/sections.py 2023-07-13 17:26:17.000000000
+0200
@@ -12,6 +12,7 @@
SECTION_OPTIONS,
SIMPLE_SCRIPT_SECTIONS,
)
+from specfile.exceptions import RPMException
from specfile.formatter import formatted
from specfile.macro_definitions import MacroDefinitions
from specfile.macros import Macros
@@ -231,19 +232,29 @@
def expand(s):
if context:
- return context.expand(s)
+ result = context.expand(
+ s, skip_parsing=getattr(expand, "skip_parsing", False)
+ )
+ # parse only once
+ expand.skip_parsing = True
+ return result
return Macros.expand(s)
def split_id(line):
content = []
separator = "\n"
tokens = re.split(r"(\s+)", line)
- if len(tokens) > 2:
+ if len(tokens) > 2 and tokens[-1].startswith("%"):
# if the last token after macro expansion starts with a
newline,
# consider it part of section content
- if expand(tokens[-1]).startswith("\n"):
- content = [tokens.pop()]
- separator = tokens.pop()
+ try:
+ expanded = expand(tokens[-1])
+ except RPMException:
+ pass
+ else:
+ if expanded.startswith("\n"):
+ content = [tokens.pop()]
+ separator = tokens.pop()
if len(tokens) > 2:
name = tokens[0]
delimiter = tokens[1]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/spec_parser.py
new/specfile-0.20.0/specfile/spec_parser.py
--- old/specfile-0.18.0/specfile/spec_parser.py 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/specfile/spec_parser.py 2023-07-13 17:26:17.000000000
+0200
@@ -49,7 +49,7 @@
def __init__(
self,
sourcedir: Path,
- macros: Optional[List[Tuple[str, str]]] = None,
+ macros: Optional[List[Tuple[str, Optional[str]]]] = None,
force_parse: bool = False,
) -> None:
self.sourcedir = sourcedir
@@ -170,7 +170,9 @@
restore(key)
def _do_parse(
- self, content: str, extra_macros: Optional[List[Tuple[str, str]]] =
None
+ self,
+ content: str,
+ extra_macros: Optional[List[Tuple[str, Optional[str]]]] = None,
) -> Tuple[rpm.spec, bool]:
"""
Parses the content of a spec file.
@@ -190,7 +192,10 @@
def get_rpm_spec(content, flags):
Macros.reinit()
for name, value in self.macros + (extra_macros or []):
- Macros.define(name, value)
+ if value is None:
+ Macros.remove(name)
+ else:
+ Macros.define(name, value)
Macros.define("_sourcedir", str(self.sourcedir))
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(content.encode())
@@ -318,7 +323,9 @@
return get_rpm_spec(content, rpm.RPMSPEC_ANYARCH), tainted
def parse(
- self, content: str, extra_macros: Optional[List[Tuple[str, str]]] =
None
+ self,
+ content: str,
+ extra_macros: Optional[List[Tuple[str, Optional[str]]]] = None,
) -> None:
"""
Parses the content of a spec file and updates the `spec` and `tainted`
attributes.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/specfile.py
new/specfile-0.20.0/specfile/specfile.py
--- old/specfile-0.18.0/specfile/specfile.py 2023-05-26 11:50:44.000000000
+0200
+++ new/specfile-0.20.0/specfile/specfile.py 2023-07-13 17:26:17.000000000
+0200
@@ -43,7 +43,7 @@
path: Union[Path, str],
sourcedir: Optional[Union[Path, str]] = None,
autosave: bool = False,
- macros: Optional[List[Tuple[str, str]]] = None,
+ macros: Optional[List[Tuple[str, Optional[str]]]] = None,
force_parse: bool = False,
) -> None:
"""
@@ -64,7 +64,7 @@
"""
self.autosave = autosave
self._path = Path(path)
- self._lines = self.path.read_text().splitlines()
+ self._lines = self._read_lines(self._path)
self._parser = SpecParser(
Path(sourcedir or self.path.parent), macros, force_parse
)
@@ -101,6 +101,10 @@
) -> None:
self.save()
+ @staticmethod
+ def _read_lines(path: Path) -> List[str]:
+ return path.read_text(encoding="utf8",
errors="surrogateescape").splitlines()
+
@property
def path(self) -> Path:
"""Path to the spec file."""
@@ -120,7 +124,7 @@
self._parser.sourcedir = Path(value)
@property
- def macros(self) -> List[Tuple[str, str]]:
+ def macros(self) -> List[Tuple[str, Optional[str]]]:
"""List of extra macro definitions."""
return self._parser.macros
@@ -154,16 +158,17 @@
def reload(self) -> None:
"""Reload the spec file content."""
- self._lines = self.path.read_text().splitlines()
+ self._lines = self._read_lines(self.path)
def save(self) -> None:
"""Save the spec file content."""
- self.path.write_text(str(self))
+ self.path.write_text(str(self), encoding="utf8",
errors="surrogateescape")
def expand(
self,
expression: str,
- extra_macros: Optional[List[Tuple[str, str]]] = None,
+ extra_macros: Optional[List[Tuple[str, Optional[str]]]] = None,
+ skip_parsing: bool = False,
) -> str:
"""
Expands an expression in the context of the spec file.
@@ -171,11 +176,15 @@
Args:
expression: Expression to expand.
extra_macros: Extra macros to be defined before expansion is
performed.
+ skip_parsing: Do not parse the spec file before expansion is
performed.
+ Defaults to False. Mutually exclusive with extra_macros. Set
this to True
+ only if you are certain that the global macro context is
up-to-date.
Returns:
Expanded expression.
"""
- self._parser.parse(str(self), extra_macros)
+ if not skip_parsing or extra_macros is not None:
+ self._parser.parse(str(self), extra_macros)
return Macros.expand(expression)
def get_active_macros(self) -> List[Macro]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile/value_parser.py
new/specfile-0.20.0/specfile/value_parser.py
--- old/specfile-0.18.0/specfile/value_parser.py 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/specfile/value_parser.py 2023-07-13
17:26:17.000000000 +0200
@@ -312,7 +312,12 @@
def expand(s):
if context:
- return context.expand(s)
+ result = context.expand(
+ s, skip_parsing=getattr(expand, "skip_parsing", False)
+ )
+ # parse only once
+ expand.skip_parsing = True
+ return result
return Macros.expand(s)
def flatten(nodes):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/specfile.egg-info/PKG-INFO
new/specfile-0.20.0/specfile.egg-info/PKG-INFO
--- old/specfile-0.18.0/specfile.egg-info/PKG-INFO 2023-05-26
11:50:53.000000000 +0200
+++ new/specfile-0.20.0/specfile.egg-info/PKG-INFO 2023-07-13
17:26:26.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: specfile
-Version: 0.18.0
+Version: 0.20.0
Summary: A library for parsing and manipulating RPM spec files.
Home-page: https://github.com/packit/specfile
Author: Red Hat
@@ -105,6 +105,16 @@
...
```
+### Defining and undefining macros
+
+```python
+# override macros loaded from system macro files
+specfile = Specfile('test.spec', macros=[('fedora', '38'), ('dist', '.fc38')])
+
+# undefine a system macro (in case it's defined)
+specfile = Specfile('test.spec', macros=[('rhel', None)])
+```
+
### Low-level manipulation
```python
@@ -243,8 +253,40 @@
specfile.url = 'https://example.com'
```
+Note that if you want to access multiple tag values, it may be noticeably
faster to do it using the `tags` context manager:
+
+```python
+# same as above, but roughly 4x times faster (parsing/saving happens only once)
+with specfile.tags() as tags:
+ print(tags.name.value)
+ print(tags.license.value)
+ print(tags.summary.value)
+ tags.url.value = 'https://example.com'
+```
+
+### Read-only access
+
+If you don't need write access, you can use the `content` property of context
managers and avoid the `with` statement:
+
+```python
+# no changes done to the tags object will be saved
+tags = specfile.tags().content
+
+print(tags.version.expanded_value)
+print(tags.release.expanded_value)
+
+# number of sources
+print(len(specfile.sources().content))
+```
+
## Caveats
### RPM macros
specfile uses RPM for parsing spec files and macro expansion. Unfortunately,
macros are always stored in a global context, which poses a problem for
multiple instances of Specfile.
+
+## Videos
+
+Here is a demo showcasing the `Specfile.update_tag()` method and its use cases:
+
+[](https://www.youtube.com/watch?v=yzMfBPdFXZY)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/tests/unit/test_macros.py
new/specfile-0.20.0/tests/unit/test_macros.py
--- old/specfile-0.18.0/tests/unit/test_macros.py 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/tests/unit/test_macros.py 2023-07-13
17:26:17.000000000 +0200
@@ -81,6 +81,11 @@
Macros.remove("test")
Macros.remove("non_existent_macro")
assert Macros.dump() == macros
+ rpm.addMacro("%test", "1")
+ rpm.addMacro("te%st%%", "2")
+ Macros.remove("%test")
+ Macros.remove("te%st%%")
+ assert Macros.dump() == macros
def test_macros_remove_failure():
@@ -88,7 +93,7 @@
# ensure that we are not stuck in an infinite loop
rpm.reloadConfig()
rpm.addMacro("foo", "bar")
-
flexmock(rpm).should_receive("expandMacro").with_args("%foo").and_raise(rpm.error)
+
flexmock(rpm).should_receive("expandMacro").with_args("%{foo}").and_raise(rpm.error)
with pytest.raises(MacroRemovalException):
Macros.remove("foo")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/tests/unit/test_options.py
new/specfile-0.20.0/tests/unit/test_options.py
--- old/specfile-0.18.0/tests/unit/test_options.py 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/tests/unit/test_options.py 2023-07-13
17:26:17.000000000 +0200
@@ -248,6 +248,16 @@
Token(TokenType.WHITESPACE, " "),
],
),
+ (
+ '-q -n %{name}-%{version}%[%{rc}?"-rc":""]',
+ [
+ Token(TokenType.DEFAULT, "-q"),
+ Token(TokenType.WHITESPACE, " "),
+ Token(TokenType.DEFAULT, "-n"),
+ Token(TokenType.WHITESPACE, " "),
+ Token(TokenType.DEFAULT,
'%{name}-%{version}%[%{rc}?"-rc":""]'),
+ ],
+ ),
],
)
def test_options_tokenize(option_string, result):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/specfile-0.18.0/tests/unit/test_spec_parser.py
new/specfile-0.20.0/tests/unit/test_spec_parser.py
--- old/specfile-0.18.0/tests/unit/test_spec_parser.py 2023-05-26
11:50:44.000000000 +0200
+++ new/specfile-0.20.0/tests/unit/test_spec_parser.py 2023-07-13
17:26:17.000000000 +0200
@@ -5,6 +5,7 @@
from pathlib import Path
import rpm
+from flexmock import flexmock
from specfile.spec_parser import SpecParser
@@ -39,3 +40,27 @@
deep_copy = copy.deepcopy(parser)
assert deep_copy == parser
assert deep_copy is not parser
+
+
+def test_spec_parser_macros():
+ flexmock(rpm).should_call("delMacro").with_args(
+ "fedora"
+ ).at_least().once().ordered()
+
flexmock(rpm).should_call("delMacro").with_args("rhel").at_least().once().ordered()
+ flexmock(rpm).should_call("addMacro").with_args("rhel",
"9").once().ordered()
+ # we don't care about the rest
+ flexmock(rpm).should_call("addMacro")
+ flexmock(rpm).should_call("delMacro")
+ parser = SpecParser(Path("."), macros=[("fedora", None), ("rhel", "9")])
+ spec, _ = parser._do_parse(
+ (
+ "Name: test\n"
+ "Version: 0.1\n"
+ "Release: 1%{?dist}\n"
+ "Summary: Test package\n"
+ "License: MIT\n"
+ "\n"
+ "%description\n"
+ "Test package\n"
+ ),
+ )