githubsphinx.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """Stolen from sphinxcontrib-issuetracker.
  2. Had to modify this as the original will make one Github API request
  3. per issue, which is not at all needed if we just want to link to issues.
  4. """
  5. from __future__ import absolute_import
  6. import re
  7. import sys
  8. from collections import namedtuple
  9. from docutils import nodes
  10. from docutils.transforms import Transform
  11. from sphinx.roles import XRefRole
  12. from sphinx.addnodes import pending_xref
  13. URL = 'https://github.com/{project}/issues/{issue_id}'
  14. Issue = namedtuple('Issue', ('id', 'title', 'url'))
  15. if sys.version_info[0] == 3:
  16. str_t = text_t = str
  17. else:
  18. str_t = basestring
  19. text_t = unicode
  20. class IssueRole(XRefRole):
  21. innernodeclass = nodes.inline
  22. class Issues(Transform):
  23. default_priority = 999
  24. def apply(self):
  25. config = self.document.settings.env.config
  26. github_project = config.github_project
  27. issue_pattern = config.github_issue_pattern
  28. if isinstance(issue_pattern, str_t):
  29. issue_pattern = re.compile(issue_pattern)
  30. for node in self.document.traverse(nodes.Text):
  31. parent = node.parent
  32. if isinstance(parent, (nodes.literal, nodes.FixedTextElement)):
  33. continue
  34. text = text_t(node)
  35. new_nodes = []
  36. last_issue_ref_end = 0
  37. for match in issue_pattern.finditer(text):
  38. head = text[last_issue_ref_end:match.start()]
  39. if head:
  40. new_nodes.append(nodes.Text(head))
  41. last_issue_ref_end = match.end()
  42. issuetext = match.group(0)
  43. issue_id = match.group(1)
  44. refnode = pending_xref()
  45. refnode['reftarget'] = issue_id
  46. refnode['reftype'] = 'issue'
  47. refnode['github_project'] = github_project
  48. reftitle = issuetext
  49. refnode.append(nodes.inline(
  50. issuetext, reftitle, classes=['xref', 'issue']))
  51. new_nodes.append(refnode)
  52. if not new_nodes:
  53. continue
  54. tail = text[last_issue_ref_end:]
  55. if tail:
  56. new_nodes.append(nodes.Text(tail))
  57. parent.replace(node, new_nodes)
  58. def make_issue_reference(issue, content_node):
  59. reference = nodes.reference()
  60. reference['refuri'] = issue.url
  61. if issue.title:
  62. reference['reftitle'] = issue.title
  63. reference.append(content_node)
  64. return reference
  65. def resolve_issue_reference(app, env, node, contnode):
  66. if node['reftype'] != 'issue':
  67. return
  68. issue_id = node['reftarget']
  69. project = node['github_project']
  70. issue = Issue(issue_id, None, URL.format(project=project,
  71. issue_id=issue_id))
  72. conttext = text_t(contnode[0])
  73. formatted_conttext = nodes.Text(conttext.format(issue=issue))
  74. formatted_contnode = nodes.inline(conttext, formatted_conttext,
  75. classes=contnode['classes'])
  76. return make_issue_reference(issue, formatted_contnode)
  77. def init_transformer(app):
  78. app.add_transform(Issues)
  79. def setup(app):
  80. app.require_sphinx('1.0')
  81. app.add_role('issue', IssueRole())
  82. app.add_config_value('github_project', None, 'env')
  83. app.add_config_value('github_issue_pattern',
  84. re.compile(r'[Ii]ssue #(\d+)'), 'env')
  85. app.connect(str('builder-inited'), init_transformer)
  86. app.connect(str('missing-reference'), resolve_issue_reference)