diff options
| author | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2024-09-05 10:52:46 +0200 |
|---|---|---|
| committer | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2024-09-19 10:20:58 +0200 |
| commit | c71bbe991fc5faa1327503e06af17fd11d717a76 (patch) | |
| tree | 5673ee4ba7169678fbfebf4592d04c2cb91e5253 /tools/release_notes/main.py | |
| parent | fe5020b7e218078562c6b9b35d005553f53255fb (diff) | |
Documentation: Release Notes
- Introduces a new section in the navigation pane called "Release Notes"
with subpages for PySide6, Shiboken6, PySide2, and Shiboken2.
- The .md files are auto-generated by the script
`tools/release_notes/main.py` and created in the directory
`sources/pyside6/doc/release_notes/`.
Fixes: PYSIDE-2853
Change-Id: I6def8b526f11a638581f29798dd6917cd435d19c
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'tools/release_notes/main.py')
| -rw-r--r-- | tools/release_notes/main.py | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/tools/release_notes/main.py b/tools/release_notes/main.py new file mode 100644 index 000000000..b9a86172d --- /dev/null +++ b/tools/release_notes/main.py @@ -0,0 +1,193 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +""" +This tool reads all the changelogs in doc/changelogs and generates .rst files for each of the +changelogs. This .rst files are then used to generate the contents of the 'Release Notes' section +in the navigation pane of the Qt for Python documentation. +""" + +import re +import logging +import shutil +from pathlib import Path +from argparse import ArgumentParser, RawTextHelpFormatter + +SECTION_NAMES = ["PySide6", "Shiboken6", "PySide2", "Shiboken2"] +DIR = Path(__file__).parent +OUTPUT_DIR = Path(f"{DIR}/../../sources/pyside6/doc/release_notes").resolve() +CHANGELOG_DIR = Path(f"{DIR}/../../doc/changelogs").resolve() + +BASE_CONTENT = """\ +.. _release_notes: + +Release Notes +============= + +This section contains the release notes for different versions of Qt for Python. + +.. toctree:: + :maxdepth: 1 + + pyside6_release_notes.md + shiboken6_release_notes.md + pyside2_release_notes.md + shiboken2_release_notes.md +""" + + +class Changelog: + def __init__(self, file_path: Path): + self.file_path = file_path + self.version = file_path.name.split("-")[-1] + self.sections = {section: [] for section in SECTION_NAMES} + # for matching lines like * PySide6 * to identify the section + self.section_pattern = re.compile(r"\* +(\w+) +\*") + # for line that start with ' -' which lists the changes + self.line_pattern = re.compile(r"^ -") + # for line that contains a bug report like PYSIDE-<bug_number> + self.bug_number_pattern = re.compile(r"\[PYSIDE-\d+\]") + + def add_line(self, section, line): + self.sections[section].append(line) + + def parsed_sections(self): + return self.sections + + def parse(self): + current_section = None + buffer = [] + + with open(self.file_path, 'r', encoding='utf-8') as file: + # convert the lines to an iterator for skip the '***' lines + lines = iter(file.readlines()) + + for line in lines: + # skip lines with all characters as '*' + if line.strip() == '*' * len(line.strip()): + continue + + match = self.section_pattern.match(line) + if match: + # if buffer has content, add it to the current section + if buffer: + self.add_line(current_section, ' '.join(buffer).strip()) + buffer = [] + current_section = match.group(1) + # skip the next line which contains '***' + try: + next(lines) + except StopIteration: + break + continue + + if current_section: + if self.line_pattern.match(line) and buffer: + self.add_line(current_section, ' '.join(buffer).strip()) + buffer = [] + + # If the line contains a reference to a bug report like [PYSIDE-<bug_number>] + # then insert a link to the reference that conforms with Sphinx syntax + bug_number = self.bug_number_pattern.search(line) + if bug_number: + bug_number = bug_number.group() + # remove the square brackets + actual_bug_number = bug_number[1:-1] + bug_number_replacement = ( + f"[{actual_bug_number}]" + f"(https://bugreports.qt.io/browse/{actual_bug_number})" + ) + line = re.sub(re.escape(bug_number), bug_number_replacement, line) + + # Add the line to the buffer + buffer.append(line.strip()) + + # Add any remaining content in the buffer to the current section + if buffer: + self.add_line(current_section, ' '.join(buffer).strip()) + + +def parse_changelogs() -> str: + ''' + Parse the changelogs in the CHANGELOG_DIR and return a list of parsed changelogs. + ''' + changelogs = [] + logging.info(f"[RELEASE_DOC] Processing changelogs in {CHANGELOG_DIR}") + for file_path in CHANGELOG_DIR.iterdir(): + # exclude changes-1.2.3 + if "changes-1.2.3" in file_path.name: + continue + logging.info(f"[RELEASE_DOC] Processing file {file_path.name}") + changelog = Changelog(file_path) + changelog.parse() + changelogs.append(changelog) + return changelogs + + +def write_md_file(section: str, changelogs: list[Changelog]): + ''' + For each section create a .md file with the following content: + + Section Name + ============ + + Version + ------- + + - Change 1 + - Change 2 + .... + ''' + file_path = OUTPUT_DIR / f"{section.lower()}_release_notes.md" + with open(file_path, 'w', encoding='utf-8') as file: + file.write(f"# {section}\n") + for changelog in changelogs: + section_contents = changelog.parsed_sections()[section] + if section_contents: + file.write(f"## {changelog.version}\n\n") + for lines in section_contents: + # separate each line with a newline + file.write(f"{lines}\n") + file.write("\n") + + +def generate_index_file(): + """Generate the index RST file.""" + index_path = OUTPUT_DIR / "index.rst" + index_path.write_text(BASE_CONTENT, encoding='utf-8') + + +def main(): + parser = ArgumentParser(description="Generate release notes from changelog", + formatter_class=RawTextHelpFormatter) + parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const", + dest="loglevel", const=logging.INFO) + args = parser.parse_args() + + logging.basicConfig(level=args.loglevel) + + # create the output directory if it does not exist + # otherwise remove its contents + if OUTPUT_DIR.is_dir(): + shutil.rmtree(OUTPUT_DIR, ignore_errors=True) + logging.info(f"[RELEASE_DOC] Removed existing {OUTPUT_DIR}") + + logging.info(f"[RELEASE_DOC] Creating {OUTPUT_DIR}") + OUTPUT_DIR.mkdir(exist_ok=True) + + logging.info("[RELEASE_DOC] Generating index.md file") + generate_index_file() + + logging.info("[RELEASE_DOC] Parsing changelogs") + changelogs = parse_changelogs() + + # sort changelogs by version number in descending order + changelogs.sort(key=lambda x: x.version, reverse=True) + + for section in SECTION_NAMES: + logging.info(f"[RELEASE_DOC] Generating {section.lower()}_release_notes.md file") + write_md_file(section, changelogs) + + +if __name__ == "__main__": + main() |
