Source

TracMathPlugin / tracmath / tracmath.py

rlo...@7322e99d-… 0516191 

rlo...@7322e99d-… 278a4dd 
rlo...@7322e99d-… 0516191 

rlo...@7322e99d-… 98c6821 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 48937e0 
Kamil Kisiel 233dced 
rlo...@7322e99d-… 3d3ee82 
mitar 59438ef 

Kamil Kisiel aa32c58 
rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… fd7f1c8 
Kamil Kisiel aa32c58 
rlo...@7322e99d-… 3d3ee82 

mitar 59438ef 

rlo...@7322e99d-… 3d3ee82 


rlo...@7322e99d-… 0516191 
rlo...@7322e99d-… 3d3ee82 










rlo...@7322e99d-… 98c6821 






rlo...@7322e99d-… 278a4dd 
rlo...@7322e99d-… 3d3ee82 

rlo...@7322e99d-… fd7f1c8 
rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… 98c6821 
Kamil Kisiel 3006446 
rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… fd7f1c8 
rlo...@7322e99d-… 278a4dd 



Kamil Kisiel 00ee220 







rlo...@7322e99d-… 278a4dd 



rlo...@7322e99d-… 3d3ee82 





rlo...@7322e99d-… 278a4dd 
















Kamil Kisiel 3006446 

rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 3006446 




















Kamil Kisiel 2ab4189 
Kamil Kisiel 3006446 





Kamil Kisiel 336545d 
Kamil Kisiel 233dced 


rlo...@7322e99d-… 3d3ee82 


rlo...@7322e99d-… 278a4dd 





Kamil Kisiel 336545d 
rlo...@7322e99d-… 98c6821 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 2ab4189 
rlo...@7322e99d-… 3d3ee82 


rlo...@7322e99d-… 98c6821 
Kamil Kisiel 2ab4189 
rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… 98c6821 






Kamil Kisiel 3400be4 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 2ab4189 
Kamil Kisiel 985fcf5 
Kamil Kisiel f97d25a 
Kamil Kisiel 88befb5 
Kamil Kisiel 233dced 
rlo...@7322e99d-… 3d3ee82 

mitar 59438ef 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 985fcf5 



Kamil Kisiel f97d25a 
Kamil Kisiel 233dced 

rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… 98c6821 
mitar 59438ef 
rlo...@7322e99d-… 98c6821 
Kamil Kisiel 3006446 
Kamil Kisiel 48937e0 


rlo...@7322e99d-… 3d3ee82 
rlo...@7322e99d-… 278a4dd 



rlo...@7322e99d-… 0516191 
Kamil Kisiel 3006446 
Kamil Kisiel 48937e0 
Kamil Kisiel 2ab4189 
rlo...@7322e99d-… 98c6821 

Kamil Kisiel 2ab4189 
Kamil Kisiel 48937e0 

rlo...@7322e99d-… 98c6821 
Kamil Kisiel 48937e0 






rlo...@7322e99d-… 98c6821 
Kamil Kisiel 3006446 

rlo...@7322e99d-… 98c6821 
Kamil Kisiel 3006446 
mitar c1e24bf 
Kamil Kisiel 3006446 



Kamil Kisiel 985fcf5 
rlo...@7322e99d-… 98c6821 
Kamil Kisiel 3006446 
Kamil Kisiel f97d25a 
rlo...@7322e99d-… 278a4dd 
Kamil Kisiel 2ab4189 
Kamil Kisiel 3006446 






Kamil Kisiel 985fcf5 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel f97d25a 



mitar c1e24bf 

Kamil Kisiel 2ab4189 
mitar c1e24bf 
rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 3006446 

rlo...@7322e99d-… 3d3ee82 
Kamil Kisiel 3006446 

mitar 59438ef 

Kamil Kisiel 3006446 
mitar 59438ef 




""" TracMath - A trac plugin that renders latex formulas within a wiki page.

This has currently been tested only on trac 0.10.4 and 0.11.
"""

import codecs
import re
import os
import os.path

from genshi.builder import tag

from trac.core import Component, implements
from trac.wiki.api import IWikiMacroProvider
from trac.wiki.api import IWikiSyntaxProvider
from trac.mimeview.api import IHTMLPreviewRenderer
from trac.web import IRequestHandler
from trac.util import escape
from trac.util.text import to_unicode
from trac.util.translation import _
from trac import mimeview

__author__ = 'Reza Lotun'
__author_email__ = 'rlotun@gmail.com'

tex_preamble = r"""
\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{bm}
\pagestyle{empty}
\begin{document}
"""

rePNG = re.compile(r'.+png$')
reGARBAGE = [
             re.compile(r'.+aux$'),
             re.compile(r'.+log$'),
             re.compile(r'.+tex$'),
             re.compile(r'.+dvi$'),
            ]
reLABEL = re.compile(r'\\label\{(.*?)\}')

class TracMathPlugin(Component):
    implements(IWikiMacroProvider, IHTMLPreviewRenderer, IRequestHandler, IWikiSyntaxProvider)

    def __init__(self):
        self._load_config()

    # IWikiSyntaxProvider methods
    #   stolen from http://trac-hacks.org/ticket/248

    def get_wiki_syntax(self):
        if self.use_dollars:
            yield (r"\$\$(?P<displaymath>.*?)\$\$", self._format_math_block)
            yield (r"\$(?P<latex>.*?)\$", self._format_math_inline)

    def _format_math_block(self, formatter, ns, match):
        return "<blockquote>" + self.expand_macro(formatter, 'latex', ns) + "</blockquote>"

    def _format_math_inline(self, formatter, ns, match):
        return self.expand_macro(formatter, 'latex', ns)

    def get_link_resolvers(self):
        return []

    # IWikiMacroProvider methods
    def get_macros(self):
        yield 'latex'

    def get_macro_description(self, name):
        if name == 'latex':
            return """
            This plugin allows embedded equations in Trac markup.
            Basically a port of http://www.amk.ca/python/code/mt-math to Trac.

            Simply use
            {{{
              {{{
              #!latex
              [latex code]
              }}}
            }}}
            for a block of LaTeX code.

            If `use_dollars` is enabled in `trac.ini`, then you can also use
            `$[latex formula]$` for inline math or `$$[latex formula]$$` for
            display math.
            """
    def expand_macro(self, formatter, name, content):
        return self._internal_render(formatter.req, name, content)

    # IHTMLPreviewRenderer methods
    def get_quality_ratio(self, mimetype):
        if mimetype in ('application/tracmath'):
            return 2
        return 0

    def render(self, req, mimetype, content, filename=None, url=None):
        text = hasattr(content, 'read') and content.read() or content
        return self._internal_render(req, 'latex', text)

    # IRequestHandler methods
    def match_request(self, req):
        return req.path_info.startswith('/tracmath')

    def process_request(self, req):
        pieces = [item for item in req.path_info.split('/tracmath') if item]

        if pieces:
            pieces = [item for item in pieces[0].split('/') if item]
            if pieces:
                name = pieces[0]
                img_path = os.path.join(self.cache_dir, name)
                return req.send_file(img_path,
                        mimeview.get_mimetype(img_path))
        return

    # Internal implementation
    def _internal_render(self, req, name, content):
        from hashlib import sha1
        from subprocess import Popen, PIPE
        import shlex

        if not name == 'latex':
            return 'Unknown macro %s' % (name)

        label = None
        for line in content.split("\n"):
            m = reLABEL.match(content)
            if m:
                label = m.group(1)

        key = sha1(content.encode('utf-8')).hexdigest()

        imgname = key + '.png'
        imgpath = os.path.join(self.cache_dir, imgname)

        if not os.path.exists(imgpath):

            texname = key + '.tex'
            texpath = os.path.join(self.cache_dir, texname)

            try:
                f = codecs.open(texpath, encoding='utf-8', mode='w')
                f.write(tex_preamble)
                f.write(content)
                f.write('\end{document}')
                f.close()
            except Exception, e:
                return self._show_err("Problem creating tex file: %s" % (e))

            os.chdir(self.cache_dir)
            cmd = str("%s -interaction nonstopmode %s" % (self.latex_cmd, texname))
            self.log.debug("Running latex command: " + cmd)
            latex_proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
            (out, err) = latex_proc.communicate()

            if len(err) and len(out):
                return self._show_err('Unable to call: %s\n%s%s' % (cmd, out, err))

            cmd = str("".join([self.dvipng_cmd,
                               " -T tight -z %s " % self.compression,
                               "-x %s -bg Transparent " % self.mag_factor,
                               "-o %s %s" % (imgname, key + '.dvi')]))
            self.log.debug("Running dvipng command: " + cmd)
            dvipng_proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
            (out, err) = dvipng_proc.communicate()

            if len(err) and len(out):
                return self._show_err('Unable to call: %s\n%s%s' % (cmd, out, err))

            self._manage_cache()
        else:
            # Touch the file to keep it live in the cache
            os.utime(imgpath, None)

        result = '<img src="%s/tracmath/%s" alt="%s" />' % (req.base_url, imgname, content)
        if label:
            result = '<a name="%s">(%s)<a/>&nbsp;%s' % (label, label, result)
        return result

    def _manage_cache(self):
        png_files = []
        for name in os.listdir(self.cache_dir):
            for ext in reGARBAGE:
                if ext.match(name):
                    os.unlink(os.path.join(self.cache_dir, name))
            if name.endswith('.png'):
                png_files.append(name)

        if len(png_files) > self.max_png:
            stats = sorted((os.stat(os.path.join(self.cache_dir, name)).st_mtime, name) 
                           for name in png_files)
            # We don't delete the last max_png elements, so remove them from the list
            del stats[-self.max_png:]
            for stat in stats:
                os.unlink(os.path.join(self.cache_dir, stat[1]))

    def _load_config(self):
        """Load the tracmath trac.ini configuration."""

        # defaults
        tmp = 'tmcache'
        latex = '/usr/bin/latex'
        dvipng = '/usr/bin/dvipng'
        max_png = 500
        mag_factor = 1200
        compression = 6

        if 'tracmath' not in self.config.sections():
            self.log.warn("The [tracmath] section is not configured in trac.ini. Using defaults.")

        self.cache_dir = self.config.get('tracmath', 'cache_dir') or tmp
        self.latex_cmd = self.config.get('tracmath', 'latex_cmd') or latex
        self.dvipng_cmd = self.config.get('tracmath', 'dvipng_cmd') or dvipng
        self.max_png = self.config.get('tracmath', 'max_png') or max_png
        self.max_png = int(self.max_png)
        self.use_dollars = self.config.get('tracmath', 'use_dollars') or "False"
        self.use_dollars = self.use_dollars.lower() in ("true", "on", "enabled")
        self.mag_factor = self.config.get('tracmath', 'mag_factor') or mag_factor
        self.compression = self.config.get('tracmath', 'compression') or compression

        if not os.path.exists(self.latex_cmd):
            self.log.error('Could not find latex binary at ' + self.latex_cmd)
        if not os.path.exists(self.dvipng_cmd):
            self.log.error('Could not find dvipng binary at ' + self.dvipng_cmd)
        if not os.path.isabs(self.cache_dir):
            self.cache_dir = os.path.join(self.env.path, self.cache_dir)
        if not os.path.exists(self.cache_dir):
            os.mkdir(self.cache_dir, 0755)

        #TODO: check correct values.
        return ''

    def _show_err(self, msg):
        """Display msg in an error box, using Trac style."""
        if isinstance(msg, str):
            msg = to_unicode(msg)
        self.log.error(msg)
        if isinstance(msg, unicode):
            msg = tag.pre(escape(msg))
        return tag.div(
                tag.strong(_("TracMath macro processor has detected an error. "
                             "Please fix the problem before continuing.")),
                msg, class_="system-message")
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.