Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement high-level manipulation of sources/patches #20

Merged
merged 2 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,20 @@ specfile.add_changelog_entry(
#### Sources and patches

```python
print(specfile.sources)
print(specfile.patches)
print(specfile.sources[0].filename)
specfile.sources.append('tests.tar.gz')
specfile.patches[0] = 'downstream.patch'
with specfile.sources() as sources:
# expanded location of the first source
print(sources[0].expanded_location)
# adding a source
sources.append('tests.tar.gz')

with specfile.patches() as patches:
# modifying location of the first patch
patches[0].location = 'downstream.patch'
# removing comments associated with the last patch
patches[-1].comments.clear()
# adding and removing patches
patches.append('another.patch')
del patches[2]

# fetching non-local sources (including patches)
specfile.download_remote_sources()
Expand Down
119 changes: 119 additions & 0 deletions specfile/sourcelist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import collections
from typing import List, Optional, overload

from specfile.rpm import Macros
from specfile.sections import Section
from specfile.tags import Comments
from specfile.types import SupportsIndex


class SourcelistEntry:
"""
Class that represents a spec file source/patch in a %sourcelist/%patchlist.

Attributes:
FrNecas marked this conversation as resolved.
Show resolved Hide resolved
location: Literal location of the source/patch as stored in the spec file.
comments: List of comments associated with the source/patch.
"""

def __init__(self, location: str, comments: Comments) -> None:
self.location = location
self.comments = comments.copy()

def __repr__(self) -> str:
comments = repr(self.comments)
return f"SourcelistEntry('{self.location}', {comments})"

@property
def expanded_location(self) -> str:
"""URL of the source/patch after expanding macros."""
return Macros.expand(self.location)


class Sourcelist(collections.UserList):
"""
Class that represents entries in a %sourcelist/%patchlist section.

Attributes:
data: List of individual sources/patches.
"""

def __init__(
self,
data: Optional[List[SourcelistEntry]] = None,
remainder: Optional[List[str]] = None,
) -> None:
"""
Constructs a `Sourcelist` object.

Args:
data: List of individual sources/patches.
remainder: Leftover lines in a section that can't be parsed into sources/patches.

Returns:
Constructed instance of `Sourcelist` class.
"""
super().__init__()
if data is not None:
self.data = data.copy()
self._remainder = remainder.copy() if remainder is not None else []

def __repr__(self) -> str:
data = repr(self.data)
remainder = repr(self._remainder)
return f"Sourcelist({data}, {remainder})"

@overload
def __getitem__(self, i: SupportsIndex) -> SourcelistEntry:
pass

@overload
def __getitem__(self, i: slice) -> "Sourcelist":
pass

def __getitem__(self, i):
if isinstance(i, slice):
return Sourcelist(self.data[i], self._remainder)
else:
return self.data[i]

def copy(self) -> "Sourcelist":
return Sourcelist(self.data, self._remainder)

@staticmethod
def parse(section: Section) -> "Sourcelist":
"""
Parses a section into sources/patches.

Args:
section: %sourcelist/%patchlist section.

Returns:
Constructed instance of `Sourcelist` class.
"""
data = []
buffer: List[str] = []
for line in section:
if line and not line.lstrip().startswith("#"):
data.append(SourcelistEntry(line, Comments.parse(buffer)))
buffer = []
else:
buffer.append(line)
return Sourcelist(data, buffer)

def get_raw_section_data(self) -> List[str]:
"""
Reconstructs section data from sources/patches.

Returns:
List of lines forming the reconstructed section data.
"""
result = []
for source in self.data:
result.extend(source.comments.get_raw_data())
result.append(source.location)
result.extend(self._remainder)
return result
Loading