diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2018-08-23 15:18:15 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2018-08-23 15:18:15 +0300 |
commit | 1a9586f1498f89f48467bc4f58a75bd40cbce3bf (patch) | |
tree | dad19efbfad7b2b1a13fa64d4aca4197e40bc7b2 | |
parent | 11688954d83f7bcd6952aff81f2b3203eaef4374 (diff) | |
download | releaselogparser-1a9586f1498f89f48467bc4f58a75bd40cbce3bf.tar.gz releaselogparser-1a9586f1498f89f48467bc4f58a75bd40cbce3bf.tar.bz2 |
Document everything. Rename ReleaseLogBase to ReleaseHistory
-rw-r--r-- | releaselog.py | 33 | ||||
-rw-r--r-- | releaselog/__init__.py | 122 | ||||
-rw-r--r-- | releaselog/cpan.py | 15 | ||||
-rw-r--r-- | releaselog/gnu.py | 16 |
4 files changed, 154 insertions, 32 deletions
diff --git a/releaselog.py b/releaselog.py index a5a7c38..3109787 100644 --- a/releaselog.py +++ b/releaselog.py @@ -5,7 +5,14 @@ from __future__ import unicode_literals import sys from optparse import OptionParser -from releaselog import ReleaseLog +from releaselog.input import ReleaseLogFile, ReleaseLogURL + +# Set utf-8 as the default encoding for Python 2.7. +try: + reload(sys) + sys.setdefaultencoding('utf-8') +except: + pass def main(): usage = '%prog [OPTIONS] ARG' @@ -27,22 +34,22 @@ def main(): parser.add_option('-n', '--count', action='store', type='int', dest='count', help='read at most that much entries') + parser.add_option('-u', '--url', + action="store_true", dest='url', + help='treat ARG as URL') (options, args) = parser.parse_args() if len(args) != 1: parser.error("bad number of arguments") - - with open(args[0], 'r') as infile: - cl = ReleaseLog(options.logtype, infile, - start=options.start, - stop=options.stop, - count=options.count) - for r in cl: - print(r) - try: - print(''.join(r.descr)) - except: - pass + + release_log = ReleaseLogURL if options.url else ReleaseLogFile + cl = release_log(options.logtype, args[0], + start=options.start, + stop=options.stop, + count=options.count) + for r in cl: + print(r) + print(''.join(r.descr)) if __name__ == '__main__': main() diff --git a/releaselog/__init__.py b/releaselog/__init__.py index 0eeb4c7..be3f688 100644 --- a/releaselog/__init__.py +++ b/releaselog/__init__.py @@ -1,3 +1,14 @@ +""" +ReleaseLog class + +Most software packages keep a history of last releases in some form. This +module provides an abstraction for handling such information. + +Release history is represented by a ReleaseLog class. Each log contains a +list of history entries in reverse chronological order. + +""" + from __future__ import print_function from __future__ import unicode_literals @@ -5,6 +16,15 @@ import re import dateparser class Release(object): + """Release - a single release history entry object + + Attributes: + + date -- datetime of the release + version -- release version number + descr -- textual description of the release + + """ date = None version = None descr = None @@ -16,19 +36,36 @@ class Release(object): return "Version %s, released at %s" % (self.version, self.date) -class ReleaseLogBase(object): - releases = [] +class ReleaseHistory(object): + """ReleaseHistory - base class for ReleaseLog implementations + + Attributes: + + history - a list of Release objects + header - a compiled regular expression that returns a match for + history entry heading lines + + """ + + history = [] def __len__(self): - return len(self.releases) + return len(self.history) + + def __getitem__(self, i): + return self.history[i] def append(self, arg): + """Appends new release to the end of release history list""" if isinstance(arg, Release): - self.releases.append(arg) + self.history.append(arg) else: raise TypeError() def parse_header(self, line): + """Matches line against the history header regexp. On match, returns + a tuple (date, version). On failure, returns (None, None). + """ date = None version = None m = self.header.match(line) @@ -38,13 +75,38 @@ class ReleaseLogBase(object): return date, version def __init__(self, lines, **kwargs): + """Create a new history object from the list of lines. The list is + split into history entries by lines that match the header compiled + regexp. Matches should contain at least two groups: + + version - part of line containing the release version + date - part of line containing the release date + + The line is matching only if both groups are not None. + + Additionally, a line starting with form-feed character (\f) or + containing a line of contiguous equals signs, optionally surrounded + by whitespace, is considered to terminate the current history entry. + + Keyword arguments: + + start=N + Start from the entry N + stop=N + Stop parsing on Nth entry + count=N + Collect at most N entries + + If all three keywords are given, the actual range of history entries + is computed as + + [start, min(start+count, stop)] + + Entries are numbered from 0. + """ date = None version = None descr = [] - # args: - # from=N - # to=N - # count=N start = None stop = None @@ -102,18 +164,56 @@ class ReleaseLogBase(object): self.append(Release(version, date, descr)) def __iter__(self): - for r in self.releases: + for r in self.history: yield(r) class ReleaseLog(object): + """A release log class. + + It is a fabric returning actual release history implementation, depending + on the first argument to constructor. Typical usage + + cl = ReleaseLog('GNU', lines, count=1) + + """ typedb = {} def __new__(cls, type, *args, **kwargs): + """Object constructor: + + ReleaseLog(type, lines, [start=N], [stop=N], [count=N] + + Arguments: + + type + Type of the history log. E.g. 'GNU' for GNU-style NEWS file, or + 'Changes', for CPAN-style Changes file. + lines + List of history lines. + + Keyword arguments are the same as in ReleaseHistory. + """ + return cls.typedb[type](*args, **kwargs) @classmethod - def deftype(cls, type, constr): - cls.typedb[type] = constr.ReleaseLog + def deftype(cls, type, mod, name='ReleaseLog'): + """Register a new history implementation. Typical usage: + + ReleaseLog.deftype(name, module) + + Arguments: + + name + Name of the implementation. It will subsequently be used as the + type argument to ReleaseLog constructor in order to require this + particular implementation. + module + Name of the module. The module should export the class ReleaseLog. + This requirement can be lifted by giving the class name as the + the third argument. + """ + cls.typedb[type] = mod.__getattribute__(name) # Initialize the ReleaseLog types from releaselog import cpan, gnu diff --git a/releaselog/cpan.py b/releaselog/cpan.py index 95c8b74..a3087bb 100644 --- a/releaselog/cpan.py +++ b/releaselog/cpan.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- -from __future__ import print_function -from __future__ import unicode_literals +""" +Implementation of the CPAN 'Changes' history format. +Usage: + + from releaselog import ReleaseLog, cpan + ReleaseLog.deftype('Changes', cpan) + +This is normally done as a part of initialization of the releaselog module. +""" import re -from releaselog import ReleaseLogBase +from releaselog import ReleaseHistory -class ReleaseLog(ReleaseLogBase): +class ReleaseLog(ReleaseHistory): header = re.compile('^(?P<version>\d[\d.]*)\s+(?P<date>.+?)\s*$') diff --git a/releaselog/gnu.py b/releaselog/gnu.py index ed9e82e..1730d1f 100644 --- a/releaselog/gnu.py +++ b/releaselog/gnu.py @@ -1,11 +1,19 @@ # -*- coding: utf-8 -*- -from __future__ import print_function -from __future__ import unicode_literals +""" +Implementation of the GNU-style 'NEWS' history format. + +Usage: + + from releaselog import ReleaseLog, gnu + ReleaseLog.deftype('NEWS', gnu) + +This is normally done as a part of initialization of the releaselog module. +""" import re -from releaselog import ReleaseLogBase +from releaselog import ReleaseHistory -class ReleaseLog(ReleaseLogBase): +class ReleaseLog(ReleaseHistory): header = re.compile(r"""^(?:\*\s+)? # optional initial section (?:(?i)version)\s+ (?P<version>\d(?:[.,]\d+){1,2} # At least MAJOR.MINOR |