123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- # vim: set fileencoding=utf-8 :
- #
- # (C) 2014-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, please see
- # <http://www.gnu.org/licenses/>
- """An RPM Changelog"""
- import locale
- import datetime
- import re
- from functools import wraps
- import gbp.log
- def c_locale(category):
- def _decorator(f):
- @wraps(f)
- def wrapper(*args, **kwargs):
- saved = locale.setlocale(category, None)
- locale.setlocale(category, 'C')
- ret = f(*args, **kwargs)
- locale.setlocale(category, saved)
- return ret
- return wrapper
- return _decorator
- class ChangelogError(Exception):
- """Problem parsing changelog"""
- pass
- class _ChangelogHeader(object):
- """The header part of one changelog section"""
- def __init__(self, pkgpolicy, time=None, **kwargs):
- self._pkgpolicy = pkgpolicy
- self._data = {'time': time}
- self._data.update(kwargs)
- def __contains__(self, key):
- return key in self._data
- def __getitem__(self, key):
- if key in self._data:
- return self._data[key]
- return None
- @c_locale(locale.LC_TIME)
- def __str__(self):
- keys = dict(self._data)
- keys['time'] = self._data['time'].strftime(
- self._pkgpolicy.Changelog.header_time_format)
- try:
- return self._pkgpolicy.Changelog.header_format % keys + '\n'
- except KeyError as err:
- raise ChangelogError("Unable to format changelog header, missing "
- "property %s" % err)
- class _ChangelogEntry(object):
- """An entry (one 'change') in an RPM changelog"""
- def __init__(self, pkgpolicy, author, text):
- """
- @param pkgpolicy: RPM packaging policy
- @type pkgpolicy: L{RpmPkgPolicy}
- @param author: author of the change
- @type author: C{str}
- @param text: message of the changelog entry
- @type text: C{str} or C{list} of C{str}
- """
- self._pkgpolicy = pkgpolicy
- self.author = author
- if isinstance(text, str):
- self._text = text.splitlines()
- else:
- self._text = text
- # Strip trailing empty lines
- while text and not text[-1].strip():
- text.pop()
- def __str__(self):
- # Currently no (re-)formatting, just raw text
- string = ""
- for line in self._text:
- string += line + '\n'
- return string
- class _ChangelogSection(object):
- """One section (set of changes) in an RPM changelog"""
- def __init__(self, pkgpolicy, *args, **kwargs):
- self._pkgpolicy = pkgpolicy
- self.header = _ChangelogHeader(pkgpolicy, *args, **kwargs)
- self.entries = []
- self._trailer = '\n'
- def __str__(self):
- text = str(self.header)
- for entry in self.entries:
- text += str(entry)
- # Add "section separator"
- text += self._trailer
- return text
- def set_header(self, *args, **kwargs):
- """Change the section header"""
- self.header = _ChangelogHeader(self._pkgpolicy, *args, **kwargs)
- def append_entry(self, entry):
- """Add a new entry to the end of the list of entries"""
- self.entries.append(entry)
- return entry
- class Changelog(object):
- """An RPM changelog"""
- def __init__(self, pkgpolicy):
- self._pkgpolicy = pkgpolicy
- self.sections = []
- def __str__(self):
- string = ""
- for section in self.sections:
- string += str(section)
- return string
- def create_entry(self, *args, **kwargs):
- """Create and return new entry object"""
- return _ChangelogEntry(self._pkgpolicy, *args, **kwargs)
- def add_section(self, *args, **kwargs):
- """Add new empty section"""
- section = _ChangelogSection(self._pkgpolicy, *args, **kwargs)
- self.sections.insert(0, section)
- return section
- class ChangelogParser(object):
- """Parser for RPM changelogs"""
- def __init__(self, pkgpolicy):
- self._pkgpolicy = pkgpolicy
- self.section_match_re = pkgpolicy.Changelog.section_match_re
- self.section_split_re = pkgpolicy.Changelog.section_split_re
- self.header_split_re = pkgpolicy.Changelog.header_split_re
- self.header_name_split_re = pkgpolicy.Changelog.header_name_split_re
- self.body_name_re = pkgpolicy.Changelog.body_name_re
- def raw_parse_string(self, string):
- """Parse changelog - only splits out raw changelog sections."""
- changelog = Changelog(self._pkgpolicy)
- ch_section = ""
- for line in string.splitlines():
- if re.match(self.section_match_re, line, re.M | re.S):
- if ch_section:
- changelog.sections.append(ch_section)
- ch_section = line + '\n'
- elif ch_section:
- ch_section += line + '\n'
- else:
- raise ChangelogError("First line in changelog is invalid")
- if ch_section:
- changelog.sections.append(ch_section)
- return changelog
- def raw_parse_file(self, changelog):
- """Parse changelog file - only splits out raw changelog sections."""
- try:
- with open(changelog) as ch_file:
- return self.raw_parse_string(ch_file.read())
- except IOError as err:
- raise ChangelogError("Unable to read changelog file: %s" % err)
- @c_locale(locale.LC_TIME)
- def _parse_section_header(self, text):
- """Parse one changelog section header"""
- # Try to split out time stamp and "changelog name"
- match = re.match(self.header_split_re, text, re.M)
- if not match:
- raise ChangelogError("Unable to parse changelog header: %s" % text)
- try:
- time = datetime.datetime.strptime(match.group('ch_time'),
- "%a %b %d %Y")
- except ValueError:
- raise ChangelogError("Unable to parse changelog header: invalid "
- "timestamp '%s'" % match.group('ch_time'))
- # Parse "name" part which consists of name and/or email and an optional
- # revision
- name_text = match.group('ch_name')
- match = re.match(self.header_name_split_re, name_text)
- if not match:
- raise ChangelogError("Unable to parse changelog header: invalid "
- "name / revision '%s'" % name_text)
- kwargs = match.groupdict()
- return _ChangelogSection(self._pkgpolicy, time=time, **kwargs)
- def _create_entry(self, author, text):
- """Create a new changelog entry"""
- return _ChangelogEntry(self._pkgpolicy, author=author, text=text)
- def _parse_section_entries(self, text, default_author):
- """Parse entries from a string and add them to a section"""
- entries = []
- entry_text = []
- author = default_author
- for line in text.splitlines():
- match = re.match(self.body_name_re, line)
- if match:
- if entry_text:
- entries.append(self._create_entry(author, entry_text))
- author = match.group('name')
- else:
- if line.startswith("-"):
- if entry_text:
- entries.append(self._create_entry(author, entry_text))
- entry_text = [line]
- else:
- if not entry_text:
- gbp.log.info("First changelog entry (%s) is garbled, "
- "entries should start with a dash ('-')" %
- line)
- entry_text.append(line)
- if entry_text:
- entries.append(self._create_entry(author, entry_text))
- return entries
- def parse_section(self, text):
- """Parse one section"""
- # Check that the first line(s) look like a changelog header
- match = re.match(self.section_split_re, text, re.M | re.S)
- if not match:
- raise ChangelogError("Doesn't look like changelog header: %s..." %
- text.splitlines()[0])
- # Parse header
- section = self._parse_section_header(match.group('ch_header'))
- header = section.header
- # Parse entries
- default_author = header['name'] if 'name' in header else header['email']
- for entry in self._parse_section_entries(match.group('ch_body'),
- default_author):
- section.append_entry(entry)
- return section
|