.. code:: python
PLUGINS += ['m-htmlsanity', 'm.code']
+ M_CODE_FILTERS_PRE = []
+ M_CODE_FILTERS_POST = []
For the Python doc theme, it's enough to mention it in :py:`PLUGINS`. The
`m.htmlsanity`_ plugin is available always, no need to mention it explicitly:
See the `m.components <{filename}/plugins/components.rst#code-math-and-graph-figure>`__
plugin for details about code figures using the :rst:`.. code-figure::`
directive.
+
+`Filters`_
+----------
+
+It's possible to supply filters that get applied both before and after a
+code snippet is rendered using the :py:`M_CODE_FILTERS_PRE` and
+:py:`M_CODE_FILTERS_POST` options. It's a dict with keys being the lexer
+name [1]_ and values being filter functions. Each function that gets string as
+an input and is expected to return a modified string. In the following example,
+all CSS code snippets have the hexadecimal color literals annotated with a
+`color swatch <{filename}/css/components.rst#color-swatches-in-code-snippets>`_:
+
+.. code:: py
+ :class: m-console-wrap
+
+ import re
+
+ _css_colors_src = re.compile(r"""<span class="mh">#(?P<hex>[0-9a-f]{6})</span>""")
+ _css_colors_dst = r"""<span class="mh">#\g<hex><span class="m-code-color" style="background-color: #\g<hex>;"></span></span>"""
+
+ M_CODE_FILTERS_POST = {
+ 'CSS': lambda code: _css_colors_src.sub(_css_colors_dst, code)
+ }
+
+.. code-figure::
+
+ .. code:: rst
+
+ .. code:: css
+
+ p.green {
+ color: #3bd267;
+ }
+
+ .. code:: css
+
+ p.green {
+ color: #3bd267;
+ }
+
+In the above case, the filter gets applied globally to all code snippets of
+given language. Sometimes it might be desirable to apply a filter only to
+specific code snippet --- in that case, the dict key is a tuple of
+:py:`(lexer, filter)` where the second item is a filter name. This filter name
+is then referenced from the :rst:`:filters:` option of the :rst:`.. code::` and
+:rst:`.. include::` directives as well as the inline :rst:`:code:` text role.
+Multiple filters can be specified when separated by spaces.
+
+.. code:: py
+
+ M_CODE_FILTERS_PRE = {
+ ('C++', 'codename'): lambda code: code.replace('DirtyMess', 'P300'),
+ ('C++', 'fix_typography'): lambda code: code.replace(' :', ':'),
+ }
+
+.. code-figure::
+
+ .. code:: rst
+
+ .. code:: cpp
+ :filters: codename fix_typography
+
+ for(auto& a : DirtyMess::managedEntities()) {
+ // ...
+ }
+
+ .. code:: cpp
+ :filters: codename fix_typography
+
+ for(auto& a : DirtyMess::managedEntities()) {
+ // ...
+ }
+
+.. [1] In order to have an unique mapping, the filters can't use the aliases
+ --- for example C++ code can be highlighted using either ``c++`` or ``cpp``
+ as a language name and the dict would need to have an entry for each. An unique lexer name is the :py:`name` field used in the particular lexer
+ source, you can also see the names in the language dropdown on the
+ `official website <http://pygments.org/demo/>`_.
import ansilexer
-def _highlight(code, language, options, is_block):
+filters_pre = None
+filters_post = None
+
+def _highlight(code, language, options, *, is_block, filters=[]):
# Use our own lexer for ANSI
if language == 'ansi':
lexer = ansilexer.AnsiLexer()
formatter = ansilexer.HtmlAnsiFormatter(**options)
else:
formatter = HtmlFormatter(nowrap=True, **options)
+
+ global filters_pre
+ # First apply local pre filters, if any
+ for filter in filters:
+ f = filters_pre.get((lexer.name, filter))
+ if f: code = f(code)
+ # Then a global pre filter, if any
+ f = filters_pre.get(lexer.name)
+ if f: code = f(code)
+
parsed = highlight(code, lexer, formatter).rstrip()
if not is_block: parsed.lstrip()
+ global filters_post
+ # First apply local post filters, if any
+ for filter in filters:
+ f = filters_post.get((lexer.name, filter))
+ if f: parsed = f(parsed)
+ # Then a global post filter, if any
+ f = filters_post.get(lexer.name)
+ if f: parsed = f(parsed)
+
return class_, parsed
class Code(Directive):
final_argument_whitespace = True
option_spec = {
'hl_lines': directives.unchanged,
- 'class': directives.class_option
+ 'class': directives.class_option,
+ 'filters': directives.unchanged
}
has_content = True
classes += self.options['classes']
del self.options['classes']
- class_, highlighted = _highlight('\n'.join(self.content), self.arguments[0], self.options, is_block=True)
+ filters = self.options.pop('filters', '').split()
+
+ class_, highlighted = _highlight('\n'.join(self.content), self.arguments[0], self.options, is_block=True, filters=filters)
classes += [class_]
content = nodes.raw('', highlighted, format='html')
return [pre]
class Include(docutils.parsers.rst.directives.misc.Include):
+ option_spec = {
+ **docutils.parsers.rst.directives.misc.Include.option_spec,
+ 'filters': directives.unchanged
+ }
+
def run(self):
"""
Verbatim copy of docutils.parsers.rst.directives.misc.Include.run()
# Not sure why language is duplicated in classes?
if language in classes: classes.remove(language)
- class_, highlighted = _highlight(utils.unescape(text), language, options, is_block=False)
+ filters = options.pop('filters', '').split()
+
+ class_, highlighted = _highlight(utils.unescape(text), language, options, is_block=False, filters=filters)
classes += [class_]
content = nodes.raw('', highlighted, format='html')
return [node], []
code.options = {'class': directives.class_option,
- 'language': directives.unchanged}
+ 'language': directives.unchanged,
+ 'filters': directives.unchanged}
-def register_mcss(**kwargs):
+def register_mcss(mcss_settings, **kwargs):
rst.directives.register_directive('code', Code)
rst.directives.register_directive('include', Include)
rst.roles.register_canonical_role('code', code)
+ global filters_pre, filters_post
+ filters_pre = mcss_settings.get('M_CODE_FILTERS_PRE', {})
+ filters_post = mcss_settings.get('M_CODE_FILTERS_POST', {})
+
# Below is only Pelican-specific functionality. If Pelican is not found, these
# do nothing.
-register = register_mcss # for Pelican
+def _pelican_configure(pelicanobj):
+ settings = {}
+ for key in ['M_CODE_FILTERS_PRE', 'M_CODE_FILTERS_POST']:
+ if key in pelicanobj.settings: settings[key] = pelicanobj.settings[key]
+
+ register_mcss(mcss_settings=settings)
+
+def register(): # for Pelican
+ import pelican.signals
+
+ pelican.signals.initialized.connect(_pelican_configure)
<pre class="m-code"> <span class="n">nope</span><span class="p">();</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span></pre>
+<section id="filters">
+<h2><a href="#filters">Filters</a></h2>
+<p>Applied by default, adding typographically correct spaces before and a color
+swatch after --- and for inline as well: <code class="m-code"><span class="nt">p</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="mh">#ff3366<span class="m-code-color" style="background-color: #ff3366;"></span></span><span class="p">;</span> <span class="p">}</span></code></p>
+<pre class="m-code"><span class="nt">p</span> <span class="p">{</span>
+ <span class="k">color</span><span class="p">:</span> <span class="mh">#ff3366<span class="m-code-color" style="background-color: #ff3366;"></span></span><span class="p">;</span>
+<span class="p">}</span></pre>
+<p>Applied explicity and then by default --- and for inline as well:
+<code class="css-filtered m-code"><span class="nt">p</span> <span class="p">{</span> <span class="k">color</span><span class="p">:</span> <span class="mh">#3bd267<span class="m-code-color" style="background-color: #3bd267;"></span></span><span class="p">;</span> <span class="p">}</span></code></p>
+<pre class="m-code"><span class="nt">p</span> <span class="p">{</span>
+ <span class="k">color</span><span class="p">:</span> <span class="mh">#3bd267<span class="m-code-color" style="background-color: #3bd267;"></span></span><span class="p">;</span>
+<span class="p">}</span></pre>
+<p>Includes too:</p>
+<pre class="m-code"><span class="nt">p</span> <span class="p">{</span>
+ <span class="k">color</span><span class="p">:</span> <span class="mh">#3bd267<span class="m-code-color" style="background-color: #3bd267;"></span></span><span class="p">;</span>
+<span class="p">}</span></pre>
+</section>
<!-- /content -->
</div>
</div>
nope();
return false;
}
+
+`Filters`_
+==========
+
+.. role:: css(code)
+ :language: css
+
+Applied by default, adding typographically correct spaces before and a color
+swatch after --- and for inline as well: :css:`p{ color:#ff3366; }`
+
+.. code:: css
+
+ p{
+ color:#ff3366;
+ }
+
+.. role:: css-filtered(code)
+ :language: css
+ :filters: lowercase replace_colors
+
+Applied explicity and then by default --- and for inline as well:
+:css-filtered:`P{ COLOR:#C0FFEE; }`
+
+.. code:: css
+ :filters: lowercase replace_colors
+
+ P{
+ COLOR:#C0FFEE;
+ }
+
+Includes too:
+
+.. include:: style.css
+ :code: css
+ :filters: lowercase replace_colors
--- /dev/null
+P{
+ COLOR:#C0FFEE;
+}
# DEALINGS IN THE SOFTWARE.
#
+import re
+
from . import PelicanPluginTestCase
+_css_colors_src = re.compile(r"""<span class="mh">#(?P<hex>[0-9a-f]{6})</span>""")
+_css_colors_dst = r"""<span class="mh">#\g<hex><span class="m-code-color" style="background-color: #\g<hex>;"></span></span>"""
+
+def _add_color_swatch(str):
+ return _css_colors_src.sub(_css_colors_dst, str)
+
class Code(PelicanPluginTestCase):
def __init__(self, *args, **kwargs):
super().__init__(__file__, '', *args, **kwargs)
# Need Source Code Pro for code
'M_CSS_FILES': ['https://fonts.googleapis.com/css?family=Source+Code+Pro:400,400i,600%7CSource+Sans+Pro:400,400i,600,600i',
'static/m-dark.css'],
- 'PLUGINS': ['m.htmlsanity', 'm.code']
+ 'PLUGINS': ['m.htmlsanity', 'm.code'],
+ 'M_CODE_FILTERS_PRE': {
+ 'CSS': lambda str: str.replace(':', ': ').replace('{', ' {'),
+ ('CSS', 'lowercase'): lambda str: str.lower(),
+ ('CSS', 'uppercase'): lambda str: str.upper(), # not used
+ },
+ 'M_CODE_FILTERS_POST': {
+ 'CSS': _add_color_swatch,
+ ('CSS', 'replace_colors'): lambda str: str.replace('#c0ffee', '#3bd267')
+ },
})
self.assertEqual(*self.actual_expected_contents('page.html'))
# DEALINGS IN THE SOFTWARE.
#
+import re
import shutil
import logging
logging.warning("LaTeX not found, fallback to rendering math as code")
M_MATH_RENDER_AS_CODE = True
+# Used by the m.code plugin docs
+
+_css_colors_src = re.compile(r"""<span class="mh">#(?P<hex>[0-9a-f]{6})</span>""")
+_css_colors_dst = r"""<span class="mh">#\g<hex><span class="m-code-color" style="background-color: #\g<hex>;"></span></span>"""
+
+M_CODE_FILTERS_PRE = {
+ ('C++', 'codename'): lambda code: code.replace('DirtyMess', 'P300::V1'),
+ ('C++', 'fix_typography'): lambda code: code.replace(' :', ':'),
+}
+M_CODE_FILTERS_POST = {
+ 'CSS': lambda code: _css_colors_src.sub(_css_colors_dst, code)
+}
+
DIRECT_TEMPLATES = ['archives']
PAGE_URL = '{slug}/'