1+ # Define a new directive `code-block` (aliased as `sourcecode`) that uses the
2+ # `pygments` source highlighter to render code in color.
3+ #
4+ # Incorporates code from the `Pygments`_ documentation for `Using Pygments in
5+ # ReST documents`_ and `Octopress`_.
6+ #
7+ # .. _Pygments: http://pygments.org/
8+ # .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/
9+ # .. _Octopress: http://octopress.org/
10+
11+ import re
12+ import os
13+ import md5
14+ import __main__
15+
16+ # Absolute path to pygments cache dir
17+ PYGMENTS_CACHE_DIR = os .path .abspath (os .path .join (os .path .dirname (__main__ .__file__ ),'../../.pygments-cache' ))
18+
19+ # Ensure cache dir exists
20+ if not os .path .exists (PYGMENTS_CACHE_DIR ):
21+ os .makedirs (PYGMENTS_CACHE_DIR )
22+
23+ from pygments .formatters import HtmlFormatter
24+
25+ from docutils import nodes
26+ from docutils .parsers .rst import directives ,Directive
27+
28+ from pygments import highlight
29+ from pygments .lexers import get_lexer_by_name ,TextLexer
30+
31+ class Pygments (Directive ):
32+ """ Source code syntax hightlighting.
33+ """
34+ required_arguments = 1
35+ optional_arguments = 0
36+ final_argument_whitespace = True
37+ string_opts = ['title' ,'url' ,'caption' ]
38+ option_spec = dict ([(key ,directives .unchanged )for key in string_opts ])
39+ has_content = True
40+
41+ def run (self ):
42+ self .assert_has_content ()
43+ try :
44+ lexer_name = self .arguments [0 ]
45+ lexer = get_lexer_by_name (lexer_name )
46+ except ValueError :
47+ # no lexer found - use the text one instead of an exception
48+ lexer_name = 'text'
49+ lexer = TextLexer ()
50+ formatter = HtmlFormatter ()
51+
52+ # Construct cache filename
53+ cache_file = None
54+ content_text = u'\n ' .join (self .content )
55+ cache_file_name = '%s-%s.html' % (lexer_name ,md5 .new (content_text ).hexdigest ())
56+ cached_path = os .path .join (PYGMENTS_CACHE_DIR ,cache_file_name )
57+
58+ # Look for cached version, otherwise parse
59+ if os .path .exists (cached_path ):
60+ cache_file = open (cached_path ,'r' )
61+ parsed = cache_file .read ()
62+ else :
63+ parsed = highlight (content_text ,lexer ,formatter )
64+
65+ # Strip pre tag and everything outside it
66+ pres = re .compile ("<pre>(.+)<\/pre>" ,re .S )
67+ stripped = pres .search (parsed ).group (1 )
68+
69+ # Create tabular code with line numbers
70+ table = '<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers">'
71+ lined = ''
72+ for idx ,line in enumerate (stripped .splitlines (True )):
73+ table += '<span class="line-number">%d</span>\n ' % (idx + 1 )
74+ lined += '<span class="line">%s</span>' % line
75+ table += '</pre></td><td class="code"><pre><code class="%s">%s</code></pre></td></tr></table></div>' % (lexer_name ,lined )
76+
77+ # Add wrapper with optional caption and link
78+ code = '<figure class="code">'
79+ if self .options :
80+ caption = ('<span>%s</span>' % self .options ['caption' ])if 'caption' in self .options else ''
81+ title = self .options ['title' ]if 'title' in self .options else 'link'
82+ link = ('<a href="%s">%s</a>' % (self .options ['url' ],title ))if 'url' in self .options else ''
83+
84+ if caption or link :
85+ code += '<figcaption>%s %s</figcaption>' % (caption ,link )
86+ code += '%s</figure>' % table
87+
88+ # Write cache
89+ if cache_file is None :
90+ cache_file = open (cached_path ,'w' )
91+ cache_file .write (parsed )
92+ cache_file .close ()
93+
94+ return [nodes .raw ('' ,code ,format = 'html' )]
95+
96+ directives .register_directive ('code-block' ,Pygments )
97+ directives .register_directive ('sourcecode' ,Pygments )