[python-debian/master] Overhaul the parsing code to follow that used by dpkg-parsechangelog.py.
James Westby
jw+debian at jameswestby.net
Tue Jul 22 17:11:58 UTC 2008
* Overhaul the parsing code to follow that used by dpkg-parsechangelog.py,
making it much more robust.
- There is now a "strict" option to turn warnings in to errors. It is
on by default.
* The file parameter in the changelog can now be a file-like object,
to save reading a file to a string first. Thanks to Tilman Koschnick.
(Closes: #487797)
---
debian/changelog | 7 +
debian/copyright | 1 +
debian_bundle/changelog.py | 1049 +++++++++++++++++++++++++-------------------
3 files changed, 609 insertions(+), 448 deletions(-)
diff --git a/debian/changelog b/debian/changelog
index c1067be..75aa718 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -5,6 +5,13 @@ python-debian (0.1.11) UNRELEASED; urgency=low
fuse's changelog.
* Allow any number of spaces before "urgency" in the header line of a
changelog, see lvm2's changelog in Ubuntu.
+ * Overhaul the parsing code to follow that used by dpkg-parsechangelog.py,
+ making it much more robust.
+ - There is now a "strict" option to turn warnings in to errors. It is
+ on by default.
+ * The file parameter in the changelog can now be a file-like object,
+ to save reading a file to a string first. Thanks to Tilman Koschnick.
+ (Closes: #487797)
[ John Wright ]
* debian/control:
diff --git a/debian/copyright b/debian/copyright
index 14f6c90..7acc0be 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -12,6 +12,7 @@ Copyright:
changelog.py, setup.py, README.Changelog and the contents of
examples/changelog/ are
Copyright (C) 2006-7 James Westby <jw+debian at jameswestby.net>
+ Copyright (C) 2008 Canonical Ltd.
debian_support.py is Copyright (C) 2005 Florian Weimer <fw at deneb.enyo.de>
diff --git a/debian_bundle/changelog.py b/debian_bundle/changelog.py
index 9c546b5..ee6be2a 100644
--- a/debian_bundle/changelog.py
+++ b/debian_bundle/changelog.py
@@ -1,5 +1,6 @@
# changelog.py -- Python module for Debian changelogs
# Copyright (C) 2006-7 James Westby <jw+debian at jameswestby.net>
+# Copyright (C) 2008 Canonical Ltd.
#
# 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
@@ -15,365 +16,528 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# The parsing code is based on that from dpkg which is:
+# Copyright 1996 Ian Jackson
+# Copyright 2005 Frank Lichtenheld <frank at lichtenheld.de>
+# and licensed under the same license as above.
+
"""This module implements facilities to deal with Debian changelogs."""
-import os
import re
import unittest
+import warnings
import debian_support
class ChangelogParseError(StandardError):
- """Indicates that the changelog could not be parsed"""
- is_user_error = True
+ """Indicates that the changelog could not be parsed"""
+ is_user_error = True
- def __init__(self, line):
- self._line=line
+ def __init__(self, line):
+ self._line=line
- def __str__(self):
- return "Could not parse changelog: "+self._line
+ def __str__(self):
+ return "Could not parse changelog: "+self._line
class ChangelogCreateError(StandardError):
- """Indicates that changelog could not be created, as all the information
- required was not given"""
+ """Indicates that changelog could not be created, as all the information
+ required was not given"""
class VersionError(StandardError):
- """Indicates that the version does not conform to the required format"""
+ """Indicates that the version does not conform to the required format"""
- is_user_error = True
+ is_user_error = True
- def __init__(self, version):
- self._version=version
+ def __init__(self, version):
+ self._version=version
- def __str__(self):
- return "Could not parse version: "+self._version
+ def __str__(self):
+ return "Could not parse version: "+self._version
class Version(debian_support.Version, object):
- """Represents a version of a Debian package."""
- # Subclassing debian_support.Version for its rich comparison
-
- def __init__(self, version):
- version = str(version)
- debian_support.Version.__init__(self, version)
-
- self.full_version = version
+ """Represents a version of a Debian package."""
+ # Subclassing debian_support.Version for its rich comparison
- def __setattr__(self, attr, value):
- """Update all the attributes, given a particular piece of the version
+ def __init__(self, version):
+ version = str(version)
+ debian_support.Version.__init__(self, version)
- Allowable values for attr, hopefully self-explanatory:
- full_version
- epoch
- upstream_version
- debian_version
-
- Any attribute starting with __ is given to object's __setattr__ method.
- """
+ self.full_version = version
- attrs = ('full_version', 'epoch', 'upstream_version', 'debian_version')
-
- if attr.startswith('_Version__'):
- object.__setattr__(self, attr, value)
- return
- elif attr not in attrs:
- raise AttributeError("Cannot assign to attribute " + attr)
-
- if attr == 'full_version':
- version = value
- p = re.compile(r'^(?:(?P<epoch>\d+):)?'
- + r'(?P<upstream_version>[A-Za-z0-9.+:~-]+?)'
- + r'(?:-(?P<debian_version>[A-Za-z0-9.~+]+))?$')
- m = p.match(version)
- if m is None:
- raise VersionError(version)
- for key, value in m.groupdict().items():
- object.__setattr__(self, key, value)
- self.__asString = version
+ def __setattr__(self, attr, value):
+ """Update all the attributes, given a particular piece of the version
+
+ Allowable values for attr, hopefully self-explanatory:
+ full_version
+ epoch
+ upstream_version
+ debian_version
+
+ Any attribute starting with __ is given to object's __setattr__ method.
+ """
+
+ attrs = ('full_version', 'epoch', 'upstream_version', 'debian_version')
+
+ if attr.startswith('_Version__'):
+ object.__setattr__(self, attr, value)
+ return
+ elif attr not in attrs:
+ raise AttributeError("Cannot assign to attribute " + attr)
+
+ if attr == 'full_version':
+ version = value
+ p = re.compile(r'^(?:(?P<epoch>\d+):)?'
+ + r'(?P<upstream_version>[A-Za-z0-9.+:~-]+?)'
+ + r'(?:-(?P<debian_version>[A-Za-z0-9.~+]+))?$')
+ m = p.match(version)
+ if m is None:
+ raise VersionError(version)
+ for key, value in m.groupdict().items():
+ object.__setattr__(self, key, value)
+ self.__asString = version
- else:
- # Construct a full version from what was given and pass it back here
- d = {}
- for a in attrs[1:]:
- if a == attr:
- d[a] = value
- else:
- d[a] = getattr(self, a)
-
- version = ""
- if d['epoch'] and d['epoch'] != '0':
- version += d['epoch'] + ":"
- version += d['upstream_version']
- if d['debian_version']:
- version += '-' + d['debian_version']
-
- self.full_version = version
-
- full_version = property(lambda self: self.__asString)
+ else:
+ # Construct a full version from what was given and pass it back here
+ d = {}
+ for a in attrs[1:]:
+ if a == attr:
+ d[a] = value
+ else:
+ d[a] = getattr(self, a)
+
+ version = ""
+ if d['epoch'] and d['epoch'] != '0':
+ version += d['epoch'] + ":"
+ version += d['upstream_version']
+ if d['debian_version']:
+ version += '-' + d['debian_version']
+
+ self.full_version = version
+
+ full_version = property(lambda self: self.__asString)
class ChangeBlock(object):
- """Holds all the information about one block from the changelog."""
-
- def __init__(self, package=None, version=None, distributions=None,
- urgency=None, changes=None, author=None, date=None):
- self.version = version
- self.package = package
- self.distributions = distributions
- self.urgency = urgency
- self._changes = changes
- self.author = author
- self.date = date
- self._trailing = 0
-
- def changes(self):
- return self._changes
-
- def add_trailing_newline(self):
- self._trailing += 1
-
- def del_trailing_newline(self):
- self._trailing -= 1
-
- def add_change(self, change):
- if self._changes is None:
- self._changes = [change]
- else:
-#Bit of trickery to keep the formatting nicer with a blank line at the end if there is one
- changes = self._changes
- changes.reverse()
- added = False
- for i in range(len(changes)):
- m = blankline.match(changes[i])
- if m is None:
- changes.insert(i, change)
- added = True
- break
- changes.reverse()
- if not added:
- changes.append(change)
- self._changes = changes
-
- def __str__(self):
- block = ""
- if self.package is None:
- raise ChangelogCreateError("Package not specified")
- block += self.package + " "
- if self.version is None:
- raise ChangelogCreateError("Version not specified")
- block += "(" + str(self.version) + ") "
- if self.distributions is None:
- raise ChangelogCreateError("Distribution not specified")
- block += self.distributions + "; "
- if self.urgency is None:
- raise ChangelogCreateError("Urgency not specified")
- block += "urgency=" + self.urgency + "\n"
- if self.changes() is None:
- raise ChangelogCreateError("Changes not specified")
- for change in self.changes():
- block += change + "\n"
- if self.author is None:
- raise ChangelogCreateError("Author not specified")
- if self.date is None:
- raise ChangelogCreateError("Date not specified")
- block += " -- " + self.author + " " + self.date + "\n"
- if self._trailing > 0:
- for i in range(self._trailing):
- block += "\n"
- return block
-
-topline = re.compile('^([a-z0-9][-a-z0-9.+]+) \(([-0-9a-zA-Z.:~+]+)\) '
- +'([-+.0-9a-zA-Z ]+); *urgency=([a-zA-Z]+)')
-blankline = re.compile('^[ \t]*$')
-change = re.compile('^[ ][ ]+.*$')
-endline = re.compile('^ -- (.*) (\w\w\w, +(\d| \d|\d\d) \w\w\w \d\d\d\d '+
- '\d\d:\d\d:\d\d [-+]\d\d\d\d( \(.*\))?)\s*$')
-endline_nodetails = re.compile('^ --(?: (.*) (\w\w\w, +(\d| \d|\d\d) \w\w\w \d\d\d\d '+
- '\d\d:\d\d:\d\d [-+]\d\d\d\d( \(.*\))?))?\s*$')
+ """Holds all the information about one block from the changelog."""
+
+ def __init__(self, package=None, version=None, distributions=None,
+ urgency=None, urgency_comment=None, changes=None,
+ author=None, date=None, other_pairs=None):
+ self._raw_version = None
+ self._set_version(version)
+ self.package = package
+ self.distributions = distributions
+ self.urgency = urgency or "unknown"
+ self.urgency_comment = urgency_comment or ''
+ self._changes = changes
+ self.author = author
+ self.date = date
+ self._trailing = []
+ self.other_pairs = other_pairs or {}
+ self._no_trailer = False
+ self._trailer_separator = " "
+
+ def _set_version(self, version):
+ if version is not None:
+ self._raw_version = str(version)
+
+ def _get_version(self):
+ return Version(self._raw_version)
+
+ version = property(_get_version, _set_version)
+
+ def other_keys_normalised(self):
+ norm_dict = {}
+ for (key, value) in other_pairs.items():
+ key = key[0].upper() + key[1:].lower()
+ m = xbcs_re.match(key)
+ if m is None:
+ key = "XS-%s" % key
+ norm_dict[key] = value
+ return norm_dict
-class Changelog(object):
- """Represents a debian/changelog file. You can ask it several things about
- the file."""
+ def changes(self):
+ return self._changes
+ def add_trailing_line(self, line):
+ self._trailing.append(line)
- def __init__(self, file=None, max_blocks=None, allow_empty_author=False):
- """Set up the Changelog for use. file is the contects of the changelog.
- """
- self._blocks = []
- if file is not None:
- try:
- self.parse_changelog(file, max_blocks, allow_empty_author=allow_empty_author)
- except ChangelogParseError:
- pass
-
-
- def parse_changelog(self, file, max_blocks=None, allow_empty_author=False):
- before = 1
- inblock = 2
-
- package = None
- version = None
- distributions = None
- urgency = None
- changes = []
- author = None
- date = None
-
- self._file = file
- state = before
- for line in self._file.split('\n'):
- if state == before:
- m = topline.match(line)
- if m is not None:
- state = inblock
- package = m.group(1)
- version = Version(m.group(2))
- distributions = m.group(3)
- urgency = m.group(4)
- else:
- m = blankline.match(line)
- if m is None:
- raise ChangelogParseError(line)
- elif len(self._blocks) > 0:
- self._blocks[-1].add_trailing_newline()
- elif state == inblock:
- m = blankline.match(line)
- if m is not None:
- changes.append(line)
- else:
- if not allow_empty_author:
- m = endline.match(line)
- else:
- m = endline_nodetails.match(line)
- if m is not None:
- state = before
- author = m.group(1)
- date = m.group(2)
- block = ChangeBlock(package, version, distributions, urgency,
- changes, author, date)
- self._blocks.append(block)
- (package, version, distributions, urgency, author, date) = \
- (None, None, None, None, None, None)
- changes = []
- if max_blocks is not None and len(self._blocks) >= max_blocks:
- break
- else:
- m = change.match(line)
- if m is None:
- raise ChangelogParseError(line)
- #TODO: maybe try and parse these more intelligently
- changes.append(line)
+ def add_change(self, change):
+ if self._changes is None:
+ self._changes = [change]
else:
- assert(False), "Unknown state: "+state
-
+ #Bit of trickery to keep the formatting nicer with a blank
+ #line at the end if there is one
+ changes = self._changes
+ changes.reverse()
+ added = False
+ for i in range(len(changes)):
+ m = blankline.match(changes[i])
+ if m is None:
+ changes.insert(i, change)
+ added = True
+ break
+ changes.reverse()
+ if not added:
+ changes.append(change)
+ self._changes = changes
+
+ def __str__(self):
+ block = ""
+ if self.package is None:
+ raise ChangelogCreateError("Package not specified")
+ block += self.package + " "
+ if self._raw_version is None:
+ raise ChangelogCreateError("Version not specified")
+ block += "(" + self._raw_version + ") "
+ if self.distributions is None:
+ raise ChangelogCreateError("Distribution not specified")
+ block += self.distributions + "; "
+ if self.urgency is None:
+ raise ChangelogCreateError("Urgency not specified")
+ block += "urgency=" + self.urgency + self.urgency_comment
+ for (key, value) in self.other_pairs.items():
+ block += ", %s=%s" % (key, value)
+ block += '\n'
+ if self.changes() is None:
+ raise ChangelogCreateError("Changes not specified")
+ for change in self.changes():
+ block += change + "\n"
+ if not self._no_trailer:
+ if self.author is None:
+ raise ChangelogCreateError("Author not specified")
+ if self.date is None:
+ raise ChangelogCreateError("Date not specified")
+ block += " -- " + self.author + self._trailer_separator \
+ + self.date + "\n"
+ for line in self._trailing:
+ block += line + "\n"
+ return block
+
+topline = re.compile('^(\w[-a-z0-9.+]+) \(([^\(\) \t]+)\)((\s+[-0-9a-z]+)+);',
+ re.IGNORECASE)
+blankline = re.compile('^\s*$')
+change = re.compile('^\s\s+.*$')
+endline = re.compile('^ -- (.*) <(.*)>( ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+'
+ '\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?\s*)$')
+endline_nodetails = re.compile('^ --(?: (.*) <(.*)>( ?)((\w+\,\s*)?\d{1,2}'
+ '\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}'
+ '(\s+\([^\\\(\)]\))?))?\s*$')
+keyvalue= re.compile('^([-0-9a-z]+)=\s*(.*\S)$', re.IGNORECASE)
+value_re = re.compile('^([-0-9a-z]+)((\s+.*)?)$', re.IGNORECASE)
+xbcs_re = re.compile('^X[BCS]+-', re.IGNORECASE)
+emacs_variables = re.compile('^(;;\s*)?Local variables:', re.IGNORECASE)
+vim_variables = re.compile('^vim:', re.IGNORECASE)
+cvs_keyword = re.compile('^\$\w+:.*\$')
+comments = re.compile('^\# ')
+more_comments = re.compile('^/\*.*\*/')
+
+old_format_re1 = re.compile('^(\w+\s+\w+\s+\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}'
+ '\s+[\w\s]*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)')
+old_format_re2 = re.compile('^(\w+\s+\w+\s+\d{1,2},?\s*\d{4})\s+(.*)'
+ '\s+(<|\()(.*)(\)|>)')
+old_format_re3 = re.compile('^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)\;?',
+ re.IGNORECASE)
+old_format_re4 = re.compile('^([\w.+-]+)(-| )(\S+) Debian (\S+)',
+ re.IGNORECASE)
+old_format_re5 = re.compile('^Changes from version (.*) to (.*):',
+ re.IGNORECASE)
+old_format_re6 = re.compile('^Changes for [\w.+-]+-[\w.+-]+:?\s*$',
+ re.IGNORECASE)
+old_format_re7 = re.compile('^Old Changelog:\s*$', re.IGNORECASE)
+old_format_re8 = re.compile('^(?:\d+:)?\w[\w.+~-]*:?\s*$')
- if state == inblock:
- raise ChangelogParseError("Unexpected EOF")
- #TODO: shouldn't be required should it?
- self._blocks[-1].del_trailing_newline()
-
- def get_version(self):
- """Return a Version object for the last version"""
- return self._blocks[0].version
+class Changelog(object):
+ """Represents a debian/changelog file. You can ask it several things
+ about the file.
+ """
- def set_version(self, version):
- """Set the version of the last changelog block
- version can be a full version string, or a Version object
- """
- self._blocks[0].version = Version(version)
-
- version = property(get_version, set_version,
- doc="Version object for last changelog block""")
-
- ### For convenience, let's expose some of the version properties
- full_version = property(lambda self: self.version.full_version,
- lambda self, v: setattr(self.version, 'full_version', v))
- epoch = property(lambda self: self.version.epoch,
- lambda self, v: setattr(self.version, 'epoch', v))
- debian_version = property(lambda self: self.version.debian_version,
- lambda self, v: setattr(self.version, 'debian_version', v))
- upstream_version = property(lambda self: self.version.upstream_version,
- lambda self, v: setattr(self.version, 'upstream_version', v))
-
- def get_package(self):
- """Returns the name of the package in the last version."""
- return self._blocks[0].package
+ def __init__(self, file=None, max_blocks=None,
+ allow_empty_author=False, strict=True):
+ """Set up the Changelog for use. file is the contects of the
+ changelog.
+ """
+ self._blocks = []
+ self.initial_blank_lines = []
+ if file is not None:
+ try:
+ self.parse_changelog(file, max_blocks=max_blocks,
+ allow_empty_author=allow_empty_author,
+ strict=strict)
+ except ChangelogParseError:
+ pass
+
+ def _parse_error(self, message, strict):
+ if strict:
+ raise ChangelogParseError(message)
+ else:
+ warnings.warn(message)
+
+ def parse_changelog(self, file, max_blocks=None,
+ allow_empty_author=False, strict=True):
+ first_heading = "first heading"
+ next_heading_or_eof = "next heading of EOF"
+ start_of_change_data = "start of change data"
+ more_changes_or_trailer = "more change data or trailer"
+ slurp_to_end = "slurp to end"
+
+ self._blocks = []
+ self.initial_blank_lines = []
+
+ current_block = ChangeBlock()
+ changes = []
+
+ state = first_heading
+ old_state = None
+ if isinstance(file, basestring):
+ if file[-1] != '\n':
+ file += '\n'
+ file = file.split('\n')[:-1]
+ for line in file:
+ if state == first_heading or state == next_heading_or_eof:
+ top_match = topline.match(line)
+ blank_match = blankline.match(line)
+ if top_match is not None:
+ if (max_blocks is not None
+ and len(self._blocks) >= max_blocks):
+ return
+ current_block.package = top_match.group(1)
+ current_block._raw_version = top_match.group(2)
+ current_block.distributions = top_match.group(3).lstrip()
+
+ pairs = line.split(";", 1)[1]
+ all_keys = {}
+ other_pairs = {}
+ for pair in pairs.split(','):
+ pair = pair.strip()
+ kv_match = keyvalue.match(pair)
+ if kv_match is None:
+ self._parse_error("Invalid key-value "
+ "pair after ';': %s" % pair, strict)
+ continue
+ key = kv_match.group(1)
+ value = kv_match.group(2)
+ if key.lower() in all_keys:
+ self._parse_error("Repeated key-value: "
+ "%s" % key.lower(), strict)
+ all_keys[key.lower()] = value
+ if key.lower() == "urgency":
+ val_match = value_re.match(value)
+ if val_match is None:
+ self._parse_error("Badly formatted "
+ "urgency value: %s" % value, strict)
+ else:
+ current_block.urgency = val_match.group(1)
+ comment = val_match.group(2)
+ if comment is not None:
+ current_block.urgency_comment = comment
+ else:
+ other_pairs[key] = value
+ current_block.other_pairs = other_pairs
+ state = start_of_change_data
+ elif blank_match is not None:
+ if state == first_heading:
+ self.initial_blank_lines.append(line)
+ else:
+ self._blocks[-1].add_trailing_line(line)
+ else:
+ emacs_match = emacs_variables.match(line)
+ vim_match = vim_variables.match(line)
+ cvs_match = cvs_keyword.match(line)
+ comments_match = comments.match(line)
+ more_comments_match = more_comments.match(line)
+ if ((emacs_match is not None or vim_match is not None)
+ and state != first_heading):
+ self._blocks[-1].add_trailing_line(line)
+ old_state = state
+ state = slurp_to_end
+ continue
+ if (cvs_match is not None or comments_match is not None
+ or more_comments_match is not None):
+ if state == first_heading:
+ self.initial_blank_lines.append(line)
+ else:
+ self._blocks[-1].add_trailing_line(line)
+ continue
+ if ((old_format_re1.match(line) is not None
+ or old_format_re2.match(line) is not None
+ or old_format_re3.match(line) is not None
+ or old_format_re4.match(line) is not None
+ or old_format_re5.match(line) is not None
+ or old_format_re6.match(line) is not None
+ or old_format_re7.match(line) is not None
+ or old_format_re8.match(line) is not None)
+ and state != first_heading):
+ self._blocks[-1].add_trailing_line(line)
+ old_state = state
+ state = slurp_to_end
+ continue
+ self._parse_error("Unexpected line while looking "
+ "for %s: %s" % (state, line), strict)
+ if state == first_heading:
+ self.initial_blank_lines.append(line)
+ else:
+ self._blocks[-1].add_trailing_line(line)
+ elif (state == start_of_change_data
+ or state == more_changes_or_trailer):
+ change_match = change.match(line)
+ end_match = endline.match(line)
+ end_no_details_match = endline_nodetails.match(line)
+ blank_match = blankline.match(line)
+ if change_match is not None:
+ changes.append(line)
+ state = more_changes_or_trailer
+ elif end_match is not None:
+ if end_match.group(3) != ' ':
+ self._parse_error("Badly formatted trailer "
+ "line: %s" % line, strict)
+ current_block._trailer_separator = end_match.group(3)
+ current_block.author = "%s <%s>" \
+ % (end_match.group(1), end_match.group(2))
+ current_block.date = end_match.group(4)
+ current_block._changes = changes
+ self._blocks.append(current_block)
+ changes = []
+ current_block = ChangeBlock()
+ state = next_heading_or_eof
+ elif end_no_details_match is not None:
+ if not allow_empty_author:
+ self._parse_error("Badly formatted trailer "
+ "line: %s" % line, strict)
+ continue
+ current_block._changes = changes
+ self._blocks.append(current_block)
+ changes = []
+ current_block = ChangeBlock()
+ state = next_heading_or_eof
+ elif blank_match is not None:
+ changes.append(line)
+ else:
+ cvs_match = cvs_keyword.match(line)
+ comments_match = comments.match(line)
+ more_comments_match = more_comments.match(line)
+ if (cvs_match is not None or comments_match is not None
+ or more_comments_match is not None):
+ changes.append(line)
+ continue
+ self._parse_error("Unexpected line while looking "
+ "for %s: %s" % (state, line), strict)
+ changes.append(line)
+ elif state == slurp_to_end:
+ if old_state == next_heading_or_eof:
+ self._blocks[-1].add_trailing_line(line)
+ else:
+ changes.append(line)
+ else:
+ assert False, "Unknown state: %s" % state
+
+ if ((state != next_heading_or_eof and state != slurp_to_end)
+ or (state == slurp_to_end and old_state != next_heading_or_eof)):
+ self._parse_error("Found eof where expected %s" % state,
+ strict)
+ current_block._changes = changes
+ current_block._no_trailer = True
+ self._blocks.append(current_block)
+
+ def get_version(self):
+ """Return a Version object for the last version"""
+ return self._blocks[0].version
+
+ def set_version(self, version):
+ """Set the version of the last changelog block
+
+ version can be a full version string, or a Version object
+ """
+ self._blocks[0].version = Version(version)
+
+ version = property(get_version, set_version,
+ doc="Version object for last changelog block""")
+
+ ### For convenience, let's expose some of the version properties
+ full_version = property(lambda self: self.version.full_version)
+ epoch = property(lambda self: self.version.epoch)
+ debian_version = property(lambda self: self.version.debian_version)
+ upstream_version = property(lambda self: self.version.upstream_version)
+
+ def get_package(self):
+ """Returns the name of the package in the last version."""
+ return self._blocks[0].package
- def set_package(self, package):
- self._blocks[0].package = package
+ def set_package(self, package):
+ self._blocks[0].package = package
- package = property(get_package, set_package,
+ package = property(get_package, set_package,
doc="Name of the package in the last version")
- def get_versions(self):
- """Returns a list of version objects that the package went through."""
- return [block.version for block in self._blocks]
+ def get_versions(self):
+ """Returns a list of version objects that the package went through."""
+ return [block.version for block in self._blocks]
- versions = property(get_versions,
+ versions = property(get_versions,
doc="List of version objects the package went through")
- def __str__(self):
- cl = ""
- for block in self._blocks:
- cl += str(block)
- return cl
+ def _raw_versions(self):
+ return [block._raw_version for block in self._blocks]
+
+ def __str__(self):
+ cl = "\n".join(self.initial_blank_lines)
+ for block in self._blocks:
+ cl += str(block)
+ return cl
- def set_distributions(self, distributions):
- self._blocks[0].distributions = distributions
- distributions = property(lambda self: self._blocks[0].distributions,
+ def set_distributions(self, distributions):
+ self._blocks[0].distributions = distributions
+ distributions = property(lambda self: self._blocks[0].distributions,
set_distributions)
- def set_urgency(self, urgency):
- self._blocks[0].urgency = urgency
- urgency = property(lambda self: self._blocks[0].urgency, set_urgency)
+ def set_urgency(self, urgency):
+ self._blocks[0].urgency = urgency
+ urgency = property(lambda self: self._blocks[0].urgency, set_urgency)
- def add_change(self, change):
- self._blocks[0].add_change(change)
+ def add_change(self, change):
+ self._blocks[0].add_change(change)
- def set_author(self, author):
- self._blocks[0].author = author
- author = property(lambda self: self._blocks[0].author, set_author)
+ def set_author(self, author):
+ self._blocks[0].author = author
+ author = property(lambda self: self._blocks[0].author, set_author)
- def set_date(self, date):
- self._blocks[0].date = date
- date = property(lambda self: self._blocks[0].date, set_date)
+ def set_date(self, date):
+ self._blocks[0].date = date
+ date = property(lambda self: self._blocks[0].date, set_date)
- def new_block(self, package=None, version=None, distributions=None,
- urgency=None, changes=None, author=None, date=None):
- block = ChangeBlock(package, version, distributions, urgency,
- changes, author, date)
- block.add_trailing_newline()
- self._blocks.insert(0, block)
+ def new_block(self, **kwargs):
+ block = ChangeBlock(**kwargs)
+ block.add_trailing_line('')
+ self._blocks.insert(0, block)
- def write_to_open_file(self, file):
- file.write(self.__str__())
+ def write_to_open_file(self, file):
+ file.write(self.__str__())
def _test():
- import doctest
- doctest.testmod()
+ import doctest
+ doctest.testmod()
- unittest.main()
+ unittest.main()
class ChangelogTests(unittest.TestCase):
- def test_create_changelog(self):
-
- c = open('test_changelog').read()
- cl = Changelog(c)
- cs = str(cl)
- clines = c.split('\n')
- cslines = cs.split('\n')
- for i in range(len(clines)):
- self.assertEqual(clines[i], cslines[i])
- self.assertEqual(len(clines), len(cslines), "Different lengths")
-
- def test_create_changelog_single_block(self):
-
- c = open('test_changelog').read()
- cl = Changelog(c, max_blocks=1)
- cs = str(cl)
- self.assertEqual(cs,
- """gnutls13 (1:1.4.1-1) unstable; urgency=HIGH
+ def test_create_changelog(self):
+ c = open('test_changelog').read()
+ cl = Changelog(c)
+ cs = str(cl)
+ clines = c.split('\n')
+ cslines = cs.split('\n')
+ for i in range(len(clines)):
+ self.assertEqual(clines[i], cslines[i])
+ self.assertEqual(len(clines), len(cslines), "Different lengths")
+
+ def test_create_changelog_single_block(self):
+ c = open('test_changelog').read()
+ cl = Changelog(c, max_blocks=1)
+ cs = str(cl)
+ self.assertEqual(cs,
+ """gnutls13 (1:1.4.1-1) unstable; urgency=HIGH
[ James Westby ]
* New upstream release.
@@ -385,159 +549,148 @@ class ChangelogTests(unittest.TestCase):
gnutls-api so that devhelp can find it.
-- Andreas Metzler <ametzler at debian.org> Sat, 15 Jul 2006 11:11:08 +0200
+
""")
- def test_modify_changelog(self):
-
- c = open('test_modify_changelog1').read()
- cl = Changelog(c)
- cl.package = 'gnutls14'
- cl.version = '1:1.4.1-2'
- cl.distributions = 'experimental'
- cl.urgency = 'medium'
- cl.add_change(' * Add magic foo')
- cl.author = 'James Westby <jw+debian at jameswestby.net>'
- cl.date = 'Sat, 16 Jul 2008 11:11:08 -0200'
- c = open('test_modify_changelog2').read()
- clines = c.split('\n')
- cslines = str(cl).split('\n')
- for i in range(len(clines)):
- self.assertEqual(clines[i], cslines[i])
- self.assertEqual(len(clines), len(cslines), "Different lengths")
-
- def test_add_changelog_section(self):
- c = open('test_modify_changelog2').read()
- cl = Changelog(c)
- cl.new_block('gnutls14', Version('1:1.4.1-3'), 'experimental', 'low',
- None, 'James Westby <jw+debian at jameswestby.net>')
-
- self.assertRaises(ChangelogCreateError, cl.__str__)
-
- cl.set_date('Sat, 16 Jul 2008 11:11:08 +0200')
- cl.add_change('')
- cl.add_change(' * Foo did not work, let us try bar')
- cl.add_change('')
-
- c = open('test_modify_changelog3').read()
- clines = c.split('\n')
- cslines = str(cl).split('\n')
- for i in range(len(clines)):
- self.assertEqual(clines[i], cslines[i])
- self.assertEqual(len(clines), len(cslines), "Different lengths")
-
- def test_strange_changelogs(self):
- """ Just opens and parses a strange changelog """
- c = open('test_strange_changelog').read()
- cl = Changelog(c)
-
- def test_set_version_with_string(self):
- c1 = Changelog(open('test_modify_changelog1').read())
- c2 = Changelog(open('test_modify_changelog1').read())
-
- c1.version = '1:2.3.5-2'
- c2.version = Version('1:2.3.5-2')
- self.assertEqual(c1.version, c2.version)
- self.assertEqual((c1.full_version, c1.epoch, c1.upstream_version,
- c1.debian_version),
- (c2.full_version, c2.epoch, c2.upstream_version,
- c2.debian_version))
-
- def test_changelog_no_author(self):
- cl_no_author = """gnutls13 (1:1.4.1-1) unstable; urgency=low
+ def test_modify_changelog(self):
+ c = open('test_modify_changelog1').read()
+ cl = Changelog(c)
+ cl.package = 'gnutls14'
+ cl.version = '1:1.4.1-2'
+ cl.distributions = 'experimental'
+ cl.urgency = 'medium'
+ cl.add_change(' * Add magic foo')
+ cl.author = 'James Westby <jw+debian at jameswestby.net>'
+ cl.date = 'Sat, 16 Jul 2008 11:11:08 -0200'
+ c = open('test_modify_changelog2').read()
+ clines = c.split('\n')
+ cslines = str(cl).split('\n')
+ for i in range(len(clines)):
+ self.assertEqual(clines[i], cslines[i])
+ self.assertEqual(len(clines), len(cslines), "Different lengths")
+
+ def test_add_changelog_section(self):
+ c = open('test_modify_changelog2').read()
+ cl = Changelog(c)
+ cl.new_block(package='gnutls14',
+ version=Version('1:1.4.1-3'),
+ distributions='experimental',
+ urgency='low',
+ author='James Westby <jw+debian at jameswestby.net>')
+
+ self.assertRaises(ChangelogCreateError, cl.__str__)
+
+ cl.set_date('Sat, 16 Jul 2008 11:11:08 +0200')
+ cl.add_change('')
+ cl.add_change(' * Foo did not work, let us try bar')
+ cl.add_change('')
+
+ c = open('test_modify_changelog3').read()
+ clines = c.split('\n')
+ cslines = str(cl).split('\n')
+ for i in range(len(clines)):
+ self.assertEqual(clines[i], cslines[i])
+ self.assertEqual(len(clines), len(cslines), "Different lengths")
+
+ def test_strange_changelogs(self):
+ """ Just opens and parses a strange changelog """
+ c = open('test_strange_changelog').read()
+ cl = Changelog(c)
+
+ def test_set_version_with_string(self):
+ c1 = Changelog(open('test_modify_changelog1').read())
+ c2 = Changelog(open('test_modify_changelog1').read())
+
+ c1.version = '1:2.3.5-2'
+ c2.version = Version('1:2.3.5-2')
+ self.assertEqual(c1.version, c2.version)
+ self.assertEqual((c1.full_version, c1.epoch, c1.upstream_version,
+ c1.debian_version),
+ (c2.full_version, c2.epoch, c2.upstream_version,
+ c2.debian_version))
+
+ def test_changelog_no_author(self):
+ cl_no_author = """gnutls13 (1:1.4.1-1) unstable; urgency=low
* New upstream release.
--
"""
- c1 = Changelog()
- c1.parse_changelog(cl_no_author, allow_empty_author=True)
- self.assertEqual(c1.author, None)
- self.assertEqual(c1.date, None)
- self.assertEqual(c1.package, "gnutls13")
- c2 = Changelog()
- self.assertRaises(ChangelogParseError, c2.parse_changelog, cl_no_author)
-
- def test_magic_version_properties(self):
- c = Changelog(open('test_changelog').read())
-
- c.debian_version = '2'
- self.assertEqual(c.debian_version, '2')
- self.assertEqual(c.full_version, '1:1.4.1-2')
-
- c.upstream_version = '1.4.2'
- self.assertEqual(c.upstream_version, '1.4.2')
- self.assertEqual(c.full_version, '1:1.4.2-2')
-
- c.epoch = '2'
- self.assertEqual(c.epoch, '2')
- self.assertEqual(c.full_version, '2:1.4.2-2')
-
- self.assertEqual(str(c.version), c.full_version)
-
- c.full_version = '1:1.4.1-1'
- self.assertEqual(c.full_version, '1:1.4.1-1')
- self.assertEqual(c.epoch, '1')
- self.assertEqual(c.upstream_version, '1.4.1')
- self.assertEqual(c.debian_version, '1')
+ c1 = Changelog()
+ c1.parse_changelog(cl_no_author, allow_empty_author=True)
+ self.assertEqual(c1.author, None)
+ self.assertEqual(c1.date, None)
+ self.assertEqual(c1.package, "gnutls13")
+ c2 = Changelog()
+ self.assertRaises(ChangelogParseError, c2.parse_changelog, cl_no_author)
+
+ def test_magic_version_properties(self):
+ c = Changelog(open('test_changelog'))
+ self.assertEqual(c.debian_version, '1')
+ self.assertEqual(c.full_version, '1:1.4.1-1')
+ self.assertEqual(c.upstream_version, '1.4.1')
+ self.assertEqual(c.epoch, '1')
+ self.assertEqual(str(c.version), c.full_version)
class VersionTests(unittest.TestCase):
- def _test_version(self, full_version, epoch, upstream, debian):
- v = Version(full_version)
- self.assertEqual(v.full_version, full_version, "Full version broken")
- self.assertEqual(v.epoch, epoch, "Epoch broken")
- self.assertEqual(v.upstream_version, upstream, "Upstram broken")
- self.assertEqual(v.debian_version, debian, "Debian broken")
-
- def testversions(self):
- self._test_version('1:1.4.1-1', '1', '1.4.1', '1')
- self._test_version('7.1.ds-1', None, '7.1.ds', '1')
- self._test_version('10.11.1.3-2', None, '10.11.1.3', '2')
- self._test_version('4.0.1.3.dfsg.1-2', None, '4.0.1.3.dfsg.1', '2')
- self._test_version('0.4.23debian1', None, '0.4.23debian1', None)
- self._test_version('1.2.10+cvs20060429-1', None, '1.2.10+cvs20060429', '1')
- self._test_version('0.2.0-1+b1', None, '0.2.0', '1+b1')
- self._test_version('4.3.90.1svn-r21976-1', None, '4.3.90.1svn-r21976', '1')
- self._test_version('1.5+E-14', None, '1.5+E', '14')
- self._test_version('20060611-0.0', None, '20060611', '0.0')
- self._test_version('0.52.2-5.1', None, '0.52.2', '5.1')
- self._test_version('7.0-035+1', None, '7.0', '035+1')
- self._test_version('1.1.0+cvs20060620-1+2.6.15-8', None,
- '1.1.0+cvs20060620-1+2.6.15', '8')
- self._test_version('1.1.0+cvs20060620-1+1.0', None, '1.1.0+cvs20060620',
- '1+1.0')
- self._test_version('4.2.0a+stable-2sarge1', None, '4.2.0a+stable',
- '2sarge1')
- self._test_version('1.8RC4b', None, '1.8RC4b', None)
- self._test_version('0.9~rc1-1', None, '0.9~rc1', '1')
- self._test_version('2:1.0.4+svn26-1ubuntu1', '2', '1.0.4+svn26',
- '1ubuntu1')
- self._test_version('2:1.0.4~rc2-1', '2', '1.0.4~rc2', '1')
-
- def test_version_updating(self):
- v = Version('1:1.4.1-1')
-
- v.debian_version = '2'
- self.assertEqual(v.debian_version, '2')
- self.assertEqual(v.full_version, '1:1.4.1-2')
-
- v.upstream_version = '1.4.2'
- self.assertEqual(v.upstream_version, '1.4.2')
- self.assertEqual(v.full_version, '1:1.4.2-2')
-
- v.epoch = '2'
- self.assertEqual(v.epoch, '2')
- self.assertEqual(v.full_version, '2:1.4.2-2')
-
- self.assertEqual(str(v), v.full_version)
-
- v.full_version = '1:1.4.1-1'
- self.assertEqual(v.full_version, '1:1.4.1-1')
- self.assertEqual(v.epoch, '1')
- self.assertEqual(v.upstream_version, '1.4.1')
- self.assertEqual(v.debian_version, '1')
+ def _test_version(self, full_version, epoch, upstream, debian):
+ v = Version(full_version)
+ self.assertEqual(v.full_version, full_version, "Full version broken")
+ self.assertEqual(v.epoch, epoch, "Epoch broken")
+ self.assertEqual(v.upstream_version, upstream, "Upstram broken")
+ self.assertEqual(v.debian_version, debian, "Debian broken")
+
+ def testversions(self):
+ self._test_version('1:1.4.1-1', '1', '1.4.1', '1')
+ self._test_version('7.1.ds-1', None, '7.1.ds', '1')
+ self._test_version('10.11.1.3-2', None, '10.11.1.3', '2')
+ self._test_version('4.0.1.3.dfsg.1-2', None, '4.0.1.3.dfsg.1', '2')
+ self._test_version('0.4.23debian1', None, '0.4.23debian1', None)
+ self._test_version('1.2.10+cvs20060429-1', None,
+ '1.2.10+cvs20060429', '1')
+ self._test_version('0.2.0-1+b1', None, '0.2.0', '1+b1')
+ self._test_version('4.3.90.1svn-r21976-1', None,
+ '4.3.90.1svn-r21976', '1')
+ self._test_version('1.5+E-14', None, '1.5+E', '14')
+ self._test_version('20060611-0.0', None, '20060611', '0.0')
+ self._test_version('0.52.2-5.1', None, '0.52.2', '5.1')
+ self._test_version('7.0-035+1', None, '7.0', '035+1')
+ self._test_version('1.1.0+cvs20060620-1+2.6.15-8', None,
+ '1.1.0+cvs20060620-1+2.6.15', '8')
+ self._test_version('1.1.0+cvs20060620-1+1.0', None,
+ '1.1.0+cvs20060620', '1+1.0')
+ self._test_version('4.2.0a+stable-2sarge1', None, '4.2.0a+stable',
+ '2sarge1')
+ self._test_version('1.8RC4b', None, '1.8RC4b', None)
+ self._test_version('0.9~rc1-1', None, '0.9~rc1', '1')
+ self._test_version('2:1.0.4+svn26-1ubuntu1', '2', '1.0.4+svn26',
+ '1ubuntu1')
+ self._test_version('2:1.0.4~rc2-1', '2', '1.0.4~rc2', '1')
+
+ def test_version_updating(self):
+ v = Version('1:1.4.1-1')
+
+ v.debian_version = '2'
+ self.assertEqual(v.debian_version, '2')
+ self.assertEqual(v.full_version, '1:1.4.1-2')
+
+ v.upstream_version = '1.4.2'
+ self.assertEqual(v.upstream_version, '1.4.2')
+ self.assertEqual(v.full_version, '1:1.4.2-2')
+
+ v.epoch = '2'
+ self.assertEqual(v.epoch, '2')
+ self.assertEqual(v.full_version, '2:1.4.2-2')
+
+ self.assertEqual(str(v), v.full_version)
+
+ v.full_version = '1:1.4.1-1'
+ self.assertEqual(v.full_version, '1:1.4.1-1')
+ self.assertEqual(v.epoch, '1')
+ self.assertEqual(v.upstream_version, '1.4.1')
+ self.assertEqual(v.debian_version, '1')
if __name__ == "__main__":
- _test()
+ _test()
-# vim:softtabstop=2 shiftwidth=2 expandtab
--
1.5.5.GIT
More information about the pkg-python-debian-commits
mailing list