Welcome, guest | Sign In | My Account | Store | Cart
2

Code samples in reStructuredText documents are normally shown as plain literal blocks. This recipe uses the SilverCity ( http://silvercity.sourceforge.net/ ) lexing package to generate syntax highlighted code blocks instead.

Python, 51 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env python

import SilverCity
import docutils.parsers.rst
import StringIO

def code_block( name, arguments, options, content, lineno,
             content_offset, block_text, state, state_machine ):
  """
  The code-block directive provides syntax highlighting for blocks
  of code.  It is used with the the following syntax::
  
  .. code-block:: CPP
     
    #include <iostream>
    
    int main( int argc, char* argv[] )
    {
      std::cout << "Hello world" << std::endl;
    }
    
  The directive requires the name of a language supported by SilverCity
  as its only argument.  All code in the indented block following
  the directive will be colourized.  Note that this directive is only
  supported for HTML writers.
  """
  language = arguments[0]
  try:
    module = getattr(SilverCity, language)
    generator = getattr(module, language+"HTMLGenerator")
  except AttributeError:
    error = state_machine.reporter.error( "No SilverCity lexer found "
      "for language '%s'." % language, 
      docutils.nodes.literal_block(block_text, block_text), line=lineno )
    return [error]
  io = StringIO.StringIO()
  generator().generate_html( io, '\n'.join(content) )
  html = '<div class="code-block">\n%s\n</div>\n' % io.getvalue()
  raw = docutils.nodes.raw('',html, format = 'html')
  return [raw]

code_block.arguments = (1,0,0)
code_block.options = {'language' : docutils.parsers.rst.directives.unchanged }
code_block.content = 1
  
# Simply importing this module will make the directive available.
docutils.parsers.rst.directives.register_directive( 'code-block', code_block )

if __name__ == "__main__":
  import docutils.core
  docutils.core.publish_cmdline(writer_name='html')

This module adds a new directive, "code-block", to docutils. The directive can be used wherever you otherwise might use a literal block. For example: <pre>.. code-block:: Python

def hello( name ): print "Hello,",name </pre> instead of, <pre>::

def hello( name ): print "Hello,",name </pre> Displaying the HTML output requires a stylesheet that merges the 'default.css' from docutils, the 'default.css' from SilverCity and the following style:

div.code-block{ margin-left: 2em ; margin-right: 2em ; background-color: #eeeeee; font-family: "Courier New", Courier, monospace; font-size: 10pt; }

The syntax-highlighted text is inserted into the docutils tree as a raw-node with HTML format. So, it will only be displayed when generating HTML documents, and will be ignored by other writers (e.g., LaTex)

You can run this as a standalone script to generate HTML, or import it into other modules that invoke the docutils HTML writer. Simply importing the module registers the code-block directive and makes it available.

6 comments

Michael Lerner 11 years, 11 months ago  # | flag

Small feature addition. I like this a lot, but I want it to stay up-to-date with the current source. I changed the recipie a bit so that you can specify a source-file instead of including source directly.

#!/usr/bin/env python

import SilverCity
import docutils.parsers.rst
import StringIO

def code_block( name, arguments, options, content, lineno,
             content_offset, block_text, state, state_machine ):
  """
  The code-block directive provides syntax highlighting for blocks
  of code.  It is used with the the following syntax::

  .. code-block:: CPP

    #include &lt;iostream&gt;

    int main( int argc, char* argv[] )
    {
      std::cout &lt;&lt; "Hello world" &lt;&lt; std::endl;
    }

  The directive requires the name of a language supported by SilverCity
  as its only argument.  All code in the indented block following
  the directive will be colourized.  Note that this directive is only
  supported for HTML writers.

  The directive can also be told to include a source file directly::

  .. code-block::
     :language: Python
     :source-file: ../myfile.py

  You cannot both specify a source-file and include code directly.
  """

  try:
    language = arguments[0]
  except IndexError:
    language = options['language']

  if content and 'source-file' in options:
    error = state_machine.reporter.error( "You cannot both specify a source-file and include code directly.",
                                          docutils.nodes.literal_block(block_text,block_text), line=lineno)
    return [error]

  if not content:
    try:
      content = [line.rstrip() for line in file(options['source-file'])]
    except KeyError:
      # source-file was not specified
      pass
  try:
    module = getattr(SilverCity, language)
    generator = getattr(module, language+"HTMLGenerator")
  except AttributeError:
    error = state_machine.reporter.error( "No SilverCity lexer found "
      "for language '%s'." % language,
      docutils.nodes.literal_block(block_text, block_text), line=lineno )
    return [error]
  io = StringIO.StringIO()
  generator().generate_html( io, '\n'.join(content) )
  html = '&lt;div class="code-block"&gt;\n%s\n&lt;/div&gt;\n' % io.getvalue()
  raw = docutils.nodes.raw('',html, format = 'html')
  return [raw]

#code_block.arguments = (1,0,0)
code_block.arguments = (0,2,1)
code_block.options = {'language' : docutils.parsers.rst.directives.unchanged,
                      'source-file' : docutils.parsers.rst.directives.path,}
code_block.content = 1

# Simply importing this module will make the directive available.
docutils.parsers.rst.directives.register_directive( 'code-block', code_block )

(comment continued...)

Michael Lerner 11 years, 11 months ago  # | flag

(...continued from previous comment)

if __name__ == "__main__":
  import docutils.core
  docutils.core.publish_cmdline(writer_name='html')

This also lets you specify language directly as an option.

Michael Lerner 11 years, 11 months ago  # | flag

oops. small correction:

if not content:
  try:
    content = [line.rstrip() for line in file(options['source-file'])]
  except KeyError:
    # source-file was not specified
    pass

should be changed to

if not content:
  try:
    content = [line.rstrip() for line in file(options['source-file'])]
  except KeyError:
    # source-file was not specified
    pass
  except IOError:
    error = state_machine.reporter.error( "Could not read file %s."%options['source-file'],
                                          docutils.nodes.literal_block(block_text,block_text), line=lineno)
    return [error]

to be a little more graceful

Alexander Belchenko 10 years, 12 months ago  # | flag

SilverCity won't work with unicode strings.

Chris Jobling 10 years, 5 months ago  # | flag

Update of recipe for new style Docutils directive. When I tried to reuse this recipe in the latest SVN snapshot of Docutils (0.5 [repository]), I discovered that the rst directive code has changed from a functional interface to a class-based interface.

After a bit of fiddling I was able to get it to work. Here is the revised code.

#!/usr/bin/env python

import SilverCity
from docutils import io, nodes, statemachine, utils
from docutils.parsers.rst import Directive, directives
import StringIO

# Modified to new class-based form by C.P. Jobling (C.P.Jobling@Swansea.ac.uk)

class CodeBlock(Directive):

   """
   The code-block directive provides syntax highlighting for blocks
   of code.  It is used with the the following syntax::

   .. code-block:: CPP

      #include &lt;iostream&gt;

      int main( int argc, char* argv[] )
      {
        std::cout &lt;&lt; "Hello world" &lt;&lt; std::endl;
      }

   The directive requires the name of a language supported by SilverCity
   as its only argument.  All code in the indented block following
   the directive will be colourized.  Note that this directive is only
   supported for HTML writers.

   The directive can also be told to include a source file directly::

   .. code-block::
      :language: Python
      :source-file: ../myfile.py

   You cannot both specify a source-file and include code directly.
   """

   final_argument_whitespace = True
   option_spec = {'language' : directives.unchanged,
                  'source-file' : directives.path,}
   has_content = True

   def run(self):
    try:
        language = self.arguments[0]
    except IndexError:
        language = self.options['language']

    if self.content and 'source-file' in self.options:
        error = self.state_machine.reporter.error(
                   "You cannot both specify a source-file and include code directly.",
                   nodes.literal_block(block_text,block_text), line=lineno)
        return [error]

        if not self.content:
             try:
                  self.content = [line.rstrip() for line infile(self.options['source-file'])]
             except KeyError:
                  # source-file was not specified pass
             except IOError:
                  error = self.state_machine.reporter.error(
                       "Could not read file %s." %self.options['source-file'],
                       nodes.literal_block(block_text, block_text), line=self.lineno)
                  return [error]

(comment continued...)

Chris Jobling 10 years, 5 months ago  # | flag

(...continued from previous comment)

        try:
             module = getattr(SilverCity, language)
             generator = getattr(module, language+"HTMLGenerator")
        except AttributeError:
             error = self.state_machine.reporter.error( "No SilverCity lexer found "
                                                        "for language '%s'." %language,
                            nodes.literal_block(block_text, block_text), line=self.lineno )
             return [error]
        io = StringIO.StringIO()
        generator().generate_html( io, '\n'.join(self.content) )
        html = '&lt;div class="code-block"&gt;\n%s\n&lt;/div&gt;\n' % io.getvalue()
        raw = nodes.raw('',html, format = 'html')
        return [raw]

During my researches for this change I discovered that the original recipe has been adapted by the TRAC developers (http://trac.edgewall.org/) where it is used in both code pretty-printing and revision browsing and with an reST directive in their wiki. Their solution is more sophisticated than the one here because it makes use of GNU/Enscript to typeset languages other than those supported by SilverCity. It would be nice if this code could be backported into Docutils.

Another comment is that this recipe could usefully subclass the Raw directive which provides support for inserting code from URLs as well as files. This also provides support for specifying text encoding of the code (although from the above comment, this would appear to be unsupported by SilverCity). Unfortunately, this is beyond my current skills!

Add a comment

Sign in to comment