dochelpers.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #!/usr/bin/python2 -O
  2. # vim: fileencoding=utf-8
  3. #
  4. # The Qubes OS Project, https://www.qubes-os.org/
  5. #
  6. # Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along
  20. # with this program; if not, write to the Free Software Foundation, Inc.,
  21. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. #
  23. '''Documentation helpers.
  24. This module contains classes and functions which help to maintain documentation,
  25. particularly our custom Sphinx extension.
  26. '''
  27. import csv
  28. import posixpath
  29. import re
  30. import urllib2
  31. import docutils
  32. import docutils.nodes
  33. import docutils.parsers.rst
  34. import docutils.parsers.rst.roles
  35. import docutils.statemachine
  36. import sphinx
  37. import sphinx.locale
  38. import sphinx.util.docfields
  39. def fetch_ticket_info(uri):
  40. '''Fetch info about particular trac ticket given
  41. :param str uri: URI at which ticket resides
  42. :rtype: mapping
  43. :raises: urllib2.HTTPError
  44. '''
  45. data = urllib2.urlopen(uri + '?format=csv').read()
  46. reader = csv.reader((line + '\n' for line in data.split('\r\n')),
  47. quoting=csv.QUOTE_MINIMAL, quotechar='"')
  48. return dict(zip(*((cell.decode('utf-8') for cell in row)
  49. for row in list(reader)[:2])))
  50. def ticket(name, rawtext, text, lineno, inliner, options=None, content=None):
  51. '''Link to qubes ticket
  52. :param str name: The role name used in the document
  53. :param str rawtext: The entire markup snippet, with role
  54. :param str text: The text marked with the role
  55. :param int lineno: The line number where rawtext appears in the input
  56. :param docutils.parsers.rst.states.Inliner inliner: The inliner instance \
  57. that called this function
  58. :param options: Directive options for customisation
  59. :param content: The directive content for customisation
  60. ''' # pylint: disable=unused-argument
  61. if options is None:
  62. options = {}
  63. ticketno = text.lstrip('#')
  64. if not ticket.isdigit():
  65. msg = inliner.reporter.error(
  66. 'Invalid ticket identificator: {!r}'.format(text), line=lineno)
  67. prb = inliner.problematic(rawtext, rawtext, msg)
  68. return [prb], [msg]
  69. app = inliner.document.settings.env.app
  70. uri = posixpath.join(app.config.ticket_base_uri, ticketno)
  71. try:
  72. info = fetch_ticket_info(uri)
  73. except urllib2.HTTPError, e:
  74. msg = inliner.reporter.error(
  75. 'Error while fetching ticket info: {!s}'.format(e), line=lineno)
  76. prb = inliner.problematic(rawtext, rawtext, msg)
  77. return [prb], [msg]
  78. docutils.parsers.rst.roles.set_classes(options)
  79. node = docutils.nodes.reference(
  80. rawtext,
  81. '#{} ({})'.format(ticketno, info['summary']),
  82. refuri=uri,
  83. **options)
  84. return [node], []
  85. class versioncheck(docutils.nodes.warning):
  86. # pylint: disable=invalid-name
  87. pass
  88. def visit(self, node):
  89. self.visit_admonition(node, 'version')
  90. def depart(self, node):
  91. self.depart_admonition(node)
  92. sphinx.locale.admonitionlabels['version'] = 'Version mismatch'
  93. class VersionCheck(docutils.parsers.rst.Directive):
  94. '''Directive versioncheck
  95. Check if current version (from ``conf.py``) equals version specified as
  96. argument. If not, generate warning.'''
  97. has_content = True
  98. required_arguments = 1
  99. optional_arguments = 0
  100. final_argument_whitespace = True
  101. option_spec = {}
  102. def run(self):
  103. current = self.state.document.settings.env.app.config.version
  104. version = self.arguments[0]
  105. if current == version:
  106. return []
  107. text = ' '.join('''This manual page was written for version **{}**, but
  108. current version at the time when this page was generated is **{}**.
  109. This may or may not mean that page is outdated or has
  110. inconsistencies.'''.format(version, current).split())
  111. node = versioncheck(text)
  112. node['classes'] = ['admonition', 'warning']
  113. self.state.nested_parse(docutils.statemachine.StringList([text]),
  114. self.content_offset, node)
  115. return [node]
  116. #
  117. # this is lifted from sphinx' own conf.py
  118. #
  119. event_sig_re = re.compile(r'([a-zA-Z-:<>]+)\s*\((.*)\)')
  120. def parse_event(env, sig, signode):
  121. # pylint: disable=unused-argument
  122. m = event_sig_re.match(sig)
  123. if not m:
  124. signode += sphinx.addnodes.desc_name(sig, sig)
  125. return sig
  126. name, args = m.groups()
  127. signode += sphinx.addnodes.desc_name(name, name)
  128. plist = sphinx.addnodes.desc_parameterlist()
  129. for arg in args.split(','):
  130. arg = arg.strip()
  131. plist += sphinx.addnodes.desc_parameter(arg, arg)
  132. signode += plist
  133. return name
  134. #
  135. # end of codelifting
  136. #
  137. def setup(app):
  138. app.add_role('ticket', ticket)
  139. app.add_config_value('ticket_base_uri',
  140. 'https://wiki.qubes-os.org/ticket/', 'env')
  141. app.add_node(versioncheck,
  142. html=(visit, depart),
  143. man=(visit, depart))
  144. app.add_directive('versioncheck', VersionCheck)
  145. fdesc = sphinx.util.docfields.GroupedField('parameter', label='Parameters',
  146. names=['param'], can_collapse=True)
  147. app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
  148. doc_field_types=[fdesc])
  149. # vim: ts=4 sw=4 et