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
This commit is contained in:
helmutm 2007-08-02 20:19:39 +00:00
parent 52cf72da7f
commit dc7aa556c5
9 changed files with 500 additions and 0 deletions

29
pyscript/README.txt Normal file
View file

@ -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::
'''
<html>
<body>
<ul>
'''
import time
print time.asctime()
'''
</ul>
</body>
</html>
'''

3
pyscript/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
$Id$
"""

47
pyscript/browser.py Normal file
View file

@ -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

81
pyscript/configure.zcml Normal file
View file

@ -0,0 +1,81 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="zope">
<interface
interface=".interfaces.IPythonPage"
type="zope.app.content.interfaces.IContentType"
/>
<class class=".script.PythonPage">
<factory
id="cybertools.pyscript.PythonPage"
title="Python Page"
description="A simple, content-based Python Page"
/>
<require
permission="zope.View"
interface=".interfaces.IPythonPage"
/>
<require
permission="zope.ManageContent"
set_attributes="source contentType"
/>
<implements
interface="zope.annotation.interfaces.IAttributeAnnotatable"
/>
</class>
<!-- browser directives -->
<browser:page
name="index.html"
for=".interfaces.IPythonPage"
class=".browser.PythonPageEval"
attribute="index"
permission="zope.View"
/>
<browser:addform
label="Add Python Page"
name="AddPythonPage.html"
schema=".interfaces.IPythonPage"
content_factory=".script.PythonPage"
permission="zope.ManageContent"
/>
<browser:addMenuItem
class=".script.PythonPage"
title="Python Page"
description="An Python Page"
permission="zope.ManageContent"
view="AddPythonPage.html"
/>
<browser:editform
for=".interfaces.IPythonPage"
schema=".interfaces.IPythonPage"
name="edit.html"
label="Edit Python Page"
class=".browser.PythonPageEditView"
template="edit.pt"
permission="zope.ManageContent"
menu="zmi_views" title="Edit"
/>
<!-- Preview view - requires zope.app.preview -->
<configure package="zope.app.preview">
<browser:page
for="cybertools.pyscript.interfaces.IPythonPage"
name="preview.html"
template="preview.pt"
permission="zope.ManageContent"
menu="zmi_views" title="Preview"
/>
</configure>
</configure>

View file

@ -0,0 +1 @@
<include package="cybertools.pyscript" />

88
pyscript/edit.pt Normal file
View file

@ -0,0 +1,88 @@
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
i18n:translate="">Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" i18n:translate=""/>
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div class="message"
tal:condition="view/syntaxError"
tal:define="err view/syntaxError">
<h3 i18n:translate="">Syntax Error:
<span tal:content="err/msg" i18n:name="msg">invalid syntax</span>
</h3>
<div i18n:translate="">
File
"<span tal:replace="err/filename" i18n:name="filename">
filename
</span>",
line <span tal:replace="err/lineno" i18n:name="lineno">10</span>,
offset <span tal:replace="err/offset" i18n:name="offset">1</span>
</div>
<pre tal:content="python:err.text+'\n'+' '*err.offset+'^'">
prin "foo"
^
</pre>
</div>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@form_macros/widget_rows" />
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="UPDATE_SUBMIT" value="Change"
i18n:attributes="value submit-button"/>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>

57
pyscript/interfaces.py Normal file
View file

@ -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.
"""

136
pyscript/script.py Normal file
View file

@ -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='<string>'):
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')

58
pyscript/tests.py Normal file
View file

@ -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()