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:
parent
52cf72da7f
commit
dc7aa556c5
9 changed files with 500 additions and 0 deletions
29
pyscript/README.txt
Normal file
29
pyscript/README.txt
Normal 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
3
pyscript/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
$Id$
|
||||||
|
"""
|
47
pyscript/browser.py
Normal file
47
pyscript/browser.py
Normal 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
81
pyscript/configure.zcml
Normal 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>
|
1
pyscript/cybertools.pyscript-configure.zcml
Normal file
1
pyscript/cybertools.pyscript-configure.zcml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<include package="cybertools.pyscript" />
|
88
pyscript/edit.pt
Normal file
88
pyscript/edit.pt
Normal 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
57
pyscript/interfaces.py
Normal 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
136
pyscript/script.py
Normal 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
58
pyscript/tests.py
Normal 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()
|
Loading…
Add table
Reference in a new issue