"""Pandoc filter that transforms mintmod commands."""
from os import environ
import panflute as pf
from slugify import slugify
from innoconv_mintmod.errors import ParseError
from innoconv_mintmod.constants import (
REGEX_PATTERNS, ELEMENT_CLASSES, EXERCISE_CMDS_ENVS)
from innoconv_mintmod.utils import (
log, destringify, parse_cmd, parse_nested_args)
from innoconv_mintmod.mintmod_filter.environments import Environments
from innoconv_mintmod.mintmod_filter.commands import Commands
from innoconv_mintmod.mintmod_filter.math import handle_math
[docs]class MintmodFilterAction:
"""The Pandoc filter is defined in this class."""
def __init__(self, debug=False):
self._debug = debug
self._commands = Commands()
self._environments = Environments()
[docs] def filter(self, elem, doc):
"""
Receive document elements.
This method receives document elements from Pandoc and delegates
handling of simple subtitutions, mintmod commands and
.ments.
:param elem: Element to handle
:type elem: :class:`panflute.base.Element`
:param doc: Document
:type doc: :class:`panflute.elements.Doc`
"""
if elem is None:
raise ValueError('elem must not be None!')
if doc is None:
raise ValueError('doc must not be None!')
# simple command subtitutions in Math environments
if isinstance(elem, pf.Math):
return handle_math(elem)
elif hasattr(elem, 'format') and elem.format == 'latex':
# block commands and environments
if isinstance(elem, pf.RawBlock):
cmd_name, cmd_args = parse_cmd(elem.text)
try:
if cmd_name == 'begin':
return self._handle_environment(elem)
return self._handle_command(cmd_name, cmd_args, elem)
except TypeError as err:
self._handle_typeerror(err, cmd_name, cmd_args, elem)
# inline commands (no inline environments!)
elif isinstance(elem, pf.RawInline):
cmd_name, cmd_args = parse_cmd(elem.text)
try:
return self._handle_command(cmd_name, cmd_args, elem)
except TypeError as err:
self._handle_typeerror(err, cmd_name, cmd_args, elem)
return None # element unchanged
def _handle_command(self, cmd_name, cmd_args, elem):
"""Parse and handle mintmod commands."""
if (bool(environ.get('INNOCONV_REMOVE_EXERCISES', False)) and
cmd_name in EXERCISE_CMDS_ENVS):
return []
function_name = 'handle_%s' % slugify(cmd_name)
func = getattr(self._commands, function_name, None)
if callable(func):
return func(cmd_args, elem)
if (not bool(environ.get('INNOCONV_IGNORE_EXERCISES', False)) or
cmd_name not in EXERCISE_CMDS_ENVS):
if len(cmd_name) == 1:
log("1-character-command '{}': {}".format(cmd_name, elem),
level='WARNING')
log("Parent: {}".format(elem.parent))
else:
log("Could not handle command %s." % cmd_name, level='WARNING')
if self._debug:
return self._unknown_command_debug(cmd_name, elem)
return None
@staticmethod
def _unknown_command_debug(cmd_name, elem):
"""Handle unknown latex commands.
Output visual feedback about the unknown command.
"""
classes = ELEMENT_CLASSES['DEBUG_UNKNOWN_CMD'] + [slugify(cmd_name)]
msg_prefix = pf.Strong(*destringify('Unhandled command:'))
if isinstance(elem, pf.Block):
div = pf.Div(classes=classes)
div.content.extend([pf.Para(msg_prefix), pf.CodeBlock(elem.text)])
return div
# RawInline
span = pf.Span(classes=classes)
span.content.extend([msg_prefix, pf.Space(), pf.Code(elem.text)])
return span
def _handle_environment(self, elem):
"""Parse and handle mintmod environments."""
match = REGEX_PATTERNS['ENV'].search(elem.text)
if match is None:
raise ParseError(
'Could not parse LaTeX environment: %s...' % elem.text[:50])
env_name = match.group('env_name')
inner_code = match.groups()[1]
if (bool(environ.get('INNOCONV_REMOVE_EXERCISES', False)) and
env_name in EXERCISE_CMDS_ENVS):
return []
# Parse optional arguments
env_args, rest = parse_nested_args(inner_code)
function_name = 'handle_%s' % slugify(env_name)
func = getattr(self._environments, function_name, None)
if callable(func):
return func(rest, env_args, elem)
if (not bool(environ.get('INNOCONV_IGNORE_EXERCISES', False)) or
env_name not in EXERCISE_CMDS_ENVS):
log("Could not handle environment %s." % env_name, level='WARNING')
if self._debug:
return self._unknown_environment_debug(env_name, elem)
return None
@staticmethod
def _unknown_environment_debug(env_name, elem):
"""Handle unknown latex environment.
Output visual feedback about the unknown environment.
"""
classes = ELEMENT_CLASSES['DEBUG_UNKNOWN_ENV'] + [slugify(env_name)]
div = pf.Div(classes=classes)
div.content.extend([
pf.Para(pf.Strong(*destringify('Unhandled environment:'))),
pf.CodeBlock(elem.text),
])
return div
@staticmethod
def _handle_typeerror(err, name, args, elem):
log('TypeError at command name={} args={} elem={}: {}'.format(
name, args, elem.__class__.__name__, err))
import traceback
traceback.print_tb(err.__traceback__)