sphinx.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. DEFAULT_TAG_WHITELIST = r'^.*$'
  14. DEFAULT_BRANCH_WHITELIST = r'^.*$'
  15. DEFAULT_REMOTE_WHITELIST = None
  16. DEFAULT_RELEASED_PATTERN = r'^tags/.*$'
  17. DEFAULT_OUTPUTDIR_FORMAT = r'{ref.name}'
  18. Version = collections.namedtuple('Version', [
  19. 'name',
  20. 'url',
  21. 'version',
  22. 'release',
  23. 'is_released',
  24. ])
  25. class VersionInfo:
  26. def __init__(self, app, context, metadata, current_version_name):
  27. self.app = app
  28. self.context = context
  29. self.metadata = metadata
  30. self.current_version_name = current_version_name
  31. def _dict_to_versionobj(self, v):
  32. return Version(
  33. name=v["name"],
  34. url=self.vpathto(v["name"]),
  35. version=v["version"],
  36. release=v["release"],
  37. is_released=v["is_released"],
  38. )
  39. @property
  40. def tags(self):
  41. return [self._dict_to_versionobj(v) for v in self.metadata.values()
  42. if v["source"] == "tags"]
  43. @property
  44. def branches(self):
  45. return [self._dict_to_versionobj(v) for v in self.metadata.values()
  46. if v["source"] != "tags"]
  47. @property
  48. def releases(self):
  49. return [self._dict_to_versionobj(v) for v in self.metadata.values()
  50. if v["is_released"]]
  51. @property
  52. def in_development(self):
  53. return [self._dict_to_versionobj(v) for v in self.metadata.values()
  54. if not v["is_released"]]
  55. def __iter__(self):
  56. for item in self.tags:
  57. yield item
  58. for item in self.branches:
  59. yield item
  60. def __getitem__(self, name):
  61. v = self.metadata.get(name)
  62. if v:
  63. return self._dict_to_versionobj(v)
  64. def vhasdoc(self, other_version_name):
  65. if self.current_version_name == other_version_name:
  66. return True
  67. other_version = self.metadata[other_version_name]
  68. return self.context["pagename"] in other_version["docnames"]
  69. def vpathto(self, other_version_name):
  70. if self.current_version_name == other_version_name:
  71. return '{}.html'.format(
  72. posixpath.split(self.context["pagename"])[-1])
  73. # Find output root
  74. current_version = self.metadata[self.current_version_name]
  75. relpath = pathlib.PurePath(current_version["outputdir"])
  76. outputroot = os.path.join(
  77. *('..' for x in relpath.joinpath(self.context["pagename"]).parent.parts)
  78. )
  79. # Find output dir of other version
  80. other_version = self.metadata[other_version_name]
  81. outputdir = posixpath.join(outputroot, other_version["outputdir"])
  82. if not self.vhasdoc(other_version_name):
  83. return posixpath.join(outputdir, 'index.html')
  84. return posixpath.join(outputdir, '{}.html'.format(self.context["pagename"]))
  85. def html_page_context(app, pagename, templatename, context, doctree):
  86. versioninfo = VersionInfo(
  87. app, context, app.config.smv_metadata, app.config.smv_current_version)
  88. context["versions"] = versioninfo
  89. context["vhasdoc"] = versioninfo.vhasdoc
  90. context["vpathto"] = versioninfo.vpathto
  91. context["current_version"] = versioninfo[app.config.smv_current_version]
  92. context["latest_version"] = versioninfo[app.config.smv_latest_version]
  93. context["html_theme"] = app.config.html_theme
  94. def config_inited(app, config):
  95. """Update the Sphinx builder.
  96. :param sphinx.application.Sphinx app: Sphinx application object.
  97. """
  98. if not config.smv_metadata:
  99. if not config.smv_metadata_path:
  100. return
  101. with open(config.smv_metadata_path, mode="r") as f:
  102. metadata = json.load(f)
  103. config.smv_metadata = metadata
  104. if not config.smv_current_version:
  105. return
  106. try:
  107. data = app.config.smv_metadata[config.smv_current_version]
  108. except KeyError:
  109. return
  110. app.connect("html-page-context", html_page_context)
  111. # Restore config values
  112. old_config = sphinx_config.Config.read(app.srcdir)
  113. old_config.pre_init_values()
  114. old_config.init_values()
  115. config.version = old_config.version
  116. config.release = old_config.release
  117. config.today = old_config.today
  118. if not config.today:
  119. config.today = sphinx_i18n.format_date(
  120. format=config.today_fmt or _('%b %d, %Y'),
  121. date=datetime.datetime.strptime(data["creatordate"], "%Y-%m-%d %H:%M:%S %z"),
  122. language=config.language)
  123. def setup(app):
  124. app.add_config_value("smv_metadata", {}, "html")
  125. app.add_config_value("smv_metadata_path", "", "html")
  126. app.add_config_value("smv_current_version", "", "html")
  127. app.add_config_value("smv_latest_version", "master", "html")
  128. app.add_config_value("smv_tag_whitelist", DEFAULT_TAG_WHITELIST, "html")
  129. app.add_config_value("smv_branch_whitelist", DEFAULT_BRANCH_WHITELIST, "html")
  130. app.add_config_value("smv_remote_whitelist", DEFAULT_REMOTE_WHITELIST, "html")
  131. app.add_config_value("smv_released_pattern", DEFAULT_RELEASED_PATTERN, "html")
  132. app.add_config_value("smv_outputdir_format", DEFAULT_OUTPUTDIR_FORMAT, "html")
  133. app.connect("config-inited", config_inited)
  134. return {
  135. "version": "0.1",
  136. "parallel_read_safe": True,
  137. "parallel_write_safe": True,
  138. }