From dc7aa556c592b306a16312e86f0e28ea4a35e810 Mon Sep 17 00:00:00 2001 From: helmutm Date: Thu, 2 Aug 2007 20:19:39 +0000 Subject: [PATCH] new package pyscript, derived from zope.app.PythonPage git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1867 fd906abe-77d9-0310-91a1-e0d9ade77398 --- pyscript/README.txt | 29 +++++ pyscript/__init__.py | 3 + pyscript/browser.py | 47 +++++++ pyscript/configure.zcml | 81 ++++++++++++ pyscript/cybertools.pyscript-configure.zcml | 1 + pyscript/edit.pt | 88 +++++++++++++ pyscript/interfaces.py | 57 ++++++++ pyscript/script.py | 136 ++++++++++++++++++++ pyscript/tests.py | 58 +++++++++ 9 files changed, 500 insertions(+) create mode 100644 pyscript/README.txt create mode 100644 pyscript/__init__.py create mode 100644 pyscript/browser.py create mode 100644 pyscript/configure.zcml create mode 100644 pyscript/cybertools.pyscript-configure.zcml create mode 100644 pyscript/edit.pt create mode 100644 pyscript/interfaces.py create mode 100644 pyscript/script.py create mode 100644 pyscript/tests.py diff --git a/pyscript/README.txt b/pyscript/README.txt new file mode 100644 index 0000000..ca4daa5 --- /dev/null +++ b/pyscript/README.txt @@ -0,0 +1,29 @@ +Python Page +=========== + +Python Page provides the user with a content object that interprets +Python in content space. To save typing and useless messing with +output, any free-standing string and print statement are considered +for output; see the example below. + + +Example +------- + +Create a new content type called "Python Page" and enter the following +code example:: + + ''' + + + + + + ''' diff --git a/pyscript/__init__.py b/pyscript/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/pyscript/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/pyscript/browser.py b/pyscript/browser.py new file mode 100644 index 0000000..dcc1777 --- /dev/null +++ b/pyscript/browser.py @@ -0,0 +1,47 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Python Page Browser Views + +$Id$ +""" + +from zope.app.form.browser.editview import EditView +from zope.app.i18n import ZopeMessageFactory as _ + + +class PythonPageEval(object): + """Evaluate the Python Page.""" + + def index(self, **kw): + """Call a Python Page""" + self.request.response.setHeader('content-type', + self.context.contentType) + return str(self.context(self.request, **kw)) + + +class PythonPageEditView(EditView): + """Edit View Class for Python Page.""" + + syntaxError = None + + def update(self): + """Update the content with the HTML form data.""" + try: + status = super(PythonPageEditView, self).update() + except SyntaxError, err: + self.syntaxError = err + status = _('A syntax error occurred.') + self.update_status = status + + return status diff --git a/pyscript/configure.zcml b/pyscript/configure.zcml new file mode 100644 index 0000000..a4783a7 --- /dev/null +++ b/pyscript/configure.zcml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pyscript/cybertools.pyscript-configure.zcml b/pyscript/cybertools.pyscript-configure.zcml new file mode 100644 index 0000000..fea2eb0 --- /dev/null +++ b/pyscript/cybertools.pyscript-configure.zcml @@ -0,0 +1 @@ + diff --git a/pyscript/edit.pt b/pyscript/edit.pt new file mode 100644 index 0000000..68d434a --- /dev/null +++ b/pyscript/edit.pt @@ -0,0 +1,88 @@ + + + +
+ +
+ +
+ +
+ +

Edit something

+ +

+ +

+ There are 6 input errors. +

+ +
+

Syntax Error: + invalid syntax +

+
+ File + " + filename + ", + line 10, + offset 1 +
+
+             prin "foo"
+                 ^
+          
+
+ +
+
+ +
+
Extra top
+
+
+ +
+ +
+
+
Extra bottom
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ + + +
+ +
+ + + diff --git a/pyscript/interfaces.py b/pyscript/interfaces.py new file mode 100644 index 0000000..0f11a0c --- /dev/null +++ b/pyscript/interfaces.py @@ -0,0 +1,57 @@ +# +# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +interface definitions for the pyscript package. + +$Id$ +""" + +from zope.interface import Interface +from zope import schema + + +class IPythonPage(Interface): + """Python Page + + The Python Page acts as a simple content type that allows you to execute + Python in content space. Additionally, if you have a free-standing + triple-quoted string, it gets converted to a print statement + automatically. + """ + + source = schema.SourceText( + title=_(u"Source"), + description=_(u"The source of the Python page."), + required=True, + ) + + contentType = schema.TextLine( + title=_(u"Content Type"), + description=_(u"The content type the script outputs."), + required=True, + default=u"text/html", + ) + + def __call__(request, **kw): + """Execute the script. + + The script will insert the `request` and all `**kw` as global + variables. Furthermore, the variables `script` and `context` (which is + the container of the script) will be added. + """ diff --git a/pyscript/script.py b/pyscript/script.py new file mode 100644 index 0000000..7af5bd5 --- /dev/null +++ b/pyscript/script.py @@ -0,0 +1,136 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Python Page + +$Id$ +""" + +__docformat__ = 'restructuredtext' + +import new +import re +from cStringIO import StringIO +from persistent import Persistent +from zope.proxy import removeAllProxies +from zope.security.untrustedpython.builtins import SafeBuiltins +from zope.security.untrustedpython.rcompile import compile +from zope.traversing.api import getParent, getPath +from zope.app.container.contained import Contained +#from zope.app.interpreter.interfaces import IInterpreter +from zope.interface import implements +from zope.app.i18n import ZopeMessageFactory as _ + +from cybertools.pyscript.interfaces import IPythonPage + + +class PythonPage(Contained, Persistent): + """Persistent Python Page - Content Type + """ + + implements(IPythonPage) + + _v_compiled = None + + def __init__(self, source=u'', contentType=u'text/plain'): + """Initialize the object.""" + super(PythonPage, self).__init__() + self.source = source + self.contentType = contentType + + def __filename(self): + if self.__parent__ is None: + filename = 'N/A' + else: + filename = getPath(self) + return filename + + def setSource(self, source): + """Set the source of the page and compile it. + + This method can raise a syntax error, if the source is not valid. + """ + self.__source = source + self.__prepared_source = self.prepareSource(source) + # Compile objects cannot be pickled + self._v_compiled = Function(self.__prepared_source, self.__filename()) + + _tripleQuotedString = re.compile( + r"^([ \t]*)[uU]?([rR]?)(('''|\"\"\")(.*)\4)", re.MULTILINE | re.DOTALL) + + def prepareSource(self, source): + """Prepare source.""" + # compile() don't accept '\r' altogether + source = source.replace("\r\n", "\n") + source = source.replace("\r", "\n") + if isinstance(source, unicode): + # Use special conversion function to work around + # compiler-module failure to handle unicode in literals + try: + source = source.encode('ascii') + except UnicodeEncodeError: + return self._tripleQuotedString.sub(_print_usrc, source) + return self._tripleQuotedString.sub(r"\1print u\2\3", source) + + + def getSource(self): + """Get the original source code.""" + return self.__source + + source = property(getSource, setSource) + + def __call__(self, request, **kw): + output = StringIO() + if self._v_compiled is None: + self._v_compiled = Function(self.__prepared_source, + self.__filename()) + kw['request'] = request + kw['script'] = self + kw['untrusted_output'] = output + kw['printed'] = output + kw['context'] = getParent(self) + kw['script_result'] = None + self._v_compiled(kw) + result = kw['script_result'] + if result == output: + result = result.getvalue() + return result + + +class Function(object): + """A compiled function. + """ + + def __init__(self, source, filename=''): + lines = [] + lines.insert(0, 'def dummy():') + for line in source.splitlines(): + lines.append(' ' + line) + lines.append('script_result = dummy()') + source = '\n'.join(lines) + print source + self.code = compile(source, filename, 'exec') + + def __call__(self, globals): + globals['__builtins__'] = SafeBuiltins + #fct = new.function(self.code, globals) + exec self.code in globals, None + #return fct() + + +def _print_usrc(match): + string = match.group(3) + raw = match.group(2) + if raw: + return match.group(1)+'print '+`string` + return match.group(1)+'print u'+match.group(3).encode('unicode-escape') diff --git a/pyscript/tests.py b/pyscript/tests.py new file mode 100644 index 0000000..bd4495b --- /dev/null +++ b/pyscript/tests.py @@ -0,0 +1,58 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for Python Page + +$Id$ +""" +import unittest + +import zope.component +from zope.interface import implements +from zope.testing.doctestunit import DocTestSuite +from zope.traversing.interfaces import IContainmentRoot +from zope.traversing.interfaces import IPhysicallyLocatable +from zope.traversing.adapters import RootPhysicallyLocatable +from zope.location.traversing import LocationPhysicallyLocatable + +from zope.app.container.contained import Contained +#from zope.app.interpreter.interfaces import IInterpreter +#from zope.app.interpreter.python import PythonInterpreter +from zope.app.testing import placelesssetup, ztapi + +class Root(Contained): + implements(IContainmentRoot) + + __parent__ = None + __name__ = 'root' + +def setUp(test): + placelesssetup.setUp() + sm = zope.component.getGlobalSiteManager() + #sm.registerUtility(PythonInterpreter, IInterpreter, 'text/server-python') + + ztapi.provideAdapter(None, IPhysicallyLocatable, + LocationPhysicallyLocatable) + ztapi.provideAdapter(IContainmentRoot, IPhysicallyLocatable, + RootPhysicallyLocatable) + + + +def test_suite(): + return unittest.TestSuite(( + DocTestSuite('cybertools.pyscript', + setUp=setUp, tearDown=placelesssetup.tearDown), + )) + +if __name__ == '__main__': + unittest.main()