code_include.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import os.path
  4. from docutils import io, nodes, statemachine, utils
  5. from docutils.utils.error_reporting import SafeString, ErrorString
  6. from docutils.parsers.rst import directives, Directive
  7. from pelican.rstdirectives import Pygments
  8. """
  9. Include Pygments highlighted code with reStructuredText
  10. =======================================================
  11. :author: Colin Dunklau
  12. Use this plugin to make writing coding tutorials easier! You can
  13. maintain the example source files separately from the actual article.
  14. Based heavily on ``docutils.parsers.rst.directives.Include``. Include
  15. a file and output as a code block formatted with pelican's Pygments
  16. directive.
  17. Note that this is broken with the Docutils 0.10 release, there's a
  18. circular import. It was fixed in trunk:
  19. http://sourceforge.net/p/docutils/bugs/214/
  20. Directives
  21. ----------
  22. .. code:: rst
  23. .. code-include:: incfile.py
  24. :lexer: string, name of the Pygments lexer to use, default 'text'
  25. :encoding: string, encoding with which to open the file
  26. :tab-width: integer, hard tabs are replaced with `tab-width` spaces
  27. :start-line: integer, starting line to begin reading include file
  28. :end-line: integer, last line from include file to display
  29. ``start-line``, and ``end-line`` have the same meaning as in the
  30. docutils ``include`` directive, that is, they index from zero.
  31. Example
  32. -------
  33. ./incfile.py:
  34. .. code:: python
  35. # These two comment lines will not
  36. # be included in the output
  37. import random
  38. insults = ['I fart in your general direction',
  39. 'your mother was a hampster',
  40. 'your father smelt of elderberries']
  41. def insult():
  42. print random.choice(insults)
  43. # This comment line will be included
  44. # ...but this one won't
  45. ./yourfile.rst:
  46. .. code:: rst
  47. How to Insult the English
  48. =========================
  49. :author: Pierre Devereaux
  50. A function to help insult those silly English knnnnnnniggets:
  51. .. code-include:: incfile.py
  52. :lexer: python
  53. :encoding: utf-8
  54. :tab-width: 4
  55. :start-line: 3
  56. :end-line: 11
  57. """
  58. class CodeInclude(Directive):
  59. """
  60. Include content read from a separate source file, and highlight
  61. it with the given lexer (using pelican.rstdirectives.CodeBlock)
  62. The encoding of the included file can be specified. Only a part
  63. of the given file argument may be included by specifying start
  64. and end line. Hard tabs will be replaced with ``tab-width``
  65. spaces.
  66. """
  67. required_arguments = 1
  68. optional_arguments = 0
  69. final_argument_whitespace = True
  70. option_spec = {'lexer': directives.unchanged,
  71. 'encoding': directives.encoding,
  72. 'tab-width': int,
  73. 'start-line': int,
  74. 'end-line': int}
  75. def run(self):
  76. """Include a file as part of the content of this reST file."""
  77. if not self.state.document.settings.file_insertion_enabled:
  78. raise self.warning('"%s" directive disabled.' % self.name)
  79. source = self.state_machine.input_lines.source(
  80. self.lineno - self.state_machine.input_offset - 1)
  81. source_dir = os.path.dirname(os.path.abspath(source))
  82. path = directives.path(self.arguments[0])
  83. path = os.path.normpath(os.path.join(source_dir, path))
  84. path = utils.relative_path(None, path)
  85. path = nodes.reprunicode(path)
  86. encoding = self.options.get(
  87. 'encoding', self.state.document.settings.input_encoding)
  88. e_handler=self.state.document.settings.input_encoding_error_handler
  89. tab_width = self.options.get(
  90. 'tab-width', self.state.document.settings.tab_width)
  91. try:
  92. self.state.document.settings.record_dependencies.add(path)
  93. include_file = io.FileInput(source_path=path,
  94. encoding=encoding,
  95. error_handler=e_handler)
  96. except UnicodeEncodeError, error:
  97. raise self.severe(u'Problems with "%s" directive path:\n'
  98. 'Cannot encode input file path "%s" '
  99. '(wrong locale?).' %
  100. (self.name, SafeString(path)))
  101. except IOError, error:
  102. raise self.severe(u'Problems with "%s" directive path:\n%s.' %
  103. (self.name, ErrorString(error)))
  104. startline = self.options.get('start-line', None)
  105. endline = self.options.get('end-line', None)
  106. try:
  107. if startline or (endline is not None):
  108. lines = include_file.readlines()
  109. rawtext = ''.join(lines[startline:endline])
  110. else:
  111. rawtext = include_file.read()
  112. except UnicodeError, error:
  113. raise self.severe(u'Problem with "%s" directive:\n%s' %
  114. (self.name, ErrorString(error)))
  115. include_lines = statemachine.string2lines(rawtext, tab_width,
  116. convert_whitespace=True)
  117. # default lexer to 'text'
  118. lexer = self.options.get('lexer', 'text')
  119. self.options['source'] = path
  120. codeblock = Pygments(self.name,
  121. [lexer], # arguments
  122. {}, # no options for this directive
  123. include_lines, # content
  124. self.lineno,
  125. self.content_offset,
  126. self.block_text,
  127. self.state,
  128. self.state_machine)
  129. return codeblock.run()
  130. def register():
  131. directives.register_directive('code-include', CodeInclude)