1"""This script renames the forthcoming section in changelog files with the upcoming version and the current date.""" 2 3from __future__ import print_function 4 5import argparse 6import datetime 7import os 8import re 9import sys 10 11from catkin_pkg.changelog import CHANGELOG_FILENAME, get_changelog_from_path 12from catkin_pkg.changelog_generator import FORTHCOMING_LABEL 13from catkin_pkg.package_version import bump_version 14from catkin_pkg.packages import find_packages, verify_equal_package_versions 15 16import docutils.core 17 18 19def get_forthcoming_label(rst): 20 document = docutils.core.publish_doctree(rst) 21 forthcoming_label = None 22 for child in document.children: 23 title = None 24 if isinstance(child, docutils.nodes.subtitle): 25 title = child 26 elif isinstance(child, docutils.nodes.section): 27 section = child 28 if len(section.children) > 0 and isinstance(section.children[0], docutils.nodes.title): 29 title = section.children[0] 30 if title and len(title.children) > 0 and isinstance(title.children[0], docutils.nodes.Text): 31 title_text = title.children[0].rawsource 32 if FORTHCOMING_LABEL.lower() in title_text.lower(): 33 if forthcoming_label: 34 raise RuntimeError('Found multiple forthcoming sections') 35 forthcoming_label = title_text 36 return forthcoming_label 37 38 39def rename_section(data, old_label, new_label): 40 valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' 41 42 def replace_section(match): 43 section_char = match.group(2)[0] 44 return new_label + '\n' + section_char * len(new_label) 45 pattern = '^(' + re.escape(old_label) + ')\n([' + re.escape(valid_section_characters) + ']+)$' 46 data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE) 47 if count == 0: 48 raise RuntimeError('Could not find section') 49 if count > 1: 50 raise RuntimeError('Found multiple matching sections') 51 return data 52 53 54def main(sysargs=None): 55 parser = argparse.ArgumentParser(description='Tag the forthcoming section in the changelog files with an upcoming version number') 56 parser.add_argument('--bump', choices=('major', 'minor', 'patch'), default='patch', help='Which part of the version number to bump? (default: %(default)s)') 57 args = parser.parse_args(sysargs) 58 59 base_path = '.' 60 61 # find packages 62 packages = find_packages(base_path) 63 if not packages: 64 raise RuntimeError('No packages found') 65 print('Found packages: %s' % ', '.join([p.name for p in packages.values()])) 66 67 # fetch current version and verify that all packages have same version number 68 old_version = verify_equal_package_versions(packages.values()) 69 new_version = bump_version(old_version, args.bump) 70 print('Tag version %s' % new_version) 71 72 # check for changelog entries 73 changelogs = [] 74 missing_forthcoming = [] 75 already_tagged = [] 76 for pkg_path, package in packages.items(): 77 changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME) 78 if not os.path.exists(changelog_path): 79 missing_forthcoming.append(package.name) 80 continue 81 changelog = get_changelog_from_path(changelog_path, package.name) 82 if not changelog: 83 missing_forthcoming.append(package.name) 84 continue 85 # check that forthcoming section exists 86 forthcoming_label = get_forthcoming_label(changelog.rst) 87 if not forthcoming_label: 88 missing_forthcoming.append(package.name) 89 continue 90 # check that new_version section does not exist yet 91 try: 92 changelog.get_content_of_version(new_version) 93 already_tagged.append(package.name) 94 continue 95 except KeyError: 96 pass 97 changelogs.append((package.name, changelog_path, changelog, forthcoming_label)) 98 if missing_forthcoming: 99 print('The following packages do not have a forthcoming section in their changelog file: %s' % ', '.join(sorted(missing_forthcoming)), file=sys.stderr) 100 if already_tagged: 101 print("The following packages do already have a section '%s' in their changelog file: %s" % (new_version, ', '.join(sorted(already_tagged))), file=sys.stderr) 102 103 # rename forthcoming sections to new_version including current date 104 new_changelog_data = [] 105 new_label = '%s (%s)' % (new_version, datetime.date.today().isoformat()) 106 for (pkg_name, changelog_path, changelog, forthcoming_label) in changelogs: 107 print("Renaming section '%s' to '%s' in package '%s'..." % (forthcoming_label, new_label, pkg_name)) 108 data = rename_section(changelog.rst, forthcoming_label, new_label) 109 new_changelog_data.append((changelog_path, data)) 110 111 print('Writing updated changelog files...') 112 for (changelog_path, data) in new_changelog_data: 113 with open(changelog_path, 'wb') as f: 114 f.write(data.encode('utf-8')) 115