aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@10gen.com>2014-01-03 18:03:15 -0500
committerA. Jesse Jiryu Davis <jesse@10gen.com>2014-01-03 18:03:15 -0500
commitc18ce238e39c2ccac92da25a6429bdade0db4dff (patch)
tree6f0caa4392c98dc9e10824bb0d1e8530310548d2
parentb6ed501695ea5f8029a228686f84c163c0cdc50b (diff)
downloadmarkdown-c18ce238e39c2ccac92da25a6429bdade0db4dff.tar.gz
markdown-c18ce238e39c2ccac92da25a6429bdade0db4dff.tar.bz2
markdown-c18ce238e39c2ccac92da25a6429bdade0db4dff.zip
Add feature for emphasizing some lines in a code block.
A code blocked headed by “:::python{1,3}” now emphasizes the first and third lines. With fences enabled, ```python{1,3} has the same effect.
-rw-r--r--markdown/extensions/codehilite.py34
-rw-r--r--markdown/extensions/fenced_code.py27
-rw-r--r--tests/test_extensions.py47
3 files changed, 97 insertions, 11 deletions
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index 7a3676b..d05d495 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -31,6 +31,22 @@ try:
except ImportError:
pygments = False
+
+def parse_hl_lines(expr):
+ """Support our syntax for emphasizing certain lines of code.
+
+ expr should be like '{1,2}' to emphasize lines 1 and 2 of a code block.
+ Returns a list of ints, the line numbers to emphasize.
+ """
+ if not expr:
+ return []
+
+ try:
+ return map(int, expr.strip('{}').split(','))
+ except ValueError:
+ return []
+
+
# ------------------ The Main CodeHilite Class ----------------------
class CodeHilite(object):
"""
@@ -49,6 +65,8 @@ class CodeHilite(object):
* css_class: Set class name of wrapper div ('codehilite' by default).
+ * hl_lines: (List of integers) Lines to emphasize, 1-indexed.
+
Low Level Usage:
>>> code = CodeHilite()
>>> code.src = 'some text' # String or anything with a .readline attr.
@@ -59,7 +77,7 @@ class CodeHilite(object):
def __init__(self, src=None, linenums=None, guess_lang=True,
css_class="codehilite", lang=None, style='default',
- noclasses=False, tab_length=4):
+ noclasses=False, tab_length=4, hl_lines=None):
self.src = src
self.lang = lang
self.linenums = linenums
@@ -68,6 +86,7 @@ class CodeHilite(object):
self.style = style
self.noclasses = noclasses
self.tab_length = tab_length
+ self.hl_lines = hl_lines or []
def hilite(self):
"""
@@ -83,7 +102,7 @@ class CodeHilite(object):
self.src = self.src.strip('\n')
if self.lang is None:
- self._getLang()
+ self._parseHeader()
if pygments:
try:
@@ -99,7 +118,8 @@ class CodeHilite(object):
formatter = HtmlFormatter(linenos=self.linenums,
cssclass=self.css_class,
style=self.style,
- noclasses=self.noclasses)
+ noclasses=self.noclasses,
+ hl_lines=self.hl_lines)
return highlight(self.src, lexer, formatter)
else:
# just escape and build markup usable by JS highlighting libs
@@ -118,7 +138,7 @@ class CodeHilite(object):
return '<pre class="%s"><code%s>%s</code></pre>\n'% \
(self.css_class, class_str, txt)
- def _getLang(self):
+ def _parseHeader(self):
"""
Determines language of a code block from shebang line and whether said
line should be removed or left in place. If the sheband line contains a
@@ -131,6 +151,7 @@ class CodeHilite(object):
(e.i.: :::python), line numbering is left in the current state - off
by default.
+ Also parses optional list of highlight lines, like :::python{1,3}
"""
import re
@@ -141,9 +162,10 @@ class CodeHilite(object):
fl = lines.pop(0)
c = re.compile(r'''
- (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons.
+ (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons
(?P<path>(?:/\w+)*[/ ])? # Zero or 1 path
(?P<lang>[\w+-]*) # The language
+ (?P<hl_lines>\{.*?})? # Maybe hl_lines
''', re.VERBOSE)
# search first line for shebang
m = c.search(fl)
@@ -159,6 +181,8 @@ class CodeHilite(object):
if self.linenums is None and m.group('shebang'):
# Overridable and Shebang exists - use line numbers
self.linenums = True
+
+ self.hl_lines = parse_hl_lines(m.group('hl_lines'))
else:
# No match
lines.insert(0, fl)
diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py
index c5aaa6a..64ff769 100644
--- a/markdown/extensions/fenced_code.py
+++ b/markdown/extensions/fenced_code.py
@@ -59,6 +59,20 @@ Optionally backticks instead of tildes as per how github's code block markdown i
~~~~~ # these tildes will not close the block
</code></pre>
+If the codehighlite extension and Pygments are installed, lines can be highlighted:
+
+ >>> text = '''
+ ... ```{1,3}
+ ... line 1
+ ... line 2
+ ... line 3
+ ... ```'''
+ >>> print markdown.markdown(text, extensions=['codehilite', 'fenced_code'])
+ <pre><code><span class="hilight">line 1</span>
+ line 2
+ <span class="hilight">line 3</span>
+ </code></pre>
+
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html>
@@ -77,7 +91,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from . import Extension
from ..preprocessors import Preprocessor
-from .codehilite import CodeHilite, CodeHiliteExtension
+from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
import re
@@ -93,10 +107,10 @@ class FencedCodeExtension(Extension):
class FencedBlockPreprocessor(Preprocessor):
- FENCED_BLOCK_RE = re.compile( \
- r'(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*)\}?)?[ ]*\n(?P<code>.*?)(?<=\n)(?P=fence)[ ]*$',
- re.MULTILINE|re.DOTALL
- )
+ FENCED_BLOCK_RE = re.compile(r'''
+(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*)\}?)?[ ]*(?P<hl_lines>\{.*?})?[ ]*\n
+(?P<code>.*?)(?<=\n)
+(?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE)
CODE_WRAP = '<pre><code%s>%s</code></pre>'
LANG_TAG = ' class="%s"'
@@ -135,7 +149,8 @@ class FencedBlockPreprocessor(Preprocessor):
css_class=self.codehilite_conf['css_class'][0],
style=self.codehilite_conf['pygments_style'][0],
lang=(m.group('lang') or None),
- noclasses=self.codehilite_conf['noclasses'][0])
+ noclasses=self.codehilite_conf['noclasses'][0],
+ hl_lines=parse_hl_lines(m.group('hl_lines')))
code = highliter.hilite()
else:
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index add759a..e918695 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -126,12 +126,33 @@ class TestCodeHilite(unittest.TestCase):
'<pre class="codehilite"><code class="language-python"># A Code Comment'
'</code></pre>')
+ def testHighlightLinesWithColon(self):
+ text = '\t:::Python{2}\n\t#line 1\n\t#line 2\n\t#line 3'
+
+ md = markdown.Markdown(extensions=['codehilite'])
+ if self.has_pygments:
+ self.assertEqual(md.convert(text),
+ '<div class="codehilite"><pre>'
+ '<span class="c">#line 1</span>\n'
+ '<span class="hll"><span class="c">#line 2</span>\n</span>'
+ '<span class="c">#line 3</span>\n'
+ '</pre></div>')
+ else:
+ self.assertEqual(md.convert(text),
+ '<pre class="codehilite"><code class="language-python">#line 1\n'
+ '#line 2\n'
+ '#line 3</code></pre>')
class TestFencedCode(unittest.TestCase):
""" Test fenced_code extension. """
def setUp(self):
self.md = markdown.Markdown(extensions=['fenced_code'])
+ self.has_pygments = True
+ try:
+ import pygments
+ except ImportError:
+ self.has_pygments = False
def testBasicFence(self):
""" Test Fenced Code Blocks. """
@@ -191,6 +212,32 @@ Fenced code block
'~~~~~ # these tildes will not close the block\n'
'</code></pre>')
+ def testFencedCodeWithHighlightLines(self):
+ """ Test Fenced Code with Highlighted Lines. """
+
+ text = '''
+```{1,3}
+line 1
+line 2
+line 3
+```'''
+ md = markdown.Markdown(extensions=[
+ 'codehilite(linenums=None,guess_lang=False)',
+ 'fenced_code'])
+
+ if self.has_pygments:
+ self.assertEqual(md.convert(text),
+ '<div class="codehilite"><pre>'
+ '<span class="hll">line 1\n</span>'
+ 'line 2\n'
+ '<span class="hll">line 3\n</span>'
+ '</pre></div>')
+ else:
+ self.assertEqual(md.convert(text),
+ '<pre class="codehilite"><code>line 1\n'
+ 'line 2\n'
+ 'line 3</code></pre>')
+
class TestHeaderId(unittest.TestCase):
""" Test HeaderId Extension. """