Hi,

I wrote a little macro for referencing values defined in the
wiki and for calculations. One has to create a page named
"wiki/Defines", with lines similar to the C preprocessor syntax:

define FOO 1
define BAR 2
define FOOBAR 1+2

One can use the defines anywhere in the wiki and calculate:

[[Calc(FOO)]] -> 1
[[Calc(BAR*3)]] -> 6
[[Calc(max(abs(sin(3*pi/2.)), cos(pi)))]] -> 1.0

Is this interesting for somebody? Or would you never ever use a
macro that makes use of eval() for security reasons? Did I
reinvent the wheel? Are there better solutions?

TIA for your comments, code attached

-- 
You received this message because you are subscribed to the Google Groups "Trac 
Users" group.
To post to this group, send email to trac-us...@googlegroups.com.
To unsubscribe from this group, send email to 
trac-users+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/trac-users?hl=en.

# Copyright (C) 2010 W. Martin Borgert <deba...@debian.org>
#
# AGPL-3

import math
import re

from trac.wiki.macros import WikiMacroBase
from trac.wiki.model import WikiPage

"""
  Activate it in 'trac.ini'

  [components]
  CalcMacro.* = enabled

  format:
    Calc(calculation)

    displays the result of a calculation

  arguments:
    calculation = a string to evaluate

  examples:
    [[Calc(1+2)]]
    will be replaced with 3
    some builtins and everything from the "math" module can be used

    furthermore one can use CPP-like defines:
      - create a page wiki/Defines
      - write you defines on that page:
        define VELOCITY 11.
        define VELOCITY_UNLADEN VELOCITY
        define VELOCITY_LADEN VELOCITY/2
      - lines, that do not follow this syntax, are ignored
      - now use the definition in Calc:
        [[Calc(99*VELOCITY)]]

    [[Calc(defines)]] prints a table of all defines and lists all built-ins
"""

class CalcMacro(WikiMacroBase):
    # CPP-like syntax: define FOO BAR+8
    _define_re = re.compile('^\s*#?\s*define\s+(\w+)\s+(.+)$', re.M)
    _hash = -1
    _localdict = {}
    _page_name = 'Defines'
    _safe_dict = None

    @staticmethod
    def make_dict():
        "create a dictionary with useful Python builtins and math funtions"
        CalcMacro._safe_dict = {}
        for k in math.__dict__.keys():
            if not k.startswith("__"):
                CalcMacro._safe_dict[k] = math.__dict__[k]
        # some builtins are useful and, hopefully, safe
        for k in ["abs", "bool", "chr", "complex", "divmod",
                  "float", "hash", "int", "hex", "long", "max", "min",
                  "oct", "pow", "round", "unichr", "False", "True"]:
            CalcMacro._safe_dict[k] = eval(k)

    @staticmethod
    def update_dict(content):
        """create the local dictionary for eval
        both useful Python functions and Wiki definitions"""
        localdict = {}
        localdict.update(CalcMacro._safe_dict)
        defines = '<table><tr><th>Define</th><th>Definition</th></tr>'
        for m in CalcMacro._define_re.finditer(content):
            try:
                key, value = m.group(1), m.group(2).strip()
                defines += '<tr><td>%s</td><td>%s</td></tr>' % (key, value)
                value = eval(value, {"__builtins__": None}, localdict)
                localdict[key] = value
            except Exception, e:
                continue
        defines += '<tr><td>List of built-ins</td><td>%s</td></tr>' % \
            ", ".join(CalcMacro._safe_dict.keys())
        defines += '</table>'
        localdict['defines'] = defines
        CalcMacro._localdict = localdict

    def expand_macro(self, formatter, name, argument):
        # only create the Python builtin dict the first time
        if not CalcMacro._safe_dict:
            CalcMacro.make_dict()
        db = self.env.get_db_cnx()
        content = WikiPage(self.env, CalcMacro._page_name, db=db).text
        content_hash = hash(content)
        # if the wiki page didn't change, no need to re-create the dict
        if CalcMacro._hash != content_hash:
            CalcMacro.update_dict(content)
            CalcMacro._hash = content_hash
        return eval(argument, {"__builtins__": None}, CalcMacro._localdict)

Reply via email to