tools.py 5.37 KB

from os import path
import subprocess
import logging
import re

import yaml
from markdown import markdown
# import mistune
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html

# setup logger for this module
logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Setup markdown renderer with support for math in LaTeX notation.
# ---------------------------------------------------------------------------
# class MathBlockGrammar(mistune.BlockGrammar):
#     block_math = re.compile(r"^\$\$(.*?)\$\$", re.DOTALL)
#     latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}", re.DOTALL)


# class MathBlockLexer(mistune.BlockLexer):
#     default_rules = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_rules

#     def __init__(self, rules=None, **kwargs):
#         if rules is None:
#             rules = MathBlockGrammar()
#         super(MathBlockLexer, self).__init__(rules, **kwargs)

#     def parse_block_math(self, m):
#         """Parse a $$math$$ block"""
#         self.tokens.append({
#             'type': 'block_math',
#             'text': m.group(1)
#         })

#     def parse_latex_environment(self, m):
#         self.tokens.append({
#             'type': 'latex_environment',
#             'name': m.group(1),
#             'text': m.group(2)
#         })


# class MathInlineGrammar(mistune.InlineGrammar):
#     math = re.compile(r"^\$(.+?)\$", re.DOTALL)
#     block_math = re.compile(r"^\$\$(.+?)\$\$", re.DOTALL)
#     text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)')


# class MathInlineLexer(mistune.InlineLexer):
#     default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules

#     def __init__(self, renderer, rules=None, **kwargs):
#         if rules is None:
#             rules = MathInlineGrammar()
#         super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)

#     def output_math(self, m):
#         return self.renderer.inline_math(m.group(1))

#     def output_block_math(self, m):
#         return self.renderer.block_math(m.group(1))


# class MarkdownWithMath(mistune.Markdown):
#     def __init__(self, renderer, **kwargs):
#         if 'inline' not in kwargs:
#             kwargs['inline'] = MathInlineLexer
#         if 'block' not in kwargs:
#             kwargs['block'] = MathBlockLexer
#         super().__init__(renderer, **kwargs)

#     def output_block_math(self):
#         return self.renderer.block_math(self.token['text'])

#     def output_latex_environment(self):
#         return self.renderer.latex_environment(self.token['name'], self.token['text'])


# class HighlightRenderer(mistune.Renderer):
#     def block_code(self, code, lang=None):
#         if lang is None:
#             return f'\n<pre><code>{mistune.escape(code)}</code></pre>\n'
#         else:
#             lexer = get_lexer_by_name(lang, stripall=True)
#             formatter = html.HtmlFormatter()
#             return highlight(code, lexer, formatter)

#     def image(self, src, title, text):
#         src = 'FIXME'  # FIXME
#         return super().image(src, title, text)

#     def block_math(self, text):
#         return r'\[ %s \]' % text

#     def inline_math(self, text):
#         return r'\( %s \)' % text


# renderer = HighlightRenderer(hard_wrap=True)
# markdown = mistune.Markdown(renderer=renderer)


# ---------------------------------------------------------------------------
# load data from yaml file
# ---------------------------------------------------------------------------
def load_yaml(filename, default=None):
    try:
        f = open(path.expanduser(filename), 'r', encoding='utf-8')
    except IOError:
        logger.error(f'Can\'t open file "{filename}"')
        return default
    else:
        with f:
            try:
                return yaml.load(f)
            except yaml.YAMLError as e:
                mark = e.problem_mark
                logger.error('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1))
                return default

# ---------------------------------------------------------------------------
# Runs a script and returns its stdout parsed as yaml, or None on error.
# ---------------------------------------------------------------------------
def run_script(script, stdin='', timeout=5):
    script = path.expanduser(script)
    try:
        p = subprocess.run([script],
            input=stdin,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            timeout=timeout,
            )
    except FileNotFoundError:
        logger.error(f'Script "{script}" not found.')
    except PermissionError:
        logger.error(f'Script "{script}" not executable. Wrong permissions?')
    except subprocess.TimeoutExpired:
        logger.error(f'Timeout {timeout}s exceeded while running "{script}".')
    else:
        if p.returncode != 0:
            logger.error(f'Script "{script}" returned error code {p.returncode}.')
        else:
            try:
                output = yaml.load(p.stdout)
            except:
                logger.error('Error parsing yaml output of "{script}"')
            else:
                return output

# ---------------------------------------------------------------------------
def md_to_html(text, q=None):
    return markdown(text)