dochelpers.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. #!/usr/bin/python2 -O
  2. # -*- coding: utf-8 -*-
  3. '''Documentation helpers
  4. This module contains classes and functions which help to mainain documentation,
  5. particulary our custom Sphinx extension.
  6. '''
  7. import csv
  8. import posixpath
  9. import re
  10. import sys
  11. import urllib2
  12. import docutils
  13. import docutils.nodes
  14. import docutils.parsers.rst
  15. import docutils.parsers.rst.roles
  16. import docutils.statemachine
  17. import sphinx
  18. import sphinx.locale
  19. import sphinx.util.docfields
  20. def fetch_ticket_info(uri):
  21. '''Fetch info about particular trac ticket given
  22. :param str uri: URI at which ticket resides
  23. :rtype: mapping
  24. :raises: urllib2.HTTPError
  25. '''
  26. data = urllib2.urlopen(uri + '?format=csv').read()
  27. reader = csv.reader((line + '\n' for line in data.split('\r\n')),
  28. quoting=csv.QUOTE_MINIMAL, quotechar='"')
  29. return dict(zip(*((cell.decode('utf-8') for cell in row) for row in list(reader)[:2])))
  30. def ticket(name, rawtext, text, lineno, inliner, options={}, content=[]):
  31. '''Link to qubes ticket
  32. :param str name: The role name used in the document
  33. :param str rawtext: The entire markup snippet, with role
  34. :param str text: The text marked with the role
  35. :param int lineno: The line noumber where rawtext appearn in the input
  36. :param docutils.parsers.rst.states.Inliner inliner: The inliner instance that called this function
  37. :param options: Directive options for customisation
  38. :param content: The directive content for customisation
  39. '''
  40. ticket = text.lstrip('#')
  41. if not ticket.isdigit():
  42. msg = inliner.reporter.error('Invalid ticket identificator: {!r}'.format(text), line=lineno)
  43. prb = inliner.problematic(rawtext, rawtext, msg)
  44. return [prb], [msg]
  45. app = inliner.document.settings.env.app
  46. uri = posixpath.join(app.config.ticket_base_uri, ticket)
  47. try:
  48. info = fetch_ticket_info(uri)
  49. except urllib2.HTTPError, e:
  50. msg = inliner.reporter.error('Error while fetching ticket info: {!s}'.format(e), line=lineno)
  51. prb = inliner.problematic(rawtext, rawtext, msg)
  52. return [prb], [msg]
  53. docutils.parsers.rst.roles.set_classes(options)
  54. node = docutils.nodes.reference(
  55. rawtext,
  56. '#{} ({})'.format(ticket, info['summary']),
  57. refuri=uri,
  58. **options)
  59. return [node], []
  60. class versioncheck(docutils.nodes.warning): pass
  61. def visit(self, node):
  62. self.visit_admonition(node, 'version')
  63. def depart(self, node):
  64. self.depart_admonition(node)
  65. sphinx.locale.admonitionlabels['version'] = 'Version mismatch'
  66. class VersionCheck(docutils.parsers.rst.Directive):
  67. '''Directive versioncheck
  68. Check if current version (from ``conf.py``) equals version specified as
  69. argument. If not, generate warning.'''
  70. has_content = True
  71. required_arguments = 1
  72. optional_arguments = 0
  73. final_argument_whitespace = True
  74. option_spec = {}
  75. def run(self):
  76. current = self.state.document.settings.env.app.config.version
  77. version = self.arguments[0]
  78. if current == version:
  79. return []
  80. text = ' '.join('''This manual page was written for version **{}**, but
  81. current version at the time when this page was generated is **{}**.
  82. This may or may not mean that page is outdated or has
  83. inconsistencies.'''.format(version, current).split())
  84. node = versioncheck(text)
  85. node['classes'] = ['admonition', 'warning']
  86. self.state.nested_parse(docutils.statemachine.StringList([text]),
  87. self.content_offset, node)
  88. return [node]
  89. #
  90. # this is lifted from sphinx' own conf.py
  91. #
  92. event_sig_re = re.compile(r'([a-zA-Z-]+)\s*\((.*)\)')
  93. def parse_event(env, sig, signode):
  94. m = event_sig_re.match(sig)
  95. if not m:
  96. signode += sphinx.addnodes.desc_name(sig, sig)
  97. return sig
  98. name, args = m.groups()
  99. signode += sphinx.addnodes.desc_name(name, name)
  100. plist = sphinx.addnodes.desc_parameterlist()
  101. for arg in args.split(','):
  102. arg = arg.strip()
  103. plist += sphinx.addnodes.desc_parameter(arg, arg)
  104. signode += plist
  105. return name
  106. #
  107. # end of codelifting
  108. #
  109. def setup(app):
  110. app.add_role('ticket', ticket)
  111. app.add_config_value('ticket_base_uri', 'https://wiki.qubes-os.org/ticket/', 'env')
  112. app.add_node(versioncheck,
  113. html=(visit, depart),
  114. man=(visit, depart))
  115. app.add_directive('versioncheck', VersionCheck)
  116. fdesc = sphinx.util.docfields.GroupedField('parameter', label='Parameters',
  117. names=['param'], can_collapse=True)
  118. app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
  119. doc_field_types=[fdesc])
  120. # vim: ts=4 sw=4 et