~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to tools/generate_release_notes.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-08 08:15:14 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101008081514-dviqzrdfwyzsqbz2
Split NEWS into per-release doc/en/release-notes/bzr-*.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
 
import os
 
19
"""Generate doc/en/release-notes/index.txt from the per-series NEWS files.
 
20
 
 
21
NEWS files are kept in doc/en/release-notes/, one file per series, e.g.
 
22
doc/en/release-notes/bzr-2.3.txt
 
23
"""
 
24
 
 
25
# XXX: add test_source test that latest doc/en/release-notes/bzr-*.txt has the
 
26
# NEWS file-id (so that merges of new work will tend to always land new NEWS
 
27
# entries in the latest series).
 
28
 
 
29
 
 
30
import os.path
 
31
import re
20
32
import sys
21
33
from optparse import OptionParser
22
34
 
23
35
 
24
 
def split_into_topics(lines, out_file, out_dir):
25
 
    """Split a large NEWS file into topics, one per release.
26
 
 
27
 
    Releases are detected by matching headings that look like
28
 
    release names. Topics are created with matching names
29
 
    replacing spaces with dashes.
 
36
preamble = """\
 
37
####################
 
38
Bazaar Release Notes
 
39
####################
 
40
 
 
41
 
 
42
.. toctree::
 
43
   :maxdepth: 1
 
44
 
 
45
"""
 
46
 
 
47
 
 
48
def natural_sort_key(file_name):
 
49
    """Split 'aaa-N.MMbbb' into ('aaa-', N, '.' MM, 'bbb')
 
50
    
 
51
    e.g. 1.10b1 will sort as greater than 1.2::
 
52
 
 
53
        >>> natural_sort_key('bzr-1.10b1.txt') > natural_sort_key('bzr-1.2.txt')
 
54
        True
30
55
    """
31
 
    topic_file = None
32
 
    for index, line in enumerate(lines):
33
 
        maybe_new_topic = line[:4] in ('bzr ', 'bzr-',)
34
 
        if maybe_new_topic and lines[index + 1].startswith('####'):
35
 
            release = line.strip()
36
 
            if topic_file is None:
37
 
                # First topic found
38
 
                out_file.write(".. toctree::\n   :maxdepth: 1\n\n")
39
 
            else:
40
 
                # close the current topic
41
 
                topic_file.close()
42
 
            topic_file = open_topic_file(out_file, out_dir, release)
43
 
        elif topic_file:
44
 
            topic_file.write(line)
45
 
        else:
46
 
            # FIXME: the 'content' directive is used for rst2html (and
47
 
            # conflicts with the 'toctree' we insert), we should get rid of
48
 
            # that once we fully switch to sphinx -- vila 20100505
49
 
            if (line.startswith('.. contents::')
50
 
                or line.startswith('   :depth:')):
51
 
                    continue
52
 
            # Still in the header - dump content straight to output
53
 
            out_file.write(line)
54
 
    if topic_file is not None:
55
 
        # Close the last topic_file opened
56
 
        topic_file.close()
57
 
 
58
 
 
59
 
def open_topic_file(out_file, out_dir, release):
60
 
    topic_name = release.replace(' ', '-')
61
 
    out_file.write("   %s <%s>\n" % (release, topic_name,))
62
 
    topic_path = os.path.join(out_dir, "%s.txt" % (topic_name,))
63
 
    result = open(topic_path, 'w')
64
 
    result.write("%s\n" % (release,))
65
 
    return result
 
56
    parts = re.findall(r'(?:[0-9]+|[^0-9]+)', file_name)
 
57
    result = []
 
58
    for part in parts:
 
59
        if re.match('^[0-9]+$', part) is not None:
 
60
            part = int(part)
 
61
        result.append(part)
 
62
    return tuple(result)
66
63
 
67
64
 
68
65
def main(argv):
69
66
    # Check usage
70
 
    parser = OptionParser(usage="%prog SOURCE DESTINATION")
 
67
    parser = OptionParser(usage="%prog OUTPUT_FILE NEWS_FILE [NEWS_FILE ...]")
71
68
    (options, args) = parser.parse_args(argv)
72
 
    if len(args) != 2:
 
69
    if len(args) < 2:
73
70
        parser.print_help()
74
71
        sys.exit(1)
75
72
 
76
73
    # Open the files and do the work
77
 
    infile_name = args[0]
78
 
    outfile_name = args[1]
79
 
    outdir = os.path.dirname(outfile_name)
80
 
    infile = open(infile_name, 'r')
81
 
    try:
82
 
        lines = infile.readlines()
83
 
    finally:
84
 
        infile.close()
85
 
    outfile = open(outfile_name, 'w')
86
 
    try:
87
 
        split_into_topics(lines, outfile, outdir)
88
 
    finally:
89
 
        outfile.close()
 
74
    out_file_name = args[0]
 
75
    news_file_names = map(os.path.basename, args[1:])
 
76
    news_file_names = sorted(news_file_names, key=natural_sort_key,
 
77
        reverse=True)
 
78
 
 
79
    out_file = open(out_file_name, 'w')
 
80
    try:
 
81
        out_file.write(preamble)
 
82
        for news_file_name in news_file_names:
 
83
            if not news_file_name.endswith('.txt'):
 
84
                raise AssertionError(
 
85
                    'NEWS file %s does not have .txt extension.'
 
86
                    % (news_file_name,))
 
87
            doc_name = news_file_name[:-4]
 
88
            link_text = doc_name.replace('-', ' ')
 
89
            out_file.write('   %s <%s>\n' % (link_text, doc_name))
 
90
    finally:
 
91
        out_file.close()
90
92
 
91
93
 
92
94
if __name__ == '__main__':