Coverage for tsfpga/tools/sphinx_doc.py: 0%

63 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-20 20:51 +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# -------------------------------------------------------------------------------------------------- 

10 

11# Standard libraries 

12import sys 

13from datetime import datetime 

14from pathlib import Path 

15from subprocess import check_call 

16from typing import Iterable, Optional 

17 

18# Third party libraries 

19from git.repo import Repo 

20from packaging.version import Version, parse 

21 

22# First party libraries 

23from tsfpga.system_utils import read_file 

24 

25 

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. 

32 

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. 

37 

38 Return: 

39 RST code with release notes. 

40 """ 

41 rst = "" 

42 

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" 

59 

60 return rst 

61 

62 

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" 

70 

71 release_notes = [] 

72 

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) 

77 

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) 

81 

82 release_notes.sort(key=sort_key, reverse=True) 

83 

84 # The "Unreleased" shall be first 

85 release_notes.insert(0, unreleased_notes_file) 

86 

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 ] 

92 

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 

98 

99 yield release, previous_release_git_tag 

100 

101 

102class Release: 

103 """ 

104 Used to represent a release. 

105 """ 

106 

107 def __init__(self, repo: Repo, release_notes_file: Path) -> None: 

108 self.release_notes_file = release_notes_file 

109 

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) 

119 

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() 

128 

129 

130def build_sphinx(build_path: Path, output_path: Path) -> None: 

131 """ 

132 Execute sphinx on command line to build HTML documentation. 

133 

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() 

141 

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) 

154 

155 index_html = output_path / "index.html" 

156 assert index_html.exists(), index_html 

157 print(f"Open with:\nfirefox {index_html} &")