Mercurial > piecrust2
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 |
