misc.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # $Id: misc.py 9037 2022-03-05 23:31:10Z milde $
  2. # Author: David Goodger <goodger@python.org>
  3. # Copyright: This module has been placed in the public domain.
  4. """
  5. Miscellaneous transforms.
  6. """
  7. __docformat__ = 'reStructuredText'
  8. from docutils import nodes
  9. from docutils.transforms import Transform
  10. class CallBack(Transform):
  11. """
  12. Inserts a callback into a document. The callback is called when the
  13. transform is applied, which is determined by its priority.
  14. For use with `nodes.pending` elements. Requires a ``details['callback']``
  15. entry, a bound method or function which takes one parameter: the pending
  16. node. Other data can be stored in the ``details`` attribute or in the
  17. object hosting the callback method.
  18. """
  19. default_priority = 990
  20. def apply(self):
  21. pending = self.startnode
  22. pending.details['callback'](pending)
  23. pending.parent.remove(pending)
  24. class ClassAttribute(Transform):
  25. """
  26. Move the "class" attribute specified in the "pending" node into the
  27. immediately following non-comment element.
  28. """
  29. default_priority = 210
  30. def apply(self):
  31. pending = self.startnode
  32. parent = pending.parent
  33. child = pending
  34. while parent:
  35. # Check for appropriate following siblings:
  36. for index in range(parent.index(child) + 1, len(parent)):
  37. element = parent[index]
  38. if (isinstance(element, nodes.Invisible)
  39. or isinstance(element, nodes.system_message)):
  40. continue
  41. element['classes'] += pending.details['class']
  42. pending.parent.remove(pending)
  43. return
  44. else:
  45. # At end of section or container; apply to sibling
  46. child = parent
  47. parent = parent.parent
  48. error = self.document.reporter.error(
  49. 'No suitable element following "%s" directive'
  50. % pending.details['directive'],
  51. nodes.literal_block(pending.rawsource, pending.rawsource),
  52. line=pending.line)
  53. pending.replace_self(error)
  54. class Transitions(Transform):
  55. """
  56. Move transitions at the end of sections up the tree. Complain
  57. on transitions after a title, at the beginning or end of the
  58. document, and after another transition.
  59. For example, transform this::
  60. <section>
  61. ...
  62. <transition>
  63. <section>
  64. ...
  65. into this::
  66. <section>
  67. ...
  68. <transition>
  69. <section>
  70. ...
  71. """
  72. default_priority = 830
  73. def apply(self):
  74. for node in self.document.findall(nodes.transition):
  75. self.visit_transition(node)
  76. def visit_transition(self, node):
  77. index = node.parent.index(node)
  78. error = None
  79. if (index == 0
  80. or isinstance(node.parent[0], nodes.title)
  81. and (index == 1
  82. or isinstance(node.parent[1], nodes.subtitle)
  83. and index == 2)):
  84. assert (isinstance(node.parent, nodes.document)
  85. or isinstance(node.parent, nodes.section))
  86. error = self.document.reporter.error(
  87. 'Document or section may not begin with a transition.',
  88. source=node.source, line=node.line)
  89. elif isinstance(node.parent[index - 1], nodes.transition):
  90. error = self.document.reporter.error(
  91. 'At least one body element must separate transitions; '
  92. 'adjacent transitions are not allowed.',
  93. source=node.source, line=node.line)
  94. if error:
  95. # Insert before node and update index.
  96. node.parent.insert(index, error)
  97. index += 1
  98. assert index < len(node.parent)
  99. if index != len(node.parent) - 1:
  100. # No need to move the node.
  101. return
  102. # Node behind which the transition is to be moved.
  103. sibling = node
  104. # While sibling is the last node of its parent.
  105. while index == len(sibling.parent) - 1:
  106. sibling = sibling.parent
  107. # If sibling is the whole document (i.e. it has no parent).
  108. if sibling.parent is None:
  109. # Transition at the end of document. Do not move the
  110. # transition up, and place an error behind.
  111. error = self.document.reporter.error(
  112. 'Document may not end with a transition.',
  113. line=node.line)
  114. node.parent.insert(node.parent.index(node) + 1, error)
  115. return
  116. index = sibling.parent.index(sibling)
  117. # Remove the original transition node.
  118. node.parent.remove(node)
  119. # Insert the transition after the sibling.
  120. sibling.parent.insert(index + 1, node)