sphinx.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. import json
  4. import pathlib
  5. import collections
  6. import logging
  7. import os
  8. import posixpath
  9. from sphinx import config as sphinx_config
  10. from sphinx.util import i18n as sphinx_i18n
  11. from sphinx.locale import _
  12. logger = logging.getLogger(__name__)
  13. DATE_FMT = "%Y-%m-%d %H:%M:%S %z"
  14. DEFAULT_TAG_WHITELIST = r"^.*$"
  15. DEFAULT_BRANCH_WHITELIST = r"^.*$"
  16. DEFAULT_REMOTE_WHITELIST = None
  17. DEFAULT_RELEASED_PATTERN = r"^tags/.*$"
  18. DEFAULT_OUTPUTDIR_FORMAT = r"{ref.name}"
  19. Version = collections.namedtuple(
  20. "Version", ["name", "url", "version", "release", "is_released",]
  21. )
  22. class VersionInfo:
  23. def __init__(self, app, context, metadata, current_version_name):
  24. self.app = app
  25. self.context = context
  26. self.metadata = metadata
  27. self.current_version_name = current_version_name
  28. def _dict_to_versionobj(self, v):
  29. return Version(
  30. name=v["name"],
  31. url=self.vpathto(v["name"]),
  32. version=v["version"],
  33. release=v["release"],
  34. is_released=v["is_released"],
  35. )
  36. @property
  37. def tags(self):
  38. return [
  39. self._dict_to_versionobj(v)
  40. for v in self.metadata.values()
  41. if v["source"] == "tags"
  42. ]
  43. @property
  44. def branches(self):
  45. return [
  46. self._dict_to_versionobj(v)
  47. for v in self.metadata.values()
  48. if v["source"] != "tags"
  49. ]
  50. @property
  51. def releases(self):
  52. return [
  53. self._dict_to_versionobj(v)
  54. for v in self.metadata.values()
  55. if v["is_released"]
  56. ]
  57. @property
  58. def in_development(self):
  59. return [
  60. self._dict_to_versionobj(v)
  61. for v in self.metadata.values()
  62. if not v["is_released"]
  63. ]
  64. def __iter__(self):
  65. for item in self.tags:
  66. yield item
  67. for item in self.branches:
  68. yield item
  69. def __getitem__(self, name):
  70. v = self.metadata.get(name)
  71. if v:
  72. return self._dict_to_versionobj(v)
  73. def vhasdoc(self, other_version_name):
  74. if self.current_version_name == other_version_name:
  75. return True
  76. other_version = self.metadata[other_version_name]
  77. return self.context["pagename"] in other_version["docnames"]
  78. def vpathto(self, other_version_name):
  79. if self.current_version_name == other_version_name:
  80. return "{}.html".format(
  81. posixpath.split(self.context["pagename"])[-1]
  82. )
  83. # Find output root
  84. current_version = self.metadata[self.current_version_name]
  85. other_version = self.metadata[other_version_name]
  86. outputroot = os.path.commonpath(
  87. (current_version["outputdir"], other_version["outputdir"])
  88. )
  89. current_outputroot = pathlib.PurePath(
  90. current_version["outputdir"]
  91. ).relative_to(outputroot)
  92. other_outputroot = pathlib.PurePath(
  93. other_version["outputdir"]
  94. ).relative_to(outputroot)
  95. relative_path_to_outputroot = os.path.join(
  96. *(
  97. ".."
  98. for x in current_outputroot.joinpath(
  99. self.context["pagename"]
  100. ).parent.parts
  101. )
  102. )
  103. # Find output dir of other version
  104. outputdir = posixpath.join(
  105. relative_path_to_outputroot, other_outputroot
  106. )
  107. if not self.vhasdoc(other_version_name):
  108. return posixpath.join(outputdir, "index.html")
  109. return posixpath.join(
  110. outputdir, "{}.html".format(self.context["pagename"])
  111. )
  112. def html_page_context(app, pagename, templatename, context, doctree):
  113. versioninfo = VersionInfo(
  114. app, context, app.config.smv_metadata, app.config.smv_current_version
  115. )
  116. context["versions"] = versioninfo
  117. context["vhasdoc"] = versioninfo.vhasdoc
  118. context["vpathto"] = versioninfo.vpathto
  119. context["current_version"] = versioninfo[app.config.smv_current_version]
  120. context["latest_version"] = versioninfo[app.config.smv_latest_version]
  121. context["html_theme"] = app.config.html_theme
  122. def config_inited(app, config):
  123. """Update the Sphinx builder.
  124. :param sphinx.application.Sphinx app: Sphinx application object.
  125. """
  126. if not config.smv_metadata:
  127. if not config.smv_metadata_path:
  128. return
  129. with open(config.smv_metadata_path, mode="r") as f:
  130. metadata = json.load(f)
  131. config.smv_metadata = metadata
  132. if not config.smv_current_version:
  133. return
  134. try:
  135. data = app.config.smv_metadata[config.smv_current_version]
  136. except KeyError:
  137. return
  138. app.connect("html-page-context", html_page_context)
  139. # Restore config values
  140. old_config = sphinx_config.Config.read(app.srcdir)
  141. old_config.pre_init_values()
  142. old_config.init_values()
  143. config.version = old_config.version
  144. config.release = old_config.release
  145. config.today = old_config.today
  146. if not config.today:
  147. config.today = sphinx_i18n.format_date(
  148. format=config.today_fmt or _("%b %d, %Y"),
  149. date=datetime.datetime.strptime(data["creatordate"], DATE_FMT),
  150. language=config.language,
  151. )
  152. def setup(app):
  153. app.add_config_value("smv_metadata", {}, "html")
  154. app.add_config_value("smv_metadata_path", "", "html")
  155. app.add_config_value("smv_current_version", "", "html")
  156. app.add_config_value("smv_latest_version", "master", "html")
  157. app.add_config_value("smv_tag_whitelist", DEFAULT_TAG_WHITELIST, "html")
  158. app.add_config_value(
  159. "smv_branch_whitelist", DEFAULT_BRANCH_WHITELIST, "html"
  160. )
  161. app.add_config_value(
  162. "smv_remote_whitelist", DEFAULT_REMOTE_WHITELIST, "html"
  163. )
  164. app.add_config_value(
  165. "smv_released_pattern", DEFAULT_RELEASED_PATTERN, "html"
  166. )
  167. app.add_config_value(
  168. "smv_outputdir_format", DEFAULT_OUTPUTDIR_FORMAT, "html"
  169. )
  170. app.connect("config-inited", config_inited)
  171. return {
  172. "version": "0.2",
  173. "parallel_read_safe": True,
  174. "parallel_write_safe": True,
  175. }