Commits

Anonymous committed 98c6821

TracMathPlugin:

Contributed some changes and suggestions by ofv@wanadoo.es.
Includes:
- parsing of trac.ini for [tracmath]
including options for cache_dir, latex_cmd, dvipng_cmd, max_png
- a simple cache manager
- unicode won't cause a traceback now

closes #1676

Comments (0)

Files changed (1)

tracmath/tracmath.py

+import codecs
 import re
 import sha
 from cStringIO import StringIO
 import os
 import sys
-import tempfile
 
 from genshi.builder import tag
 
 \begin{document}
 """
 
-cacheDirectory = '/tmp'
+rePNG = re.compile(r'.+png$')
+reGARBAGE = [
+             re.compile(r'.+aux$'),
+             re.compile(r'.+log$'),
+             re.compile(r'.+tex$'),
+             re.compile(r'.+dvi$'),
+            ]
 
 class TracMathPlugin(Component):
     implements(IWikiMacroProvider, IHTMLPreviewRenderer, IRequestHandler)
 
+    def __init__(self):
+        self.load_config()
+
+    def load_config(self):
+        """Load the tracmath trac.ini configuration."""
+
+        # defaults
+        tmp = '/tmp/tracmath'
+        latex = '/usr/bin/latex'
+        dvipng = '/usr/bin/dvipng'
+        max_png = 500
+
+        if 'tracmath' not in self.config.sections():
+            pass    # TODO: do something
+
+        self.cacheDirectory = 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)
+                                                        
+        if not os.path.exists(self.cacheDirectory):
+            os.mkdir(self.cacheDirectory, 0777)
+
+        #TODO: check correct values.
+        return ''
+
+    def show_err(self, msg):
+        """Display msg in an error box, using Trac style."""
+        buf = StringIO()
+        buf.write('<div id="content" class="error"><div class="message"> \n\
+                   <strong>TracMath macro processor has detected an \n\
+                   error. Please fix the problem before continuing. \n\
+                   </strong> <pre>%s</pre> \n\
+                   </div></div>' % escape(msg))
+        self.log.error(msg)
+        return buf
 
     # IWikiMacroProvider methods
     def get_macros(self):
         if name == 'latex':
             return 'LaTeX'
 
-    # WANT TO USE THIS!
-    #def expand_macro(formatter, name, content):
-    #    """ Called by formatter when rendering the parsed wiki text """
-    #    pass
-
-    def render_macro(self, req, name, content):
+    def internal_render(self, req, name, content):
         if not name == 'latex':
             return 'Unknown macro %s' % (name)
 
-        key = sha.new(content).hexdigest()
+        key = sha.new(content.encode('utf-8')).hexdigest()
+
         imgname = key + '.png'
-        imgpath = os.path.join(cacheDirectory, imgname)
+        imgpath = os.path.join(self.cacheDirectory, imgname)
 
         if not os.path.exists(imgpath):
-            fd, texfn = tempfile.mkstemp(suffix='.tex', prefix='tracmath')
 
-            f = os.fdopen(fd, 'w+')
+            texname = key + '.tex'
+            texpath = os.path.join(self.cacheDirectory, texname)
 
-            f.write(tex_preamble)
-            f.write(content)
-            f.write('\end{document}')
+            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))
 
-            f.close()
-
-            os.chdir(cacheDirectory)
-            cmd = '/usr/bin/latex %s' % texfn
+            os.chdir(self.cacheDirectory)
+            cmd = self.latex_cmd + ' %s' % texname
             pin, pout, perr = os.popen3(cmd)
             pin.close()
             out = pout.read()
             if len(err) and len(out):
                 return 'Unable to call: %s %s %s' % (cmd, out, err)
 
-            tex_base = os.path.basename(texfn.replace('.tex', ''))
-            tex_path = os.path.dirname(texfn)
-
-            cmd = "/usr/bin/dvipng -T tight -x 1200 -z 9 -bg Transparent " \
-                    + "-o %s %s" % (imgname, tex_base)
+            cmd = "".join([self.dvipng_cmd,
+                    " -T tight -x 1200 -z 9 -bg Transparent ",
+                    "-o %s %s" % (imgname, key + '.dvi')])
             pin, pout, perr = os.popen3(cmd)
             pin.close()
             out = pout.read()
             err = perr.read()
 
-        #if len(err) and len(out):
-        #    return 'Unable to call: %s %s %s' % (cmd, out, err)
+            if len(err) and len(out):
+                pass # TODO: check for real errors
+
+            self.manage_cache()
 
         return '<img src="%s/tracmath/%s" />' % (req.base_url, imgname)
+    
+    def manage_cache(self):
+
+        ftime = []
+
+        for name in os.listdir(self.cacheDirectory):
+
+            for ext in reGARBAGE:
+                if ext.match(name):
+                    os.unlink(os.path.join(self.cacheDirectory, name))
+
+            if rePNG.match(name):
+                info = os.stat(os.path.join(self.cacheDirectory, name))
+                ftime.append((info[7], name))
+
+        ftime.sort(reverse=True)
+
+        numfiles = len(ftime)
+        files = (name for _, name in ftime)
         
+        while numfiles > self.max_png:
+            name = files.next()
+            os.unlink(os.path.join(self.cacheDirectory, name))
+            numfiles -= 1
+
+
+    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'):
 
     def render(self, req, mimetype, content, filename=None, url=None):
         text = hasattr(content, 'read') and content.read() or content
-        return self.render_macro(req, name, text)
+        return self.internal_render(req, name, text)
 
     # IRequestHandler methods
     def match_request(self, req):
-        #return re.match(r'/tracmath(?:_trac)?(?:/.*)?$', req.path_info)
         return req.path_info.startswith('/tracmath')
 
     def process_request(self, req):
             pieces = [item for item in pieces[0].split('/') if item]
             if pieces:
                 name = pieces[0]
-                img_path = os.path.join(cacheDirectory, name)
+                img_path = os.path.join(self.cacheDirectory, name)
                 return req.send_file(img_path,
                         mimeview.get_mimetype(img_path))
         return