Coverage for tsfpga/tools/sphinx_doc.py: 0%
63 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 20:52 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
8# A set of methods for building sphinx docs. Should be reusable between projects.
9# --------------------------------------------------------------------------------------------------
11# Standard libraries
12import sys
13from datetime import datetime
14from pathlib import Path
15from subprocess import check_call
16from typing import Iterable, Optional
18# Third party libraries
19from git.repo import Repo
20from packaging.version import Version, parse
22# First party libraries
23from tsfpga.system_utils import read_file
26def generate_release_notes(
27 repo_root: Path, release_notes_directory: Path, project_name: str
28) -> str:
29 """
30 Generate release notes in RST format based on a directory full of release note files.
31 Will match each file to a git tag.
33 Arguments:
34 repo_root: Git commands will be executed here.
35 release_notes_directory: Location of release notes files.
36 project_name: Name of project will be used for the GitHub link.
38 Return:
39 RST code with release notes.
40 """
41 rst = ""
43 for release, previous_release_git_tag in _get_release_notes_files(
44 repo_root=repo_root, release_notes_directory=release_notes_directory
45 ):
46 heading = f"{release.version} ({release.date})"
47 rst += heading + "\n"
48 rst += "-" * len(heading) + "\n"
49 rst += "\n"
50 if previous_release_git_tag is not None:
51 diff_url = (
52 f"https://github.com/{project_name}/{project_name}/compare/"
53 f"{previous_release_git_tag}...{release.git_tag}"
54 )
55 rst += f"`Changes since previous release <{diff_url}>`__\n"
56 rst += "\n"
57 rst += read_file(release.release_notes_file)
58 rst += "\n"
60 return rst
63def _get_release_notes_files(
64 repo_root: Path, release_notes_directory: Path
65) -> Iterable[tuple["Release", Optional[str]]]:
66 """
67 Iterate the release notes.
68 """
69 unreleased_notes_file = release_notes_directory / "unreleased.rst"
71 release_notes = []
73 # Get all versioned release notes files and sort them in order newest -> oldest
74 for release_notes_file in release_notes_directory.glob("*.rst"):
75 if not release_notes_file == unreleased_notes_file:
76 release_notes.append(release_notes_file)
78 # Sort by parsing the version number in the file name. Newest to oldest.
79 def sort_key(path: Path) -> Version:
80 return parse(path.stem)
82 release_notes.sort(key=sort_key, reverse=True)
84 # The "Unreleased" shall be first
85 release_notes.insert(0, unreleased_notes_file)
87 repo = Repo(repo_root)
88 releases = [
89 Release(repo=repo, release_notes_file=release_notes_file)
90 for release_notes_file in release_notes
91 ]
93 for idx, release in enumerate(releases):
94 if idx == len(releases) - 1:
95 previous_release_git_tag = None
96 else:
97 previous_release_git_tag = releases[idx + 1].git_tag
99 yield release, previous_release_git_tag
102class Release:
103 """
104 Used to represent a release.
105 """
107 def __init__(self, repo: Repo, release_notes_file: Path) -> None:
108 self.release_notes_file = release_notes_file
110 version = release_notes_file.stem
111 if version == "unreleased":
112 self.version = "Unreleased"
113 self.git_tag = "main"
114 self.date = "YYYY-MM-DD"
115 else:
116 self.version = version
117 self.git_tag = "v" + self.version
118 self.date = self.get_git_date_from_tag(repo=repo, tag=self.git_tag)
120 @staticmethod
121 def get_git_date_from_tag(repo: Repo, tag: str) -> str:
122 """
123 Get a formatted date string, gathered from git log based on tag name.
124 """
125 timestamp = repo.tag(f"refs/tags/{tag}").commit.committed_date
126 time = datetime.fromtimestamp(timestamp)
127 return f"{time.day} {time:%B} {time.year}".lower()
130def build_sphinx(build_path: Path, output_path: Path) -> None:
131 """
132 Execute sphinx on command line to build HTML documentation.
134 Arguments:
135 build_path: The location that contains ``conf.py`` and ``index.rst``.
136 output_path: Where to place the generated HTML.
137 """
138 # Since we set the working directory when making the system call, the paths must be absolute
139 build_path = build_path.resolve()
140 output_path = output_path.resolve()
142 cmd = [
143 sys.executable,
144 "-m",
145 "sphinx",
146 # Turn warnings into errors.
147 "-W",
148 # Show full traceback upon error.
149 "-T",
150 str(build_path),
151 str(output_path),
152 ]
153 check_call(cmd, cwd=build_path)
155 index_html = output_path / "index.html"
156 assert index_html.exists(), index_html
157 print(f"Open with:\nfirefox {index_html} &")