changeset 0:1eea6356d2e5

initial import of webcalc
author k0s <k0scist@gmail.com>
date Mon, 07 Sep 2009 15:09:03 -0400
parents
children 12ac99c240ca
files setup.py webcalc.ini webcalc/__init__.py webcalc/factory.py webcalc/formatters.py webcalc/templates/index.html webcalc/webcalc.py
diffstat 7 files changed, 243 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+from setuptools import setup, find_packages
+
+version = "0.1"
+
+setup(name='webcalc',
+      version=version,
+      description="web-based calculator",
+      long_description="""
+""",
+      classifiers=[], # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+      author='Jeff Hammel',
+      author_email='k0scist@gmail.com',
+      url='',
+      license="GPL",
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+         'WebOb',	
+         'Paste',
+         'PasteScript',
+         'genshi',
+         'simplejson'
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [paste.app_factory]
+      main = webcalc.factory:factory
+
+      [webcalc.formatters]
+      text/plain = webcalc.formatters:CSVformat
+      application/json = webcalc.formatters:JSONformat
+      """,
+      )
+      
new file mode 100644
--- /dev/null
+++ b/webcalc.ini
@@ -0,0 +1,21 @@
+#!/usr/bin/env paster
+
+[DEFAULT]
+debug = true
+email_to = k0scist@gmail.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5151
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = webcalc
+
+set debug = false
+
+[app:webcalc]
+paste.app_factory = webcalc.factory:factory
new file mode 100644
--- /dev/null
+++ b/webcalc/__init__.py
@@ -0,0 +1,1 @@
+#
new file mode 100644
--- /dev/null
+++ b/webcalc/factory.py
@@ -0,0 +1,12 @@
+from webcalc import WebcalcView
+from paste.httpexceptions import HTTPExceptionHandler
+
+def factory(global_conf, **app_conf):
+    """create a webob view and wrap it in middleware"""
+    keystr = 'webcalc.'
+    args = dict([(key.split(keystr, 1)[-1], value)
+                 for key, value in app_conf.items()
+                 if key.startswith(keystr) ])
+    app = WebcalcView(**args)
+    return HTTPExceptionHandler(app)
+    
new file mode 100644
--- /dev/null
+++ b/webcalc/formatters.py
@@ -0,0 +1,20 @@
+from StringIO import StringIO
+
+def CSVformat(values):
+    if not values:
+        return ''
+    keys = sorted([key for key in values[0].keys()
+                   if key != 'result'])
+    keys.append('result')
+    
+    buffer = StringIO()
+    if len(keys) > 1:
+        print >> buffer, ','.join(keys)
+    for value in values:
+        print >> buffer, ','.join([str(value[key])
+                                   for key in keys])
+    return buffer.getvalue()
+
+def JSONformat(values):
+    import simplejson
+    return simplejson.dumps({'values': values})
new file mode 100644
--- /dev/null
+++ b/webcalc/templates/index.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/">
+<body>
+Hello ${subject}!
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webcalc/webcalc.py
@@ -0,0 +1,144 @@
+"""
+webcalc: a view with webob
+"""
+
+import math
+from pkg_resources import iter_entry_points
+from pkg_resources import resource_filename
+from webob import Request, Response, exc
+
+class WebcalcView(object):
+
+    ### class level variables
+    defaults = { 'formatter': 'text/plain' }
+
+    def __init__(self, **kw):
+        for key in self.defaults:
+            setattr(self, key, kw.get(key, self.defaults[key]))
+        self.formatters = {}
+        for entry_point in iter_entry_points('webcalc.formatters'):
+            try:
+                formatter = entry_point.load()
+            except:
+                continue
+            self.formatters[entry_point.name] = formatter
+
+        assert self.formatter in self.formatters
+
+        self.response_functions = { 'GET': self.get,
+                                    'POST': self.get,
+                                    }
+
+    ### methods dealing with HTTP
+    def __call__(self, environ, start_response):
+        request = Request(environ)
+        res = self.make_response(request)
+        return res(environ, start_response)
+                                
+    def make_response(self, request):
+        return self.response_functions.get(request.method, self.error)(request)
+
+    def get_response(self, text, content_type='text/html'):
+        res = Response(content_type=content_type, body=text)
+        return res
+
+    def get(self, request):
+        """
+        return response to a GET requst
+        """
+
+        
+        equation = request.path_info.strip('/').strip()
+        if not equation:
+            return self.get_response('<html><body><form method="post"><input type="text" name="equation"/></form></body></html>')
+
+
+        # limit scope of execution
+        forbidden = set([';', '=', 'import', 'from', 'with'])
+        forbidden.update(dir(__builtins__))
+        assert not [ i for i in forbidden
+                     if i in equation ]
+        math_globals = dict([(i, getattr(math, i))
+                             for i in dir(math)
+                             if not i.startswith('_')])
+        
+        # get the range of variables
+        variables = []
+        assert 'result' not in request.GET
+        for name, value in request.GET.items():
+            if name == 'format':
+                continue
+            values = self.values(value)
+            if variables:
+                if len(values) == 1:
+                    for var in variables:
+                        var[name] = values[0]
+                else:
+                    old_variables = [ i.copy() for i in variables[:] ]
+                    variables = []
+                    for val in values:
+                        row = [ i.copy() for i in old_variables ]
+                        for item in row:
+                            item[name] = val
+                        variables.extend(row)
+            else:
+                variables = [ { name: val } 
+                              for val in values ]
+
+        # evaluate the equation
+        if variables:
+            for var in variables:
+                result = float(eval(equation, math_globals, var))
+                var['result'] = result
+        else:
+            result = float(eval(equation, math_globals, {}))
+            variables = [{'result': result}]
+
+        # format the result
+        format = request.params.get('format') or request.content_type or self.formatter
+        formatter = self.formatters.get(format, self.formatters[self.formatter])
+        return self.get_response(formatter(variables), format)
+
+    def post(self, request):
+        """
+        return response to a POST request
+        """
+        import pdb;  pdb.set_trace()
+        
+        return exc.HTTPOk("Ok")
+
+    def error(self, request):
+        """deal with non-supported methods"""
+        return exc.HTTPMethodNotAllowed("Only %r operations are allowed" % self.response_functions.keys())
+        
+
+    def values(self, string):
+        """
+        >>> values('1,2,3,4')
+        [1,2,3,4]
+        >>> values('1..0.2..2')
+        [1,1.2,1.4,1.6,1.8,2]
+        """
+        retval = []
+        tokens = [i.strip() for i in string.split(',')
+                  if i.strip()]
+        for token in tokens:
+            try: 
+                value = float(token)
+                retval.append(value)
+                continue
+            except ValueError:
+                pass
+            start, step, end = token.split('..')
+            start = float(start)
+            step = float(step)
+            end = float(end)
+            
+            values = [ start ]
+            npoints = (end - start)/step
+            npoints = int(npoints)
+            values.extend([i*step + start 
+                           for i in range(1, npoints)])
+            values.append(end)
+            retval.extend(values)
+        return retval