diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2018-08-24 22:24:07 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2018-08-24 22:32:47 +0300 |
commit | 5b49acb829c3fd077f7946ef8a3ba6ee3d6de888 (patch) | |
tree | 578ad2dd0be9b0aa952ba43a02e0ad1d25359cd6 | |
parent | b14aa091903560d2f156e6f3549d373c65fe3b2d (diff) | |
download | releaselogparser-5b49acb829c3fd077f7946ef8a3ba6ee3d6de888.tar.gz releaselogparser-5b49acb829c3fd077f7946ef8a3ba6ee3d6de888.tar.bz2 |
Implement Python-style release log format
* releaselog.py (main): Explicitly add newline characters.
* releaselog/__init__.py (end_of_entry_rx): New attribute.
(parse_header): Return a triple: (date, version, line), where
line is first line of the description or None.
(is_end_of_entry): New method.
(__init__): Strip trailing newline from the description lines.
* releaselog/format/python.py: New file.
-rw-r--r-- | releaselog.py | 2 | ||||
-rw-r--r-- | releaselog/__init__.py | 32 | ||||
-rw-r--r-- | releaselog/format/python.py | 54 |
3 files changed, 81 insertions, 7 deletions
diff --git a/releaselog.py b/releaselog.py index 3109787..c8eeff3 100644 --- a/releaselog.py +++ b/releaselog.py | |||
@@ -49,7 +49,7 @@ def main(): | |||
49 | count=options.count) | 49 | count=options.count) |
50 | for r in cl: | 50 | for r in cl: |
51 | print(r) | 51 | print(r) |
52 | print(''.join(r.descr)) | 52 | print('\n'.join(r.descr)) |
53 | 53 | ||
54 | if __name__ == '__main__': | 54 | if __name__ == '__main__': |
55 | main() | 55 | main() |
diff --git a/releaselog/__init__.py b/releaselog/__init__.py index dfee183..7cb9c7d 100644 --- a/releaselog/__init__.py +++ b/releaselog/__init__.py | |||
@@ -44,9 +44,13 @@ class ReleaseHistory(object): | |||
44 | history - a list of Release objects | 44 | history - a list of Release objects |
45 | header - a compiled regular expression that returns a match for | 45 | header - a compiled regular expression that returns a match for |
46 | history entry heading lines | 46 | history entry heading lines |
47 | end_of_entry_rx - a compiled regular expression returning a match for | ||
48 | end of entry. Can be None | ||
47 | """ | 49 | """ |
48 | 50 | ||
49 | history = [] | 51 | history = [] |
52 | |||
53 | end_of_entry_rx = re.compile('^(\f|^\s*=+\s*$)') | ||
50 | 54 | ||
51 | def __len__(self): | 55 | def __len__(self): |
52 | return len(self.history) | 56 | return len(self.history) |
@@ -62,16 +66,30 @@ class ReleaseHistory(object): | |||
62 | raise TypeError() | 66 | raise TypeError() |
63 | 67 | ||
64 | def parse_header(self, line): | 68 | def parse_header(self, line): |
65 | """Matches line against the history header regexp. On match, returns | 69 | """Matche input line against the history header regexp. On match, |
66 | a tuple (date, version). On failure, returns (None, None). | 70 | return a tuple (date, version, startdescr), where date is the |
71 | release date (datetime), version is the release version number, and | ||
72 | startdescr is the first line of the description or None. | ||
73 | On failure, return (None, None, None). | ||
67 | """ | 74 | """ |
68 | date = None | 75 | date = None |
69 | version = None | 76 | version = None |
77 | rest = None | ||
70 | m = self.header.match(line) | 78 | m = self.header.match(line) |
71 | if m: | 79 | if m: |
72 | version = m.group('version') | 80 | version = m.group('version') |
73 | date = dateparser.parse(m.group('date')) | 81 | date = dateparser.parse(m.group('date')) |
74 | return date, version | 82 | try: |
83 | rest = m.group('rest') | ||
84 | if len(rest) == 0: | ||
85 | rest = None | ||
86 | except IndexError: | ||
87 | pass | ||
88 | return date, version, rest | ||
89 | |||
90 | def is_end_of_entry(self, line): | ||
91 | return (self.end_of_entry_rx.match(line) | ||
92 | if self.end_of_entry_rx else False) | ||
75 | 93 | ||
76 | def __init__(self, lines, **kwargs): | 94 | def __init__(self, lines, **kwargs): |
77 | """Create a new history object from the list of lines. The list is | 95 | """Create a new history object from the list of lines. The list is |
@@ -134,7 +152,7 @@ class ReleaseHistory(object): | |||
134 | 152 | ||
135 | i = 0 | 153 | i = 0 |
136 | for line in lines: | 154 | for line in lines: |
137 | (d, v) = self.parse_header(line) | 155 | (d, v, r) = self.parse_header(line) |
138 | if d: | 156 | if d: |
139 | if date: | 157 | if date: |
140 | self.append(Release(version, date, descr)) | 158 | self.append(Release(version, date, descr)) |
@@ -151,14 +169,16 @@ class ReleaseHistory(object): | |||
151 | date = d | 169 | date = d |
152 | version = v | 170 | version = v |
153 | descr = [] | 171 | descr = [] |
154 | elif re.match('^(\f|^\s*=+\s*$)', line): | 172 | if r: |
173 | descr.append(r) | ||
174 | elif self.is_end_of_entry(line): | ||
155 | if date: | 175 | if date: |
156 | self.append(Release(version, date, descr)) | 176 | self.append(Release(version, date, descr)) |
157 | date = None | 177 | date = None |
158 | version = None | 178 | version = None |
159 | descr = [] | 179 | descr = [] |
160 | elif date: | 180 | elif date: |
161 | descr.append(line) | 181 | descr.append(line.rstrip("\n")) |
162 | if date: | 182 | if date: |
163 | self.append(Release(version, date, descr)) | 183 | self.append(Release(version, date, descr)) |
164 | 184 | ||
diff --git a/releaselog/format/python.py b/releaselog/format/python.py new file mode 100644 index 0000000..7c2a218 --- /dev/null +++ b/releaselog/format/python.py | |||
@@ -0,0 +1,54 @@ | |||
1 | # -*- coding: utf-8 -*- | ||
2 | """ | ||
3 | Implementation of two release log formats used in most Python packages. | ||
4 | |||
5 | The two formats are: | ||
6 | |||
7 | * Each entry begins with the line | ||
8 | |||
9 | v<Version>, <Date> -- <String> | ||
10 | |||
11 | where <String> is the beginning of the description. More description lines | ||
12 | may follow. | ||
13 | |||
14 | * Each entry begins with the line | ||
15 | |||
16 | <Version> (<Date>) | ||
17 | |||
18 | followed by the description text. This format is used, among others, by | ||
19 | GNU Texinfo. | ||
20 | |||
21 | The PythonLogFormat class discovers the actual format by finding the first | ||
22 | input line that matches any of the above patterns. | ||
23 | |||
24 | """ | ||
25 | |||
26 | import re | ||
27 | from releaselog import ReleaseHistory | ||
28 | |||
29 | class PythonLogFormat(ReleaseHistory): | ||
30 | format = ['Python', 'python'] | ||
31 | header = None | ||
32 | header_rx = [ | ||
33 | re.compile("""^[vV](?P<version>\d[\d.]*)\s* | ||
34 | ,\s* | ||
35 | (?P<date>.*?) | ||
36 | \s+-+\s* | ||
37 | (?P<rest>.*)$ | ||
38 | """, re.X), | ||
39 | re.compile("""^(?P<version>\d[\d.]*) | ||
40 | \s* | ||
41 | (?P<date>.*) | ||
42 | """, re.X) | ||
43 | ] | ||
44 | |||
45 | def parse_header(self, line): | ||
46 | if self.header: | ||
47 | return super(PythonLogFormat, self).parse_header(line) | ||
48 | else: | ||
49 | for rx in self.header_rx: | ||
50 | if rx.match(line): | ||
51 | self.header = rx | ||
52 | return super(PythonLogFormat, self).parse_header(line) | ||
53 | return (None, None, None) | ||
54 | |||