godot_descriptions.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # -*- coding: utf-8 -*-
  2. """
  3. godot_descriptions
  4. ~~~~~~~~~~~~~~~~~~
  5. Sphinx extension to automatically generate HTML meta description tags
  6. for all pages. Also comes with some special support for Godot class docs.
  7. :copyright: Copyright 2021 by The Godot Engine Community
  8. :license: MIT.
  9. based on the work of Takayuki Shimizukawa on OpenGraph support for Sphinx,
  10. see: https://github.com/sphinx-contrib/ogp
  11. """
  12. import re
  13. from docutils import nodes
  14. from sphinx import addnodes
  15. class DescriptionGenerator:
  16. def __init__(self, document, pagename="", n_sections_max=3, max_length=220):
  17. self.document = document
  18. self.text_list = []
  19. self.max_length = max_length
  20. self.current_length = 0
  21. self.n_sections = 0
  22. self.n_sections_max = n_sections_max
  23. self.pagename = pagename
  24. self.is_class = pagename.startswith("classes/")
  25. self.stop_word_reached = False
  26. def dispatch_visit(self, node):
  27. if (
  28. self.stop_word_reached
  29. or self.current_length > self.max_length
  30. or self.n_sections > self.n_sections_max
  31. ):
  32. return
  33. if isinstance(node, addnodes.compact_paragraph) and node.get("toctree"):
  34. raise nodes.SkipChildren
  35. add = True
  36. if isinstance(node, nodes.paragraph):
  37. text = node.astext()
  38. if self.is_class:
  39. # Skip OOP hierarchy info for description
  40. if (
  41. text.startswith("Inherits:")
  42. or text.startswith("Inherited By:")
  43. or text.strip() == "Example:"
  44. ):
  45. add = False
  46. # If we're in a class doc and reached the first table,
  47. # stop adding to the description
  48. if text.strip() == "Properties":
  49. self.stop_word_reached = True
  50. add = False
  51. if add:
  52. self.text_list.append(text)
  53. self.current_length = self.current_length + len(text)
  54. if add and isinstance(node, nodes.section):
  55. self.n_sections += 1
  56. def dispatch_departure(self, node):
  57. pass
  58. def format_description(self, desc):
  59. # Replace newlines with spaces
  60. desc = re.sub("\r|\n", " ", desc)
  61. # Replace multiple spaces with single spaces
  62. desc = re.sub("\\s+", " ", desc)
  63. # Escape double quotes for HTML
  64. desc = re.sub('"', """, desc)
  65. return desc
  66. def create_description(self, cutoff_suffix="..."):
  67. text = " ".join(self.text_list)
  68. text = self.format_description(text)
  69. # Cut to self.max_length, add cutoff_suffix at end
  70. if len(text) > self.max_length:
  71. text = text[: self.max_length - len(cutoff_suffix)].strip() + cutoff_suffix
  72. return text
  73. def generate_description(app, pagename, templatename, context, doctree):
  74. if not doctree:
  75. return
  76. generator = DescriptionGenerator(doctree, pagename)
  77. doctree.walkabout(generator)
  78. description = (
  79. '<meta name="description" content="' + generator.create_description() + '" />\n'
  80. )
  81. if not '<meta name="description"' in context["metatags"]:
  82. context["metatags"] += description
  83. def setup(app):
  84. # Hook into Sphinx for all pages to
  85. # generate meta description tag and add to meta tag list
  86. app.connect("html-page-context", generate_description)
  87. return {
  88. "parallel_read_safe": True,
  89. "parallel_write_safe": True,
  90. }