comparison util/generate_changelog.py @ 544:9a00e694b42c

cm: Changelog generator script.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 31 Jul 2015 23:35:07 -0700
parents
children 442cf576ae25
comparison
equal deleted inserted replaced
543:bedfa0156bf7 544:9a00e694b42c
1 import os
2 import os.path
3 import re
4 import sys
5 import subprocess
6
7
8 hg_log_template = ("{if(tags, '>>{tags};{date|shortdate}\n')}"
9 "{desc|firstline}\n\n")
10
11 re_add_tag_changeset = re.compile('^Added tag [^\s]+ for changeset [\w\d]+$')
12 re_merge_pr_changeset = re.compile('^Merge pull request')
13 re_tag = re.compile('^\d+\.\d+\.\d+([ab]\d+)?(rc\d+)?$')
14 re_change = re.compile('^(\w+):')
15 re_clean_code_span = re.compile('([^\s])``([^\s]+)')
16
17 category_commands = [
18 'chef', 'bake', 'find', 'help', 'import', 'init', 'paths', 'plugin',
19 'plugins', 'prepare', 'purge', 'root', 'routes', 'serve',
20 'showconfig', 'showrecord', 'sources', 'theme', 'themes']
21 category_core = [
22 'internal', 'bug', 'templating', 'formatting', 'performance',
23 'data', 'config', 'rendering', 'render', 'debug', 'reporting',
24 'linker', 'pagination', 'routing', 'caching']
25 category_project = ['build', 'cm', 'docs', 'tests', 'setup']
26 categories = [
27 ('commands', category_commands),
28 ('core', category_core),
29 ('project', category_project),
30 ('miscellaneous', None)]
31 category_names = list(map(lambda i: i[0], categories))
32
33
34 def generate():
35 out_file = 'CHANGELOG.rst'
36 if len(sys.argv) > 1:
37 out_file = sys.argv[1]
38
39 print("Generating %s" % out_file)
40
41 if not os.path.exists('.hg'):
42 raise Exception("You must run this script from the root of a "
43 "Mercurial clone of the PieCrust repository.")
44 hglog = subprocess.check_output([
45 'hg', 'log',
46 '--rev', 'reverse(::master)',
47 '--template', hg_log_template])
48 hglog = hglog.decode('utf8')
49
50 templates = _get_templates()
51
52 with open(out_file, 'w') as fp:
53 fp.write(templates['header'])
54
55 skip = False
56 in_desc = False
57 current_version = 0
58 current_version_info = None
59 current_changes = None
60 for line in hglog.splitlines():
61 if line == '':
62 skip = False
63 in_desc = False
64 continue
65
66 if not in_desc and line.startswith('>>'):
67 tags, tag_date = line[2:].split(';')
68 if re_tag.match(tags):
69 if current_version > 0:
70 _write_version_changes(
71 templates,
72 current_version, current_version_info,
73 current_changes, fp)
74
75 current_version += 1
76 current_version_info = tags, tag_date
77 current_changes = {}
78 in_desc = True
79 else:
80 skip = True
81 continue
82
83 if skip or current_version == 0:
84 continue
85
86 if re_add_tag_changeset.match(line):
87 continue
88 if re_merge_pr_changeset.match(line):
89 continue
90
91 m = re_change.match(line)
92 if m:
93 ch_type = m.group(1)
94 for cat_name, ch_types in categories:
95 if ch_types is None or ch_type in ch_types:
96 msgs = current_changes.setdefault(cat_name, [])
97 msgs.append(line)
98 break
99 else:
100 assert False, ("Change '%s' should have gone in the "
101 "misc. bucket." % line)
102 else:
103 msgs = current_changes.setdefault('miscellaneous', [])
104 msgs.append(line)
105
106 if current_version > 0:
107 _write_version_changes(
108 templates,
109 current_version, current_version_info,
110 current_changes, fp)
111
112
113 def _write_version_changes(templates, version, version_info, changes, fp):
114 tokens = {
115 'num': str(version),
116 'version': version_info[0],
117 'date': version_info[1]}
118 tpl = _multi_replace(templates['version_title'], tokens)
119 fp.write(tpl)
120
121 for i, cat_name in enumerate(category_names):
122 msgs = changes.get(cat_name)
123 if not msgs:
124 continue
125
126 tokens = {
127 'sub_num': str(i),
128 'category': cat_name.title()}
129 tpl = _multi_replace(templates['category_title'], tokens)
130 fp.write(tpl)
131
132 for msg in msgs:
133 msg = msg.replace('`', '``').rstrip('\n')
134 msg = re_clean_code_span.sub(r'\1`` \2', msg)
135 fp.write('* ' + msg + '\n')
136
137
138 def _multi_replace(s, tokens):
139 for token in tokens:
140 s = s.replace('%%%s%%' % token, tokens[token])
141 return s
142
143
144 def _get_templates():
145 tpl_dir = os.path.join(os.path.dirname(__file__), 'changelog')
146 tpls = {}
147 for name in os.listdir(tpl_dir):
148 tpl = _get_template(os.path.join(tpl_dir, name))
149 name_no_ext, _ = os.path.splitext(name)
150 tpls[name_no_ext] = tpl
151 return tpls
152
153
154 def _get_template(filename):
155 with open(filename, 'r', encoding='utf8') as fp:
156 return fp.read()
157
158
159 if __name__ == '__main__':
160 generate()
161