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