1#!/usr/bin/python3 2 3# Copyright 2009-2010 Canonical Ltd. 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 19"""Generate doc/en/release-notes/index.txt from the per-series NEWS files. 20 21NEWS files are kept in doc/en/release-notes/, one file per series, e.g. 22doc/en/release-notes/brz-2.3.txt 23""" 24 25# XXX: add test_source test that latest doc/en/release-notes/brz-*.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 30import os.path 31import re 32import sys 33from optparse import OptionParser 34 35 36preamble_plain = """\ 37#################### 38Breezy Release Notes 39#################### 40 41 42.. contents:: List of Releases 43 :depth: 2 44 45""" 46 47preamble_sphinx = """\ 48#################### 49Breezy Release Notes 50#################### 51 52 53.. toctree:: 54 :maxdepth: 2 55 56""" 57 58 59def natural_sort_key(file_name): 60 """Split 'aaa-N.MMbbb' into ('aaa-', N, '.' MM, 'bbb') 61 62 e.g. 1.10b1 will sort as greater than 1.2:: 63 64 >>> natural_sort_key('brz-1.10b1.txt') > natural_sort_key('brz-1.2.txt') 65 True 66 """ 67 file_name = os.path.basename(file_name) 68 parts = re.findall(r'(?:[0-9]+|[^0-9]+)', file_name) 69 result = [] 70 for part in parts: 71 if re.match('^[0-9]+$', part) is not None: 72 part = int(part) 73 result.append(part) 74 return tuple(result) 75 76 77def output_news_file_sphinx(out_file, news_file_name): 78 news_file_name = os.path.basename(news_file_name) 79 if not news_file_name.endswith('.txt'): 80 raise AssertionError( 81 'NEWS file %s does not have .txt extension.' 82 % (news_file_name,)) 83 doc_name = news_file_name[:-4] 84 link_text = doc_name.replace('-', ' ') 85 out_file.write(' %s <%s>\n' % (link_text, doc_name)) 86 87 88def output_news_file_plain(out_file, news_file_name): 89 with open(news_file_name, 'r') as f: 90 lines = f.readlines() 91 title = os.path.basename(news_file_name)[len('brz-'):-len('.txt')] 92 for line in lines: 93 if line == '####################\n': 94 line = '#' * len(title) + '\n' 95 elif line == 'Breezy Release Notes\n': 96 line = title + '\n' 97 elif line == '.. toctree::\n': 98 continue 99 elif line == ' :maxdepth: 1\n': 100 continue 101 out_file.write(line) 102 out_file.write('\n\n') 103 104 105def main(argv): 106 # Check usage 107 parser = OptionParser(usage="%prog OUTPUT_FILE NEWS_FILE [NEWS_FILE ...]") 108 (options, args) = parser.parse_args(argv) 109 if len(args) < 2: 110 parser.print_help() 111 sys.exit(1) 112 113 # Open the files and do the work 114 out_file_name = args[0] 115 news_file_names = sorted(args[1:], key=natural_sort_key, reverse=True) 116 117 if os.path.basename(out_file_name) == 'index.txt': 118 preamble = preamble_sphinx 119 output_news_file = output_news_file_sphinx 120 else: 121 preamble = preamble_plain 122 output_news_file = output_news_file_plain 123 124 with open(out_file_name, 'w') as out_file: 125 out_file.write(preamble) 126 for news_file_name in news_file_names: 127 output_news_file(out_file, news_file_name) 128 129 130if __name__ == '__main__': 131 main(sys.argv[1:]) 132