ActiveState Code

Recipe 252170: Syntax-highlighted code blocks for docutils


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
 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')

Discussion

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.

Comments

  1. 1. At 2:12 p.m. on 1 apr 2005, Michael Lerner said:

    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...)

  2. 2. At 2:12 p.m. on 1 apr 2005, Michael Lerner said:

    (...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.

  3. 3. At 2:19 p.m. on 1 apr 2005, Michael Lerner said:

    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

  4. 4. At 4:16 a.m. on 6 mar 2006, Alexander Belchenko said:

    SilverCity won't work with unicode strings.

  5. 5. At 10:03 a.m. on 14 sep 2006, Chris Jobling said:

    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...)

  6. 6. At 10:03 a.m. on 14 sep 2006, Chris Jobling said:

    (...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!

Sign in to comment