aboutsummaryrefslogtreecommitdiff
path: root/system/python
diff options
context:
space:
mode:
authorAlTheKiller <AlTheKiller@svn>2009-09-23 01:49:50 +0000
committerAlTheKiller <AlTheKiller@svn>2009-09-23 01:49:50 +0000
commit45285e8a9300cd754a760560640b75b09f98035e (patch)
treead9f093885ad5c98e9dd4156674e7691c22ed0a2 /system/python
step 3/4: Move linuxport to trunk. How'd I get roped into this?
git-svn-id: https://xbmc.svn.sourceforge.net/svnroot/xbmc/trunk@23097 568bbfeb-2a22-0410-94d2-cc84cf5bfa90
Diffstat (limited to 'system/python')
-rwxr-xr-xsystem/python/DLLs/_socket.pydbin0 -> 49152 bytes
-rwxr-xr-xsystem/python/DLLs/_ssl.pydbin0 -> 483328 bytes
-rwxr-xr-xsystem/python/DLLs/bz2.pydbin0 -> 77824 bytes
-rwxr-xr-xsystem/python/DLLs/pyexpat.pydbin0 -> 135168 bytes
-rwxr-xr-xsystem/python/DLLs/select.pydbin0 -> 7680 bytes
-rwxr-xr-xsystem/python/DLLs/unicodedata.pydbin0 -> 405504 bytes
-rwxr-xr-xsystem/python/DLLs/zlib.pydbin0 -> 69632 bytes
-rw-r--r--system/python/python24.dllbin0 -> 1871872 bytes
-rw-r--r--system/python/python24.zlibbin0 -> 1394340 bytes
-rw-r--r--system/python/readme.txt16
-rw-r--r--system/python/spyce/CHANGES566
-rw-r--r--system/python/spyce/Cookie.py557
-rw-r--r--system/python/spyce/LICENCE40
-rw-r--r--system/python/spyce/README15
-rw-r--r--system/python/spyce/RELEASE21
-rw-r--r--system/python/spyce/THANKS57
-rw-r--r--system/python/spyce/fcgi.py265
-rw-r--r--system/python/spyce/installHelper.py181
-rw-r--r--system/python/spyce/makefile160
-rw-r--r--system/python/spyce/modules/automaton.py79
-rw-r--r--system/python/spyce/modules/compress.py84
-rw-r--r--system/python/spyce/modules/cookie.py50
-rw-r--r--system/python/spyce/modules/error.py171
-rw-r--r--system/python/spyce/modules/include.py117
-rw-r--r--system/python/spyce/modules/pool.py45
-rw-r--r--system/python/spyce/modules/redirect.py53
-rw-r--r--system/python/spyce/modules/request.py224
-rw-r--r--system/python/spyce/modules/response.py186
-rw-r--r--system/python/spyce/modules/session.py368
-rw-r--r--system/python/spyce/modules/spylambda.py55
-rw-r--r--system/python/spyce/modules/stdout.py104
-rw-r--r--system/python/spyce/modules/taglib.py102
-rw-r--r--system/python/spyce/modules/template.py68
-rw-r--r--system/python/spyce/modules/toc.py240
-rw-r--r--system/python/spyce/modules/transform.py155
-rwxr-xr-xsystem/python/spyce/run_spyceCGI.py21
-rwxr-xr-xsystem/python/spyce/run_spyceCmd.py22
-rwxr-xr-xsystem/python/spyce/run_spyceModpy.py49
-rw-r--r--system/python/spyce/spyce.conf162
-rw-r--r--system/python/spyce/spyce.mime495
-rw-r--r--system/python/spyce/spyce.nsi208
-rw-r--r--system/python/spyce/spyce.nsi.in208
-rwxr-xr-xsystem/python/spyce/spyce.py674
-rw-r--r--system/python/spyce/spyce.spec.in74
-rw-r--r--system/python/spyce/spyceApache.conf76
-rwxr-xr-xsystem/python/spyce/spyceCGI.py48
-rw-r--r--system/python/spyce/spyceCache.py152
-rwxr-xr-xsystem/python/spyce/spyceCmd.py336
-rw-r--r--system/python/spyce/spyceCompile.py1111
-rw-r--r--system/python/spyce/spyceConfig.py370
-rw-r--r--system/python/spyce/spyceException.py116
-rw-r--r--system/python/spyce/spyceLock.py120
-rw-r--r--system/python/spyce/spyceModpy.py138
-rw-r--r--system/python/spyce/spyceModule.py55
-rw-r--r--system/python/spyce/spyceTag.py260
-rw-r--r--system/python/spyce/spyceUtil.py157
-rw-r--r--system/python/spyce/spyceWWW.py324
-rw-r--r--system/python/spyce/spyceXbmc.py25
-rw-r--r--system/python/spyce/tree.py70
-rwxr-xr-xsystem/python/spyce/verchk.py36
60 files changed, 9286 insertions, 0 deletions
diff --git a/system/python/DLLs/_socket.pyd b/system/python/DLLs/_socket.pyd
new file mode 100755
index 0000000000..330876876a
--- /dev/null
+++ b/system/python/DLLs/_socket.pyd
Binary files differ
diff --git a/system/python/DLLs/_ssl.pyd b/system/python/DLLs/_ssl.pyd
new file mode 100755
index 0000000000..206953b598
--- /dev/null
+++ b/system/python/DLLs/_ssl.pyd
Binary files differ
diff --git a/system/python/DLLs/bz2.pyd b/system/python/DLLs/bz2.pyd
new file mode 100755
index 0000000000..dcf006ee61
--- /dev/null
+++ b/system/python/DLLs/bz2.pyd
Binary files differ
diff --git a/system/python/DLLs/pyexpat.pyd b/system/python/DLLs/pyexpat.pyd
new file mode 100755
index 0000000000..0132861751
--- /dev/null
+++ b/system/python/DLLs/pyexpat.pyd
Binary files differ
diff --git a/system/python/DLLs/select.pyd b/system/python/DLLs/select.pyd
new file mode 100755
index 0000000000..8a747d9e23
--- /dev/null
+++ b/system/python/DLLs/select.pyd
Binary files differ
diff --git a/system/python/DLLs/unicodedata.pyd b/system/python/DLLs/unicodedata.pyd
new file mode 100755
index 0000000000..0ca6ed0e21
--- /dev/null
+++ b/system/python/DLLs/unicodedata.pyd
Binary files differ
diff --git a/system/python/DLLs/zlib.pyd b/system/python/DLLs/zlib.pyd
new file mode 100755
index 0000000000..3066c14bc9
--- /dev/null
+++ b/system/python/DLLs/zlib.pyd
Binary files differ
diff --git a/system/python/python24.dll b/system/python/python24.dll
new file mode 100644
index 0000000000..91591b1f26
--- /dev/null
+++ b/system/python/python24.dll
Binary files differ
diff --git a/system/python/python24.zlib b/system/python/python24.zlib
new file mode 100644
index 0000000000..e1bb9c4101
--- /dev/null
+++ b/system/python/python24.zlib
Binary files differ
diff --git a/system/python/readme.txt b/system/python/readme.txt
new file mode 100644
index 0000000000..b49b242e5a
--- /dev/null
+++ b/system/python/readme.txt
@@ -0,0 +1,16 @@
+python directory structure
+
+DLLs dir:
+ Library directory used by python.
+ python exensions (.pyd) from cvs can be placed here
+
+Lib dir:
+ Library directory used by python.
+ Here can new packages or modules be installed by the user.
+
+Spyce dir:
+ Here is where the Spyce installation remains, this is only there for use with psp (python server pages)
+ on the goahead webserver.
+
+dev note:
+unicodedata.pyd is taken from a window installation, so you won't find the source in cvs for it \ No newline at end of file
diff --git a/system/python/spyce/CHANGES b/system/python/spyce/CHANGES
new file mode 100644
index 0000000000..1bb104509e
--- /dev/null
+++ b/system/python/spyce/CHANGES
@@ -0,0 +1,566 @@
+Change Log
+
+v1.3.13
+ improved performance (approx. 3x) of single-threaded print
+ exceptions in non-default modules reported like errors in script code
+ fix: mysession.spy: session depends on pool, so switched import order
+ fix: faulty session handlers thrown out on error
+ - takes care of annoying NameError: 'pool' not found
+ fix: sys.path always returned to original state, even on error
+ globals accessible via python modules
+ - applied patch contributed by: Niko Matsakis
+ iterable objects allowed within 'for' tag
+ - modified patch submitted by: Stefan Behnel
+ spyce lambdas can return values
+ updated spyceLock.py to eliminate Python FutureWarning for large constants
+ added include.spyceStr(), idea from Santtu Pajukanta
+ fix: error module performs response.clearFilters
+ - thanks to Colin Gillespie for reporting this
+ turning on spyce.DEBUG_ERROR will also display when modules start/finish
+
+v1.3.12
+ added fix to prevent reparsing of POST stream on
+ internal redirect (modified Conan Albrecht's contribution)
+ documentation updates
+ fix: parsing totally empty files threw exception
+ user request: iterate over request object
+ modified semantics of active tags
+ - singleton tags now behave like paired tags with empty bodies
+ - defined a number of flags in spyceTag to reduce level of indentation
+ in generated python code where unnecessary:
+ conditional, loops, catches, mustend
+ - see tag documentation
+ updated core tag library accordingly
+ fix: dump_handler with binary files truncating on Windows
+ fix: files with DOS linebreaks
+ fix: both <%. and <%@ can now begin a spyce directive
+ directory listings of Spyce webserver look nicer, Apache-like
+ added form active tag library
+ added PATH_INFO functionality to spyceWWW
+
+v1.3.11
+ user request: daemon webserver mode
+ fix: mod_python flush problem
+ performance: rewrote tokenizer/parser
+ - no longer using clusmy parser generator package
+ - still pure python, compilation times between 2-6x faster
+ user request: expose functionality to define spyceProcess function
+ with arbitrary parameters, and pass in parameters... helps
+ Coil framework with Spyce integration
+ fix: spyce webserver not performing path manipulations correctly
+ on Windows
+ fix: spyce.mime file not copied for .rpm and Windows installers
+
+v1.3.10
+ Default development configuration changed to:
+ Apache 2.0.40 and Python 2.2.x
+ Release testing will be performed:
+ both on Linux and Windows
+ under CGI, FastCGI and mod_python
+ Other versions of Apache and Python should continue to work, but
+ will not be tested. I am depending on user feedback to catch any
+ errant bugs under these older configurations.
+ fix: spyceWWW properly deals with directory URLs that don't end in '/'
+ fix: request.getpost1/postget1() now accept default values
+ fix: memory cache checks file permission as well as modification time
+ fix: makefile was including .pyc/.pyo files in tarball
+ fix: spyce.vim syntax highlighting for spyce lambdas
+ fix: error module should be loaded last to avoid stdout module being
+ unloaded on error, thereby causing print statements to no longer go
+ to the browser during error handling
+ fix: error module setHandler used incorrect variable name, causing
+ setHandler to fail
+ updated spyce.vim syntax file for JSP/ASP like delimeters
+ spyce.vim now included in vim distribution
+ rpm generates spyceParserTable.py
+ (allowing for different versions of python)
+ added 'no-store' and 'must-revalidate' to response.uncacheable()
+ added pageerror configuration option to modify default page-level handler
+ rpm now requires http >2.0 and python >2.2 installed
+
+v1.3.9
+ spyceWWW web server improved
+ - configuration options integrated into spyce.conf
+ - handler mechanism created
+ - defined spyce and dump handlers
+ - reads Apache format mime-type definition files
+ - .spy files ==> spyce handler; rest ==> dump handler
+ - can display directory listings
+ - configuration options added accordingly
+ - corresponding documentation changed
+ documentation restructured to explain common configuration file
+ options in the runtime section
+ fix: docs/examples/*.gif added to rpm and windows installer
+ expanded section on how to get Spyce running under IIS via CGI
+
+v1.3.8
+ user request:
+ modified request.get/post/get1/post1/env() to accept default values
+ (note: will break code that provided caseInsensitive parameter by position)
+ added request.getpost/getpost1/postget/postget1/default() methods
+ bug fixes: python 1.5 backwards compatibility issues in the following
+ online examples: gif.spy, myPortal.spy, mysession.spy
+
+v1.3.7
+ support for ASP-style delimeters -- <% %>
+ use of Bastion eliminated, due to Python deprecation
+
+v1.3.6
+ info.spy example updated to deal with implicitly loaded taglib module
+ minor documentation fix for doc-mod_include
+ quotes for the PythonPath in httpd.conf
+
+v1.3.5
+ taglib and spylambda modules loaded implicitly only when needed
+ (i.e. when tags or spyce lambdas are actually used in a given file)
+ make install made more portable; removed install -D switch
+ EOFError now handled for file-based spyce caching (strange Windows bug?)
+ improvements to automaton module
+
+v1.3.4
+ doc updates - session module
+ minor mod_python bug - filename attribute used over environment
+ fix - windows installer unable to find python executable in some cases
+
+v1.3.3
+ examples/info.spy added
+ keep track of spyce entry point, added to spyce header
+ fix - CGI fails (only on Apache2.0!) with GET info due v1.3.2 changes
+ fix - typo in core:if tag
+
+v1.3.2
+ mod_python 3.0.1 compatibility
+ - switched to sre module, despite stack limits, because
+ pre module conflicts with pcre shared object that apache uses
+ (actually, just fails on some complicate reg.exps!)
+ This implies that very, very long spyce files might fail, until
+ sre module implements a state-machine-based reg.exp engine.
+ - apacheRequest.connection.child_num mysteriously removed,
+ therefore using os.getpid() in spyceModpyRequest.getServerID()
+ spyceApache.conf tweaked (should be more compatible)
+ installHelper.py converts backslash to forward slash
+ for httpd.conf on Windows
+ switched from pre to sre module in spyceCompile.py
+ - reason: Apache 2.0.x uses different pcre library from Python
+ causing failure under mod_python
+ - pre was used over the default (sre) because sre implementation is
+ stack-based and encountered overruns... Oh, well! Don't write
+ Spyce files that blow the stack until sre is fixed.
+
+v1.3.1
+ fix - wrapped thread-unsafe yacc-like package with lock
+ renamed util module to spyceUtil.py
+ - conflict with python1.5 site-package
+ renamed cache.py, lock.py (just in case)
+ make website update script faster
+
+v1.3.0
+ active tags introduced
+ - see: http://spyce.sourceforge.net/doc-tag.html
+ - [[.taglib]] directive added
+ - taglib spyce module added
+ - compiler changes to deal with active tags
+ - tagging infrastructure (spyceTag, spyceTagPlus, spyceTagLibrary)
+ - see: spyceTags.py
+ - user-defined active tag libraries possible
+ - see: http://spyce.sourceforge.net/doc-tag_new.html
+ - core active tag library
+ see: tags/core.py
+ see: http://spyce.sourceforge.net/doc-tag_core.html
+ - tag libraries loaded from same path as modules
+ - compiler syntax checking improved
+ - check for unbalanced parens
+ - check for unbalanced active tags
+ - extensible syntax checking for active tags
+
+v1.2.10
+ bugfix - typo in spyceWWW caused threading mode startup failure
+
+v1.2.9
+ stdout.push() can now accept no file argument
+ stdout.pop() now returns captured output
+ stdout.capture() added
+ see: examples/stdout.spy and stdout module docs
+ session_user session handler added in session module
+ see: examples/mysession.spy and session module docs
+ spylambda.define() can now memoize
+ see: http://spyce.sourceforge.net/doc-mod_lambda.html
+ memoized spyce lambda syntax: [[spy! ...: ...]]
+ see: http://spyce.sourceforge.net/doc-lang_lambda.html
+ slight modification to spyce.vim syntax file
+ response.addHeader() now support replacement
+ response.timestamp(), expire(), expireRel(), lastModified()
+ and uncacheable() methods added
+ see: http://spyce.sourceforge.net/doc-mod_response.html
+ performance!
+
+v1.2.8
+ links page added
+ spyce VIM syntax file updated; deals with spyce lambdas
+ include module improvements
+ - 'vars' field added
+ - included file can return value
+ - documentation updated, specifically regarding use of 'context'
+
+v1.2.7
+ internal restructuring continues
+ - separated spyce exceptions
+ - separated spyce configuration
+ - expanded spyce API and spyceServer
+ - spyce.spyceDone now imported as spyceDone
+ simplified spyceModule
+ - renamed wrapper field, to _api
+ - old spyceModule available as spyceModulePlus
+ - all standard modules updated
+ fixed - syntax errors were not reported properly
+ file-based spyce caching, with config option
+ performance improvements
+
+v1.2.6
+ single and multi-page documentation
+ minor fixes:
+ - NoCloseOut.flush() added
+ - BufferedOutput.flush() flushes sub-stream
+ - template module pointed at new location of cache code
+
+v1.2.5
+ spyceAPI defined: module access to spyceWrapper object restricted
+ - see: http://spyce.sourceforge.net/doc-mod_new.html
+ - (in general, will be moving towards restricted execution space)
+ toc module improved; add level(), l1()...l9() methods
+ server-level debug option added to config file
+ - see: http://spyce.sourceforge.net/doc-conf_common_debug.html
+ - debug Spyce module deprecated
+ engine now supports recursive requests (include spyce from itself)
+ sys.stdout (and therefore print statements) made thread-safe
+ spyce engine supports concurrent requests
+ server-level concurrency option added to config file
+ - see: http://spyce.sourceforge.net/doc-conf_common_concurrency.html
+ - spyce webserver operates in single, forking and threading modes
+ server-level Spyce module caching
+ - replaces Spyce-level module caching
+ - caching-related code separated from wrapper
+ code compilation seperated from wrapper (spyce.spyceCode)
+ autodetect when PYTHONOPTIMIZE causes lexer/parser failure
+ minor fixes and performance tweaks
+
+v1.2.4
+ fix - new PLY parser uses reflection at runtime to read
+ documentation strings containing grammar, thus you
+ should not run Python in optimize mode, thus
+ mod_python option in spyceApache.conf changed.
+ fix - python 1.5 compatible .spy files for docs
+
+v1.2.3
+ fix - code for new tokenizer/parser made python 1.5.2 compatible
+
+v1.2.2
+ fix - PATH_INFO via CGI
+ fix - magic (#!) on first line treated as comment
+
+v1.2.1
+ complete rewrite of spyce tokenizer and parser
+ - using PLY, table-driven
+ added spyce lambdas to language
+
+v1.2.0
+ contrib section added
+ support for SPYCE_PATH environment variable
+ lots of documentation fixes
+ decided spyce was mature enough for 1.2.0
+
+v1.1.46
+ feature request: improved examples page on website
+
+v1.1.45
+ site and documentation revamp
+ refactored the spyceModule class (see spyceModule.py)
+ altered all standard modules to conform to new internal design
+ new table-of-contents (toc) module (see docs)
+ improved stdout module (see docs)
+ added push() and pop() methods
+ now loaded implicitly
+ exception tracebacks in chunks identify specific error lines
+ file globbing added to -O command-line option
+
+v1.1.44
+ module directive deprecated
+ replaced with import tag
+ import tag accepts args attribute
+ calls module init() method at location of directive
+ init() methods added to modules: session, compress
+ see: http://spyce.sourceforge.net/doc_lang_directive.html
+ http://spyce.sourceforge.net/doc_mod.html
+ http://spyce.sourceforge.net/doc_mod_compress.html
+ http://spyce.sourceforge.net/doc_mod_session.html
+ http://spyce.sourceforge.net/doc_mod_new.html
+ bugfix - modules finalized on redirect
+
+v1.1.43
+ bugfix - included files not inheriting modules properly
+ bugfix - transform module inside included file
+
+v1.1.42
+ renamed spyce.conf to spyceApache.conf
+ renamed spyceApache to spyceModpy
+ renamed run_spyceApache to run_spyceModpy (affect spyceApache.conf)
+ added server-level configuration file functionality
+ server module search path
+ modules to load at startup
+ server-level error handler
+ global server variables
+ see: docs/doc_conf_common.html
+ added response.isCancelled() function
+ see: docs/doc_mod_response.html
+ bugfix - early client disconnect caused problems under mod_python
+
+v1.1.41
+ extended HTTP response constants to conform to spec
+ extended HTML entity encoded characters to conform to spec
+ modified internal buffering semantics to allow eliminiation of special
+ case code for specific HTTP return codes (redirects) in the common path
+ performance improvements
+ convenience functions transform.html_encode() and url_encode() added
+ error module added: handles errors that occur during spyce processing
+ bugfix - HTTP return codes propagated correctly under mod_python
+
+v1.1.40
+ bugfix - spyce syntax error propagated properly
+ response headers cleared on an internal redirect
+ case insensitive request.get,post,get1,post1,file
+
+v1.1.39
+ modified how filter module injects itself into output stream
+ added response.addFilter() to allow piped functionality
+ on the output stream, modules can insert write, writeStatic,
+ writeExpr, flush and clear handlers
+ added compress module for dynamic compression functionality
+ compress module documentation
+ renamed filter module to transform (name conflict with Python builtin)
+ sys.path forced to be absolute before changing directory in CGI mode
+ bugfix - spyce path trimmed to just filename when directory changed for
+ CGI processing
+ bugfix - spyce web server closes sockets
+
+v1.1.38
+ spyce can now run as a (proxy) web server
+ spyce -l [-p port] <server root>
+
+v1.1.37
+ spyceDone exception to stop spyce processing
+ raise spyceDone, see gif.spy, fileupload.spy examples
+ response.close() deprecated
+ not needed with spyceDone functionality
+ cPickle used in session module
+ improved session serialization performance
+
+v1.1.36
+ redirect.externalRefresh now has url= in string
+ internal redirect fixed
+ bug fix - consecutive compact line removal now possible
+ examples added: hello2.spy, form.spy
+ handle ISINDEX CGI queries that have extra command-line parameters
+ Status CGI header used for spyce redirect return codes
+
+v1.1.35
+ bug - fixed cgi chdir in case of local directory
+ request - invoke spyce engine programmatically with spyce string
+ source tarball does not contain extra CVS junk
+
+v1.1.34
+ fixed apache config bug in windows installer
+
+v1.1.33
+ appended current Spyce file's directory to sys.path
+
+v1.1.32
+ minor documentation tweaks
+ names attribute added to [[.module ]] tag
+ request.__getitem__() added
+ chdir in cgi mode
+
+v1.1.31
+ windows installer improved: apache configuration and restart
+ fixed - handling of initial spaces in multi-line strings in python chunks
+
+v1.1.30
+ red page marker in docs
+ created undefined windows lock variables
+
+v1.1.29
+ documentation split up
+ rpm is now noarch
+
+v1.1.28
+ include.dump() now has binary option
+ stdout changed to binary mode on windows for cgi purposes
+ fixed session_dir handler bug on windows
+
+v1.1.27
+ fcgi implemented on windows too
+ windows installer
+
+v1.1.26
+ fixed - nasty bug with the new module behaviour
+ small improvements to documentation and examples
+ improved request.uri() function
+
+v1.1.25
+ fixed - fcgi module broke on windows
+
+v1.1.24
+ line compaction improved
+ module behaviour on include.spyce() defined
+
+v1.1.23
+ lots of changes so that: it works on Python 1.5.2 now too!
+ file-based session handler now uses pid, and file locks
+ live examples on sourceforge
+
+v1.1.22
+ fixed Python v2.1.1-related bugs.
+ improved installation process and documentation
+ rpm more likely to succeed - uses fcgi or drops back to cgi
+ no longer mod_python based by default
+
+v1.1.21 (faulty release)
+ stochastic session clean up; no more threading dependency
+ documentation: better installation notes
+ peep-hole optimizer
+
+v1.1.20 (faulty release)
+ created explicit (swappable) cache infrastructure
+ BUG ** Spyce also works on Python v2.1
+ request - request.post(),post1() works in includes
+ documentation: cheetah install, ...
+
+v1.1.19
+ filter module
+
+v1.1.18
+ fcgi support added
+ X-Spyce header added
+ documentation: how to write new modules
+
+v1.1.17
+ feature request - compaction algorithm improved
+
+v1.1.16
+ generalised session.setHandler (session handler selection mechanism)
+ gdbm, bsd db session handlers added
+
+v1.1.15
+ minor makefile and rpm script changes
+ handling of multi-line strings in python code
+ response.flush() added
+
+v1.1.14
+ wrappers to check python version
+
+v1.1.13
+ added new language construct: "Python chunks"
+
+v1.1.12
+ stdout module redirects stdout to response object
+ added writeln() to response module
+
+v1.1.11
+ fixed lots of CGI bugs:
+ reported bug - headers not sent
+ session module thread prevented script death
+ added spyce.ONE_SHOT variable
+ cookie module fixed
+ gif.spy example fixed
+ external redirect fixed
+
+v1.1.10
+ performance:
+ implemented semantic cache for spyce compilation
+ templating module performs caching
+ lots of commenting
+
+v1.1.9
+ templating module (cheetah integration)
+ documentation
+
+v1.1.8
+ automaton module
+ documentation
+
+v1.1.7
+ associative array access to session and cookie information
+ added pool module
+ documentation
+ comments emitted as tokens
+ syntax highlighting function: include.spycecode
+ documentation
+
+v1.1.6
+ dynamically loading modules
+
+v1.1.5
+ redirect module added
+
+v1.1.4
+ response.unbuffer()
+
+v1.1.3
+ support for file upload
+ request.get1(),post1()
+
+v1.1.2
+ more reliable exception location reporting
+
+v1.1.1
+ static includes
+ module search path
+
+v1.1.0
+ Implemented modules -- major rewrite.
+ Changed includes, sessions, cookies, ... everything into modules
+ Changed the generated "stub", though this is mostly under-the-covers
+ Rewrote most of the documentation
+
+v1.0.5
+ CGI support
+ Expanded install docs
+
+v1.0.4
+ Many doc updates
+ Autosession support
+ changed directives tags to use html-like attributes
+
+v1.0.3
+ Automatic session cleanup
+ Updated pilpel image
+
+v1.0.2
+ Handle 403 - Forbidden
+ Handle 404 - Not Found
+
+v1.0.1
+ Tracking original spyce code locations in generated code
+ Reporting runtime exceptions in original spyce code
+ Reporting syntax (compile) exceptions in original spyce code
+
+v1.0 - Initial release
+ Documentation
+ Added [[.nocompact]] and [[.compact]]
+ Allowed escaped \[[ and \]] in HTML
+ Added session support, with on-disk implementation
+ Realised and implemented command-line
+ Added cookies
+ Added http header calls
+ Added get and post support
+ Created request and response objects
+ Added [[.include]]
+ Added [[.funcion]] and [[./function]]
+ Create in-memory spyce cache
+ Wrote a token-based Brace Converter
+ Added [[ ]] and [[= ]]
+ Created Spyce compiler shell
+ Wrote initial mod_python "hello world" handler
+ Read up on mod_python
+ Looked at PyServ
+ Attempted to engineer a WebWare-based solution
+
diff --git a/system/python/spyce/Cookie.py b/system/python/spyce/Cookie.py
new file mode 100644
index 0000000000..c2f0281cee
--- /dev/null
+++ b/system/python/spyce/Cookie.py
@@ -0,0 +1,557 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+# Cookie.py taken from Python 2.2, and modified it to work in Python 1.5 -- RB
+
+__doc__ = 'Cookie parsing functionality'
+
+####
+# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software
+# and its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Timothy O'Malley not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
+# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+#
+
+import string
+from UserDict import UserDict
+
+try:
+ from cPickle import dumps, loads
+except ImportError:
+ from pickle import dumps, loads
+
+try:
+ import re
+except ImportError:
+ raise ImportError, "Cookie.py requires 're' from Python 1.5 or later"
+
+__all__ = ["CookieError","BaseCookie","SimpleCookie","SerialCookie",
+ "SmartCookie","Cookie"]
+
+#
+# Define an exception visible to External modules
+#
+class CookieError(Exception):
+ pass
+
+
+# These quoting routines conform to the RFC2109 specification, which in
+# turn references the character definitions from RFC2068. They provide
+# a two-way quoting algorithm. Any non-text character is translated
+# into a 4 character sequence: a forward-slash followed by the
+# three-digit octal equivalent of the character. Any '\' or '"' is
+# quoted with a preceeding '\' slash.
+#
+# These are taken from RFC2068 and RFC2109.
+# _LegalChars is the list of chars which don't require "'s
+# _Translator hash-table for fast quoting
+#
+
+ascii_lowercase = string.join(map(lambda c: chr(ord('a')+c), range(ord('z')-ord('a')+1)),'')
+ascii_uppercase = string.join(map(lambda c: chr(ord('A')+c), range(ord('z')-ord('a')+1)),'')
+
+_LegalChars = ascii_lowercase + ascii_uppercase + string.digits + "!#$%&'*+-.^_`|~"
+_Translator = {
+ '\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
+ '\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
+ '\006' : '\\006', '\007' : '\\007', '\010' : '\\010',
+ '\011' : '\\011', '\012' : '\\012', '\013' : '\\013',
+ '\014' : '\\014', '\015' : '\\015', '\016' : '\\016',
+ '\017' : '\\017', '\020' : '\\020', '\021' : '\\021',
+ '\022' : '\\022', '\023' : '\\023', '\024' : '\\024',
+ '\025' : '\\025', '\026' : '\\026', '\027' : '\\027',
+ '\030' : '\\030', '\031' : '\\031', '\032' : '\\032',
+ '\033' : '\\033', '\034' : '\\034', '\035' : '\\035',
+ '\036' : '\\036', '\037' : '\\037',
+
+ '"' : '\\"', '\\' : '\\\\',
+
+ '\177' : '\\177', '\200' : '\\200', '\201' : '\\201',
+ '\202' : '\\202', '\203' : '\\203', '\204' : '\\204',
+ '\205' : '\\205', '\206' : '\\206', '\207' : '\\207',
+ '\210' : '\\210', '\211' : '\\211', '\212' : '\\212',
+ '\213' : '\\213', '\214' : '\\214', '\215' : '\\215',
+ '\216' : '\\216', '\217' : '\\217', '\220' : '\\220',
+ '\221' : '\\221', '\222' : '\\222', '\223' : '\\223',
+ '\224' : '\\224', '\225' : '\\225', '\226' : '\\226',
+ '\227' : '\\227', '\230' : '\\230', '\231' : '\\231',
+ '\232' : '\\232', '\233' : '\\233', '\234' : '\\234',
+ '\235' : '\\235', '\236' : '\\236', '\237' : '\\237',
+ '\240' : '\\240', '\241' : '\\241', '\242' : '\\242',
+ '\243' : '\\243', '\244' : '\\244', '\245' : '\\245',
+ '\246' : '\\246', '\247' : '\\247', '\250' : '\\250',
+ '\251' : '\\251', '\252' : '\\252', '\253' : '\\253',
+ '\254' : '\\254', '\255' : '\\255', '\256' : '\\256',
+ '\257' : '\\257', '\260' : '\\260', '\261' : '\\261',
+ '\262' : '\\262', '\263' : '\\263', '\264' : '\\264',
+ '\265' : '\\265', '\266' : '\\266', '\267' : '\\267',
+ '\270' : '\\270', '\271' : '\\271', '\272' : '\\272',
+ '\273' : '\\273', '\274' : '\\274', '\275' : '\\275',
+ '\276' : '\\276', '\277' : '\\277', '\300' : '\\300',
+ '\301' : '\\301', '\302' : '\\302', '\303' : '\\303',
+ '\304' : '\\304', '\305' : '\\305', '\306' : '\\306',
+ '\307' : '\\307', '\310' : '\\310', '\311' : '\\311',
+ '\312' : '\\312', '\313' : '\\313', '\314' : '\\314',
+ '\315' : '\\315', '\316' : '\\316', '\317' : '\\317',
+ '\320' : '\\320', '\321' : '\\321', '\322' : '\\322',
+ '\323' : '\\323', '\324' : '\\324', '\325' : '\\325',
+ '\326' : '\\326', '\327' : '\\327', '\330' : '\\330',
+ '\331' : '\\331', '\332' : '\\332', '\333' : '\\333',
+ '\334' : '\\334', '\335' : '\\335', '\336' : '\\336',
+ '\337' : '\\337', '\340' : '\\340', '\341' : '\\341',
+ '\342' : '\\342', '\343' : '\\343', '\344' : '\\344',
+ '\345' : '\\345', '\346' : '\\346', '\347' : '\\347',
+ '\350' : '\\350', '\351' : '\\351', '\352' : '\\352',
+ '\353' : '\\353', '\354' : '\\354', '\355' : '\\355',
+ '\356' : '\\356', '\357' : '\\357', '\360' : '\\360',
+ '\361' : '\\361', '\362' : '\\362', '\363' : '\\363',
+ '\364' : '\\364', '\365' : '\\365', '\366' : '\\366',
+ '\367' : '\\367', '\370' : '\\370', '\371' : '\\371',
+ '\372' : '\\372', '\373' : '\\373', '\374' : '\\374',
+ '\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
+ }
+
+def _quote(str, LegalChars=_LegalChars,
+ join=string.join, idmap=string._idmap, translate=string.translate):
+ #
+ # If the string does not need to be double-quoted,
+ # then just return the string. Otherwise, surround
+ # the string in doublequotes and precede quote (with a \)
+ # special characters.
+ #
+ if "" == translate(str, idmap, LegalChars):
+ return str
+ else:
+ return '"' + join( map(_Translator.get, str, str), "" ) + '"'
+# end _quote
+
+
+_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
+_QuotePatt = re.compile(r"[\\].")
+
+def _unquote(str, join=string.join, atoi=string.atoi):
+ # If there aren't any doublequotes,
+ # then there can't be any special characters. See RFC 2109.
+ if len(str) < 2:
+ return str
+ if str[0] != '"' or str[-1] != '"':
+ return str
+
+ # We have to assume that we must decode this string.
+ # Down to work.
+
+ # Remove the "s
+ str = str[1:-1]
+
+ # Check for special sequences. Examples:
+ # \012 --> \n
+ # \" --> "
+ #
+ i = 0
+ n = len(str)
+ res = []
+ while 0 <= i < n:
+ Omatch = _OctalPatt.search(str, i)
+ Qmatch = _QuotePatt.search(str, i)
+ if not Omatch and not Qmatch: # Neither matched
+ res.append(str[i:])
+ break
+ # else:
+ j = k = -1
+ if Omatch: j = Omatch.start(0)
+ if Qmatch: k = Qmatch.start(0)
+ if Qmatch and ( not Omatch or k < j ): # QuotePatt matched
+ res.append(str[i:k])
+ res.append(str[k+1])
+ i = k+2
+ else: # OctalPatt matched
+ res.append(str[i:j])
+ res.append( chr( atoi(str[j+1:j+4], 8) ) )
+ i = j+4
+ return join(res, "")
+# end _unquote
+
+# The _getdate() routine is used to set the expiration time in
+# the cookie's HTTP header. By default, _getdate() returns the
+# current time in the appropriate "expires" format for a
+# Set-Cookie header. The one optional argument is an offset from
+# now, in seconds. For example, an offset of -3600 means "one hour ago".
+# The offset may be a floating point number.
+#
+
+_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+
+_monthname = [None,
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
+ from time import gmtime, time
+ now = time()
+ year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
+ return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \
+ (weekdayname[wd], day, monthname[month], year, hh, mm, ss)
+
+
+#
+# A class to hold ONE key,value pair.
+# In a cookie, each such pair may have several attributes.
+# so this class is used to keep the attributes associated
+# with the appropriate key,value pair.
+# This class also includes a coded_value attribute, which
+# is used to hold the network representation of the
+# value. This is most useful when Python objects are
+# pickled for network transit.
+#
+
+class Morsel(UserDict):
+ # RFC 2109 lists these attributes as reserved:
+ # path comment domain
+ # max-age secure version
+ #
+ # For historical reasons, these attributes are also reserved:
+ # expires
+ #
+ # This dictionary provides a mapping from the lowercase
+ # variant on the left to the appropriate traditional
+ # formatting on the right.
+ _reserved = { "expires" : "expires",
+ "path" : "Path",
+ "comment" : "Comment",
+ "domain" : "Domain",
+ "max-age" : "Max-Age",
+ "secure" : "secure",
+ "version" : "Version",
+ }
+ _reserved_keys = _reserved.keys()
+
+ def __init__(self):
+ # Set defaults
+ self.key = self.value = self.coded_value = None
+ UserDict.__init__(self)
+
+ # Set default attributes
+ for K in self._reserved_keys:
+ UserDict.__setitem__(self, K, "")
+ # end __init__
+
+ def __setitem__(self, K, V):
+ K = string.lower(K)
+ if not K in self._reserved_keys:
+ raise CookieError("Invalid Attribute %s" % K)
+ UserDict.__setitem__(self, K, V)
+ # end __setitem__
+
+ def isReservedKey(self, K):
+ return string.lower(K) in self._reserved_keys
+ # end isReservedKey
+
+ def set(self, key, val, coded_val,
+ LegalChars=_LegalChars,
+ idmap=string._idmap, translate=string.translate ):
+ # First we verify that the key isn't a reserved word
+ # Second we make sure it only contains legal characters
+ if string.lower(key) in self._reserved_keys:
+ raise CookieError("Attempt to set a reserved key: %s" % key)
+ if "" != translate(key, idmap, LegalChars):
+ raise CookieError("Illegal key value: %s" % key)
+
+ # It's a good key, so save it.
+ self.key = key
+ self.value = val
+ self.coded_value = coded_val
+ # end set
+
+ def output(self, attrs=None, header = "Set-Cookie:"):
+ return "%s %s" % ( header, self.OutputString(attrs) )
+
+ __str__ = output
+
+ def __repr__(self):
+ return '<%s: %s=%s>' % (self.__class__.__name__,
+ self.key, repr(self.value) )
+
+ def js_output(self, attrs=None):
+ # Print javascript
+ return """
+ <SCRIPT LANGUAGE="JavaScript">
+ <!-- begin hiding
+ document.cookie = \"%s\"
+ // end hiding -->
+ </script>
+ """ % ( self.OutputString(attrs), )
+ # end js_output()
+
+ def OutputString(self, attrs=None):
+ # Build up our result
+ #
+ result = []
+ RA = result.append
+
+ # First, the key=value pair
+ RA("%s=%s;" % (self.key, self.coded_value))
+
+ # Now add any defined attributes
+ if attrs is None:
+ attrs = self._reserved_keys
+ items = self.items()
+ items.sort()
+ for K,V in items:
+ if V == "": continue
+ if K not in attrs: continue
+ if K == "expires" and type(V) == type(1):
+ RA("%s=%s;" % (self._reserved[K], _getdate(V)))
+ elif K == "max-age" and type(V) == type(1):
+ RA("%s=%d;" % (self._reserved[K], V))
+ elif K == "secure":
+ RA("%s;" % self._reserved[K])
+ else:
+ RA("%s=%s;" % (self._reserved[K], V))
+
+ # Return the result
+ return string.join(result, " ")
+ # end OutputString
+# end Morsel class
+
+
+
+#
+# Pattern for finding cookie
+#
+# This used to be strict parsing based on the RFC2109 and RFC2068
+# specifications. I have since discovered that MSIE 3.0x doesn't
+# follow the character rules outlined in those specs. As a
+# result, the parsing rules here are less strict.
+#
+
+_LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]"
+_CookiePattern = re.compile(
+ r"(?x)" # This is a Verbose pattern
+ r"(?P<key>" # Start of group 'key'
+ ""+ _LegalCharsPatt +"+?" # Any word of at least one letter, nongreedy
+ r")" # End of group 'key'
+ r"\s*=\s*" # Equal Sign
+ r"(?P<val>" # Start of group 'val'
+ r'"(?:[^\\"]|\\.)*"' # Any doublequoted string
+ r"|" # or
+ ""+ _LegalCharsPatt +"*" # Any word or empty string
+ r")" # End of group 'val'
+ r"\s*;?" # Probably ending in a semi-colon
+ )
+
+
+# At long last, here is the cookie class.
+# Using this class is almost just like using a dictionary.
+# See this module's docstring for example usage.
+#
+class BaseCookie(UserDict):
+ # A container class for a set of Morsels
+ #
+
+ def value_decode(self, val):
+ """real_value, coded_value = value_decode(STRING)
+ Called prior to setting a cookie's value from the network
+ representation. The VALUE is the value read from HTTP
+ header.
+ Override this function to modify the behavior of cookies.
+ """
+ return val, val
+ # end value_encode
+
+ def value_encode(self, val):
+ """real_value, coded_value = value_encode(VALUE)
+ Called prior to setting a cookie's value from the dictionary
+ representation. The VALUE is the value being assigned.
+ Override this function to modify the behavior of cookies.
+ """
+ strval = str(val)
+ return strval, strval
+ # end value_encode
+
+ def __init__(self, input=None):
+ UserDict.__init__(self)
+ if input: self.load(input)
+ # end __init__
+
+ def __set(self, key, real_value, coded_value):
+ """Private method for setting a cookie's value"""
+ M = self.get(key, Morsel())
+ M.set(key, real_value, coded_value)
+ UserDict.__setitem__(self, key, M)
+ # end __set
+
+ def __setitem__(self, key, value):
+ """Dictionary style assignment."""
+ rval, cval = self.value_encode(value)
+ self.__set(key, rval, cval)
+ # end __setitem__
+
+ def output(self, attrs=None, header="Set-Cookie:", sep="\n"):
+ """Return a string suitable for HTTP."""
+ result = []
+ items = self.items()
+ items.sort()
+ for K,V in items:
+ result.append( V.output(attrs, header) )
+ return string.join(result, sep)
+ # end output
+
+ __str__ = output
+
+ def __repr__(self):
+ L = []
+ items = self.items()
+ items.sort()
+ for K,V in items:
+ L.append( '%s=%s' % (K,repr(V.value) ) )
+ return '<%s: %s>' % (self.__class__.__name__, string.join(L))
+
+ def js_output(self, attrs=None):
+ """Return a string suitable for JavaScript."""
+ result = []
+ items = self.items()
+ items.sort()
+ for K,V in items:
+ result.append( V.js_output(attrs) )
+ return string.join(result, "")
+ # end js_output
+
+ def load(self, rawdata):
+ """Load cookies from a string (presumably HTTP_COOKIE) or
+ from a dictionary. Loading cookies from a dictionary 'd'
+ is equivalent to calling:
+ map(Cookie.__setitem__, d.keys(), d.values())
+ """
+ if type(rawdata) == type(""):
+ self.__ParseString(rawdata)
+ else:
+ self.update(rawdata)
+ return
+ # end load()
+
+ def __ParseString(self, str, patt=_CookiePattern):
+ i = 0 # Our starting point
+ n = len(str) # Length of string
+ M = None # current morsel
+
+ while 0 <= i < n:
+ # Start looking for a cookie
+ match = patt.search(str, i)
+ if not match: break # No more cookies
+
+ K,V = match.group("key"), match.group("val")
+ i = match.end(0)
+
+ # Parse the key, value in case it's metainfo
+ if K[0] == "$":
+ # We ignore attributes which pertain to the cookie
+ # mechanism as a whole. See RFC 2109.
+ # (Does anyone care?)
+ if M:
+ M[ K[1:] ] = V
+ elif string.lower(K) in Morsel._reserved_keys:
+ if M:
+ M[ K ] = _unquote(V)
+ else:
+ rval, cval = self.value_decode(V)
+ self.__set(K, rval, cval)
+ M = self[K]
+ # end __ParseString
+# end BaseCookie class
+
+class SimpleCookie(BaseCookie):
+ """SimpleCookie
+ SimpleCookie supports strings as cookie values. When setting
+ the value using the dictionary assignment notation, SimpleCookie
+ calls the builtin str() to convert the value to a string. Values
+ received from HTTP are kept as strings.
+ """
+ def value_decode(self, val):
+ return _unquote( val ), val
+ def value_encode(self, val):
+ strval = str(val)
+ return strval, _quote( strval )
+# end SimpleCookie
+
+class SerialCookie(BaseCookie):
+ """SerialCookie
+ SerialCookie supports arbitrary objects as cookie values. All
+ values are serialized (using cPickle) before being sent to the
+ client. All incoming values are assumed to be valid Pickle
+ representations. IF AN INCOMING VALUE IS NOT IN A VALID PICKLE
+ FORMAT, THEN AN EXCEPTION WILL BE RAISED.
+
+ Note: Large cookie values add overhead because they must be
+ retransmitted on every HTTP transaction.
+
+ Note: HTTP has a 2k limit on the size of a cookie. This class
+ does not check for this limit, so be careful!!!
+ """
+ def value_decode(self, val):
+ # This could raise an exception!
+ return loads( _unquote(val) ), val
+ def value_encode(self, val):
+ return val, _quote( dumps(val) )
+# end SerialCookie
+
+class SmartCookie(BaseCookie):
+ """SmartCookie
+ SmartCookie supports arbitrary objects as cookie values. If the
+ object is a string, then it is quoted. If the object is not a
+ string, however, then SmartCookie will use cPickle to serialize
+ the object into a string representation.
+
+ Note: Large cookie values add overhead because they must be
+ retransmitted on every HTTP transaction.
+
+ Note: HTTP has a 2k limit on the size of a cookie. This class
+ does not check for this limit, so be careful!!!
+ """
+ def value_decode(self, val):
+ strval = _unquote(val)
+ try:
+ return loads(strval), val
+ except:
+ return strval, val
+ def value_encode(self, val):
+ if type(val) == type(""):
+ return val, _quote(val)
+ else:
+ return val, _quote( dumps(val) )
+# end SmartCookie
+
+
+###########################################################
+# Backwards Compatibility: Don't break any existing code!
+
+# We provide Cookie() as an alias for SmartCookie()
+Cookie = SmartCookie
+
+#
+###########################################################
+
+#Local Variables:
+#tab-width: 4
+#end:
diff --git a/system/python/spyce/LICENCE b/system/python/spyce/LICENCE
new file mode 100644
index 0000000000..4bd60a0d8e
--- /dev/null
+++ b/system/python/spyce/LICENCE
@@ -0,0 +1,40 @@
+Copyright (c) 2002-03 Rimon Barr.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice and
+this LICENCE in its entirety including the disclaimer. The LICENCE of this
+product may only be modified by the Copyright holder.
+
+2. Redistributions in binary form must reproduce the above copyright notice
+and this LICENCE in its entirety including the disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. The end-user documentation included with the redistribution, if any, must
+include the following acknowledgment: "This product uses Spyce, Copyright
+Rimon Barr." Alternately, this acknowledgment may appear in the software
+itself, wherever such third-party acknowledgments normally appear. The
+documentation must also provide a instructions on how to receive an original
+Spyce distribution, preferably a link to the website
+http://spyce.sourceforge.net.
+
+4. The names "Spyce", or "Rimon Barr" must not be used to endorse or promote
+products derived from this software without prior written permission. For
+written permission, please contact rimon-AT-acm.org.
+
+5. Products derived from this software may not be called "Spyce", nor may
+"Spyce" appear in their names, without prior written permission of the
+Copyright holder.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RIMON BARR
+OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/system/python/spyce/README b/system/python/spyce/README
new file mode 100644
index 0000000000..1e52701737
--- /dev/null
+++ b/system/python/spyce/README
@@ -0,0 +1,15 @@
+SPYCE - Python Server Pages
+Copyright 2002-03. Rimon Barr <rimon AT acm DOT org>
+---------------------------------------------------------------------
+
+Description: Python-based dynamic HTML scripting engine.
+
+Name: SPYCE - Python Server Pages
+Author: Rimon Barr <rimon AT acm DOT org>
+
+Please refer to the LICENCE file
+
+For system documentation:
+ Run "make docs"
+ Browse the "docs/index.html" file
+
diff --git a/system/python/spyce/RELEASE b/system/python/spyce/RELEASE
new file mode 100644
index 0000000000..abf32fb70e
--- /dev/null
+++ b/system/python/spyce/RELEASE
@@ -0,0 +1,21 @@
+To release a new version of spyce:
+
+- Run: make clean
+- Edit spyce.py: change version (possibly release)
+- Edit CHANGES
+- Run: cvs update; cvs commit
+- Perform release testing
+- Run: make upload (and do the sourceforge file release)
+- Run: make sf
+- Run: make clean
+- post to freshmeat
+ spyce-users@sourceforge.net
+ python-announce@python.org
+ python-list@python.org
+ python-web-modules@yahoogroups.com
+ cheetahtemplate-discuss@lists.sourceforge.net
+ ? mod_python@modpython.org
+ ? webware-discuss@lists.sourceforge.net
+ ? sixthdev@yahoogroups.com
+ ? quixote-users@mems-exchange.org
+ ? php, jsp, apache, cherrypy
diff --git a/system/python/spyce/THANKS b/system/python/spyce/THANKS
new file mode 100644
index 0000000000..48b0b4cad2
--- /dev/null
+++ b/system/python/spyce/THANKS
@@ -0,0 +1,57 @@
+This file contains mention of people that deserve thanks.
+If you feel that you should be added, please email me
+at <rimon AT acm DOT org>
+
+Thanks (in reverse-chronological order) to:
+
+Brandon Beck <bbeck@austin.rr.com>
+ For the daemon mode suggestion
+
+Conan C. Albrecht <conan@warp.byu.edu>
+ Much help with the tags, the built-in webserver, suggestions,
+ bug reports, and fixes ...
+
+Fred Moscicki <fred@computronix.com>
+ Bug reports.
+
+Adrien Plisson <rien@yeepa.org>
+ Lots of bug reports, help with module development,
+ and input of many ideas.
+
+The NullSoft crew:
+ For NSIS SuperPiMP installer
+ http://www.nullsoft.com/free/nsis/
+
+Tino Lange <Tino.Lange@gmx.de>
+ Inspiring my work to get the Spyce engine and modules down from
+ Python 2.2 to Python 1.5.2.
+
+John J Smith <johnjsmith@rediffmail.com>
+ Finding bugs. Email discussions that led to improvements
+ in the Spyce line compacting mode, and the way modules behave
+ in included files.
+
+Piers Lauder <piers@cs.su.oz.au>
+ Email discussions that led to Python chunks (ala Poor Man's Zope),
+ filters (ala Cheetah), and some other ideas.
+
+The Cheetah team
+ http://www.cheetahtemplate.org/
+
+Natalya Katsnelson <nk74@cornell.edu>:
+ For the Spyce mascot, "Pilpel".
+
+Dave Wallace <dwallace@delanet.com>:
+ Provided initial idea in Webware's PSP implementation to add braces
+ to Python code, solving the indentation problem.
+
+Gregory Trubetskoy <grisha@modpython.org>:
+ For the mod_python project, upon which this work depends
+ http://www.modpython.org/
+
+The SourceForge team
+ http://www.sourceforge.net
+
+The Apache Team
+ http://www.apache.org/
+
diff --git a/system/python/spyce/fcgi.py b/system/python/spyce/fcgi.py
new file mode 100644
index 0000000000..d5d0f6d96d
--- /dev/null
+++ b/system/python/spyce/fcgi.py
@@ -0,0 +1,265 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+# Taken originally from: http://alldunn.com/python/fcgi.py
+# Edited a fair bit. -- RB
+
+__doc__ = 'Python Fast CGI implementation'
+
+import os, sys, string, socket, errno, cgi
+from cStringIO import StringIO
+import spyceUtil
+
+##################################################
+# Constants
+#
+
+# Protol constants: record types
+FCGI_BEGIN_REQUEST = 1
+FCGI_ABORT_REQUEST = 2
+FCGI_END_REQUEST = 3
+FCGI_PARAMS = 4
+FCGI_STDIN = 5
+FCGI_STDOUT = 6
+FCGI_STDERR = 7
+FCGI_DATA = 8
+FCGI_GET_VALUES = 9
+FCGI_GET_VALUES_RESULT = 10
+FCGI_UNKNOWN_TYPE = 11
+FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
+# Protocol constants: FCGI_BEGIN_REQUEST flag mask
+FCGI_KEEP_CONN = 1
+# Protocol constants: FCGI_BEGIN_REQUEST role
+FCGI_RESPONDER = 1
+FCGI_AUTHORIZER = 2
+FCGI_FILTER = 3
+# Protocol constants: FCGI_END_REQUEST protocolStatus
+FCGI_REQUEST_COMPLETE = 0 # ok
+FCGI_CANT_MPX_CONN = 1 # can not multiplex
+FCGI_OVERLOADED = 2 # too busy
+FCGI_UNKNOWN_ROLE = 3 # role unknown
+# Protocol constants: management record types
+FCGI_NULL_REQUEST_ID = 0
+
+# Protocol setting: maximum number of requests
+FCGI_MAX_REQS = 1
+FCGI_MAX_CONNS = 1
+# Protocol setting: can multiplex?
+FCGI_MPXS_CONNS = 0
+# Protocol setting: FastCGI protocol version
+FCGI_VERSION_1 = 1
+
+##################################################
+# Protocol
+#
+
+class record:
+ def __init__(self):
+ self.version = FCGI_VERSION_1
+ self.recType = FCGI_UNKNOWN_TYPE
+ self.reqId = FCGI_NULL_REQUEST_ID
+ self.content = ""
+ def readRecord(self, sock):
+ # read content
+ hdr = map(ord, self.readExact(sock, 8))
+ self.version = hdr[0]
+ self.recType = hdr[1]
+ self.reqId = (hdr[2]<<8)+hdr[3]
+ contentLength = (hdr[4]<<8)+hdr[5]
+ paddingLength = hdr[6]
+ self.content = self.readExact(sock, contentLength)
+ self.readExact(sock, paddingLength)
+ # parse
+ c = self.content
+ if self.recType == FCGI_BEGIN_REQUEST:
+ self.role = (ord(c[0])<<8) + ord(c[1])
+ self.flags = ord(c[2])
+ elif self.recType == FCGI_UNKNOWN_TYPE:
+ self.unknownType = ord(c[0])
+ elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+ self.values={}
+ pos=0
+ while pos < len(c):
+ name, value, pos = self.decodePair(c, pos)
+ self.values[name] = value
+ elif self.recType == FCGI_END_REQUEST:
+ b = map(ord, c[0:5])
+ self.appStatus = (b[0]<<24) + (b[1]<<16) + (b[2]<<8) + b[3]
+ self.protocolStatus = b[4]
+ def writeRecord(self, sock):
+ content = self.content
+ if self.recType == FCGI_BEGIN_REQUEST:
+ content = chr(self.role>>8) + chr(self.role & 255) + chr(self.flags) + 5*'\000'
+ elif self.recType == FCGI_UNKNOWN_TYPE:
+ content = chr(self.unknownType) + 7*'\000'
+ elif self.recType==FCGI_GET_VALUES or self.recType==FCGI_PARAMS:
+ content = ""
+ for i in self.values.keys():
+ content = content + self.encodePair(i, self.values[i])
+ elif self.recType==FCGI_END_REQUEST:
+ v = self.appStatus
+ content = chr((v>>24)&255) + chr((v>>16)&255) + chr((v>>8)&255) + chr(v&255)
+ content = content + chr(self.protocolStatus) + 3*'\000'
+ cLen = len(content)
+ eLen = (cLen + 7) & (0xFFFF - 7) # align to an 8-byte boundary
+ padLen = eLen - cLen
+ hdr = [ self.version, self.recType, self.reqId >> 8,
+ self.reqId & 255, cLen >> 8, cLen & 255, padLen, 0]
+ hdr = string.joinfields(map(chr, hdr), '')
+ sock.send(hdr + content + padLen*'\000')
+ def readExact(self, sock, amount):
+ data = ''
+ while amount and len(data) < amount:
+ data = data + sock.recv(amount-len(data))
+ return data
+ def decodePair(self, s, pos):
+ nameLen=ord(s[pos]) ; pos=pos+1
+ if nameLen & 128:
+ b=map(ord, s[pos:pos+3]) ; pos=pos+3
+ nameLen=((nameLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
+ valueLen=ord(s[pos]) ; pos=pos+1
+ if valueLen & 128:
+ b=map(ord, s[pos:pos+3]) ; pos=pos+3
+ valueLen=((valueLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
+ name = s[pos:pos+nameLen] ; pos = pos + nameLen
+ value = s[pos:pos+valueLen] ; pos = pos + valueLen
+ return name, value, pos
+ def encodePair(self, name, value):
+ l=len(name)
+ if l<128: s=chr(l)
+ else: s=chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255)
+ l=len(value)
+ if l<128: s=s+chr(l)
+ else: s=s+chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255)
+ return s + name + value
+
+class FCGI:
+ def __init__(self, port=None):
+ # environment variables
+ try:
+ self.FCGI_PORT = int(os.environ['FCGI_PORT'])
+ except:
+ self.FCGI_PORT = None
+ if port: self.FCGI_PORT = port
+ self.FCGI_PORT = None
+ try:
+ self.FCGI_ALLOWED_ADDR = os.environ['FCGI_WEB_SERVER_ADDRS']
+ self.FCGI_ALLOWED_ADDR = map(string.strip, string.split(self.FCGI_ALLOWED_ADDR, ','))
+ except:
+ self.FCGI_ALLOWED_ADDR = None
+ self.firstCall = 1
+ self.clearState()
+ self.socket = None
+ self.createServerSocket()
+ def createServerSocket(self):
+ if self.FCGI_PORT:
+ s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.set_reuse_addr()
+ s.bind(('127.0.0.1', self.FCGI_PORT))
+ else:
+ try:
+ s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
+ s.getpeername()
+ except socket.error, (err, errmsg):
+ if err!=errno.ENOTCONN:
+ return
+ except:
+ return
+ self.socket = s
+ def accept(self):
+ if not self.socket: # plain CGI
+ if self.firstCall:
+ result = sys.stdin, spyceUtil.NoCloseOut(sys.stdout), sys.stderr, os.environ
+ else:
+ return 0
+ else: # FCGI
+ if not self.firstCall:
+ self.send()
+ result = self.recv()
+ self.firstCall = 0
+ return result
+ def clearState(self):
+ self.reqID = 0
+ self.connection = None
+ self.environ = {}
+ self.stdin = StringIO()
+ self.stderr = StringIO()
+ self.stdout = StringIO()
+ self.data = StringIO()
+ def send(self):
+ self.stderr.seek(0,0)
+ self.stdout.seek(0,0)
+ self.sendStream(FCGI_STDERR, self.stderr.read())
+ self.sendStream(FCGI_STDOUT, self.stdout.read())
+ r=record()
+ r.recType=FCGI_END_REQUEST
+ r.reqId=self.reqID
+ r.appStatus=0
+ r.protocolStatus=FCGI_REQUEST_COMPLETE
+ r.writeRecord(self.connection)
+ self.connection.close()
+ self.clearState()
+ def sendStream(self, streamType, streamData):
+ r=record()
+ r.recType = streamType
+ r.reqId = self.reqID
+ data = streamData
+ while data:
+ r.content, data = data[:8192], data[8192:]
+ r.writeRecord(self.connection)
+ r.content='' ; r.writeRecord(self.connection)
+ def recv(self):
+ self.connection, address = self.socket.accept()
+ # rimtodo: filter to serve only allowed addresses
+ # if good_addrs!=None and addr not in good_addrs:
+ # raise 'Connection from invalid server!'
+ remaining=1
+ while remaining:
+ r=record(); r.readRecord(self.connection)
+ if r.recType in [FCGI_GET_VALUES]: # management records
+ if r.recType == FCGI_GET_VALUES:
+ r.recType = FCGI_GET_VALUES_RESULT
+ v={}
+ vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
+ 'FCGI_MAX_REQS' : FCGI_MAX_REQS,
+ 'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
+ for i in r.values.keys():
+ if vars.has_key(i): v[i]=vars[i]
+ r.values=vars
+ r.writeRecord(self.connection)
+ elif r.reqId == 0: # management record of unknown type
+ r2 = record()
+ r2.recType = FCGI_UNKNOWN_TYPE ; r2.unknownType = r.recType
+ r2.writeRecord(self.connection)
+ continue
+ elif r.reqId != self.reqID and r.recType != FCGI_BEGIN_REQUEST:
+ continue # ignore inactive requests
+ elif r.recType == FCGI_BEGIN_REQUEST and self.reqID != 0:
+ continue # ignore BEGIN_REQUESTs in the middle of request
+ if r.recType == FCGI_BEGIN_REQUEST: # begin request
+ self.reqID = r.reqId
+ if r.role == FCGI_AUTHORIZER: remaining=1
+ elif r.role == FCGI_RESPONDER: remaining=2
+ elif r.role == FCGI_FILTER: remaining=3
+ elif r.recType == FCGI_PARAMS: # environment
+ if r.content == '': remaining=remaining-1
+ else:
+ for i in r.values.keys():
+ self.environ[i] = r.values[i]
+ elif r.recType == FCGI_STDIN: # stdin
+ if r.content == '': remaining=remaining-1
+ else: self.stdin.write(r.content)
+ elif r.recType == FCGI_DATA: # data
+ if r.content == '': remaining=remaining-1
+ else: self.data.write(r.content)
+ # end while
+ self.stdin.seek(0,0)
+ self.data.seek(0,0)
+ # return CGI environment
+ return self.stdin, spyceUtil.NoCloseOut(self.stdout), self.stderr, self.environ
+
diff --git a/system/python/spyce/installHelper.py b/system/python/spyce/installHelper.py
new file mode 100644
index 0000000000..e371fb9e2b
--- /dev/null
+++ b/system/python/spyce/installHelper.py
@@ -0,0 +1,181 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = 'Spyce install helper script'
+
+import os, imp, sys, getopt, string, re, time
+
+CONF_BEGIN_MARK = '### BEGIN SPYCE CONFIG MARKER'
+CONF_END_MARK = '### END SPYCE CONFIG MARKER'
+HTTPD_LOCATIONS = [
+ '/etc/httpd/conf',
+ r'C:\Program Files\Apache Group\Apache2\conf',
+ r'C:\Program Files\Apache Group\Apache\conf',
+ '/etc',
+ '/']
+APACHE_EXE_LOCATIONS = [
+ r'C:\Program Files\Apache Group\Apache2\bin',
+ r'C:\Program Files\Apache Group\Apache2',
+ r'C:\Program Files\Apache Group\Apache\bin',
+ r'C:\Program Files\Apache Group\Apache',
+]
+SYS_LOCATIONS = [
+ r'C:\winnt\system32',
+]
+
+def endsWith(s, suffix):
+ suffixLen = len(suffix)
+ return s[-suffixLen:] == suffix
+
+def listDirFilter(dir, extension):
+ files = os.listdir(dir)
+ files = filter(lambda file: endsWith(file, extension), files)
+ return files
+
+def compilePythonDir(dir):
+ print '** Compiling Python files in: %s' % dir
+ for file in listDirFilter(dir, '.py'):
+ print 'Compiling: %s' % file
+ try:
+ p = os.path.join(dir, file)
+ f = None
+ try:
+ f = open(p, 'r')
+ imp.load_source(os.path.split(file)[1][:-3], p, f)
+ finally:
+ if f: f.close()
+ except: pass
+
+def compileSpyceDir(dir):
+ import spyceCmd
+ print '** Processing Spyce files in: %s' % dir
+ for file in listDirFilter(dir, '.spy'):
+ print 'Processing: %s' % file
+ sys.argv = ['', '-o', os.path.join(dir, file[:-4]+'.html'), os.path.join(dir, file)]
+ spyceCmd.spyceMain()
+
+def findLine(array, line):
+ line = string.strip(line)
+ for i in range(len(array)):
+ if re.search(line, string.strip(array[i])):
+ return i
+ return None
+
+def unconfig(s):
+ lines = string.split(s, '\n')
+ begin = findLine(lines, CONF_BEGIN_MARK)
+ end = findLine(lines, CONF_END_MARK)
+ if begin!=None and end!=None and end>begin:
+ del lines[begin:end+1]
+ s = string.join(lines, '\n')
+ return s
+
+def config(s, root):
+ append = readFile('spyceApache.conf')
+ root = re.sub(r'\\', '/', root)
+ append = string.replace(append, 'XXX', root)
+ append = string.split(append, '\n')
+ if os.name=='nt':
+ row = findLine(append, 'ScriptInterpreterSource')
+ append[row] = string.strip(re.sub('#', '', append[row]))
+ lines = [s] + [CONF_BEGIN_MARK] + append + [CONF_END_MARK]
+ s = string.join(lines, '\n')
+ return s
+
+def readFile(filename):
+ f = None
+ try:
+ f = open(filename, 'r')
+ return f.read()
+ finally:
+ if f: f.close()
+
+def writeFileBackup(filename, new):
+ old = readFile(filename)
+ backupname = filename + '.save'
+ f = None
+ try:
+ f = open(backupname, 'w')
+ f.write(old)
+ finally:
+ if f: f.close()
+ f = None
+ try:
+ f = open(filename, 'w')
+ f.write(new)
+ finally:
+ if f: f.close()
+
+def locateFile(file, locations):
+ def visit(arg, dirname, names, file=file):
+ path = os.path.join(dirname, file)
+ if os.path.exists(path):
+ arg.append(path)
+ if arg:
+ del names[:]
+ found = []
+ for path in locations:
+ os.path.walk(path, visit, found)
+ if found:
+ return found[0]
+
+def configHTTPD(spyceroot):
+ print '** Searching for httpd.conf...'
+ file = locateFile('httpd.conf', HTTPD_LOCATIONS)
+ if file:
+ print '** Modifying httpd.conf'
+ s = readFile(file)
+ s = unconfig(s)
+ s = config(s, spyceroot)
+ writeFileBackup(file, s)
+
+def unconfigHTTPD():
+ print '** Searching for httpd.conf...'
+ file = locateFile('httpd.conf', HTTPD_LOCATIONS)
+ if file:
+ print '** Modifying httpd.conf'
+ s = readFile(file)
+ s = unconfig(s)
+ writeFileBackup(file, s)
+
+def restartApache():
+ print '** Searching for apache.exe...'
+ file = locateFile('apache.exe', APACHE_EXE_LOCATIONS)
+ cmd = locateFile('cmd.exe', SYS_LOCATIONS)
+ if file and cmd:
+ print '** Restarting Apache'
+ os.spawnl(os.P_WAIT, cmd, '/c "%s" -k restart'%file)
+ return
+ print 'Could not find apache.exe'
+
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], '',
+ ['py=', 'spy=', 'apache=', 'apacheUN',
+ 'apacheRestart']);
+ except getopt.error:
+ print "Syntax error"
+ return -1
+ for o, a in opts:
+ if o == "--py":
+ compilePythonDir(a); return 0
+ if o == "--spy":
+ compileSpyceDir(a); return 0
+ if o == "--apache":
+ configHTTPD(a); return 0
+ if o == "--apacheUN":
+ unconfigHTTPD(); return 0
+ if o == "--apacheRestart":
+ restartApache(); return 0
+ print "Syntax error"
+ return -1
+
+if __name__=='__main__':
+ sys.exit(main())
diff --git a/system/python/spyce/makefile b/system/python/spyce/makefile
new file mode 100644
index 0000000000..ef667d983f
--- /dev/null
+++ b/system/python/spyce/makefile
@@ -0,0 +1,160 @@
+PYTHON := $(shell which python)
+SPYCE_VERSION = $(shell $(PYTHON) -c "import spyce; print spyce.__version__")
+SPYCE_RELEASE = $(shell $(PYTHON) -c "import spyce; print spyce.__release__")
+
+SRC := $(wildcard *.py) $(wildcard modules/*.py) $(wildcard tags/*.py)
+OTHER := CHANGES LICENCE README THANKS spyceApache.conf spyce.conf.eg spyce.mime
+DOC_SRC := $(wildcard docs/*.spy) docs/*.gif docs/examples/*.gif
+DOC = $(wildcard docs/*.html) docs/*.gif
+EXAMPLES := $(wildcard docs/examples/*.spy) $(wildcard docs/examples/*.spi) $(wildcard docs/examples/*.tmpl) $(wildcard docs/examples/*.py) $(wildcard docs/examples/*.gif)
+
+SFUSER := batripler
+SFCVSanon := :pserver:anonymous@cvs1.sourceforge.net:/cvsroot/spyce
+SFCVSdev := :ext:$(SFUSER)@cvs1.sourceforge.net:/cvsroot/spyce
+SFCVS := $(SFCVSdev)
+SF_SPYCE_DIR := /home/groups/s/sp/spyce
+
+ssh := ssh -1 -x
+scp := scp -o Protocol=1
+
+COMPILED = $(SRC:.py=.pyc) $(SRC:.py=.pyo)
+
+# install paths
+DESTDIR := /
+INSTALL_ROOT := $(DESTDIR)
+INSTALL_CODE := $(INSTALL_ROOT)/usr/share
+
+RPMROOT := /home/barr/misc/rpm
+
+.PHONY: all compile clean remake tgz rpm www
+
+all: compile docs
+
+# make this a dependency every time you use $PYTHON or $SPYCE_VERSION
+versionchk: $(PYTHON)
+ $(PYTHON) verchk.py
+ touch versionchk
+
+clean: versionchk
+ -rm versionchk
+ -rm -f spyce.spec spyce.nsi
+ -rm -f *.pyc modules/*.pyc tags/*.pyc
+ -rm -f *.pyo modules/*.pyo tags/*.pyo
+ -rm -f spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz
+ -rm -f spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).noarch.rpm
+ -rm -f $(RPMROOT)/SOURCES/spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz
+ -rm -f $(RPMROOT)/SRPMS/spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).src.rpm
+ -rm -f $(RPMROOT)/RPMS/noarch/spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).noarch.rpm
+ -rm -rf $(RPMROOT)/BUILD/spyce-$(SPYCE_VERSION)
+ -$(MAKE) -C docs clean
+
+perm:
+ chmod a+r -R .
+ chmod a+rx . `find . -type d`
+ chmod a+x run_*.py verchk.py
+ chmod a+x spyce.py spyceCGI.py
+
+remake: clean all
+
+compile: $(COMPILED)
+
+# make documentation
+docs: compile
+ @$(MAKE) -C docs
+
+# make source tarball
+tgz: versionchk
+ @echo "Making clean source tarball: spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz"
+ -rm -rf spyce-$(SPYCE_VERSION)
+ cvs -d $(SFCVS) export -d spyce-$(SPYCE_VERSION) -D now spyce
+ chmod a+r -R spyce-$(SPYCE_VERSION)
+ chmod a+x `find spyce-$(SPYCE_VERSION) -type d`
+ chmod a+x spyce-$(SPYCE_VERSION)/run_*.py spyce-$(SPYCE_VERSION)/verchk.py
+ chmod a+x spyce-$(SPYCE_VERSION)/spyce.py spyce-$(SPYCE_VERSION)/spyceCGI.py
+ # process .nsi.in so that we can just use .tgz to make .exe installer
+ cd spyce-$(SPYCE_VERSION); make spyce.nsi; rm -f *.pyc *.pyo; cd ..
+ tar --totals -czf spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz spyce-$(SPYCE_VERSION)
+ rm -rf spyce-$(SPYCE_VERSION)
+
+# make rpm
+rpm: versionchk tgz spyce.spec
+ cp spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz $(RPMROOT)/SOURCES
+ rpmbuild -ba spyce.spec
+ cp $(RPMROOT)/RPMS/noarch/spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).noarch.rpm .
+
+# install Spyce (used for manual install and by rpm scripts)
+install: compile docs
+ @echo "Installing Spyce python module in: $(INSTALL_CODE)/spyce"
+ @for f in `find . -type d`; do \
+ mkdir -p $(INSTALL_CODE)/spyce/$$f; \
+ chmod a+rx $(INSTALL_CODE)/spyce/$$f; \
+ done
+ @for f in $(SRC) $(COMPILED) $(OTHER) $(DOC) $(EXAMPLES); do \
+ install -m 644 $$f $(INSTALL_CODE)/spyce/$$f; \
+ done
+ @chmod a+rx $(INSTALL_CODE)/spyce/run_*.py $(INSTALL_CODE)/spyce/verchk.py
+ @chmod a+rx $(INSTALL_CODE)/spyce/spyce.py $(INSTALL_CODE)/spyce/spyceCGI.py
+
+# uninstall Spyce (used for manual install and by rpm scripts)
+uninstall:
+ @echo "Removing main Spyce directory: $(INSTALL_CODE)/spyce"
+ @-rm -rf $(INSTALL_CODE)/spyce
+
+# generate python compiled (bytecode) files
+$(COMPILED): $(SRC)
+ $(PYTHON) -c "exec '''import py_compile,string,os\nfor i in string.split('$+', ' '):\n print 'Compiling .pyc: '+i; py_compile.compile(i)\n'''"
+ $(PYTHON) -OO -c "exec '''import py_compile,string,os\nfor i in string.split('$+', ' '):\n print 'Compiling .pyo: '+i; py_compile.compile(i)\n'''"
+ chmod a+r $(COMPILED)
+
+%.pyc: %.py versionchk
+ $(PYTHON) -c "import py_compile; py_compile.compile('$<')"
+ chmod a+r $<
+
+%.pyo: %.py versionchk
+ $(PYTHON) -O -c "import py_compile; py_compile.compile('$<')"
+ chmod a+r $<
+
+spyce.spec: spyce.spec.in spyce.py
+ cat spyce.spec.in | sed "s/__VERSION__/${SPYCE_VERSION}/" | sed "s/__RELEASE__/${SPYCE_RELEASE}/" > spyce.spec
+
+spyce.nsi: spyce.nsi.in spyce.py
+ cat spyce.nsi.in | sed "s/__VERSION__/${SPYCE_VERSION}/" | sed "s/__RELEASE__/${SPYCE_RELEASE}/" > spyce.nsi
+
+
+# update sourceforge
+sf: clean tgz compile
+ -rm -r www
+ mkdir www; mkdir www/htdocs; mkdir www/htdocs/examples; mkdir www/cgi-bin; mkdir www/cgi-bin/eg
+ # docs
+ @$(MAKE) -C docs sf
+ cp docs/*.html docs/*.gif docs/*.ico www/htdocs
+ # examples
+ cp docs/examples/*.spy docs/examples/*.spi docs/examples/*.tmpl www/htdocs/examples
+ # exec
+ cp spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz www
+ cd www; tar -xzf spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz; mv spyce-$(SPYCE_VERSION) spyce; rm spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz; cd spyce; make compile; cd ../..
+ # cgi
+ cp docs/examples/*.spy docs/examples/*.spi docs/examples/*.py docs/examples/*.tmpl docs/examples/*.gif www/cgi-bin/eg
+ cd www; for f in cgi-bin/eg/*.spy; do spyce/misc/addfirstline.sh $$f '#!/usr/bin/python ../../spyce/run_spyceCGI.py'; done; cd ..
+ # package it all up
+ tar -czvf www.tgz www/
+ -rm -r www
+ # send it over
+ $(scp) www.tgz $(SFUSER)@shell.sourceforge.net:.
+ -rm www.tgz
+ make clean
+ # unpackage it there
+ $(ssh) $(SFUSER)@shell.sourceforge.net 'tar -xzf www.tgz; rm www.tgz; cd www; chmod -R a+r .; chmod a+x `find . -type d`; chmod a+x cgi-bin/eg/*.spy; cd spyce; make perm; cd ../..; chown -R :spyce www; chmod -R g+rw .; chmod g+x `find www -type d`'
+ # out with the old and in with the new
+ $(ssh) $(SFUSER)@shell.sourceforge.net 'pushd $(SF_SPYCE_DIR); rm -rf cgi-bin htdocs spyce; popd; mv www/* $(SF_SPYCE_DIR); rmdir www'
+
+sfcontrib:
+ # contribs
+ python run_spyceCmd.py -O contrib/*.spy
+ $(scp) -r contrib $(SFUSER)@shell.sourceforge.net:$(SF_SPYCE_DIR)/htdocs
+
+# upload files to sourceforge file release system
+upload: versionchk rpm
+ ncftpput upload.sourceforge.net incoming spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).tgz spyce-$(SPYCE_VERSION)-$(SPYCE_RELEASE).noarch.rpm
+ make clean
+
diff --git a/system/python/spyce/modules/automaton.py b/system/python/spyce/modules/automaton.py
new file mode 100644
index 0000000000..68a8c9a8ae
--- /dev/null
+++ b/system/python/spyce/modules/automaton.py
@@ -0,0 +1,79 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+
+__doc__ = '''Automaton module allows Spyce users to create websites with
+state machine-based application flows. One can define an automaton
+programmatically using the start(), transition() and begin methods. The
+automaton is the executed (one step per request) using the step() method. This
+method accepts the current state, which should be managed by the user
+preferably via a session (keeping the information on the server), or possibly
+by get, post or cookie. The step() method then calls the recv() function on
+the given state, which returns an edge label. This edge points to the new
+state. The step() method then calls the send() method of the new state to
+generate the page content. The user should encode the new state in this
+content, or use on a subsequent request.'''
+
+SEND = 0
+RECV = 1
+EDGES = 2
+
+class automaton(spyceModule):
+ def start(self):
+ "Initialise an empty automaton"
+ self.clear()
+ def clear(self):
+ self._nodes = {}
+ self._edges = {}
+ # defining the automaton
+ def state(self, name, send, recv):
+ "Add a new automaton state"
+ self._nodes[name] = send, recv
+ self.transition(name, None, name)
+ def transition(self, state1, edge, state2):
+ "Add a new automaton transition"
+ if not self._nodes.has_key(state1):
+ raise 'state %s does not exist' % state1
+ if not self._nodes.has_key(state2):
+ raise 'state %s does not exist' % state2
+ self._edges[(state1, edge)] = state2
+ node=state
+ edge=transition
+ def begin(self, name):
+ if not self._nodes.has_key(name):
+ raise 'state %s does not exist' % name
+ self._begin = name
+ def define(self, sm, start):
+ self.clear()
+ for s1 in sm.keys():
+ self.node(s1, sm[s1][SEND], sm[s1][RECV])
+ for s1 in sm.keys():
+ for e in sm[s1][EDGES].keys():
+ self.edge(s1, e, sm[s1][EDGES][e])
+ self.begin(start)
+
+ # running the automaton
+ def step(self, state=None):
+ """Run the automaton one step: recv (old state), transition,
+ send (new state)"""
+ if state==None:
+ state = self._begin
+ else:
+ try: _, recv = self._nodes[state]
+ except: raise 'invalid state: %s' % state
+ edge = recv()
+ try: state = self._edges[(state, edge)]
+ except: raise 'invalid transition: %s,%s' % (state, edge)
+ try: send, _ = self._nodes[state]
+ except: raise 'invalid state: %s' % state
+ send()
+
+
+# rimtodo: cached state-machines
+
diff --git a/system/python/spyce/modules/compress.py b/system/python/spyce/modules/compress.py
new file mode 100644
index 0000000000..5ff0b02299
--- /dev/null
+++ b/system/python/spyce/modules/compress.py
@@ -0,0 +1,84 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+from cStringIO import StringIO
+import gzip, string, spyceUtil
+
+__doc__ = '''Compress module provides dynamic compression.'''
+
+OUTPUT_POSITION = 95
+
+class compress(spyceModule):
+ def start(self):
+ # install compress filter into response module
+ self._filter = FilterCompress(self)
+ self._api.getModule('response').addFilter(OUTPUT_POSITION, self._filter)
+ def finish(self, theError=None):
+ if not theError:
+ self._filter.close()
+ def init(self, gzip=None, spaces=None):
+ if gzip: self.gzip()
+ if spaces: self.spaces()
+ def spaces(self, on=1):
+ self._filter.setSpaceMode(on)
+ def gzip(self, level=None):
+ self._filter.setGZIP(level)
+
+class FilterCompress(Filter):
+ def __init__(self, module):
+ self._module = module
+ self._buf = StringIO()
+ self._flushed = 0
+ self._space = 0
+ self._gzip = None
+ self._gzipfile = None
+ def writeStatic(self, s):
+ self.write(s)
+ def writeExpr(self, s, **kwargs):
+ self.write(str(s))
+ def setSpaceMode(self, on):
+ self._space = on
+ def setGZIP(self, level):
+ if self._flushed:
+ raise 'output already flushed'
+ encodings = self._module._api.getModule('request').getHeader('Accept-Encoding')
+ if not encodings or string.find(encodings, 'gzip')<0:
+ return # ensure the browser can cope
+ if level==0:
+ self._gzip = None
+ self._gzipfile = None
+ else:
+ self._gzipfile = StringIO()
+ if level:
+ self._gzip = gzip.GzipFile(mode='w', fileobj=self._gzipfile, compresslevel=level)
+ else:
+ self._gzip = gzip.GzipFile(mode='w', fileobj=self._gzipfile)
+ def write(self, s, *args, **kwargs):
+ self._buf.write(s)
+ def flushImpl(self, final=0):
+ self._flushed = 1
+ s = self._buf.getvalue()
+ self._buf = StringIO()
+ if self._space:
+ s = spyceUtil.spaceCompact(s)
+ if self._gzip:
+ self._gzip.write(s)
+ self._gzip.flush()
+ if final:
+ self._module._api.getModule('response').addHeader('Content-Encoding', 'gzip')
+ self._gzip.close()
+ self._gzip = None
+ s = self._gzipfile.getvalue()
+ self._gzipfile.truncate(0)
+ self.next.write(s)
+ def clearImpl(self):
+ self._buf = StringIO()
+ def close(self):
+ self.flushImpl(1)
+
diff --git a/system/python/spyce/modules/cookie.py b/system/python/spyce/modules/cookie.py
new file mode 100644
index 0000000000..e17ad05a3f
--- /dev/null
+++ b/system/python/spyce/modules/cookie.py
@@ -0,0 +1,50 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import Cookie, time, calendar
+
+__doc__ = """Cookie module gives users full control over browser cookie
+functionality. """
+
+class cookie(spyceModule):
+ def start(self):
+ self._cookie = None
+ def get(self, key=None):
+ "Get brower cookie(s)"
+ if self._cookie == None:
+ self._cookie = {}
+ cookie = Cookie.SimpleCookie(self._api.getModule('request').env('HTTP_COOKIE'))
+ for c in cookie.keys():
+ self._cookie[c] = cookie[c].value
+ if key:
+ if self._cookie.has_key(key):
+ return self._cookie[key]
+ else: return self._cookie
+ def __getitem__(self, key):
+ "Get browser cookie(s)"
+ return self.get(key)
+ def set(self, key, value, expire=None, domain=None, path=None, secure=0):
+ "Set browser cookie"
+ if value==None: # delete (set to expire one week ago)
+ return self.set(key, '', -60*60*24*7, domain, path, secure)
+ text = '%s=%s' % (key, value)
+ if expire != None: text = text + ';EXPIRES=%s' % time.strftime(
+ '%a, %d-%b-%y %H:%M:%S GMT',
+ time.gmtime(time.time()+expire))
+ if domain: text = text + ';DOMAIN=%s' % domain
+ if path: text = text + ';PATH=%s' % path
+ if secure: text = text + ';SECURE'
+ self._api.getModule('response').addHeader('Set-Cookie', text)
+ def delete(self, key):
+ "Delete browser cookie"
+ self.set(key, None)
+ def __delitem__(self, key):
+ "Delete browser cookie"
+ return self.delete(self, key)
+
diff --git a/system/python/spyce/modules/error.py b/system/python/spyce/modules/error.py
new file mode 100644
index 0000000000..db396294e2
--- /dev/null
+++ b/system/python/spyce/modules/error.py
@@ -0,0 +1,171 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import spyceException
+import os
+
+__doc__ = '''Error module provides error-handling functionality.'''
+
+class error(spyceModule):
+ def start(self):
+ self._error = None
+ pageerrorType, pageerrorData = self._api.getPageError()
+ self.handler = lambda self, pageerrorType=pageerrorType, pageerrorData=pageerrorData: spyceHandler(self, pageerrorData, pageerrorType)
+ def finish(self, theError=None):
+ self._error = theError
+ self._fromFile = self._api.getFilename()
+ if theError:
+ self.handler(self)
+ def setHandler(self, fn):
+ if not type(fn)==type(spyceHandler):
+ raise 'parameter should be a function'
+ self.handler = fn
+ def setStringHandler(self, s):
+ if not type(s)==type(''):
+ raise 'parameter should be a string of spyce code'
+ self.handler = lambda self, s=s: spyceHandler(self, s, 'string')
+ def setFileHandler(self, f):
+ if not type(f)==type(''):
+ raise 'parameter should be a filename'
+ self.handler = lambda self, f=f: spyceHandler(self, f)
+ def getHandler(self):
+ return self.handler
+ def isError(self):
+ return not not self._error
+ def getMessage(self):
+ if self._error: return self._error.msg
+ def getType(self):
+ if self._error: return self._error.type
+ def getFile(self):
+ if self._error: return self._fromFile
+ def getTraceback(self):
+ if self._error: return self._error.traceback
+ def getString(self):
+ if self._error: return self._error.str
+ def __repr__(self):
+ if not self._error: return 'None'
+ return 'type: %s, string: %s, msg: %s, file: %s' % (
+ self.getType(), self.getString(), self.getMessage(), self.getFile())
+
+def spyceHandler(errorModule, spyceCode, type='file'):
+ try:
+ responseModule = errorModule._api.getModule('response')
+ responseModule.clear()
+ responseModule.clearHeaders()
+ responseModule.clearFilters()
+ responseModule.setContentType('text/html')
+ responseModule.setReturnCode(errorModule._api.getResponse().RETURN_OK)
+ except: pass
+ try:
+ s, file = None, None
+ if type=='file':
+ file = os.path.join(os.path.dirname(errorModule._api.getFilename()), spyceCode)
+ code = errorModule._api.spyceFile(file)
+ elif type=='string':
+ file = '<string>'
+ code = errorModule._api.spyceString(spyceCode)
+ else:
+ raise 'unrecognized handler type'
+ try:
+ s = code.newWrapper()
+ modules = errorModule._api.getModules()
+ for name in modules.keys():
+ s.setModule(name, modules[name]) # include modules as well!
+ s.spyceInit(errorModule._api.getRequest(), errorModule._api.getResponse())
+ errmod = s._startModule('error', None, None, 1)
+ errmod._error = errorModule._error
+ errmod._fromFile = errorModule._fromFile
+ s.spyceProcess()
+ finally:
+ if s:
+ s.spyceDestroy()
+ code.returnWrapper(s)
+ except spyceException.spyceRuntimeException, e:
+ errorModule._error = e
+ errorModule._fromFile = file
+ if (type, spyceCode) == ('string', defaultErrorTemplate):
+ raise # avoid infinite loop
+ else:
+ spyceHandler(errorModule, defaultErrorTemplate, 'string')
+
+defaultErrorTemplate = '''
+[[.module name=transform]]
+[[transform.expr('html_encode')]]
+<html>
+<title>Spyce exception: [[=error.getMessage()]]</title>
+<body>
+<table border=0>
+ <tr><td colspan=2><h1>Spyce exception</h1></td></tr>
+ <tr><td valign=top><b>File:</b></td><td>[[=error.getFile()]]</tr>
+ <tr><td valign=top><b>Message:</b></td>
+ <td><pre>[[=error.getMessage()]]</pre></tr>
+ <tr><td valign=top><b>Stack:</b></td><td>
+ [[ for frame in error.getTraceback(): { ]]
+ [[=frame[0] ]]:[[=frame[1] ]], in [[=frame[2] ]]:<br>
+ <table border=0><tr><td width=10></td><td>
+ <pre>[[=frame[3] ]]</pre>
+ </td></tr></table>
+ [[ } ]]
+ </td></tr>
+</table>
+</body></html>
+'''
+
+def serverHandler(theServer, theRequest, theResponse, theError):
+ try:
+ theResponse.clear()
+ theResponse.clearHeaders()
+ theResponse.setContentType('text/html')
+ theResponse.setReturnCode(theResponse.RETURN_OK)
+ except: pass
+ s = None
+ try:
+ spycecode = theServer.spyce_cache[('string', serverErrorTemplate)]
+ s = spycecode.newWrapper()
+ s.spyceInit(theRequest, theResponse)
+ s.getModule('error')._error = theError
+ s.spyceProcess()
+ finally:
+ if s:
+ s.spyceDestroy()
+ spycecode.returnWrapper(s)
+
+
+serverErrorTemplate = '''
+[[.module name=transform]]
+[[import string, spyceException
+ if isinstance(error._error, spyceException.spyceNotFound): { ]]
+ <html><body>
+ [[=error._error.file]] not found
+ [[response.setReturnCode(response._api.getResponse().RETURN_NOT_FOUND)]]
+ </body></html>
+[[ } elif isinstance(error._error, spyceException.spyceForbidden): { ]]
+ <html><body>
+ [[=error._error.file]] forbidden
+ [[response.setReturnCode(response._api.getResponse().RETURN_FORBIDDEN)]]
+ </body></html>
+[[ } elif isinstance(error._error, spyceException.spyceSyntaxError): { ]]
+ <html><body><pre>
+ [[=transform.html_encode(`error._error`)]]
+ </pre></body></html>
+[[ } elif isinstance(error._error, spyceException.pythonSyntaxError): { ]]
+ <html><body><pre>
+ [[=transform.html_encode(`error._error`)]]
+ </pre></body></html>
+[[ } elif isinstance(error._error, SyntaxError): { ]]
+ <html><body><pre>
+ Syntax error at [[=error._error.filename]]:[[=error._error.lineno]] -
+ [[=transform.html_encode(error._error.text)]] [[
+ if not error._error.offset==None: {
+ print ' '*error._error.offset+'^'
+ }
+ ]]
+ </pre></body></html>
+[[ } else: { raise error._error } ]]
+'''
diff --git a/system/python/spyce/modules/include.py b/system/python/spyce/modules/include.py
new file mode 100644
index 0000000000..ab7bd9ddb1
--- /dev/null
+++ b/system/python/spyce/modules/include.py
@@ -0,0 +1,117 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import os, re
+
+__doc__ = """Include module is used to assist the inclusion of abitrary
+elements/files into a Spyce file. It can also support the notion of an
+'inclusion context'."""
+
+class include(spyceModule):
+ def start(self):
+ self.context = None
+ self.vars = None
+ self.fromFile = None
+ def spyce(self, file, context=None):
+ "Include a Spyce file"
+ file = os.path.join(os.path.dirname(self._api.getFilename()), file)
+ result = s = code = None
+ try:
+ code = self._api.spyceFile(file)
+ s = code.newWrapper()
+ modules = self._api.getModules()
+ for name in modules.keys():
+ s.setModule(name, modules[name]) # include module as well!
+ s.spyceInit(self._api.getRequest(), self._api.getResponse())
+ incmod = s._startModule('include', None, None, 1)
+ incmod.context = context
+ if type(context)==type({}):
+ incmod.vars = spyceVars(context)
+ incmod.fromFile = self._api.getFilename()
+ result = s.spyceProcess()
+ finally:
+ if s:
+ s.spyceDestroy()
+ code.returnWrapper(s)
+ return result
+ def spyceStr(self, file, context=None):
+ stdout = self._api.getModule('stdout')
+ stdout.push()
+ try:
+ result = self.spyce(file, context)
+ finally:
+ output = stdout.pop()
+ return output
+ def dump(self, file, binary=0):
+ "Include a plain text file, verbatim"
+ file = os.path.join(os.path.dirname(self._api.getFilename()), file)
+ f = None
+ try:
+ if binary: mode='rb'
+ else: mode='r'
+ f = open(file, mode)
+ buf = f.read()
+ finally:
+ if f: f.close()
+ return buf
+ def spycecode(self, file=None, string=None, html=None, code=None, eval=None, directive=None, comment=None):
+ "Emit formatted Spyce code"
+ if not html: html = ('<font color="#000000"><b>', '</b></font>')
+ if not code: code = ('<font color="#0000CC">', '</font>')
+ if not eval: eval = ('<font color="#CC0000">', '</font>')
+ if not directive: directive = ('<font color="#CC00CC">', '</font>')
+ if not comment: comment = ('<font color="#777777">', '</font>')
+ import spyceCompile
+ from StringIO import StringIO
+ if (file and string) or (not file and not string):
+ raise 'must specify either file or string, and not both'
+ if file:
+ f = None
+ try:
+ file = os.path.join(os.path.dirname(self._api.getFilename()), file)
+ f = open(file, 'r')
+ string = f.read()
+ finally:
+ if f: f.close()
+ html_encode = self._api.getModule('transform').html_encode
+ try:
+ tokens = spyceCompile.spyceTokenize(string)
+ buf = StringIO()
+ markupstack = []
+ buf.write(html[0]); markupstack.append(html[1])
+ for type, text, _, _ in tokens:
+ if type in (spyceCompile.T_STMT, spyceCompile.T_CHUNK, spyceCompile.T_CHUNKG,):
+ buf.write(code[0]); markupstack.append(code[1])
+ if type in (spyceCompile.T_LAMBDA,):
+ buf.write(html[0]); markupstack.append(html[1])
+ if type in (spyceCompile.T_EVAL,):
+ buf.write(eval[0]); markupstack.append(eval[1])
+ if type in (spyceCompile.T_DIRECT,):
+ buf.write(directive[0]); markupstack.append(directive[1])
+ if type in (spyceCompile.T_CMNT,):
+ buf.write(comment[0]); markupstack.append(comment[1])
+ buf.write(html_encode(text))
+ if type in (spyceCompile.T_END_CMNT, spyceCompile.T_END,):
+ buf.write(markupstack.pop())
+ while markupstack:
+ buf.write(markupstack.pop())
+ return buf.getvalue()
+ except:
+ raise
+ raise 'error tokenizing!'
+
+class spyceVars:
+ def __init__(self, vars):
+ self.__dict__['vars'] = vars
+ def __getattr__(self, name):
+ try: return self.__dict__['vars'][name]
+ except KeyError: raise AttributeError
+ def __setattr__(self, name, value):
+ self.__dict__['vars'][name] = value
+
diff --git a/system/python/spyce/modules/pool.py b/system/python/spyce/modules/pool.py
new file mode 100644
index 0000000000..5b8d1a5c99
--- /dev/null
+++ b/system/python/spyce/modules/pool.py
@@ -0,0 +1,45 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+
+__doc__ = """Pool module supports the creation of server-pooled objects. The
+single pool is shared among all Spyce execution context residing on a given
+server, and remains until the server dies. It is often useful to store
+database connections, and other variables that are expensive to compute on a
+per-request basis. """
+
+class pool(spyceModule):
+ def start(self):
+ "Define or retrieve the pool."
+ self._serverobject = self._api.getServerObject()
+ if 'pool' not in dir(self._serverobject):
+ self._serverobject.pool = {}
+ self.server = self._api.getServerGlobals()
+ def __getitem__(self, key):
+ "Get an item from the pool."
+ return self._serverobject.pool[key]
+ def __setitem__(self, key, value):
+ "Set an item in the pool."
+ self._serverobject.pool[key] = value
+ def __delitem__(self, key):
+ "Delete an item in the pool."
+ del self._serverobject.pool[key]
+ def keys(self):
+ "Return the pool hash keys."
+ return self._serverobject.pool.keys()
+ def values(self):
+ "Return the pool hash values."
+ return self._serverobject.pool.values()
+ def has_key(self, key):
+ "Test of existence of key in pool."
+ return self._serverobject.pool.has_key(key)
+ def clear(self):
+ "Purge the pool of all items."
+ return self._serverobject.pool.clear()
+
diff --git a/system/python/spyce/modules/redirect.py b/system/python/spyce/modules/redirect.py
new file mode 100644
index 0000000000..9d767cac32
--- /dev/null
+++ b/system/python/spyce/modules/redirect.py
@@ -0,0 +1,53 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import spyceException
+
+__doc__ = '''Redirect module provides support for different kinds of request
+redirection, currently: internal, external and externalRefresh.
+- internal: flush the current output bufffer (assuming it has not been sent)
+ and raise an appropriate exception that will start the processing of the
+ new file, if left to percolate all the way to the Spyce engine. The
+ browser url does not change.
+- external: send an HTTP return code that signals a permanent or temporary
+ page move, depending on the boolean parameter. Spyce file execution will
+ continue to termination, but the output buffer is flushed at the end and a
+ special redirect document is generated. The browser is expected, as per the
+ standard, to immediately redirect and perform a new request, thus the url
+ will change.
+- externalRefresh: send an HTTP Refresh header that requests a page refresh to
+ a (possibly) new location within some number of seconds. The current Spyce
+ page will be displayed until that time. This is often used to display a page
+ before redirecting the browser to a file download.
+'''
+
+class redirect(spyceModule):
+ def start(self):
+ self.clear = 0
+ def finish(self, theError=None):
+ if not theError:
+ if self.clear:
+ self._api.getModule('response').clear()
+ def internal(self, file):
+ "Perform an internal redirect."
+ self._api.getModule('response').clearHeaders()
+ self._api.getModule('response').clear()
+ file = os.path.join(os.path.dirname(self._api.getFilename()), file)
+ raise spyceException.spyceRedirect(file)
+ def external(self, url, permanent=0):
+ "Perform an external redirect."
+ self._api.getModule('response').addHeader('Location', url)
+ if permanent:
+ self._api.getModule('response').setReturnCode(self._api.getResponse().RETURN_MOVED_PERMANENTLY)
+ else:
+ self._api.getModule('response').setReturnCode(self._api.getResponse().RETURN_MOVED_TEMPORARILY)
+ self.clear = 1
+ def externalRefresh(self, url, sec=0):
+ "Perform an external redirect, via refresh."
+ self._api.getModule('response').addHeader('Refresh', '%d; URL=%s' % (sec, url))
diff --git a/system/python/spyce/modules/request.py b/system/python/spyce/modules/request.py
new file mode 100644
index 0000000000..f1a1cef5df
--- /dev/null
+++ b/system/python/spyce/modules/request.py
@@ -0,0 +1,224 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import cgi, string, urlparse, spyceUtil
+
+__doc__ = """Request module provides access to the browser request
+information. """
+
+class request(spyceModule):
+ def start(self):
+ "Initialise module variables"
+ self._get = None
+ self._postfields = None
+ def uri(self, component=None):
+ "Return request URI, or URI component"
+ theuri = self._api.getRequest().env()['REQUEST_URI']
+ if not component:
+ return theuri
+ else:
+ component = string.lower(component)
+ if component == 'scheme': component = 0
+ elif component == 'location': component = 1
+ elif component == 'path': component = 2
+ elif component == 'parameters': component = 3
+ elif component == 'query': component = 4
+ elif component == 'fragment': component = 5
+ else: raise 'unknown uri component'
+ return urlparse.urlparse(theuri)[component]
+ def uri_scheme(self):
+ "Return request URI scheme, ie. http (usually)"
+ return urlparse.urlparse(self.uri())[0]
+ def uri_location(self):
+ "Return request URI scheme, ie. http (usually)"
+ return urlparse.urlparse(self.uri())[1]
+ def uri_path(self):
+ "Return request URI path component"
+ return urlparse.urlparse(self.uri())[2]
+ def method(self):
+ "Return request method: get/post/..."
+ return string.upper(self._api.getRequest().env()['REQUEST_METHOD'])
+ def query(self):
+ "Return request query string"
+ return self._api.getRequest().env()['QUERY_STRING']
+ def filename(self, relative=None):
+ "Return original Spyce filename"
+ myfile = self._api.getFilename()
+ if relative==None:
+ return myfile
+ else:
+ return os.path.normpath(os.path.join(os.path.dirname(myfile), relative))
+ def default(self, value, value2):
+ "Return value, or value2 if value==None"
+ if value==None: return value2
+ return value
+ def _getInit(self):
+ if self._get==None:
+ self._get = cgi.parse_qs(self.query(), 1)
+ self._get1 = {}
+ self._getL = {}
+ self._getL1 = {}
+ for key in self._get.keys():
+ self._getL[string.lower(key)] = []
+ for key in self._get.keys():
+ keyL = string.lower(key)
+ self._getL[keyL] = self._getL[keyL] + self._get[key]
+ for key in self._get.keys():
+ self._get1[key] = self._get[key][0]
+ for keyL in self._getL.keys():
+ self._getL1[keyL] = self._getL[keyL][0]
+ def get(self, name=None, default=None, ignoreCase=0):
+ "Return GET parameter(s) list(s)"
+ self._getInit()
+ if ignoreCase:
+ if name: name = string.lower(name)
+ value = spyceUtil.extractValue(self._getL, name)
+ else:
+ value = spyceUtil.extractValue(self._get, name)
+ return self.default(value, default)
+ def get1(self, name=None, default=None, ignoreCase=0):
+ "Return single GET parameter(s)"
+ self._getInit()
+ if ignoreCase:
+ if name: name = string.lower(name)
+ value = spyceUtil.extractValue(self._getL1, name)
+ else:
+ value = spyceUtil.extractValue(self._get1, name)
+ return self.default(value, default)
+ def _postInit(self):
+ if self._postfields==None:
+ if hasattr(self._api.getRequest(), 'spycepostinfo'):
+ # stream was already parsed (possibly this is an internal redirect)
+ (self._post, self._post1, self._file,
+ self._postL, self._postL1, self._fileL,
+ self._postfields) = self._api.getRequest().spycepostinfo
+ return
+ self._post = {}
+ self._post1 = {}
+ self._file = {}
+ self._postL = {}
+ self._postL1 = {}
+ self._fileL = {}
+ self._postfields={}
+ try: len = int(str(self.env('CONTENT_LENGTH')))
+ except: len=0
+ if self.method()=='POST' and len:
+ self._postfields = cgi.FieldStorage(fp=self._api.getRequest(), environ=self.env(), keep_blank_values=1)
+ for key in self._postfields.keys():
+ if type(self._postfields[key]) == type( [] ):
+ self._post[key] = map(lambda attr: attr.value, self._postfields[key])
+ self._post1[key] = self._post[key][0]
+ elif not self._postfields[key].filename:
+ self._post[key] = [self._postfields[key].value]
+ self._post1[key] = self._post[key][0]
+ else:
+ self._file[key] = self._fileL[string.lower(key)] = self._postfields[key]
+ for key in self._post.keys():
+ self._postL[string.lower(key)] = []
+ for key in self._post.keys():
+ keyL = string.lower(key)
+ self._postL[keyL] = self._postL[keyL] + self._post[key]
+ for keyL in self._postL.keys():
+ self._postL1[keyL] = self._postL[keyL][0]
+ # save parsed information in request object to prevent reparsing (on redirection)
+ self._api.getRequest().spycepostinfo = (self._post, self._post1, self._file,
+ self._postL, self._postL1, self._fileL, self._postfields)
+ def post(self, name=None, default=None, ignoreCase=0):
+ "Return POST parameter(s) list(s)"
+ self._postInit()
+ if ignoreCase:
+ if name: name = string.lower(name)
+ value = spyceUtil.extractValue(self._postL, name)
+ else:
+ value = spyceUtil.extractValue(self._post, name)
+ return self.default(value, default)
+ def post1(self, name=None, default=None, ignoreCase=0):
+ "Return single POST parameter(s)"
+ self._postInit()
+ if ignoreCase:
+ if name: name = string.lower(name)
+ value = spyceUtil.extractValue(self._postL1, name)
+ else:
+ value = spyceUtil.extractValue(self._post1, name)
+ return self.default(value, default)
+ def file(self, name=None, ignoreCase=0):
+ "Return POSTed file(s)"
+ self._postInit()
+ if ignoreCase:
+ if name: name = string.lower(name)
+ return spyceUtil.extractValue(self._fileL, name)
+ else:
+ return spyceUtil.extractValue(self._file, name)
+ def env(self, name=None, default=None):
+ "Return other request (CGI) environment variables"
+ return self.default(self._api.getRequest().env(name), default)
+ def getHeader(self, type=None):
+ "Return browser HTTP header(s)"
+ return self._api.getRequest().getHeader(type)
+ def __getitem__(self, key):
+ if type(key) == type(0):
+ return self.getpost().keys()[key]
+ else:
+ v = self.get1(key)
+ if v!=None: return v
+ v = self.post1(key)
+ if v!=None: return v
+ v = self.file(key)
+ if v!=None: return v
+ def __repr__(self):
+ return ''
+ def __multidict(self, *args):
+ args = list(args)
+ args.reverse()
+ dict = {}
+ for d in args:
+ for k in d.keys():
+ dict[k] = d[k]
+ return dict
+ def getpost(self, name=None, default=None, ignoreCase=0):
+ "Return get() if not None, otherwise post() if not None, otherwise default"
+ if name==None:
+ self._getInit()
+ self._postInit()
+ return self.__multidict(self._get, self._post)
+ else:
+ value = self.get(name, None, ignoreCase)
+ if value==None: value = self.post(name, default, ignoreCase)
+ return value
+ def getpost1(self, name=None, default=None, ignoreCase=0):
+ "Return get1() if not None, otherwise post1() if not None, otherwise default"
+ if name==None:
+ self._getInit()
+ self._postInit()
+ return self.__multidict(self._get1, self._post1)
+ else:
+ value = self.get1(name, None, ignoreCase)
+ if value==None: value = self.post1(name, default, ignoreCase)
+ return value
+ def postget(self, name=None, default=None, ignoreCase=0):
+ "Return post() if not None, otherwise get() if not None, otherwise default"
+ if name==None:
+ self._getInit()
+ self._postInit()
+ return self.__multidict(self._post, self._get)
+ else:
+ value = self.post(name, None, ignoreCase)
+ if value==None: value = self.get(name, default, ignoreCase)
+ return value
+ def postget1(self, name=None, default=None, ignoreCase=0):
+ "Return post1() if not None, otherwise get1() if not None, otherwise default"
+ if name==None:
+ self._getInit()
+ self._postInit()
+ return self.__multidict(self._post1, self._get1)
+ else:
+ value = self.post1(name, None, ignoreCase)
+ if value==None: value = self.get1(name, default, ignoreCase)
+ return value
+
diff --git a/system/python/spyce/modules/response.py b/system/python/spyce/modules/response.py
new file mode 100644
index 0000000000..e68ba363a9
--- /dev/null
+++ b/system/python/spyce/modules/response.py
@@ -0,0 +1,186 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import string, time
+
+__doc__ = '''Response module provides user control over the browser
+response.'''
+
+class response(spyceModule):
+ def start(self):
+ self.clearFilters()
+ self._unbuffer = 0
+ self._ioerror = 0
+ self._api.registerResponseCallback(self.syncResponse)
+ self.syncResponse()
+ def syncResponse(self):
+ self._response = self._api.getResponse()
+ def finish(self, theError=None):
+ self._api.unregisterResponseCallback(self.syncResponse)
+ if not theError:
+ self._filter.flush(1)
+ def clearFilters(self):
+ self._filter = FilterUnify(self)
+ self._filterList = [(99, self._filter)]
+ def addFilter(self, level, filter):
+ 'Inject filter functions into output stream at given level of precedence'
+ filterExists = None
+ for i in range(len(self._filterList)):
+ l, _ = self._filterList[i]
+ if l==level:
+ _, filterExists = self._filterList[i]
+ del self._filterList[i]
+ break
+ if filter:
+ self._filterList.append((level, filter))
+ self._filterList.sort()
+ for i in range(len(self._filterList)-1):
+ l1, f1 = self._filterList[i]
+ l2, f2 = self._filterList[i+1]
+ f1.setNext(f2)
+ _, self._filter = self._filterList[0]
+ return filterExists
+
+ # user functions
+ def write(self, s):
+ "Write out a dynamic (code) string."
+ try:
+ self._filter.write(s)
+ if self._unbuffer: self.flush()
+ except IOError:
+ self._ioerror = 1
+ def writeln(self, s):
+ "Writeln out a dynamic (code) string."
+ self.write(s+'\n')
+ def writeStatic(self, s):
+ "Write out a static string."
+ try:
+ self._filter.writeStatic(s)
+ if self._unbuffer: self.flush()
+ except IOError:
+ self._ioerror = 1
+ def writeExpr(self, s, **kwargs):
+ "Write out an expression result."
+ try:
+ apply(self._filter.writeExpr, (s,), kwargs)
+ if self._unbuffer: self.flush()
+ except IOError:
+ self._ioerror = 1
+ def clear(self):
+ "Clear the output buffer. (must not be unbuffered)"
+ self._filter.clear()
+ def flush(self, stopFlag=0):
+ "Flush resident buffer."
+ try:
+ self._filter.flush(stopFlag)
+ except IOError:
+ self._ioerror = 1
+ def setContentType(self, ct):
+ "Set document content type. (must not be unbuffered)"
+ self._response.setContentType(ct)
+ def setReturnCode(self, code):
+ "Set HTTP return (status) code"
+ self._response.setReturnCode(int(code))
+ def isCancelled(self):
+ return self._ioerror
+ def addHeader(self, type, data, replace=0):
+ "Add an HTTP header. (must not be unbuffered)"
+ if string.find(type, ':') != -1:
+ raise 'HTTP header type should not contain ":" (colon).'
+ self._response.addHeader(type, data, replace)
+ def clearHeaders(self):
+ "Clear all HTTP headers (must not be unbuffered)"
+ self._response.clearHeaders()
+ def unbuffer(self):
+ "Turn off output stream buffering; flush immediately to browser."
+ self._unbuffer = 1
+ self.flush()
+ def timestamp(self, thetime=None):
+ "Timestamp response with a HTTP Date header"
+ self.addHeader('Date', _genTimestampString(thetime), 1)
+ def expires(self, thetime=None):
+ "Add HTTP expiration headers"
+ self.addHeader('Expires', _genTimestampString(thetime), 1)
+ def expiresRel(self, secs=0):
+ "Set response expiration (relative to now) with a HTTP Expires header"
+ self.expires(int(time.time())+secs)
+ def lastModified(self, thetime=-1):
+ "Set last modification time"
+ if thetime==-1:
+ filename = self._api.getFilename()
+ if not filename or not os.path.exists(filename):
+ raise 'request filename not found; can not determine last modification time'
+ thetime = os.stat(filename)[9] # file ctime
+ self.addHeader('Last-Modified', _genTimestampString(thetime), 1)
+ # ensure last modified before timestamp, at least when we're generating it
+ if thetime==None: self.timestamp()
+ def uncacheable(self):
+ "Ensure that compliant clients and proxies don't cache this response"
+ self.addHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
+ self.addHeader('Pragma', 'no-cache')
+ def __repr__(self):
+ s = []
+ s.append('filters: %s' % len(self._filterList))
+ s.append('unbuffered: %s' % self._unbuffer)
+ return string.join(s, ', ')
+
+class Filter:
+ def setNext(self, filter):
+ self.next = filter
+ def write(self, s):
+ s = self.dynamicImpl(s)
+ self.next.write(s)
+ def writeStatic(self, s):
+ s = self.staticImpl(s)
+ self.next.writeStatic(s)
+ def writeExpr(self, s, **kwargs):
+ s = apply(self.exprImpl, (s,), kwargs)
+ apply(self.next.writeExpr, (s,), kwargs)
+ def flush(self, stopFlag=0):
+ self.flushImpl()
+ self.next.flush(stopFlag)
+ def clear(self):
+ self.clearImpl()
+ self.next.clear()
+ def dynamicImpl(self, s, *args, **kwargs):
+ raise 'not implemented'
+ def staticImpl(self, s, *args, **kwargs):
+ raise 'not implemented'
+ def exprImpl(self, s, *args, **kwargs):
+ raise 'not implemented'
+ def flushImpl(self):
+ raise 'not implemented'
+ def clearImpl(self):
+ raise 'not implemented'
+
+class FilterUnify(Filter):
+ def __init__(self, mod):
+ self.mod = mod
+ self.mod._api.registerResponseCallback(self.syncResponse)
+ self.syncResponse()
+ def syncResponse(self):
+ response = self.mod._api.getResponse()
+ self.write = response.write
+ self.writeStatic = response.write
+ self.flush = response.flush
+ self.clear = response.clear
+ def writeExpr(self, s, **kwargs):
+ self.write(str(s))
+ def setNext(self, filter):
+ pass # we are always at the end
+
+def _genTimestampString(thetime=None):
+ "Generate timestamp string"
+ if thetime==None:
+ thetime = int(time.time())
+ if type(thetime)==type(0):
+ thetime = time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(thetime))
+ if type(thetime)==type(''):
+ return thetime
+ raise 'thetime value should be None or string or integer (seconds)'
diff --git a/system/python/spyce/modules/session.py b/system/python/spyce/modules/session.py
new file mode 100644
index 0000000000..ca0b202188
--- /dev/null
+++ b/system/python/spyce/modules/session.py
@@ -0,0 +1,368 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import re, time, string, random
+import spyceLock
+try:
+ import cPickle
+ pickle = cPickle
+except:
+ import pickle
+
+__doc__ = '''Session module provides support for session management - the
+storage of variables on the server between requests under some short
+identifier.
+
+A user must call setHandler() to determine how the sessions are stored, before
+using the other session methods. The get(), set() and delete() methods provide
+access to the session information.
+
+The autoSession() method will turn on the automatic session management
+(loading and saving the session). When automatic session management is turned
+on the session information, identifier, parameter name and browser method are
+stored in the variables called auto, autoID, autoName and autoMethod,
+respectively.'''
+
+class session(spyceModule):
+ def start(self):
+ "Initialise the session module variables."
+ self._serverobject = self._api.getServerObject()
+ if 'session' not in dir(self._serverobject):
+ self._serverobject.session = sessionHandlerRegistry()
+ self._handler = None
+ self._clearAutoSession()
+ def finish(self, theError=None):
+ "Save the session, if automatic session management is turned on."
+ if self.autoID:
+ self.set(self.auto, self.autoExpire, self.autoID)
+ if self.autoMethod=='cookie':
+ self._api.getModule('cookie').set(self.autoName, self.autoID)
+ sessionCleanup(self._serverobject.session)
+ def init(self, handler=None, *args, **kwargs):
+ if handler:
+ session = apply(self.setHandler, (handler,)+args)
+ if kwargs.has_key('auto') and kwargs['auto']:
+ auto = kwargs['auto']
+ if type(auto) != type(()):
+ auto = (auto,)
+ apply(session.autoSession, auto)
+ def setHandler(self, file_name, *params):
+ "Select a session handler."
+ file_name = string.split(file_name, ':')
+ if len(file_name)==1: file, name = None, file_name[0]
+ else: file, name = file_name[:2]
+ if file: handler = self._api.loadModule(name, file, self._api.getFilename())
+ else: handler = eval(name)
+ self._handler = apply(handler, (self,)+params)
+ self._serverobject.session.add(self._handler)
+ return self
+ def get(self, id): # method deletes session, if stale
+ "Retrieve session information."
+ if not self._handler: raise 'call setHandler to initialise'
+ return self._handler.get(id)
+ def delete(self, id=None):
+ "Delete session information."
+ if not self._handler: raise 'call setHandler to initialise'
+ if not id:
+ id = self.autoID
+ self._clearAutoSession()
+ return self._handler.delete(id)
+ def set(self, state, expire, id=None):
+ "Set session information."
+ if not self._handler: raise 'call setHandler to initialise'
+ return self._handler.set(state, expire, id)
+ def clear(self):
+ "Clear all session information in current handler."
+ if not self._handler: raise 'call setHandler to initialise'
+ return self._handler.clear()
+ def autoSession(self, expire, method='cookie', name='spyceSession'):
+ "Turn on automatic session management."
+ if not self._handler: raise 'call setHandler to initialise'
+ method = string.lower(method)
+ if method=='cookie': self.autoID = self._api.getModule('cookie').get(name)
+ elif method=='post': self.autoID = self._api.getModule('request').post1(name)
+ elif method=='get': self.autoID = self._api.getModule('request').get1(name)
+ else: raise 'runtime error: invalid autosession method'
+ self.autoMethod = method
+ self.autoName = name
+ self.autoExpire = expire
+ self.auto = None
+ if self.autoID:
+ self.auto = self.get(self.autoID)
+ if not self.auto: self.autoID = None
+ if not self.autoID: # generate a sessionid
+ self.autoID = self.set(None, self.autoExpire)
+ def _clearAutoSession(self):
+ self.auto = None
+ self.autoID = None
+ self.autoMethod = None
+ self.autoName = None
+ self.autoExpire = None
+
+##################################################
+# Cleanup
+#
+
+# expire sessions every n requests in expectation
+SESSION_EXPIRE_CHECK = 50
+
+class sessionHandlerRegistry:
+ "Registry of all used session handlers."
+ def __init__(self):
+ self.handlers = {}
+ def add(self, handler):
+ self.handlers[handler.getHandlerID()] = handler
+ def list(self):
+ return self.handlers.values()
+ def remove(self, handler):
+ del self.handlers[handler.getHandlerID()]
+
+def sessionCleanup(registry):
+ """Iterates through all session handlers and sessions to perform session
+ cleanup"""
+ if random.randrange(SESSION_EXPIRE_CHECK): return
+ for handler in registry.list():
+ try:
+ sessions = handler.keys()
+ for s in sessions:
+ handler.get(s) # will delete stale sessions
+ except:
+ registry.remove(handler)
+
+
+##################################################
+# Session handlers
+#
+
+class sessionHandler:
+ '''All session handlers should subclass this, and implement the methods
+ marked: 'not implemented'.'''
+ def __init__(self, sessionModule):
+ self.childnum = sessionModule._api.getServerID()
+ def getHandlerID(self):
+ raise 'not implemented'
+ def get(self, id): # method should delete, if session is stale
+ raise 'not implemented'
+ def delete(self, id):
+ raise 'not implemented'
+ def clear(self):
+ raise 'not implemented'
+ def set(self, state, expire, id=None):
+ raise 'not implemented'
+ def keys(self):
+ raise 'not implemented'
+ def __getitem__(self, key):
+ return self.get(key)
+ def __delitem__(self, key):
+ return self.delete(key)
+
+##################################################
+# File-based session handler
+#
+
+class session_dir(sessionHandler):
+ def __init__(self, sessionModule, dir):
+ sessionHandler.__init__(self, sessionModule)
+ if not os.path.exists(dir):
+ raise "session directory '%s' does not exist" % dir
+ self.dir = dir
+ self.prefix = 'spy'
+ self.BINARY_MODE = 1
+ def getHandlerID(self):
+ return 'session_dir', self.childnum, self.dir
+ def get(self, id):
+ if not id: return None
+ filename = os.path.join(self.dir, self.prefix+id)
+ f=None
+ sessionInfo = None
+ try:
+ f=open(filename, 'r')
+ sessionInfo = pickle.load(f)
+ f.close()
+ except:
+ try:
+ if f: f.close()
+ os.unlink(filename)
+ except: pass
+ if sessionInfo:
+ if time.time() > sessionInfo['expire']:
+ self.delete(id)
+ return None
+ else: return sessionInfo['state']
+ else: return None
+ def delete(self, id):
+ try:
+ filename = os.path.join(self.dir, self.prefix+id)
+ os.remove(filename)
+ except: pass
+ def clear(self):
+ for id in self.keys():
+ self.delete(id)
+ def set(self, state, expire, id=None):
+ f=None
+ try:
+ if id:
+ filename = os.path.join(self.dir, self.prefix+id)
+ f=open(filename, 'w')
+ else:
+ filename, f, id = openUniqueFile(self.dir, self.prefix, ('%d_' % self.childnum))
+ sessionInfo = {}
+ sessionInfo['expire'] = int(time.time())+expire
+ sessionInfo['state'] = state
+ pickle.dump(sessionInfo, f, self.BINARY_MODE)
+ f.close()
+ except:
+ try:
+ if f: f.close()
+ except: pass
+ raise
+ return id
+ def keys(self):
+ sessions = os.listdir(self.dir)
+ sessions = filter(lambda s, p=self.prefix: s[:len(p)]==p, sessions)
+ sessions = map(lambda s, self=self: s[len(self.prefix):], sessions)
+ return sessions
+
+# requires unique (dir, prefix)
+def openUniqueFile(dir, prefix, unique, mode='w', max=1000000):
+ filelock = spyceLock.fileLock(os.path.join(dir, prefix))
+ filelock.lock(1)
+ try:
+ id = "%06d"%random.randrange(max)
+ filename = os.path.join(dir, prefix+unique+id)
+ while os.path.exists(filename):
+ id = str(random.randrange(max))
+ filename = os.path.join(dir, prefix+unique+id)
+ f = None
+ f = open(filename, mode)
+ return filename, f, unique+id
+ finally:
+ filelock.unlock()
+
+##################################################
+# Hash file session handlers
+#
+
+class sessionHandlerDBM(sessionHandler):
+ def __init__(self, sessionModule, filename):
+ sessionHandler.__init__(self, sessionModule)
+ self.filename = filename
+ self.dbm = None
+ self.BINARY_MODE = 1
+ self.dbm_type = None # redefine in subclass
+ def getHandlerID(self):
+ return 'session_'+self.dbm_type, self.childnum, self.filename
+ def _open(self):
+ raise 'need to implement'
+ def _close(self):
+ if self.dbm:
+ self.dbm.close()
+ self.dbm = None
+ def get(self, id):
+ if not id: return None
+ self._open()
+ try:
+ expire, state = None, None
+ if self.dbm.has_key(id):
+ expire, state = pickle.loads(self.dbm[id])
+ if expire!=None and time.time() > expire:
+ self.delete(id)
+ state = None
+ return state
+ finally:
+ self._close()
+ def delete(self, id):
+ self._open()
+ try:
+ if self.dbm.has_key(id):
+ del self.dbm[id]
+ finally:
+ self._close()
+ def clear(self):
+ if os.path.exists(self.filename):
+ os.unlink(self.filename)
+ def set(self, state, expire, id=None):
+ self._open()
+ try:
+ if not id:
+ id = generateKey(self.dbm, self.childnum)
+ value = pickle.dumps( (int(time.time())+expire, state), self.BINARY_MODE)
+ self.dbm[id] = value
+ return id
+ finally:
+ self._close()
+ def keys(self):
+ self._open()
+ try:
+ return self.dbm.keys()
+ finally:
+ self._close()
+
+def opendb(dbm_session_handler, module, filename, flags):
+ mod = __import__(module)
+ if not dbm_session_handler.dbm:
+ dbm_session_handler.dbm = mod.open(filename, flags)
+
+class session_gdbm(sessionHandlerDBM):
+ def __init__(self, sessionModule, filename):
+ sessionHandlerDBM.__init__(self, sessionModule, filename)
+ self.dbm_type = 'gdbm'
+ def _open(self):
+ opendb(self, self.dbm_type, self.filename, 'cu')
+
+class session_bsddb(sessionHandlerDBM):
+ def __init__(self, sessionModule, filename):
+ sessionHandlerDBM.__init__(self, sessionModule, filename)
+ self.dbm_type = 'bsddb'
+ def _open(self):
+ opendb(self, 'dbhash', self.filename, 'c')
+
+def generateKey(hash, prefix, max = 1000000):
+ prefix = str(prefix)+'_'
+ key = random.randrange(max)
+ while hash.has_key(prefix+str(key)):
+ key = random.randrange(max)
+ key = prefix+str(key)
+ hash[key] = pickle.dumps(None, 1)
+ return key
+
+
+##################################################
+# User callback session handlers
+#
+
+class session_user(sessionHandler):
+ '''User-callback session handler'''
+ def __init__(self, sessionModule, getf, setf, delf, idsf, info=None):
+ self.serverID = sessionModule._api.getServerID()
+ self.info = info
+ self.getf = getf
+ self.setf = setf
+ self.delf = delf
+ self.idsf = idsf
+ def getHandlerID(self):
+ return 'session_user', self.serverID, self.info
+ def get(self, id): # method should delete, if session is stale
+ return self.getf(self.info, id)
+ def set(self, state, expire, id):
+ return self.setf(self.info, state, expire, self.serverID, id)
+ def delete(self, id):
+ return self.delf(self.info, id)
+ def keys(self):
+ return self.idsf(self.info)
+ def clear(self):
+ for id in self.keys():
+ self.delete(id)
+
+##################################################
+# database-based session handlers
+#
+
+# rimtodo: database-based session handler
+
diff --git a/system/python/spyce/modules/spylambda.py b/system/python/spyce/modules/spylambda.py
new file mode 100644
index 0000000000..60600faa71
--- /dev/null
+++ b/system/python/spyce/modules/spylambda.py
@@ -0,0 +1,55 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import string
+
+__doc__ = """spylambda module produces functions from spyce strings."""
+
+class spylambda(spyceModule):
+ def define(self, sig, code, memoize=0):
+ # compile spyce to cache errors early
+ spycecode = self._api.spyceString((code, sig))
+ if string.strip(sig): sigcomma = sig + ','
+ def processSpyce(args, kwargs, self=self, spycecode=spycecode):
+ s = None
+ try:
+ s = spycecode.newWrapper()
+ modules = self._api.getModules()
+ for name in modules.keys():
+ s.setModule(name, modules[name])
+ s.spyceInit(self._api.getRequest(), self._api.getResponse())
+ result = apply(s.spyceProcess, args, kwargs)
+ finally:
+ if s:
+ s.spyceDestroy()
+ spycecode.returnWrapper(s)
+ return result
+ if memoize:
+ def memoizer(f, id, stdout=self._api.getModule('stdout')):
+ def memoized(args, kwargs, f=f, id=id, stdout=stdout):
+ key = id, `args, kwargs`
+ try: r, s = stdout.memoizeCache[key]
+ except:
+ r, s = stdout.memoizeCache[key] = apply(stdout.capture, (f, args, kwargs))
+ print s
+ return r
+ return memoized
+ processSpyce = memoizer(processSpyce, code)
+ def makeArgProcessor(f):
+ dict = { 'f': f }
+ exec '''
+def processArg(*args, **kwargs):
+ return f(args, kwargs)
+''' in dict
+ return dict['processArg']
+ return makeArgProcessor(processSpyce)
+ def __call__(self, sig, code, memoize=0):
+ return self.define(sig, code)
+ def __repr__(self):
+ return ''
diff --git a/system/python/spyce/modules/stdout.py b/system/python/spyce/modules/stdout.py
new file mode 100644
index 0000000000..c7b7ab7e98
--- /dev/null
+++ b/system/python/spyce/modules/stdout.py
@@ -0,0 +1,104 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+from spyceUtil import NoCloseOut
+from cStringIO import StringIO
+
+__doc__ = '''Sets the thread-safe server stdout to the response object for
+convenience of using print statements, and supports output redirection.'''
+
+class stdout(spyceModule):
+ def start(self):
+ # thread-safe stdout swap
+ self.stdout = self._api.getStdout()
+ self._api.setStdout(myResponseWrapper(self))
+ # output redirection stack
+ self.outputStack = []
+ # memoize storage
+ try: self.memoizeCache = self._api.getServerObject().memoized
+ except AttributeError:
+ self.memoizeCache = self._api.getServerObject().memoized = {}
+ def finish(self, theError=None):
+ # close all redirects
+ while self.outputStack:
+ self.pop()
+ # thread-safe stdout swap back
+ self._api.setStdout(self.stdout)
+ def push(self, file=None):
+ 'Redirect stdout to buffer'
+ old_response = self._api.getResponse()
+ old_response_mod = self._api.getModule('response')
+ new_response = spyceCaptureResponse(old_response)
+ self._api.setResponse(new_response)
+ new_response_mod = self._api.spyceModule('response', 'response.py')(self._api)
+ self._api.setModule('response', new_response_mod)
+ new_response_mod.start()
+ self.outputStack.append( (file, old_response, old_response_mod) )
+ def pop(self):
+ 'Return buffer value, and possible write to file'
+ self._api.getModule('response').finish()
+ buffer = self._api.getResponse().getCapturedOutput()
+ file, old_response, old_response_mod = self.outputStack.pop()
+ self._api.setModule('response', old_response_mod)
+ self._api.setResponse(old_response)
+ if file:
+ file = os.path.join(os.path.dirname(self._api.getFilename()), file)
+ out = None
+ try:
+ out = open(file, 'w')
+ out.write(buffer)
+ finally:
+ if out: out.close()
+ return buffer
+ def capture(self, _spyceReserved, *args, **kwargs):
+ 'Capture the output side-effects of a function'
+ f = _spyceReserved # placeholder not to collide with kwargs
+ self.push()
+ r = apply(f, args, kwargs)
+ s = self.pop()
+ return r, s
+ def __repr__(self):
+ return 'depth: %s' % len(self.outputStack)
+
+class myResponseWrapper:
+ def __init__(self, mod):
+ self._mod = mod
+ mod._api.registerModuleCallback(self.syncResponse)
+ self.syncResponse()
+ def syncResponse(self):
+ response = self._mod._api.getModule('response')
+ # functions (for performance)
+ self.write = response.write
+ self.writeln = response.writeln
+ self.flush = response.flush
+ def close(self):
+ raise 'method not allowed'
+
+class spyceCaptureResponse:
+ "Capture output, and let everything else through."
+ def __init__(self, old_response):
+ self._old_response = old_response
+ self._buf = StringIO()
+ def write(self, s):
+ self._buf.write(s)
+ def close(self):
+ raise 'cannot close output while capturing output'
+ def clear(self):
+ self._buf = StringIO()
+ def sendHeaders(self):
+ raise 'cannot sendHeaders while capturing output!'
+ def flush(self, stopFlag=0):
+ pass
+ def unbuffer(self):
+ raise 'cannot unbuffer output while capturing output!'
+ def __getattr__(self, name):
+ return eval('self._old_response.%s'%name)
+ def getCapturedOutput(self):
+ return self._buf.getvalue()
+
diff --git a/system/python/spyce/modules/taglib.py b/system/python/spyce/modules/taglib.py
new file mode 100644
index 0000000000..2631ff16fc
--- /dev/null
+++ b/system/python/spyce/modules/taglib.py
@@ -0,0 +1,102 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import string
+
+__doc__ = '''Spyce tags functionality.'''
+
+class taglib(spyceModule):
+ def start(self):
+ self.context = {}
+ self.stack = []
+ self.taglibs = {}
+ self._api.registerModuleCallback(self.__syncModules)
+ self.__syncModules()
+ def __syncModules(self):
+ modules = self._api.getModules()
+ for name in modules.keys():
+ self.context[name] = modules[name]
+ self.mod_response = modules['response']
+ self.mod_stdout = modules['stdout']
+ def finish(self, theError):
+ self._api.unregisterModuleCallback(self.__syncModules)
+ try:
+ for taglib in self.taglibs.keys():
+ self.unload(taglib)
+ finally:
+ del self.context
+ # load and unload tag libraries
+ def load(self, libname, libfrom=None, libas=None):
+ thename = libname
+ if libas: thename = libas
+ if self.taglibs.has_key(thename):
+ raise 'tag library with that name already loaded'
+ lib = self._api.spyceTaglib(
+ libname, libfrom, self._api.getFilename())(libname)
+ lib.start()
+ self.taglibs[thename] = lib
+ def unload(self, libname):
+ lib = None
+ try:
+ lib = self.taglibs[libname]
+ del self.taglibs[libname]
+ except KeyError: pass
+ if lib: lib.finish()
+ # tag processing
+ def tagPush(self, libname, tagname, attr, pair):
+ try: parent = self.stack[-1]
+ except: parent = None
+ tag = self.taglibs[libname].getTag(tagname, attr, pair, parent)
+ self.stack.append(tag)
+ def tagPop(self):
+ self.outPopCond()
+ if self.stack: self.stack.pop()
+ def getTag(self):
+ return self.stack[-1]
+ def outPush(self):
+ tag = self.stack[-1]
+ if tag.buffer:
+ tag.setBuffered(1)
+ return self.mod_stdout.push()
+ def outPopCond(self):
+ tag = self.stack[-1]
+ if tag.getBuffered():
+ tag.setBuffered(0)
+ return self.mod_stdout.pop()
+ def tagBegin(self):
+ tag = self.getTag()
+ tag.setContext(self.context)
+ tag.setOut(self.mod_response)
+ result = apply(tag.begin, (), tag._attrs)
+ self.outPush()
+ return result
+ def tagBody(self):
+ contents = self.outPopCond()
+ tag = self.getTag()
+ tag.setContext(self.context)
+ tag.setOut(self.mod_response)
+ result = tag.body(contents)
+ if result: self.outPush()
+ return result
+ def tagEnd(self):
+ self.outPopCond()
+ tag = self.getTag()
+ tag.setContext(self.context)
+ tag.setOut(self.mod_response)
+ return tag.end()
+ def tagCatch(self):
+ self.outPopCond()
+ tag = self.getTag()
+ tag.setOut(self.mod_response)
+ tag.catch(sys.exc_info()[0])
+ def __repr__(self):
+ return 'prefixes: %s; stack: %s' % (
+ string.join(self.taglibs.keys(), ', '),
+ string.join(map(lambda x: x.name, self.stack), ', '))
+
diff --git a/system/python/spyce/modules/template.py b/system/python/spyce/modules/template.py
new file mode 100644
index 0000000000..ee7d130e72
--- /dev/null
+++ b/system/python/spyce/modules/template.py
@@ -0,0 +1,68 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import spyceException, spyceCache
+import os
+
+__doc__ = """Template module provides templating functionality: the ability to separate
+form from function, or HTML page design from programming code. This module
+currently provides support for the Cheetah template engine.
+"""
+
+class template(spyceModule):
+ def cheetah(self, filename, lookup=None):
+ "Hook into the Cheetah template engine."
+ # check whether cheetah installed
+ from Cheetah.Compiler import Compiler
+ # define template cache
+ if not self._api.getModule('pool').has_key('cheetahCache'):
+ self._api.getModule('pool')['cheetahCache'] = spyceCache.semanticCache(spyceCache.memoryCache(), cheetahValid, cheetahGenerate)
+ cheetahCache = self._api.getModule('pool')['cheetahCache']
+ # absolute filename, relative to script filename
+ filename = os.path.abspath(os.path.join(
+ os.path.dirname(self._api.getFilename()), filename))
+ # set lookup variables
+ if lookup == None:
+ import inspect
+ lookup = [inspect.currentframe().f_back.f_locals, inspect.currentframe().f_back.f_globals]
+ elif type(lookup)!=type([]):
+ lookup = [lookup]
+ # compile (or get cached) and run template
+ return cheetahCache[filename](searchList=lookup)
+
+##################################################
+# Cheetah semantic cache helper functions
+#
+
+def cheetahValid(filename, validity):
+ try:
+ return os.path.getmtime(filename) == validity
+ except: return 0
+
+def cheetahGenerate(filename):
+ # check permissions
+ if not os.path.exists(filename):
+ raise spyceException.spyceNotFound()
+ if not os.access(filename, os.R_OK):
+ raise spyceException.spyceForbidden()
+ # read the template
+ f = None
+ try:
+ f = open(filename, 'r')
+ buf = f.read()
+ finally:
+ if f: f.close()
+ # compile template, get timestamp
+ mtime = os.path.getmtime(filename)
+ from Cheetah.Compiler import Compiler
+ code = Compiler(source=buf).__str__()
+ dict = {}
+ exec code in dict
+ return mtime, dict['GenTemplate']
+
diff --git a/system/python/spyce/modules/toc.py b/system/python/spyce/modules/toc.py
new file mode 100644
index 0000000000..904f9ca03c
--- /dev/null
+++ b/system/python/spyce/modules/toc.py
@@ -0,0 +1,240 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import tree
+
+__doc__ = '''Table-of-Contents module helps in creating indexed documents NB:
+The TOC module may force two passes of a file, if the first pass TOC emitted
+was not accurate. The second pass occurs via a redirect, so all modules are
+reinitialized. Unfortunately, this breaks things like include.context...'''
+
+ROOT_NAME = 'root'
+
+class toc(spyceModule):
+
+ def start(self):
+ if not self._api.getModule('pool').has_key('toc'):
+ self._api.getModule('pool')['toc'] = {}
+ try:
+ self.oldtree, self.oldtags = self._api.getModule('pool')['toc'][self._api.getFilename()]
+ except (KeyError, TypeError):
+ self.oldtree = tree.tree( (ROOT_NAME, [], None) )
+ self.oldtags = {ROOT_NAME: self.oldtree}
+ # tree data: (tag, numbering, data)
+ self.tree = tree.tree((ROOT_NAME, [], None))
+ self.tags = {ROOT_NAME: self.tree}
+ self.node = self.tree
+ self.numbering = []
+ self.autotag = 0
+ self.tocShown = 0
+ self.fDOC_PUSH = None
+ self.fDOC_POP = None
+ self.fDOC_START = None
+ self.fDOC_END = None
+ self.fTOC_PUSH = None
+ self.fTOC_POP = None
+ self.fTOC_ENTRY = None
+ def finish(self, theError):
+ if not theError:
+ self.tree.computePreChain()
+ regenerate = not (self.oldtree == self.tree)
+ file = self._api.getFilename()
+ self._api.getModule('pool')['toc'][file] = self.tree, self.tags
+ self.oldtree.delete()
+ self.oldtree = None
+ self.oldtags = None
+ if self.tocShown and regenerate:
+ self._api.getModule('redirect').internal(file)
+
+ # set callbacks
+ def setDOC_PUSH(self, f):
+ self.fDOC_PUSH = f
+ def setDOC_POP(self, f):
+ self.fDOC_POP = f
+ def setDOC_START(self, f):
+ self.fDOC_START = f
+ def setDOC_END(self, f):
+ self.fDOC_END = f
+ def setTOC_PUSH(self, f):
+ self.fTOC_PUSH = f
+ def setTOC_POP(self, f):
+ self.fTOC_POP = f
+ def setTOC_ENTRY(self, f):
+ self.fTOC_ENTRY = f
+
+ # sectioning
+ def begin(self, data, tag=None, number=1):
+ self._emit(self.node, self.fDOC_PUSH)
+ self.numbering = _in(self.numbering)
+ if number:
+ self.numbering = _inc(self.numbering)
+ self.node = self.node.append( (tag, self.numbering, data) )
+ else:
+ self.node = self.node.append( (tag, None, data) )
+ if not tag: tag = self._genTag()
+ self.tags[tag] = self.node
+ self._emit(self.node, self.fDOC_START)
+ def end(self):
+ self._emit(self.node, self.fDOC_END)
+ self.numbering = _out(self.numbering)
+ self.node = self.node.parent
+ self._emit(self.node, self.fDOC_POP)
+ def next(self, data, tag=None, number=1):
+ self._emit(self.node, self.fDOC_END)
+ self.node = self.node.parent
+ if number:
+ self.numbering = _inc(self.numbering)
+ self.node = self.node.append( (tag, self.numbering, data) )
+ else:
+ self.node = self.node.append( (tag, None, data) )
+ if not tag: tag = self._genTag()
+ self.tags[tag] = self.node
+ self._emit(self.node, self.fDOC_START)
+ def anchor(self, data, tag=ROOT_NAME):
+ self.tree.data = tag, [], data
+ self.tags[tag] = self.tree
+
+ # shortcuts
+ b=begin
+ e=end
+ n=next
+
+ # sectioning by depth
+ def level(self, depth, data, tag=None):
+ curdepth = self.getDepth()
+ if curdepth > depth: # indent
+ while curdepth > depth:
+ self.end()
+ curdepth = self.getDepth()
+ self.next(data, tag)
+ elif curdepth < depth: # outdent
+ while curdepth < depth - 1:
+ self.begin(None)
+ curdepth = self.getDepth()
+ self.begin(data, tag)
+ else: # next
+ self.next(data, tag)
+ def l1(self, data, tag=None):
+ self.level(1, data, tag)
+ def l2(self, data, tag=None):
+ self.level(2, data, tag)
+ def l3(self, data, tag=None):
+ self.level(3, data, tag)
+ def l4(self, data, tag=None):
+ self.level(4, data, tag)
+ def l5(self, data, tag=None):
+ self.level(5, data, tag)
+ def l6(self, data, tag=None):
+ self.level(6, data, tag)
+ def l7(self, data, tag=None):
+ self.level(7, data, tag)
+ def l8(self, data, tag=None):
+ self.level(8, data, tag)
+ def l9(self, data, tag=None):
+ self.level(9, data, tag)
+
+ # show toc
+ def showTOC(self):
+ self.tocShown = 1
+ self._tocHelper(self.oldtree)
+ def _tocHelper(self, node):
+ self._emit(node, self.fTOC_ENTRY)
+ if node.children:
+ self._emit(node, self.fTOC_PUSH)
+ for c in node.children:
+ self._tocHelper(c)
+ self._emit(node, self.fTOC_POP)
+
+ # current state
+ def getTag(self, node=None):
+ self.tocShown = 1
+ if not node: node = self.node
+ tag, numbering, data = node.data
+ return tag
+ def getNumbering(self, tag=None):
+ self.tocShown = 1
+ try:
+ node = self.node
+ if tag: node = self.oldtags[tag]
+ tag, numbering, data = node.data
+ return numbering
+ except KeyError:
+ return None
+ def getData(self, tag=None):
+ self.tocShown = 1
+ try:
+ node = self.node
+ if tag: node = self.oldtags[tag]
+ tag, numbering, data = node.data
+ return data
+ except KeyError:
+ return None
+ def getDepth(self, tag=None):
+ self.tocShown = 1
+ try:
+ node = self.node
+ if tag: node = self.tags[tag]
+ return node.depth
+ except KeyError:
+ return None
+ def getNextTag(self, tag=None):
+ self.tocShown = 1
+ try:
+ if not tag: tag = self.getTag()
+ tag = self.oldtags[tag].next
+ if tag==None: return None
+ return self.getTag(tag)
+ except KeyError:
+ return None
+ def getPrevTag(self, tag=None):
+ self.tocShown = 1
+ try:
+ if not tag: tag = self.getTag()
+ node = self.oldtags[tag].prev
+ if node==None: return None
+ return self.getTag(node)
+ except KeyError:
+ return None
+ def getParentTag(self, tag=None):
+ self.tocShown = 1
+ try:
+ if not tag: tag = self.getTag()
+ node = self.oldtags[tag].parent
+ if node==None: return None
+ return self.getTag(node)
+ except KeyError:
+ return None
+ def getChildrenTags(self, tag=None):
+ self.tocShown = 1
+ try:
+ if not tag: tag = self.getTag()
+ nodes = self.oldtags[tag].children
+ return map(self.getTag, nodes)
+ except KeyError:
+ return None
+
+ # internal helpers
+ def _genTag(self):
+ tag = 'auto_'+str(self.autotag)
+ self.autotag = self.autotag + 1
+ return tag
+ def _emit(self, node, f):
+ tag, numbering, data = node.data
+ if f: s = f(node.depth, tag, numbering, data)
+
+# hierarchical counting
+def _inc(numbering, inc=1):
+ return numbering[:-1]+[numbering[-1]+inc]
+def _in(numbering, start=0):
+ return numbering+[start]
+def _out(numbering):
+ return numbering[:-1]
+
+def defaultOutput(tag, numbering, data):
+ return reduce(lambda s, i: '%s%d.' % (s, i), numbering, '') + ' ' + str(data)
diff --git a/system/python/spyce/modules/transform.py b/system/python/spyce/modules/transform.py
new file mode 100644
index 0000000000..1ca4f62a42
--- /dev/null
+++ b/system/python/spyce/modules/transform.py
@@ -0,0 +1,155 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+from spyceModule import spyceModule
+import types, re, string
+
+__doc__ = '''Transform module intercepts different kinds of Spyce ouput, and
+can install functions to perform processing. It also includes some standard
+Spyce transformation functions.'''
+
+OUTPUT_POSITON = 20
+
+class transform(spyceModule):
+ def start(self):
+ self.ident = lambda x, **kwargs: x
+ self._filter = FilterFn(self.ident, self.ident, self.ident)
+ # install filter functions into response module
+ self._prevfilter = self._api.getModule('response').addFilter(OUTPUT_POSITON, self._filter)
+ def finish(self, theError=None):
+ self._prevfilter = self._api.getModule('response').addFilter(OUTPUT_POSITON, self._prevfilter)
+ # set filters
+ def dynamic(self, fn=None):
+ if not fn: fn = self.ident
+ self._filter.dynamicFilter = self.create(fn)
+ def static(self, fn=None):
+ if not fn: fn = self.ident
+ self._filter.staticFilter = self.create(fn)
+ def expr(self, fn=None):
+ if not fn: fn = self.ident
+ self._filter.exprFilter = self.create(fn)
+ # create filter
+ def create(self, fn):
+ '''Create filter function.'''
+ if fn==None or fn==() or fn==[]:
+ # identity
+ return self.ident
+ elif type(fn) == types.FunctionType:
+ # function type
+ return fn
+ elif type(fn) == type(''):
+ # string
+ file_name = string.split(fn, ':')
+ if len(file_name)==1: file, name = None, file_name[0]
+ else: file, name = file_name[:2]
+ if file: fn = loadModule(name, file, self._api.getFilename())
+ else: fn = eval(name)
+ return fn
+ elif type(fn) == type(()) or type(fn) == type([]):
+ # tuple or array
+ fn0 = self.create(fn[0])
+ fn1 = self.create(fn[1:])
+ def filterfn(x, _fn0=fn0, _fn1=fn1, **kwargs):
+ x = apply(_fn0, (x,), kwargs)
+ return apply(_fn1, (x,), kwargs)
+ return filterfn
+ # commonly used transformations
+ def html_encode(self, s, also='', **kwargs):
+ '''Return HTML-encoded string.'''
+ return html_encode(s, also)
+ def url_encode(self, s, **kwargs):
+ '''Return url-encoded string.'''
+ return url_encode(s)
+ def __repr__(self):
+ return 'static: %s, expr: %s, dynamic: %s' % (
+ str(self._filter.staticFilter!=self.ident),
+ str(self._filter.exprFilter!=self.ident),
+ str(self._filter.dynamicFilter!=self.ident),
+ )
+
+class FilterFn(Filter):
+ def __init__(self, dynamicFilter=None, staticFilter=None, exprFilter=None):
+ ident = lambda x: x
+ if not dynamicFilter: dynamicFilter = ident
+ if not staticFilter: staticFilter = ident
+ if not exprFilter: exprFilter = ident
+ self.dynamicFilter = dynamicFilter
+ self.staticFilter = staticFilter
+ self.exprFilter = exprFilter
+ def dynamicImpl(self, s, *args, **kwargs):
+ return apply(self.dynamicFilter, (s,)+args, kwargs)
+ def staticImpl(self, s, *args, **kwargs):
+ return apply(self.staticFilter, (s,)+args, kwargs)
+ def exprImpl(self, s, *args, **kwargs):
+ return apply(self.exprFilter, (s,)+args, kwargs)
+ def flushImpl(self):
+ pass
+ def clearImpl(self):
+ pass
+
+# standard transformation functions
+def ignore_none(o, **kwargs):
+ '''Does not print None.'''
+ if o==None: return ''
+ else: return o
+
+def silence(o, **kwargs):
+ '''Gobbles anything.'''
+ return ''
+
+def truncate(o, maxlen=None, **kwargs):
+ '''Limits output to a maximum string length.'''
+ if maxlen!=None: return str(o)[:maxlen]
+ else: return o
+
+_html_enc = {
+ chr(34): '&quot;', chr(38): '&amp;', chr(60): '&lt;', chr(62): '&gt;',
+ chr(160): '&nbsp;', chr(161): '&iexcl;', chr(162): '&cent;', chr(163): '&pound;',
+ chr(164): '&curren;', chr(165): '&yen;', chr(166): '&brvbar;', chr(167): '&sect;',
+ chr(168): '&uml;', chr(169): '&copy;', chr(170): '&ordf;', chr(171): '&laquo;',
+ chr(172): '&not;', chr(173): '&shy;', chr(174): '&reg;', chr(175): '&macr;',
+ chr(176): '&deg;', chr(177): '&plusmn;', chr(178): '&sup2;', chr(179): '&sup3;',
+ chr(180): '&acute;', chr(181): '&micro;', chr(182): '&para;', chr(183): '&middot;',
+ chr(184): '&cedil;', chr(185): '&sup1;', chr(186): '&ordm;', chr(187): '&raquo;',
+ chr(188): '&frac14;', chr(189): '&frac12;', chr(190): '&frac34;', chr(191): '&iquest;',
+ chr(192): '&Agrave;', chr(193): '&Aacute;', chr(194): '&Acirc;', chr(195): '&Atilde;',
+ chr(196): '&Auml;', chr(197): '&Aring;', chr(198): '&AElig;', chr(199): '&Ccedil;',
+ chr(200): '&Egrave;', chr(201): '&Eacute;', chr(202): '&Ecirc;', chr(203): '&Euml;',
+ chr(204): '&Igrave;', chr(205): '&Iacute;', chr(206): '&Icirc;', chr(207): '&Iuml;',
+ chr(208): '&ETH;', chr(209): '&Ntilde;', chr(210): '&Ograve;', chr(211): '&Oacute;',
+ chr(212): '&Ocirc;', chr(213): '&Otilde;', chr(214): '&Ouml;', chr(215): '&times;',
+ chr(216): '&Oslash;', chr(217): '&Ugrave;', chr(218): '&Uacute;', chr(219): '&Ucirc;',
+ chr(220): '&Uuml;', chr(221): '&Yacute;', chr(222): '&THORN;', chr(223): '&szlig;',
+ chr(224): '&agrave;', chr(225): '&aacute;', chr(226): '&acirc;', chr(227): '&atilde;',
+ chr(228): '&auml;', chr(229): '&aring;', chr(230): '&aelig;', chr(231): '&ccedil;',
+ chr(232): '&egrave;', chr(233): '&eacute;', chr(234): '&ecirc;', chr(235): '&euml;',
+ chr(236): '&igrave;', chr(237): '&iacute;', chr(238): '&icirc;', chr(239): '&iuml;',
+ chr(240): '&eth;', chr(241): '&ntilde;', chr(242): '&ograve;', chr(243): '&oacute;',
+ chr(244): '&ocirc;', chr(245): '&otilde;', chr(246): '&ouml;', chr(247): '&divide;',
+ chr(248): '&oslash;', chr(249): '&ugrave;', chr(250): '&uacute;', chr(251): '&ucirc;',
+ chr(252): '&uuml;', chr(253): '&yacute;', chr(254): '&thorn;', chr(255): '&yuml;',
+}
+_html_ch = re.compile(r'['+reduce(lambda n, i: n+i, _html_enc.keys())+']')
+def html_encode(o, also='', **kwargs):
+ '''Return HTML-encoded string.'''
+ o = _html_ch.sub(lambda match: _html_enc[match.group(0)], str(o))
+ for c in also:
+ try: r=_html_enc[c]
+ except: r='&#%d;' % ord(c)
+ o=o.replace(c, r)
+ return o
+
+_url_ch = re.compile(r'[^A-Za-z0-9_.!~*()-]') # RFC 2396 section 2.3
+def url_encode(o, **kwargs):
+ '''Return URL-encoded string.'''
+ return _url_ch.sub(lambda match: "%%%02X" % ord(match.group(0)), str(o))
+
+_nb_space_ch = re.compile(' ')
+def nb_space(o, **kwargs):
+ '''Return string with spaces converted to be non-breaking.'''
+ return _nb_space_ch.sub(lambda match: '&nbsp;', str(o))
diff --git a/system/python/spyce/run_spyceCGI.py b/system/python/spyce/run_spyceCGI.py
new file mode 100755
index 0000000000..fe7fb4832a
--- /dev/null
+++ b/system/python/spyce/run_spyceCGI.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Version checking spyceCGI.py wrapper.'''
+
+script = 'spyceCGI.py'
+
+import sys, os, verchk
+
+if __name__ == '__main__':
+ spycePath = os.path.abspath(os.path.dirname(sys.modules['verchk'].__file__))
+ sys.argv[0] = os.path.join(spycePath, script)
+ sys.argv.insert(0, os.path.join(spycePath, 'verchk.py'))
+ execfile(sys.argv[0])
diff --git a/system/python/spyce/run_spyceCmd.py b/system/python/spyce/run_spyceCmd.py
new file mode 100755
index 0000000000..ddf248ab00
--- /dev/null
+++ b/system/python/spyce/run_spyceCmd.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Version checking spyceCmd.py wrapper.'''
+
+script = 'spyceCmd.py'
+
+import sys, os, verchk
+
+if __name__ == '__main__':
+ spycePath = os.path.abspath(os.path.dirname(sys.modules['verchk'].__file__))
+ sys.argv[0] = os.path.join(spycePath, script)
+ sys.argv.insert(0, os.path.join(spycePath, 'verchk.py'))
+ execfile(sys.argv[0])
+
diff --git a/system/python/spyce/run_spyceModpy.py b/system/python/spyce/run_spyceModpy.py
new file mode 100755
index 0000000000..5a1cd4348e
--- /dev/null
+++ b/system/python/spyce/run_spyceModpy.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Version checking spyceModpy.py wrapper.'''
+
+try:
+ import _apache
+ from mod_python import apache
+except: pass
+
+def spyceMain(apacheRequest):
+ return spyceMainVersion(apacheRequest)
+
+def spyceMainVersion(apacheRequest):
+ "Version checking Apache entry point."
+ import verchk
+ if not verchk.checkversion(verchk.REQUIRED):
+ import sys
+ apacheRequest.content_type = 'text/plain'
+ apacheRequest.send_http_header()
+ apacheRequest.write('Spyce can not run on this version of Python.\n')
+ apacheRequest.write('Python version '+sys.version[:3]+' detected.\n')
+ apacheRequest.write('Python version '+verchk.REQUIRED+' or greater required.\n')
+ try:
+ return apache.OK
+ except: pass
+ else:
+ global spyceMain
+ import spyceModpy
+ spyceMain = spyceModpy.spyceMain
+ return spyceModpy.spyceMain(apacheRequest)
+
+if __name__ == '__main__':
+ print "********** ERROR: **********"
+ print "This program can not be run from the command-line."
+ print "Use run_spyceCmd.py, or run via Apache."
+ print "For configuring Apache, have a look at 'spyceApache.conf'."
+ print
+ print "Also, please read the documentation at:"
+ print " http://spyce.sourceforge.net"
+ print "for other options."
+
diff --git a/system/python/spyce/spyce.conf b/system/python/spyce/spyce.conf
new file mode 100644
index 0000000000..7b65e178c7
--- /dev/null
+++ b/system/python/spyce/spyce.conf
@@ -0,0 +1,162 @@
+###############
+#
+# Spyce configuration file
+# -- an example
+#
+###############
+
+
+
+###############
+#
+# The [spyce] section defines the main spyce configuration options
+#
+
+[spyce]
+
+#
+# The spyce path determines which directories are searched for when
+# loading modules and tag libraries. The Spyce installation directory
+# is always searched first. Any directories in the SPYCE_PATH
+# environment are also searched.
+#
+
+path: Q:\python\spyce
+
+
+#
+# The import option can be used to pre-load various Python modules
+# during engine initialization.
+#
+
+# import: myModule, myModule2
+
+
+#
+# The error option sets the server-level error handler; file-level
+# error handling is defined within Spyce scripts using the error
+# module. The format of this option is MODULE:FUNCTION. The server
+# will call the error handler as:
+# MODULE.FUNCTION(request, response, error)
+# if a server-level error should occur.
+#
+
+#error: error:serverHandler
+
+
+#
+# The pageerror option sets the default page-level error handler.
+# The format of this option is one of:
+# string:MODULE:VARIABLE
+# file:FILE
+# where the lowercase words are literals.
+#
+
+#pageerror: string:error:defaultErrorTemplate
+
+
+#
+# The concurrency option is used for long-lived engines (i.e. not for
+# CGI or command-line processing), and sets the concurrency level for
+# the engine. Legal values are 'thread' (or 'threading') and 'fork'
+# (or 'forking'). Any other value will result in serial request
+# processing, which also is the default.
+#
+
+concurrency: thread
+#concurrency: fork
+
+
+#
+# The cache option affects the underlying cache mechanism that the
+# server uses to maintain compiled Spyce scripts. The general format
+# for this option is TYPE:INFO, where TYPE defines the cache handler
+# and INFO is specific to that cache handler. Currently, Spyce
+# supports two cache handlers:
+# - memory: the default, takes no parameters
+# - file: store compiled Spyce scripts to files on disk in some
+# directory; the INFO is the directory to use
+#
+
+#cache: file:Q:\python\temp\
+
+
+#
+# The debug option affects the caching of compiled Spyce files and
+# Spyce modules. When it is turned on, then caching is disabled. It
+# should NOT be used in a production environment, as compilation is
+# not a optimized (fast) process. The values '0', 'off' or 'false'
+# disable debugging. Any other value turns it on. The default, if
+# omitted is off.
+#
+
+#debug: 1
+
+
+
+###############
+#
+# The globals section defines server-wide constants. The values can be
+# arbitrary Python expressions. These values are evaluated and stored
+# in a hashtable under the given option name. The hashtable is
+# accessible as "pool.globals" within any Spyce file (with the pool
+# method loaded), or as self.wrapper.server.globals within any Spyce
+# module.
+#
+
+[globals]
+
+#name: "My Website"
+#four: 2+2
+
+
+
+###############
+#
+# The www section defines options for the built-in Spyce web server.
+#
+
+[www]
+
+#
+# The root option defines the document root of the webserver from
+# which all requests are processed. This option can be overridden from
+# the command-line. The default is the current directory when the
+# server is started.
+#
+
+root: Q:\web
+
+
+#
+# The port option defines which TCP port the server will listen on.
+# The default is port 80.
+#
+
+#port: 8000
+
+
+#
+# The mime option is a comma-separated list of files. The files should
+# be definitions of mime-types for common file extensions in the
+# standard Apache format. The default is to read the spyce.mime file
+# in Spyce installation directory.
+#
+
+#mime: /etc/mime.types
+
+
+#
+# The ext_ and ext_foo options define the default handler and the
+# handler used for files ending in .foo, respectively. The currently
+# supported handlers are:
+# spyce - process the file at the requested path a spyce script
+# dump - transfer the file at the requested path verbatim,
+# providing an appropriate "Content-type" header, if it is known.
+# By default, all .spy files are processed via the spyce handler, and
+# all others through the dump handler.
+#
+
+#ext_html: spyce
+#ext_: spyce
+
diff --git a/system/python/spyce/spyce.mime b/system/python/spyce/spyce.mime
new file mode 100644
index 0000000000..b9e5a936f3
--- /dev/null
+++ b/system/python/spyce/spyce.mime
@@ -0,0 +1,495 @@
+# This is the mime.types file from the Apache web server distribution (1.3.22)
+# with local modifications.
+
+# This file controls what Internet media types are sent to the client for
+# given file extension(s). Sending the correct media type to the client
+# is important so they know how to handle the content of the file.
+# Extra types can either be added here or by using an AddType directive
+# in your config files. For more information about Internet media types,
+# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
+# registry is at <ftp://ftp.iana.org/in-notes/iana/assignments/media-types/>.
+
+# MIME type Extension
+application/EDI-Consent
+application/EDI-X12
+application/EDIFACT
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atomicmail
+application/batch-SMTP
+application/beep+xml
+application/cals-1840
+application/commonground
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/eshop
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/font-tdpfr
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathematica-old
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/qsig
+application/remote-printing
+application/riscos
+application/rtf rtf
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/timestamp-query
+application/timestamp-reply
+application/vemmi
+application/vnd.3M.Post-it-Notes
+application/vnd.FloGraphIt
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.aether.imp
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.businessobjects
+application/vnd.bmi
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.comsocaller
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.ctc-posml
+application/vnd.cybank
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hhe.lesson-player
+application/vnd.hp-HPGL
+application/vnd.hp-PCL
+application/vnd.hp-PCLXL
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.MiniPay
+application/vnd.ibm.modcap
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.koan
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.EDM
+application/vnd.novadigm.EDX
+application/vnd.novadigm.EXT
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-xhtml-print+xml
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.svd
+application/vnd.swiftview-ics
+application/vnd.triscape.mxs
+application/vnd.trueapp
+application/vnd.truedoc
+application/vnd.tve-trigger
+application/vnd.ufdl
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio
+application/vnd.vividence.scriptfile
+application/vnd.wap.sic sic
+application/vnd.wap.slc slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf
+application/vnd.xara
+application/vnd.xfdl
+application/vnd.yellowriver-custom-menu
+application/whoispp-query
+application/whoispp-response
+application/wita
+application/wordperfect5.1
+application/x-bcpio bcpio
+application/x-bzip2 bz2
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip gz tgz
+application/x-hdf hdf
+application/x-javascript js
+application/x-kword kwd kwt
+application/x-kspread ksp
+application/x-kpresenter kpr kpt
+application/x-kchart chrt
+application/x-killustrator kil
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-ogg ogg
+
+application/x-rpm rpm
+
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x400-bp
+application/xhtml+xml xhtml xht
+application/xml
+application/xml-dtd
+application/xml-external-parsed-entity
+application/zip zip
+audio/32kadpcm
+audio/g.722.1
+audio/l16
+audio/midi mid midi kar
+audio/mp4a-latm
+audio/mpa-robust
+audio/mpeg mpga mp2 mp3
+audio/parityfec
+audio/prs.sid
+audio/telephone-event
+audio/tone
+audio/vnd.cisco.nse
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds
+audio/vnd.everad.plj
+audio/vnd.lucent.voice
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800
+audio/vnd.nuera.ecelp7470
+audio/vnd.nuera.ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.vmx.cvsd
+audio/x-mpegurl m3u
+audio/x-realaudio ra
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm
+image/g3fax
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/naplps
+image/png png
+image/prs.btif
+image/prs.pti
+image/tiff tiff tif
+image/vnd.cns.inf2
+image/vnd.djvu djvu djv
+image/vnd.dwg
+image/vnd.dxf
+image/vnd.fastbidsheet
+image/vnd.fpx
+image/vnd.fst
+image/vnd.fujixerox.edmics-mmr
+image/vnd.fujixerox.edmics-rlc
+image/vnd.mix
+image/vnd.net-fpx
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff
+image/x-cmu-raster ras
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822
+message/s-http
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf
+model/vnd.flatland.3dml
+model/vnd.gdl
+model/vnd.gs-gdl
+model/vnd.gtw
+model/vnd.mts
+model/vnd.vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar
+text/css css
+text/directory
+text/enriched
+text/parityfec
+text/plain asc txt
+text/prs.lines.tag
+text/rfc822-headers
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/t140
+text/uri-list
+text/vnd.DMClientScript
+text/vnd.IPTC.NITF
+text/vnd.IPTC.NewsML
+text/vnd.abc
+text/vnd.curl
+text/vnd.flatland.3dml
+text/vnd.fly
+text/vnd.fmi.flexstor
+text/vnd.in3d.3dml
+text/vnd.in3d.spot
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.wap.si si
+text/vnd.wap.sl sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+text/xml xml xsl
+text/xml-external-parsed-entity
+video/mp4v-es
+video/mpeg mpeg mpg mpe
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/vnd.fvt
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu
+video/vnd.mts
+video/vnd.nokia.interleaved-multimedia
+video/vnd.vivo
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
+audio/x-pn-realaudio rmm ram
+audio/vnd.rn-realaudio ra
+application/smil smi smil
+text/vnd.rn-realtext rt
+video/vnd.rn-realvideo rv
+image/vnd.rn-realflash rf swf
+application/x-shockwave-flash2-preview rf swf
+application/sdp sdp
+application/x-sdp sdp
+application/vnd.rn-realmedia rm
+image/vnd.rn-realpix rp
+audio/wav wav
+audio/x-wav wav
+audio/x-pn-wav wav
+audio/x-pn-windows-acm wav
+audio/basic au
+audio/x-pn-au au
+audio/aiff aiff af
+audio/x-aiff aiff af
+audio/x-pn-aiff aiff af
+text/html html htm
diff --git a/system/python/spyce/spyce.nsi b/system/python/spyce/spyce.nsi
new file mode 100644
index 0000000000..fd7a4d674d
--- /dev/null
+++ b/system/python/spyce/spyce.nsi
@@ -0,0 +1,208 @@
+; spyce.nsi
+; Spyce Installer (NSIS script)
+
+;#####################################
+;VERSION
+
+!define VERSION 1.3.13
+!define RELEASE 1
+
+;#####################################
+;DEFINES
+
+!define NAME Spyce
+!define NAME_SMALL spyce
+!define Desc "Spyce - Python Server Pages"
+!define REG_PROG "SOFTWARE\${NAME}"
+!define REG_UNINST "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}"
+!define PYTHON "python.exe"
+!define REG_PYTHONLOC "${REG_PROG}"
+
+!define COMPILE 1
+
+;#####################################
+;OPTIONS
+
+OutFile "${NAME_SMALL}-${VERSION}.exe"
+InstallDir $PROGRAMFILES\${NAME_SMALL}
+InstallDirRegKey HKLM ${REG_PROG} "location"
+
+Name "${NAME}"
+Caption "${NAME} Windows Installer"
+UninstallCaption "${NAME} Windows Uninstaller"
+DirText "${NAME} Windows Installer"
+ComponentText "${NAME} Windows Installer"
+CompletedText "${NAME} Windows Installer is finished"
+UninstallText "${NAME} Windows Uninstaller"
+BrandingText " "
+
+CRCCheck on
+AutoCloseWindow true
+EnabledBitmap misc/one-check.bmp
+DisabledBitmap misc/one-nocheck.bmp
+ShowInstDetails show
+ShowUninstDetails show
+;BGGradient
+SilentUnInstall silent
+Icon misc\pics\spyce-border.ico ; MUST contain a 32x32x16 color icon
+UninstallIcon misc\pics\spyce-border.ico
+WindowIcon on
+SetOverwrite on
+SetCompress auto
+SetDatablockOptimize on
+SetDateSave off
+
+;#####################################
+;SECTIONS
+
+Section "${NAME} engine"
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ SetOutPath $INSTDIR
+ ; create and register uninstaller
+ WriteUninstaller "uninstall.exe"
+ WriteRegStr HKLM ${REG_PROG} "location" "$INSTDIR"
+ WriteRegStr HKLM "${REG_UNINST}" "DisplayName" "${NAME}: ${DESC} (remove only)"
+ WriteRegStr HKLM "${REG_UNINST}" "UninstallString" '"$INSTDIR\uninstall.exe"'
+
+ ; copy spyce engine files
+ File *.py
+ File CHANGES LICENCE README THANKS spyceApache.conf spyce.conf.eg misc\pics\spyce.ico spyce.mime
+ SetOutPath "$INSTDIR\modules"
+ File modules\*.py
+ SetOutPath "$INSTDIR\tags"
+ File tags\*.py
+ SetOutPath -
+ ; pre-compile the sources
+ !ifdef COMPILE
+ DetailPrint "Compile Spyce sources."
+ ExecWait `"$9" "$INSTDIR\spyceParser.py"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"`
+ !endif
+SectionEnd
+
+Section "${NAME} documentation"
+ SectionIn RO
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ ; copy Spyce documentation files
+ SetOutPath "$INSTDIR\docs"
+ File docs\*.spy docs\*.gif
+ SetOutPath "$INSTDIR\docs\examples"
+ File docs\examples\*.spy docs\examples\*.spi docs\examples\*.tmpl docs\examples\*.py docs\examples\*.gif
+ SetOutPath "$INSTDIR\docs\inc"
+ File docs\inc\*.spi
+ SetOutPath -
+ ; compile documentation
+ !ifdef COMPILE
+ DetailPrint "Compile Spyce documentation."
+ ExecWait `"$9" "$INSTDIR\run_spyceCmd.py" "-O" "$INSTDIR\docs\*.spy"`
+ !endif
+SectionEnd
+
+SectionDivider "Options"
+
+Section "Create start menu shortcuts"
+ CreateDirectory "$SMPROGRAMS\${NAME}"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation.lnk" "$INSTDIR\docs\index.html" "" "$INSTDIR\spyce.ico"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation -- localhost.lnk" "http://localhost/spyce/" "" ""
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Online.lnk" "http://spyce.sf.net/" "" ""
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Examples.lnk" "$INSTDIR\docs\examples" "" "$INSTDIR\spyce.ico"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall Spyce.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
+SectionEnd
+
+Section "Create shell extensions"
+ WriteRegStr HKCR ".spy" "" "SpyceFile"
+ WriteRegStr HKCR "SpyceFile" "" "Spyce dynamic HTML file"
+ WriteRegStr HKCR "SpyceFile\DefaultIcon" "" $INSTDIR\spyce.ico,0
+ WriteRegStr HKCR "SpyceFile\shell\open\command" "" 'notepad.exe "%1"'
+ WriteRegStr HKCR "SpyceFile\shell\compile" "" "Compile Spyce"
+ WriteRegStr HKCR "SpyceFile\shell\compile\command" "" '"$9" "$INSTDIR\run_spyceCmd.py" -O "%1"'
+ WriteRegStr HKCR "SpyceFile\shell" "" "compile"
+SectionEnd
+
+Section "Configure Apache"
+ DetailPrint "Configuring Apache..."
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apache=$INSTDIR"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"`
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$\nApache reconfigured and restarted.$\nIf everything is ok, you should be able to browse to: http://localhost/spyce/$\nIf not, please check your httpd.conf file and/or restart Apache."
+
+SectionEnd
+
+
+Section "Uninstall"
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ DetailPrint "Unconfiguring Apache..."
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheUN"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"`
+ RMDir /r "$INSTDIR"
+ RMDir /r "$SMPROGRAMS\${NAME}"
+ DeleteRegKey HKLM ${REG_UNINST}
+ DeleteRegKey HKLM ${REG_PROG}
+ DeleteRegKey HKCR ".spy"
+ DeleteRegKey HKCR "SpyceFile"
+SectionEnd
+
+;#####################################
+;FUNCTIONS
+
+Function detectPython
+ ; see if there is any python interpreter
+ ClearErrors
+ ExecShell "open" "${PYTHON}" `-c "print 'Python is alive!'"` SW_SHOWMINIMIZED
+ IfErrors 0 NoAbort
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to find Python interpreter. Please install Python first."
+ Abort
+ NoAbort:
+ ; find out where it is
+ GetTempFileName $9
+ GetTempFileName $8
+ FileOpen $7 $9 w
+ FileWrite $7 'import sys$\n'
+ FileWrite $7 "f=open(r'$8', 'w')$\n"
+ FileWrite $7 'f.write(sys.executable)$\n'
+ FileWrite $7 'f.close()$\n'
+ FileClose $7
+ ExecShell "open" "${PYTHON}" `"$9"` SW_SHOWMINIMIZED
+ IntOp $0 0 + 0
+ Loop:
+ FileOpen $7 $8 r
+ FileRead $7 $6
+ FileClose $7
+ StrCmp $6 "" 0 EndLoop
+ Sleep 100
+ IntOp $0 $0 + 1
+ IntCmp $0 50 EndLoop
+ Goto Loop
+ EndLoop:
+ Delete $9
+ StrCpy $9 "$6" ; put the python path in $9 -- GLOBAL
+ StrCmp $9 "" 0 NoAbort2
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Mechanism for discovering Python path via sys.executable did not work.$\nSorry, but automatic installation is unable to proceed. Please contact the author."
+ Abort
+ NoAbort2:
+ WriteRegStr HKLM ${REG_PYTHONLOC} "python" "$9"
+FunctionEnd
+
+Function .onInit
+ Call detectPython
+FunctionEnd
+
+Function .onInstSuccess
+ DetailPrint "Spyce successfully installed."
+ MessageBox MB_OK "Spyce successfully installed."
+ ExecShell "open" "$INSTDIR\docs\index.html" SW_SHOWMAXIMIZED
+FunctionEnd
+
+Function un.onInit
+ MessageBox MB_YESNO|MB_ICONQUESTION "Are you sure that you want to uninstall Spyce?" IDYES NoAbort
+ Abort
+ NoAbort:
+FunctionEnd
+
+Function un.onUninstSuccess
+ MessageBox MB_OK "Spyce successfully uninstalled."
+FunctionEnd
diff --git a/system/python/spyce/spyce.nsi.in b/system/python/spyce/spyce.nsi.in
new file mode 100644
index 0000000000..1005f7a59e
--- /dev/null
+++ b/system/python/spyce/spyce.nsi.in
@@ -0,0 +1,208 @@
+; spyce.nsi
+; Spyce Installer (NSIS script)
+
+;#####################################
+;VERSION
+
+!define VERSION __VERSION__
+!define RELEASE __RELEASE__
+
+;#####################################
+;DEFINES
+
+!define NAME Spyce
+!define NAME_SMALL spyce
+!define Desc "Spyce - Python Server Pages"
+!define REG_PROG "SOFTWARE\${NAME}"
+!define REG_UNINST "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}"
+!define PYTHON "python.exe"
+!define REG_PYTHONLOC "${REG_PROG}"
+
+!define COMPILE 1
+
+;#####################################
+;OPTIONS
+
+OutFile "${NAME_SMALL}-${VERSION}.exe"
+InstallDir $PROGRAMFILES\${NAME_SMALL}
+InstallDirRegKey HKLM ${REG_PROG} "location"
+
+Name "${NAME}"
+Caption "${NAME} Windows Installer"
+UninstallCaption "${NAME} Windows Uninstaller"
+DirText "${NAME} Windows Installer"
+ComponentText "${NAME} Windows Installer"
+CompletedText "${NAME} Windows Installer is finished"
+UninstallText "${NAME} Windows Uninstaller"
+BrandingText " "
+
+CRCCheck on
+AutoCloseWindow true
+EnabledBitmap misc/one-check.bmp
+DisabledBitmap misc/one-nocheck.bmp
+ShowInstDetails show
+ShowUninstDetails show
+;BGGradient
+SilentUnInstall silent
+Icon misc\pics\spyce-border.ico ; MUST contain a 32x32x16 color icon
+UninstallIcon misc\pics\spyce-border.ico
+WindowIcon on
+SetOverwrite on
+SetCompress auto
+SetDatablockOptimize on
+SetDateSave off
+
+;#####################################
+;SECTIONS
+
+Section "${NAME} engine"
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ SetOutPath $INSTDIR
+ ; create and register uninstaller
+ WriteUninstaller "uninstall.exe"
+ WriteRegStr HKLM ${REG_PROG} "location" "$INSTDIR"
+ WriteRegStr HKLM "${REG_UNINST}" "DisplayName" "${NAME}: ${DESC} (remove only)"
+ WriteRegStr HKLM "${REG_UNINST}" "UninstallString" '"$INSTDIR\uninstall.exe"'
+
+ ; copy spyce engine files
+ File *.py
+ File CHANGES LICENCE README THANKS spyceApache.conf spyce.conf.eg misc\pics\spyce.ico spyce.mime
+ SetOutPath "$INSTDIR\modules"
+ File modules\*.py
+ SetOutPath "$INSTDIR\tags"
+ File tags\*.py
+ SetOutPath -
+ ; pre-compile the sources
+ !ifdef COMPILE
+ DetailPrint "Compile Spyce sources."
+ ExecWait `"$9" "$INSTDIR\spyceParser.py"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"`
+ ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"`
+ !endif
+SectionEnd
+
+Section "${NAME} documentation"
+ SectionIn RO
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ ; copy Spyce documentation files
+ SetOutPath "$INSTDIR\docs"
+ File docs\*.spy docs\*.gif
+ SetOutPath "$INSTDIR\docs\examples"
+ File docs\examples\*.spy docs\examples\*.spi docs\examples\*.tmpl docs\examples\*.py docs\examples\*.gif
+ SetOutPath "$INSTDIR\docs\inc"
+ File docs\inc\*.spi
+ SetOutPath -
+ ; compile documentation
+ !ifdef COMPILE
+ DetailPrint "Compile Spyce documentation."
+ ExecWait `"$9" "$INSTDIR\run_spyceCmd.py" "-O" "$INSTDIR\docs\*.spy"`
+ !endif
+SectionEnd
+
+SectionDivider "Options"
+
+Section "Create start menu shortcuts"
+ CreateDirectory "$SMPROGRAMS\${NAME}"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation.lnk" "$INSTDIR\docs\index.html" "" "$INSTDIR\spyce.ico"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation -- localhost.lnk" "http://localhost/spyce/" "" ""
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Online.lnk" "http://spyce.sf.net/" "" ""
+ CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Examples.lnk" "$INSTDIR\docs\examples" "" "$INSTDIR\spyce.ico"
+ CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall Spyce.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
+SectionEnd
+
+Section "Create shell extensions"
+ WriteRegStr HKCR ".spy" "" "SpyceFile"
+ WriteRegStr HKCR "SpyceFile" "" "Spyce dynamic HTML file"
+ WriteRegStr HKCR "SpyceFile\DefaultIcon" "" $INSTDIR\spyce.ico,0
+ WriteRegStr HKCR "SpyceFile\shell\open\command" "" 'notepad.exe "%1"'
+ WriteRegStr HKCR "SpyceFile\shell\compile" "" "Compile Spyce"
+ WriteRegStr HKCR "SpyceFile\shell\compile\command" "" '"$9" "$INSTDIR\run_spyceCmd.py" -O "%1"'
+ WriteRegStr HKCR "SpyceFile\shell" "" "compile"
+SectionEnd
+
+Section "Configure Apache"
+ DetailPrint "Configuring Apache..."
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apache=$INSTDIR"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"`
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$\nApache reconfigured and restarted.$\nIf everything is ok, you should be able to browse to: http://localhost/spyce/$\nIf not, please check your httpd.conf file and/or restart Apache."
+
+SectionEnd
+
+
+Section "Uninstall"
+ ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python"
+ DetailPrint "Unconfiguring Apache..."
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheUN"`
+ ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"`
+ RMDir /r "$INSTDIR"
+ RMDir /r "$SMPROGRAMS\${NAME}"
+ DeleteRegKey HKLM ${REG_UNINST}
+ DeleteRegKey HKLM ${REG_PROG}
+ DeleteRegKey HKCR ".spy"
+ DeleteRegKey HKCR "SpyceFile"
+SectionEnd
+
+;#####################################
+;FUNCTIONS
+
+Function detectPython
+ ; see if there is any python interpreter
+ ClearErrors
+ ExecShell "open" "${PYTHON}" `-c "print 'Python is alive!'"` SW_SHOWMINIMIZED
+ IfErrors 0 NoAbort
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to find Python interpreter. Please install Python first."
+ Abort
+ NoAbort:
+ ; find out where it is
+ GetTempFileName $9
+ GetTempFileName $8
+ FileOpen $7 $9 w
+ FileWrite $7 'import sys$\n'
+ FileWrite $7 "f=open(r'$8', 'w')$\n"
+ FileWrite $7 'f.write(sys.executable)$\n'
+ FileWrite $7 'f.close()$\n'
+ FileClose $7
+ ExecShell "open" "${PYTHON}" `"$9"` SW_SHOWMINIMIZED
+ IntOp $0 0 + 0
+ Loop:
+ FileOpen $7 $8 r
+ FileRead $7 $6
+ FileClose $7
+ StrCmp $6 "" 0 EndLoop
+ Sleep 100
+ IntOp $0 $0 + 1
+ IntCmp $0 50 EndLoop
+ Goto Loop
+ EndLoop:
+ Delete $9
+ StrCpy $9 "$6" ; put the python path in $9 -- GLOBAL
+ StrCmp $9 "" 0 NoAbort2
+ MessageBox MB_OK|MB_ICONEXCLAMATION "Mechanism for discovering Python path via sys.executable did not work.$\nSorry, but automatic installation is unable to proceed. Please contact the author."
+ Abort
+ NoAbort2:
+ WriteRegStr HKLM ${REG_PYTHONLOC} "python" "$9"
+FunctionEnd
+
+Function .onInit
+ Call detectPython
+FunctionEnd
+
+Function .onInstSuccess
+ DetailPrint "Spyce successfully installed."
+ MessageBox MB_OK "Spyce successfully installed."
+ ExecShell "open" "$INSTDIR\docs\index.html" SW_SHOWMAXIMIZED
+FunctionEnd
+
+Function un.onInit
+ MessageBox MB_YESNO|MB_ICONQUESTION "Are you sure that you want to uninstall Spyce?" IDYES NoAbort
+ Abort
+ NoAbort:
+FunctionEnd
+
+Function un.onUninstSuccess
+ MessageBox MB_OK "Spyce successfully uninstalled."
+FunctionEnd
diff --git a/system/python/spyce/spyce.py b/system/python/spyce/spyce.py
new file mode 100755
index 0000000000..3a21f36d16
--- /dev/null
+++ b/system/python/spyce/spyce.py
@@ -0,0 +1,674 @@
+#!/usr/bin/env python
+
+__version__ = '1.3.13'
+__release__ = '1'
+
+DEBUG_ERROR = 0
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to LICENCE for legalese
+#
+# Name: spyce
+# Author: Rimon Barr <rimon-AT-acm.org>
+# Start date: 8 April 2002
+# Purpose: Python Server Pages
+# WWW: http://spyce.sourceforge.net/
+# CVS: $Id$
+##################################################
+
+# note: doc string used in documentation: doc/index.spy
+__doc__ = '''SPYCE is a server-side language that supports simple and
+efficient Python-based dynamic HTML generation, otherwise called <i>Python
+Server Pages</i> (PSP). Those who are familiar with JSP, PHP, or ASP and like
+Python, should have a look at Spyce. Its modular design makes it very flexible
+and extensible. It can also be used as a command-line utility for static text
+pre-processing or as a web-server proxy.'''
+
+import sys, os, copy, string, imp
+import spyceConfig, spyceCompile, spyceException
+import spyceModule, spyceTag
+import spyceLock, spyceCache, spyceUtil
+
+##################################################
+# Spyce engine globals
+#
+
+# spyceServer object - one per engine instance
+SPYCE_SERVER = None
+def getServer(
+ config_file=None,
+ overide_www_port=None,
+ overide_www_root=None,
+ force=0):
+ global SPYCE_SERVER
+ if force or not SPYCE_SERVER:
+ SPYCE_SERVER = spyceServer(
+ config_file=config_file,
+ overide_www_root=overide_www_root,
+ overide_www_port=overide_www_port,
+ )
+ return SPYCE_SERVER
+
+SPYCE_GLOBALS = None
+def getServerGlobals():
+ global SPYCE_GLOBALS
+ return SPYCE_GLOBALS
+
+SPYCE_LOADER = 'spyceLoader'
+SPYCE_ENTRY = 'SPYCE_ENTRY'
+DEFAULT_MODULES = ('request', 'response', 'stdout', 'error')
+
+##################################################
+# Spyce core objects
+#
+
+class spyceServerObject:
+ "serverObject placeholder"
+ pass
+
+class spyceServer:
+ "One per server, stored in SPYCE_SERVER (above) at processing of first request."
+ def __init__(self,
+ config_file=None,
+ overide_www_root=None,
+ overide_www_port=None,
+ ):
+ global SPYCE_GLOBALS
+ # server object
+ self.serverobject = spyceServerObject()
+ # http headers
+ try: self.entry = os.environ[SPYCE_ENTRY]
+ except: self.entry = 'UNKNOWN'
+ self.spyceHeader = 'Spyce/%s_%s Python/%s' % (self.entry, str(__version__), sys.version[:3])
+ # configuration dictionary
+ self.config = spyceConfig.spyceConfig(
+ file=config_file,
+ overide_www_root=overide_www_root,
+ overide_www_port=overide_www_port,
+ )
+ # server globals/constants
+ self.globals = self.config.getSpyceGlobals()
+ SPYCE_GLOBALS = self.globals # hack
+ # now finish processing config file; this way imported modules have
+ # access to the globals
+ self.config.process ()
+ # spyce module search path
+ self.path = self.config.getSpycePath()
+ # concurrency mode
+ self.concurrency = self.config.getSpyceConcurrency()
+ # imports
+ self.imports = self.config.getSpyceImport()
+ # debug mode
+ self.debug = self.config.getSpyceDebug()
+ # spyce cache
+ type, info = self.config.getSpyceCache()
+ if type in ('file',):
+ type = spyceCache.fileCache(info)
+ elif type in ('mem', 'memory'):
+ type = spyceCache.memoryCache(info)
+ else: type = spyceCache.memoryCache()
+ if self.debug: type = None
+ self.spyce_cache = spyceCache.semanticCache(type, spyceCacheValid, spyceCacheGenerate)
+ # spyce module cache
+ self.module_cache = {}
+ if self.debug:
+ self.module_cache = None
+ # page error handler
+ pageerror = self.config.getSpycePageError()
+ if pageerror[0]=='string':
+ pageerror = pageerror[0], self.loadModule(pageerror[2], pageerror[1]+'.py')
+ self.pageerror = pageerror
+ # engine error handler
+ error = self.config.getSpyceError()
+ self.error = self.loadModule(error[1], error[0]+'.py')
+ # spyce thread-safe stdout object
+ if self.concurrency == spyceConfig.SPYCE_CONCURRENCY_THREAD:
+ self.stdout = spyceUtil.ThreadedWriter(sys.stdout)
+ self.lock = spyceLock.threadLock()
+ sys.stdout = self.stdout
+ else:
+ self.stdout = None
+ self.lock = spyceLock.dummyLock()
+ # set sys.stdout
+ def loadModule(self, name, file=None, rel_file=None):
+ "Find and load a module, with caching"
+ if not file: file=name+'.py'
+ key = name, file, rel_file
+ if self.module_cache!=None:
+ try: return self.module_cache[key]
+ except: pass # cache miss
+ def loadModuleHelper(file=file, rel_file=rel_file, path=self.path):
+ if rel_file: path = path + [os.path.dirname(rel_file)]
+ for p in path:
+ f=None
+ try:
+ p = os.path.join(p, file)
+ if os.path.exists(p) and os.access(p, os.R_OK):
+ f = open(p, 'r')
+ return imp.load_source(SPYCE_LOADER, p, f)
+ finally:
+ if f: f.close()
+ raise 'unable to find module "%s" in path: %s' % (file, path)
+ # load and cache module
+ dict = {'loadModuleHelper': loadModuleHelper}
+ exec 'foo = loadModuleHelper()' in dict
+ mod = eval('dict["foo"].%s' % name)
+ if self.module_cache!=None:
+ self.module_cache[key] = mod
+ return mod
+ def fileHandler(self, request, response, filename, sig='', args=None, kwargs=None):
+ return self.commonHandler(request, response, ('file', (filename, sig)), args, kwargs)
+ def stringHandler(self, request, response, code, sig='', args=None, kwargs=None):
+ return self.commonHandler(request, response, ('string', (code, sig)), args, kwargs)
+ def commonHandler(self, request, response, spyceInfo, args=None, kwargs=None):
+ "Handle a request. This method is NOT thread safe."
+ try:
+ thespyce = theError = None
+ try:
+ spycecode = self.spyce_cache[spyceInfo]
+ thespyce = spycecode.newWrapper()
+ response.addHeader('X-Spyce', self.spyceHeader, 1)
+ try:
+ thespyce.spyceInit(request, response)
+ if args==None: args=[]
+ if kwargs==None: kwargs={}
+ apply(thespyce.spyceProcess, args, kwargs)
+ except spyceException.spyceRuntimeException, theError: pass
+ finally:
+ if DEBUG_ERROR and theError:
+ sys.stderr.write(`theError`+'\n')
+ if thespyce:
+ thespyce.spyceDestroy(theError)
+ spycecode.returnWrapper(thespyce)
+ except spyceException.spyceDone: pass
+ except spyceException.spyceRedirect, e:
+ return spyceFileHandler(request, response, e.filename)
+ except KeyboardInterrupt: raise
+ except (spyceException.spyceNotFound, spyceException.spyceForbidden,
+ spyceException.spyceSyntaxError, spyceException.pythonSyntaxError,
+ SyntaxError), e:
+ return self.error(self, request, response, e)
+ except SystemExit: pass
+ except:
+ errorString = spyceUtil.exceptionString()
+ try:
+ import cgi
+ response.clear()
+ response.write('<html><pre>\n')
+ response.write('Unexpected exception: (please report!)\n')
+ response.write(cgi.escape(errorString))
+ response.write('\n</pre></html>\n')
+ response.returncode = response.RETURN_OK
+ except:
+ sys.stderr.write(errorString+'\n')
+ return response.returncode
+
+class spyceRequest:
+ """Underlying Spyce request object. All implementations (CGI, Apache...)
+ should subclass and implement the methods marked 'not implemented'."""
+ def __init__(self):
+ self._in = None
+ def read(self, limit=None):
+ if limit:
+ return self._in.read(limit)
+ else:
+ return self._in.read()
+ def readline(self, limit=None):
+ if limit:
+ return self._in.readline(limit)
+ else:
+ return self._in.readline()
+ def env(self, name=None):
+ raise 'not implemented'
+ def getHeader(self, type=None):
+ raise 'not implemented'
+ def getServerID(self):
+ raise 'not implemented'
+
+class spyceResponse:
+ """Underlying Spyce response object. All implementations (CGI, Apache...)
+ should subclass and implement the methods marked 'not implemented', and
+ also properly define the RETURN codes."""
+ RETURN_CONTINUE = 100
+ RETURN_SWITCHING_PROTOCOLS = 101
+ RETURN_OK = 200
+ RETURN_CREATED = 201
+ RETURN_ACCEPTED = 202
+ RETURN_NON_AUTHORITATIVE_INFORMATION = 203
+ RETURN_NO_CONTENT = 204
+ RETURN_RESET_CONTENT = 205
+ RETURN_PARTIAL_CONTENT = 206
+ RETURN_MULTIPLE_CHOICES = 300
+ RETURN_MOVED_PERMANENTLY = 301
+ RETURN_MOVED_TEMPORARILY = 302
+ RETURN_SEE_OTHER = 303
+ RETURN_NOT_MODIFIED = 304
+ RETURN_USE_PROXY = 305
+ RETURN_TEMPORARY_REDIRECT = 307
+ RETURN_BAD_REQUEST = 400
+ RETURN_UNAUTHORIZED = 401
+ RETURN_PAYMENT_REQUIRED = 402
+ RETURN_FORBIDDEN = 403
+ RETURN_NOT_FOUND = 404
+ RETURN_METHOD_NOT_ALLOWED = 405
+ RETURN_NOT_ACCEPTABLE = 406
+ RETURN_PROXY_AUTHENTICATION_REQUIRED = 407
+ RETURN_REQUEST_TIMEOUT = 408
+ RETURN_CONFLICT = 409
+ RETURN_GONE = 410
+ RETURN_LENGTH_REQUIRED = 411
+ RETURN_PRECONDITION_FAILED = 412
+ RETURN_REQUEST_ENTITY_TOO_LARGE = 413
+ RETURN_REQUEST_URI_TOO_LONG = 414
+ RETURN_UNSUPPORTED_MEDIA_TYPE = 415
+ RETURN_REQUEST_RANGE_NOT_SATISFIABLE = 416
+ RETURN_EXPECTATION_FAILED = 417
+ RETURN_INTERNAL_SERVER_ERROR = 500
+ RETURN_NOT_IMPLEMENTED = 501
+ RETURN_BAD_GATEWAY = 502
+ RETURN_SERVICE_UNAVAILABLE = 503
+ RETURN_GATEWAY_TIMEOUT = 504
+ RETURN_HTTP_VERSION_NOT_SUPPORTED = 505
+ RETURN_CODE = {
+ RETURN_CONTINUE: 'CONTINUE',
+ RETURN_SWITCHING_PROTOCOLS: 'SWITCHING PROTOCOLS',
+ RETURN_OK: 'OK',
+ RETURN_CREATED: 'CREATED',
+ RETURN_ACCEPTED: 'ACCEPTED',
+ RETURN_NON_AUTHORITATIVE_INFORMATION: 'NON AUTHORITATIVE INFORMATION',
+ RETURN_NO_CONTENT: 'NO CONTENT',
+ RETURN_RESET_CONTENT: 'RESET CONTENT',
+ RETURN_PARTIAL_CONTENT: 'PARTIAL CONTENT',
+ RETURN_MULTIPLE_CHOICES: 'MULTIPLE CHOICES',
+ RETURN_MOVED_PERMANENTLY: 'MOVED PERMANENTLY',
+ RETURN_MOVED_TEMPORARILY: 'MOVED TEMPORARILY',
+ RETURN_SEE_OTHER: 'SEE OTHER',
+ RETURN_NOT_MODIFIED: 'NOT MODIFIED',
+ RETURN_USE_PROXY: 'USE PROXY',
+ RETURN_TEMPORARY_REDIRECT: 'TEMPORARY REDIRECT',
+ RETURN_BAD_REQUEST: 'BAD REQUEST',
+ RETURN_UNAUTHORIZED: 'UNAUTHORIZED',
+ RETURN_PAYMENT_REQUIRED: 'PAYMENT REQUIRED',
+ RETURN_FORBIDDEN: 'FORBIDDEN',
+ RETURN_NOT_FOUND: 'NOT FOUND',
+ RETURN_METHOD_NOT_ALLOWED: 'METHOD NOT ALLOWED',
+ RETURN_NOT_ACCEPTABLE: 'NOT ACCEPTABLE',
+ RETURN_PROXY_AUTHENTICATION_REQUIRED: 'PROXY AUTHENTICATION REQUIRED',
+ RETURN_REQUEST_TIMEOUT: 'REQUEST TIMEOUT',
+ RETURN_CONFLICT: 'CONFLICT',
+ RETURN_GONE: 'GONE',
+ RETURN_LENGTH_REQUIRED: 'LENGTH REQUIRED',
+ RETURN_PRECONDITION_FAILED: 'PRECONDITION FAILED',
+ RETURN_REQUEST_ENTITY_TOO_LARGE: 'REQUEST ENTITY TOO LARGE',
+ RETURN_REQUEST_URI_TOO_LONG: 'REQUEST URI TOO LONG',
+ RETURN_UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED MEDIA TYPE',
+ RETURN_REQUEST_RANGE_NOT_SATISFIABLE: 'REQUEST RANGE NOT SATISFIABLE',
+ RETURN_EXPECTATION_FAILED: 'EXPECTATION FAILED',
+ RETURN_INTERNAL_SERVER_ERROR: 'INTERNAL SERVER ERROR',
+ RETURN_NOT_IMPLEMENTED: 'NOT IMPLEMENTED',
+ RETURN_BAD_GATEWAY: 'BAD GATEWAY',
+ RETURN_SERVICE_UNAVAILABLE: 'SERVICE UNAVAILABLE',
+ RETURN_GATEWAY_TIMEOUT: 'GATEWAY TIMEOUT',
+ RETURN_HTTP_VERSION_NOT_SUPPORTED: 'HTTP VERSION NOT SUPPORTED',
+ }
+ def __init__(self):
+ pass
+ def write(self, s):
+ raise 'not implemented'
+ def writeErr(self, s):
+ raise 'not implemented'
+ def close(self):
+ raise 'not implemented'
+ def clear(self):
+ raise 'not implemented'
+ def sendHeaders(self):
+ raise 'not implemented'
+ def clearHeaders(self):
+ raise 'not implemented'
+ def setContentType(self, content_type):
+ raise 'not implemented'
+ def setReturnCode(self, code):
+ raise 'not implemented'
+ def addHeader(self, type, data, replace=0):
+ raise 'not implemented'
+ def flush(self):
+ raise 'not implemented'
+ def unbuffer(self):
+ raise 'not implemented'
+
+class spyceCode:
+ '''Takes care of compiling the Spyce file, and generating a wrapper'''
+ def __init__(self, code, filename=None, sig='', limit=3):
+ # store variables
+ self._filename = filename
+ self._limit = limit
+ # generate code
+ self._code, self._coderefs, self._modrefs = \
+ spyceCompile.spyceCompile(code, filename, sig, getServer())
+ self._wrapperQueue = []
+ self._wrappersMade = 0
+ # wrappers
+ def newWrapper(self):
+ "Get a wrapper for this code from queue, or make new one"
+ try: return self._wrapperQueue.pop()
+ except IndexError: pass
+ self._wrappersMade = self._wrappersMade + 1
+ return spyceWrapper(self)
+ def returnWrapper(self, w):
+ "Return wrapper back to queue after use"
+ if len(self._wrapperQueue)<self._limit:
+ self._wrapperQueue.append(w)
+ # serialization
+ def __getstate__(self):
+ return self._filename, self._code, self._coderefs, self._modrefs, self._limit
+ def __setstate__(self, state):
+ self._filename, self._code, self._coderefs, self._modrefs, self._limit = state
+ self._wrapperQueue = []
+ self._wrappersMade = 0
+ # accessors
+ def getCode(self):
+ "Return processed Spyce (i.e. Python) code"
+ return self._code
+ def getFilename(self):
+ "Return source filename, if it exists"
+ return self._filename
+ def getCodeRefs(self):
+ "Return python-to-Spyce code line references"
+ return self._coderefs
+ def getModRefs(self):
+ "Return list of import references in Spyce code"
+ return self._modrefs
+
+class spyceWrapper:
+ """Wrapper object runs the entire show, bringing together the code, the
+ Spyce environment, the request and response objects and the modules. It is
+ NOT thread safe - new instances are generated as necessary by spyceCode!
+ This object is generated by a spyceCode object. The common Spyce handler
+ code calls the 'processing' functions. Module writers interact with this
+ object via the spyceModuleAPI calls. This is arguable the trickiest portion
+ of the Spyce so don't touch unless you know what you are doing."""
+ def __init__(self, spycecode):
+ # store variables
+ self._spycecode = spycecode
+ # api object
+ self._api = self
+ # module tracking
+ self._modCache = {}
+ self._modstarted = []
+ self._modules = {}
+ # compile python code
+ self._codeenv = { spyceCompile.SPYCE_WRAPPER: self._api }
+ try: exec self._spycecode.getCode() in self._codeenv
+ except SyntaxError: raise spyceException.pythonSyntaxError(self)
+ self._freshenv = self._codeenv.keys()
+ # spyce hooks
+ noop = lambda *args: None
+ self.process = self._codeenv[spyceCompile.SPYCE_PROCESS_FUNC]
+ try: self.init = self._codeenv[spyceCompile.SPYCE_INIT_FUNC]
+ except: self.init = noop
+ try: self.destroy = self._codeenv[spyceCompile.SPYCE_DESTROY_FUNC]
+ except: self.destroy = noop
+ # request, response
+ self._response = self._request = None
+ self._responseCallback = {}
+ self._moduleCallback = {}
+ def _startModule(self, name, file=None, as=None, force=0):
+ "Initialise module for current request."
+ if as==None: as=name
+ if force or not self._codeenv.has_key(as):
+ modclass = getServer().loadModule(name, file, self._spycecode.getFilename())
+ mod = modclass(self._api)
+ self.setModule(as, mod, 0)
+ if DEBUG_ERROR:
+ sys.stderr.write(as+'.start\n')
+ mod.start()
+ self._modstarted.append((as, mod))
+ else: mod = self._codeenv[as]
+ return mod
+ # spyce processing
+ def spyceInit(self, request, response):
+ "Initialise a Spyce for processing."
+ self._request = request
+ self._response = response
+ for mod in DEFAULT_MODULES:
+ self._startModule(mod)
+ self._modstarteddefault = self._modstarted
+ self._modstarted = []
+ for (modname, modfrom, modas) in self.getModRefs():
+ self._startModule(modname, modfrom, modas, 1)
+ exec '_spyce_start()' in { '_spyce_start': self.init }
+ def spyceProcess(self, *args, **kwargs):
+ "Process the current Spyce request."
+ path = sys.path
+ try:
+ if self._spycecode.getFilename():
+ path = copy.copy(sys.path)
+ sys.path.append(os.path.dirname(self._spycecode.getFilename()))
+ dict = { '_spyce_process': self.process,
+ '_spyce_args': args, '_spyce_kwargs': kwargs, }
+ exec '_spyce_result = apply(_spyce_process, _spyce_args, _spyce_kwargs)' in dict
+ return dict['_spyce_result']
+ finally:
+ sys.path = path
+ def spyceDestroy(self, theError=None):
+ "Cleanup after the request processing."
+ try:
+ exec '_spyce_finish()' in { '_spyce_finish': self.destroy }
+ # explicit modules
+ self._modstarted.reverse()
+ for as, mod in self._modstarted:
+ try:
+ if DEBUG_ERROR:
+ sys.stderr.write(as+'.finish\n')
+ mod.finish(theError)
+ except spyceException.spyceDone: pass
+ except spyceException.spyceRedirect, e:
+ return spyceFileHandler(self._request, self._response, e.filename)
+ except KeyboardInterrupt: raise
+ except SystemExit: pass
+ except: theError = spyceException.spyceRuntimeException(self._api)
+ finishError = None
+ # default modules
+ self._modstarteddefault.reverse()
+ for as, mod in self._modstarteddefault:
+ try:
+ if DEBUG_ERROR:
+ sys.stderr.write(as+'.finish\n')
+ mod.finish(theError)
+ except: finishError = 1
+ self._request = None
+ self._response = None
+ if finishError: raise
+ finally:
+ self.spyceCleanup()
+ def spyceCleanup(self):
+ "Sweep the Spyce environment."
+ self._modstarteddefault = []
+ self._modstarted = []
+ self._modules = {}
+ if self._freshenv!=None:
+ for e in self._codeenv.keys():
+ if e not in self._freshenv:
+ del self._codeenv[e]
+ # API methods
+ def getFilename(self):
+ "Return filename of current Spyce"
+ return self._spycecode.getFilename()
+ def getCode(self):
+ "Return processed Spyce (i.e. Python) code"
+ return self._spycecode.getCode()
+ def getCodeRefs(self):
+ "Return python-to-Spyce code line references"
+ return self._spycecode.getCodeRefs()
+ def getModRefs(self):
+ "Return list of import references in Spyce code"
+ return self._spycecode.getModRefs()
+ def getServerObject(self):
+ "Return unique (per engine instance) server object"
+ return getServer().serverobject
+ def getServerGlobals(self):
+ "Return server configuration globals"
+ return getServer().globals
+ def getServerID(self):
+ "Return unique server identifier"
+ return self._request.getServerID()
+ def getPageError(self):
+ "Return default page error value"
+ return getServer().pageerror
+ def getRequest(self):
+ "Return internal request object"
+ return self._request
+ def getResponse(self):
+ "Return internal response object"
+ return self._response
+ def setResponse(self, o):
+ "Set internal response object"
+ self._response = o
+ for f in self._responseCallback.keys(): f()
+ def registerResponseCallback(self, f):
+ "Register a callback for when internal response changes"
+ self._responseCallback[f] = 1
+ def unregisterResponseCallback(self, f):
+ "Unregister a callback for when internal response changes"
+ try: del self._responseCallback[f]
+ except KeyError: pass
+ def getModules(self):
+ "Return references to currently loaded modules"
+ return self._modules
+ def getModule(self, name):
+ """Get module reference. The module is dynamically loaded and initialised
+ if it does not exist (ie. if it was not explicitly imported, but requested
+ by another module during processing)"""
+ return self._startModule(name)
+ def setModule(self, name, mod, notify=1):
+ "Add existing module (by reference) to Spyce namespace (used for includes)"
+ self._codeenv[name] = mod
+ self._modules[name] = mod
+ if notify:
+ for f in self._moduleCallback.keys():
+ f()
+ def delModule(self, name, notify=1):
+ "Add existing module (by reference) to Spyce namespace (used for includes)"
+ del self._codeenv[name]
+ del self._modules[name]
+ if notify:
+ for f in self._moduleCallback.keys():
+ f()
+ def getGlobals(self):
+ "Return the Spyce global namespace dictionary"
+ return self._codeenv
+ def registerModuleCallback(self, f):
+ "Register a callback for modules change"
+ self._moduleCallback[f] = 1
+ def unregisterModuleCallback(self, f):
+ "Unregister a callback for modules change"
+ try: del self._moduleCallback[f]
+ except KeyError: pass
+ def spyceFile(self, file):
+ "Return a spyceCode object of a file"
+ return getServer().spyce_cache[('file', file)]
+ def spyceString(self, code):
+ "Return a spyceCode object of a string"
+ return getServer().spyce_cache[('string', code)]
+ def spyceModule(self, name, file=None, rel_file=None):
+ "Return Spyce module class"
+ return getServer().loadModule(name, file, rel_file)
+ def spyceTaglib(self, name, file=None, rel_file=None):
+ "Return Spyce taglib class"
+ return getServer().loadModule(name, file, rel_file)
+ def setStdout(self, out):
+ "Set the stdout stream (thread-safe)"
+ serverout = getServer().stdout
+ if serverout: serverout.setObject(out)
+ else: sys.stdout = out
+ def getStdout(self):
+ "Get the stdout stream (thread-safe)"
+ serverout = getServer().stdout
+ if serverout: return serverout.getObject()
+ else: return sys.stdout
+
+##################################################
+# Spyce cache
+#
+
+def spyceFileCacheValid(key, validity):
+ "Determine whether compiled Spyce is valid"
+ try:
+ filename, sig = key
+ except:
+ filename, sig = key, ''
+ try:
+ if not os.path.exists(filename):
+ return 0
+ if not os.access(filename, os.R_OK):
+ return 0
+ return os.path.getmtime(filename) == validity
+ except KeyboardInterrupt: raise
+ except:
+ return 0
+
+def spyceFileCacheGenerate(key):
+ "Generate new Spyce wrapper (recompiles)."
+ try:
+ filename, sig = key
+ except:
+ filename, sig = key, ''
+ # ensure file exists and we have permissions
+ if not os.path.exists(filename):
+ raise spyceException.spyceNotFound(filename)
+ if not os.access(filename, os.R_OK):
+ raise spyceException.spyceForbidden(filename)
+ # generate
+ mtime = os.path.getmtime(filename)
+ f = None
+ try:
+ f = open(filename)
+ code = f.read()
+ finally:
+ if f: f.close()
+ s = spyceCode(code, filename=filename, sig=sig)
+ return mtime, s
+
+def spyceStringCacheValid(code, validity):
+ return 1
+
+def spyceStringCacheGenerate(key):
+ try:
+ code, sig = key
+ except:
+ code, sig = key, ''
+ s = spyceCode(code, sig=sig)
+ return None, s
+
+def spyceCacheValid((type, key), validity):
+ return {
+ 'string': spyceStringCacheValid,
+ 'file': spyceFileCacheValid,
+ }[type](key, validity)
+
+def spyceCacheGenerate((type, key)):
+ return {
+ 'string': spyceStringCacheGenerate,
+ 'file': spyceFileCacheGenerate,
+ }[type](key)
+
+
+##################################################
+# Spyce common entry points
+#
+
+def spyceFileHandler(request, response, filename, sig='', args=None, kwargs=None, config_file=None):
+ return _spyceCommonHandler(request, response, ('file', (filename, sig)), args, kwargs, config_file)
+
+def spyceStringHandler(request, response, code, sig='', args=None, kwargs=None, config_file=None):
+ return _spyceCommonHandler(request, response, ('string', (code, sig)), args, kwargs, config_file)
+
+def _spyceCommonHandler(request, response, spyceInfo, args=None, kwargs=None, config_file=None):
+ return getServer(config_file).commonHandler(request, response, spyceInfo, args, kwargs)
+
+if __name__ == '__main__':
+ execfile('run_spyceCmd.py')
+
diff --git a/system/python/spyce/spyce.spec.in b/system/python/spyce/spyce.spec.in
new file mode 100644
index 0000000000..b32a535f41
--- /dev/null
+++ b/system/python/spyce/spyce.spec.in
@@ -0,0 +1,74 @@
+%define name spyce
+%define version __VERSION__
+%define release __RELEASE__
+
+%define httpd_conf /etc/httpd/conf/httpd.conf
+%define httpd_conf_beginline ### BEGIN SPYCE CONFIG MARKER
+%define httpd_conf_endline ### END SPYCE CONFIG MARKER
+%define html_dir /var/www/html
+
+Summary: SPYCE - Python Server Pages, Python-based HTML scripting engine
+Name: %{name}
+Version: %{version}
+Release: %{release}
+Group: System/Servers
+Packager: Rimon Barr <rimon AT acm DOT org>
+URL: http://spyce.sourceforge.net
+License: Refer to LICENCE.TXT
+BuildArchitectures: noarch
+BuildRoot: %{_builddir}/%{name}-root
+#BuildRequires: python >= 2.2
+Requires: python >= 2.2
+Requires: httpd >= 2.0
+Source0: %{name}-%{version}-%{release}.tgz
+
+%description
+SPYCE is a server-side engine that supports simple and efficient Python-based
+dynamic HTML generation. Those who like Python and are familiar with JSP, or
+PHP, or ASP, should have a look at this engine. It allows one to generate
+dynamic HTML content just as easily, using Python for the dynamic parts. Its
+modular design makes it very flexible and extensible. It can also be used as a
+command-line utility for HTML pre-processing.
+
+%prep
+%setup -q
+
+%build
+make all
+
+%install
+rm -rf ${RPM_BUILD_ROOT}
+make DESTDIR=${RPM_BUILD_ROOT} install
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+/usr/share/spyce
+
+%post
+ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce
+ln -sf /usr/share/spyce/docs /usr/share/doc/spyce
+echo -n "Adding Spyce config to httpd.conf..."
+cp %httpd_conf %httpd_conf.spyce-install.bak
+sed -e "/%httpd_conf_beginline/,/%httpd_conf_endline/d" \
+ < %httpd_conf.spyce-install.bak > %httpd_conf
+echo "%httpd_conf_beginline" >> %httpd_conf
+cat /usr/share/spyce/spyceApache.conf | sed -e "s+XXX+/usr/share/spyce+g" >> %httpd_conf
+echo "%httpd_conf_endline" >> %httpd_conf
+echo " done."
+/usr/sbin/apachectl graceful
+
+%postun
+if [ $1 == 0 ]; then
+ rm -f /usr/bin/spyce
+ rm -f /usr/share/doc/spyce
+ rm -f %html_dir/spyce
+ echo -n "Removing Spyce config from httpd.conf..."
+ cp %httpd_conf %httpd_conf.spyce-uninstall.bak
+ sed -e "/%httpd_conf_beginline/,/%httpd_conf_endline/d" \
+ < %httpd_conf.spyce-uninstall.bak > %httpd_conf
+ echo " done."
+ /usr/sbin/apachectl graceful
+fi
diff --git a/system/python/spyce/spyceApache.conf b/system/python/spyce/spyceApache.conf
new file mode 100644
index 0000000000..63602f9bf2
--- /dev/null
+++ b/system/python/spyce/spyceApache.conf
@@ -0,0 +1,76 @@
+
+# XXX = Spyce program directory
+
+# This section asks your web server to serve the
+# Spyce documentation from http://localhost/spyce/.
+
+Alias /spyce/ "XXX/docs/"
+<Directory "XXX/docs">
+ Options Indexes
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+
+###################
+# Spyce via cgi or fcgi
+
+# This section is the default. It provides a default
+# mechanism to process .spy files. On a vanilla Apache
+# installation this will be done via CGI, which is
+# quite slow. If the FastCGI module is properly
+# installed, should automatically be used instead.
+
+AddHandler spyce-cgi-handler spy
+Action spyce-cgi-handler "/spyce-cgi/run_spyceCGI.py"
+ScriptAlias /spyce-cgi/ "XXX/"
+<Location /spyce-cgi/>
+ <IfModule mod_fastcgi.c>
+ # If mod_fastcgi not installed, we get plain cgi
+ SetHandler fastcgi-script
+ </IfModule>
+</Location>
+# If FastCGI is installed, it will be picked up
+# automatically. On Linux, you can also omit this section
+# and use a dynamic fcgi server instead.
+<IfModule mod_fastcgi.c>
+ FastCgiServer "XXX/run_spyceCGI.py" -port 7654 -initial-env FCGI_PORT=7654
+</IfModule>
+# On Windows ONLY, please uncomment the following line.
+# ScriptInterpreterSource registry
+
+###################
+# Spyce via mod_python
+
+# This section allows Spyce to be invoked via the mod_python,
+# yet another alternative with decent performance. Comment
+# the CGI/FastCGI section above entirely, and uncomment the
+# following lines, if you choose to use this instead.
+# (Note that the doubly commented lines, can remain commented
+# depending on your configuration).
+
+#<IfModule mod_python.c>
+# AddHandler python-program .spy
+# PythonHandler run_spyceModpy::spyceMain
+# PythonPath "sys.path+[r'XXX']"
+# #PythonOption SPYCE_CONFIG "/mydir/spyce.conf"
+# #PythonOptimize On
+#</IfModule>
+
+###################
+# Spyce via proxy (on port 8000)
+
+# This section direct Apache to process Spyce requests via
+# a Spyce proxy server. Comment the CGI/FastCGI section above,
+# and uncomment the following lines.
+# NB: Remember to start the Spyce proxy server...
+# spyce -l -p 8000 /document_root
+# If you would like to run your server on another port,
+# start the proxy on that port (using the -p switch)
+# and change the RewriteRule below accordingly.
+
+#<IfModule mod_rewrite.c>
+# RewriteEngine On
+# RewriteRule ^(.*\.spy) http://localhost:8000$1 [p]
+#</IfModule>
+
diff --git a/system/python/spyce/spyceCGI.py b/system/python/spyce/spyceCGI.py
new file mode 100755
index 0000000000..d7c9a26a6d
--- /dev/null
+++ b/system/python/spyce/spyceCGI.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import os, sys
+import spyceCmd, spyce
+import fcgi
+
+__doc__ = '''(F)CGI-based Spyce entry point.'''
+
+def findScriptFile(path):
+ origpath = path
+ while path and not path=='/':
+ if os.path.isfile(path):
+ return path
+ path = os.path.dirname(path)
+ return origpath
+
+def doSpyce( (stdin, stdout, stderr, environ) ):
+ path = None
+ if len(sys.argv)<=1 or not os.path.isfile(sys.argv[1]):
+ try: path = findScriptFile(environ['PATH_TRANSLATED'])
+ except: pass
+ result = spyceCmd.spyceMain(cgimode=1, cgiscript=path,
+ stdout=stdout, stdin=stdin, stderr=stderr, environ=environ)
+ return result
+
+def main():
+ cgi = fcgi.FCGI()
+ more = cgi.accept()
+ if cgi.socket: os.environ[spyce.SPYCE_ENTRY] = 'fcgi'
+ else: os.environ[spyce.SPYCE_ENTRY] = 'cgi'
+ while more:
+ doSpyce(more)
+ more = cgi.accept()
+
+if __name__=='__main__':
+ if sys.platform == "win32":
+ import os, msvcrt
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+ main()
+
diff --git a/system/python/spyce/spyceCache.py b/system/python/spyce/spyceCache.py
new file mode 100644
index 0000000000..ff5bd4c944
--- /dev/null
+++ b/system/python/spyce/spyceCache.py
@@ -0,0 +1,152 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import md5, binascii, os, string
+try: from cPickle import dumps, loads
+except: from pickle import dumps, loads
+
+__doc__ = '''Caching related functionality.'''
+
+# rimtodo: specify some sort of cache size limit
+
+##################################################
+# Generic cache
+#
+
+class cache:
+ "Generic cache"
+ def __getitem__(self, key):
+ raise 'not implemented'
+ def __setitem__(self, key, value):
+ raise 'not implemented'
+ def __delitem__(self, key):
+ raise 'not implemented'
+ def keys(self):
+ raise 'not implemented'
+ def has_key(self, key):
+ raise 'not implemented'
+
+##################################################
+# Storage caches
+#
+
+class memoryCache(cache):
+ "In-memory cache"
+ def __init__(self, infoStr=None):
+ self.cache = {}
+ self.info = infoStr
+ def __getitem__(self, key):
+ return self.cache[key]
+ def __setitem__(self, key, value):
+ self.cache[key]=value
+ def __delitem__(self, key):
+ del self.cache[key]
+ def keys(self):
+ return self.cache.keys()
+ def has_key(self, key):
+ return self.cache.has_key(key)
+
+class fileCache(cache):
+ "File-based cache"
+ def __init__(self, infoStr):
+ self._cachedir = string.strip(infoStr)
+ def __getitem__(self, key):
+ filename = os.path.join(self._cachedir, self._encodeKey(key))
+ f = None
+ try:
+ try:
+ f = open(filename, 'r')
+ return loads(f.read())
+ finally:
+ if f: f.close()
+ except IOError: pass
+ except EOFError: pass
+ raise KeyError()
+ def __setitem__(self, key, value):
+ try:
+ if self[key]==value: return
+ except KeyError: pass
+ filename = os.path.join(self._cachedir, self._encodeKey(key))
+ f = None
+ try:
+ f = open(filename, 'w')
+ f.write(dumps(value,1))
+ finally:
+ if f: f.close()
+ def __delitem__(self, key):
+ filename = os.path.join(self._cachedir, self._encodeKey(key))
+ if os.path.exists(filename):
+ os.remove(filename)
+ def keys(keys):
+ raise 'not implemented'
+ def has_key(self, key):
+ try:
+ self[key]
+ return 1
+ except KeyError:
+ return 0
+ def _encodeKey(self, key):
+ return 'spyceCache-'+binascii.hexlify(md5.new(str(key)).digest())
+
+
+##################################################
+# Policy caches
+#
+
+#rimtodo:
+
+##################################################
+# Semantic cache
+#
+
+class semanticCache(cache):
+ """Cache that knows how to validate and generate its own data. Note, that the
+ cache stores elements as (validity, data) tuples. The valid is a function
+ invoked as valid(key,validity), returning a boolean; and generate is a
+ function invoked as generate(key) returning (validity, data). The get()
+ method returns only the data."""
+ def __init__(self, cache, valid, generate):
+ self.valid = valid
+ self.generate = generate
+ self.cache = cache
+ def get(self, key):
+ "Get (or generate) a cache element."
+ if self.cache:
+ if not self.cache.has_key(key) or not self.valid(key, self.cache[key][0]):
+ self.cache[key] = self.generate(key)
+ return self.cache[key][1]
+ else:
+ return self.generate(key)[1]
+ def purge(self, key):
+ "Remove a cache element, if it exists."
+ if self.cache.has_key(key):
+ del self.cache[key]
+ # standard dictionary methods
+ def __getitem__(self, key):
+ return self.get(key)
+ def __delitem__(self, key):
+ return self.purge(key)
+ def has_key(self, key):
+ if self.cache:
+ return self.cache.has_key()
+ else:
+ return 0
+ def keys(self):
+ if self.cache:
+ return self.cache.keys()
+ else:
+ return []
+ def values(self):
+ if self.cache:
+ return map(lambda x: x[1], self.cache.values())
+ else:
+ return []
+ def clear(self):
+ if self.cache:
+ self.cache.clear()
+
diff --git a/system/python/spyce/spyceCmd.py b/system/python/spyce/spyceCmd.py
new file mode 100755
index 0000000000..896354bd2e
--- /dev/null
+++ b/system/python/spyce/spyceCmd.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import getopt, sys, traceback, os, string, glob, copy
+import spyce, spyceException, spyceUtil
+
+__doc__ = '''Command-line and CGI-based Spyce entry point.'''
+
+##################################################
+# Output
+#
+
+# output version
+def showVersion(out=sys.stdout):
+ "Emit version information."
+ out.write('spyce v'+spyce.__version__+', by Rimon Barr: ')
+ out.write('Python Server Pages\n')
+
+# output syntax
+def showUsage(out=sys.stdout):
+ "Emit command-line usage information."
+ showVersion(out)
+ out.write('Command-line usage:\n')
+ out.write(' spyce [-c] [-o filename.html] <filename.spy>\n')
+ out.write(' spyce [-w] <filename.spy> <-- CGI\n')
+ out.write(' spyce -O filename(s).spy <-- batch process\n')
+ out.write(' spyce -l [-p port] [-d file ] [<root>] <-- proxy server\n')
+ out.write(' spyce -h | -v\n')
+ out.write(' -h, -?, --help display this help information\n')
+ out.write(' -v, --version display version\n')
+ out.write(' -o, --output send output to given file\n')
+ out.write(' -O send outputs of multiple files to *.html\n')
+ out.write(' -c, --compile compile only; do not execute\n')
+ out.write(' -w, --web cgi mode: emit headers (or use run_spyceCGI.py)\n')
+ out.write(' -q, --query set QUERY_STRING environment variable\n')
+ out.write(' -l, --listen run in HTTP server mode\n')
+ out.write(' -d, --daemon run as a daemon process with given pidfile\n')
+ out.write(' -p, --port listen on given port, default 80\n')
+ out.write(' --conf [file] Spyce configuration file\n')
+ out.write('To configure Apache, please refer to: spyceApache.conf\n')
+ out.write('For more details, refer to the documentation.\n')
+ out.write(' http://spyce.sourceforge.net\n')
+ out.write('Send comments, suggestions and bug reports to <rimon-AT-acm.org>.\n')
+
+##################################################
+# Request / Response handlers
+#
+
+class spyceCmdlineRequest(spyce.spyceRequest):
+ 'CGI/Command-line Spyce request object. (see spyce.spyceRequest)'
+ def __init__(self, input, env, filename):
+ spyce.spyceRequest.__init__(self)
+ self._in = input
+ self._env = copy.copy(env)
+ if not self._env.has_key('SERVER_SOFTWARE'):
+ self._env['SERVER_SOFTWARE'] = 'spyce %s Command-line' % spyce.__version__
+ if not self._env.has_key('REQUEST_URI'):
+ self._env['REQUEST_URI']=filename
+ if not self._env.has_key('REQUEST_METHOD'):
+ self._env['REQUEST_METHOD']='spyce'
+ if not self._env.has_key('QUERY_STRING'):
+ self._env['QUERY_STRING']=''
+ self._headers = {
+ 'Content-Length': self.env('CONTENT_LENGTH'),
+ 'Content-Type': self.env('CONTENT_TYPE'),
+ 'User-Agent': self.env('HTTP_USER_AGENT'),
+ 'Accept': self.env('HTTP_ACCEPT'),
+ 'Accept-Encoding': self.env('HTTP_ACCEPT_ENCODING'),
+ 'Accept-Language': self.env('HTTP_ACCEPT_LANGUAGE'),
+ 'Accept-Charset': self.env('HTTP_ACCEPT_CHARSET'),
+ 'Cookie': self.env('HTTP_COOKIE'),
+ 'Referer': self.env('HTTP_REFERER'),
+ 'Host': self.env('HTTP_HOST'),
+ 'Connection': self.env('HTTP_CONNECTION'),
+ 'Keep-Alive': self.env('HTTP_KEEP_ALIVE'),
+ }
+ def env(self, name=None):
+ return spyceUtil.extractValue(self._env, name)
+ def getHeader(self, type=None):
+ return spyceUtil.extractValue(self._headers, type)
+ def getServerID(self):
+ return os.getpid()
+
+class spyceCmdlineResponse(spyce.spyceResponse):
+ 'CGI/Command-line Spyce response object. (see spyce.spyceResponse)'
+ def __init__(self, out, err, cgimode=0):
+ spyce.spyceResponse.__init__(self)
+ if not cgimode:
+ self.RETURN_OK = 0
+ self.RETURN_CODE[self.RETURN_OK] = 'OK'
+ self.origout = out
+ self.out = spyceUtil.BufferedOutput(out)
+ self.err = err
+ self.cgimode = cgimode
+ self.headers = []
+ self.headersSent = 0
+ self.CT = None
+ self.returncode = self.RETURN_OK
+ # functions (for performance)
+ self.write = self.out.write
+ self.writeErr = self.err.write
+ self.clear = self.out.clear
+ def close(self):
+ self.flush()
+ self.out.close()
+ def sendHeaders(self):
+ if self.cgimode and not self.headersSent:
+ resultText = spyceUtil.extractValue(self.RETURN_CODE, self.returncode)
+ self.origout.write('Status: %3d "%s"\n' % (self.returncode, resultText))
+ if not self.CT:
+ self.setContentType('text/html')
+ self.origout.write('Content-Type: %s\n' % self.CT)
+ for h in self.headers:
+ self.origout.write('%s: %s\n'%h)
+ self.origout.write('\n')
+ self.headersSent = 1
+ def clearHeaders(self):
+ if self.headersSent:
+ raise 'headers already sent'
+ self.headers = []
+ def setContentType(self, content_type):
+ if self.headersSent:
+ raise 'headers already sent'
+ self.CT = content_type
+ def setReturnCode(self, code):
+ if self.headersSent:
+ raise 'headers already sent'
+ self.returncode = code
+ def addHeader(self, type, data, replace=0):
+ if self.headersSent:
+ raise 'headers already sent'
+ if type=='Content-Type':
+ self.setContentType(data)
+ else:
+ if replace:
+ self.headers = filter(lambda (type, _), t2=type: type!=t2, self.headers)
+ self.headers.append((type, data))
+ def flush(self, stopFlag=0):
+ if stopFlag: return
+ self.sendHeaders()
+ self.out.flush()
+ def unbuffer(self):
+ self.sendHeaders()
+ self.out.unbuffer()
+
+##################################################
+# Daemonizing
+#
+
+def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', pidfile=None):
+ '''Forks current process into a daemon. stdin, stdout, and stderr arguments
+ are file names that are opened and used in place of the standard file descriptors
+ in sys.stdin, sys.stdout, and sys.stderr, which default to /dev/null.
+ Note that stderr is unbuffered, so output may interleave with unexpected order
+ if shares destination with stdout.'''
+ def forkToChild():
+ try:
+ if os.fork()>0: sys.exit(0) # exit parent.
+ except OSError, e:
+ sys.stderr.write("fork failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+ # First fork; decouple
+ forkToChild()
+ os.chdir("/")
+ os.umask(0)
+ os.setsid()
+ # Second fork; create pidfile; redirect descriptors
+ forkToChild()
+ pid = str(os.getpid())
+ if pidfile:
+ f = open(pidfile,'w+')
+ f.write("%s\n" % pid)
+ f.close()
+ si = open(stdin, 'r')
+ so = open(stdout, 'a+')
+ se = open(stderr, 'a+', 0)
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+ # I am a daemon!
+
+##################################################
+# Command-line entry point
+#
+
+#for debugging/profiling only
+#sys.stdout = spyceUtil.NoCloseOut(sys.stdout)
+
+def spyceMain(cgimode=0, cgiscript=None,
+ stdout=sys.stdout, stdin=sys.stdin, stderr=sys.stderr, environ=os.environ):
+ "Command-line and CGI entry point."
+ # defaults
+ compileonlyMode = 0
+ outputFilename = None
+ defaultOutputFilename = 0
+ httpmode = 0
+ httpport = None
+ httproot = None
+ daemon = None
+ configFile = None
+ # parse options
+ if cgimode and cgiscript:
+ args = [cgiscript]
+ else:
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'h?vco:Owq:ld:p:',
+ ['help', 'version', 'compile', 'output=', 'web',
+ 'query=', 'listen', 'daemon=', 'port=', 'conf=',])
+ except getopt.error:
+ if cgimode:
+ stdout.write('Content-Type: text/plain\n\n')
+ stdout.write('syntax: unknown switch used\n')
+ stdout.write('Use -h option for help.\n')
+ return -1
+ for o, a in opts:
+ if o in ("-v", "--version"):
+ showVersion(); return
+ if o in ("-h", "--help", "-?"):
+ showUsage(); return
+ if o in ("-c", "--compileonly"):
+ compileonlyMode = 1
+ if o in ("-o", "--output"):
+ outputFilename = a
+ if o in ("-O", ):
+ defaultOutputFilename = 1
+ if o in ("-w", "--web"):
+ cgimode = 1
+ if o in ("-q", "--query"):
+ environ['QUERY_STRING'] = a
+ if o in ("-l", "--listen"):
+ httpmode = 1
+ if o in ("-d", "--daemon"):
+ daemon = a
+ if o in ("-p", "--port"):
+ try: httpport = int(a)
+ except:
+ stdout.write('syntax: port must be integer\n')
+ stdout.write('Use -h option for help.\n')
+ return -1
+ if o in ("--conf", ):
+ configFile = a
+
+ # web server mode
+ if httpmode:
+ if len(args):
+ httproot = args[0]
+ import spyceWWW
+ return spyceWWW.spyceHTTPserver(httpport, httproot, config_file=configFile, daemon=daemon)
+ # some checks
+ if not cgimode and not defaultOutputFilename and len(args)>1:
+ stdout.write('syntax: too many files to process\n')
+ stdout.write('Use -h option for help.\n')
+ return -1
+ # file globbing
+ if defaultOutputFilename:
+ globbed = map(glob.glob, args)
+ args = []
+ for g in globbed:
+ for f in g:
+ args.append(f)
+ if not len(args):
+ if cgimode:
+ stdout.write('Content-Type: text/plain\n\n')
+ stdout.write('syntax: please specify a spyce file to process\n')
+ stdout.write('Use -h option for help.\n')
+ return -1
+ # run spyce
+ result=0
+ try:
+ while len(args):
+ result=0
+ script = args[0]
+ del args[0]
+ if cgimode:
+ dir = os.path.dirname(script)
+ if dir:
+ script = os.path.basename(script)
+ os.chdir(dir)
+ try:
+ output = stdout
+ if defaultOutputFilename:
+ outputFilename = os.path.splitext(script)[0]+'.html'
+ stdout.write('Processing: %s\n'%script)
+ stdout.flush()
+ if outputFilename:
+ output = None
+ output = open(outputFilename, 'w')
+ if compileonlyMode:
+ s = spyce.getServer().spyce_cache['file', script]
+ output.write(s.getCode())
+ output.write('\n')
+ else:
+ request = spyceCmdlineRequest(stdin, environ, script)
+ response = spyceCmdlineResponse(output, stderr, cgimode)
+ result = spyce.spyceFileHandler(request, response, script)
+ response.close()
+ except KeyboardInterrupt: raise
+ except SystemExit: pass
+ except (spyceException.spyceForbidden, spyceException.spyceNotFound), e:
+ if cgimode:
+ stdout.write('Content-Type: text/plain\n\n')
+ stdout.write(str(e)+'\n')
+ except:
+ if cgimode:
+ stdout.write('Content-Type: text/plain\n\n')
+ stdout.write(spyceUtil.exceptionString()+'\n')
+ if output:
+ output.close()
+ except KeyboardInterrupt:
+ stdout.write('Break!\n')
+ return result
+
+ABSPATH = 1
+if ABSPATH:
+ for i in range(len(sys.path)):
+ sys.path[i] = os.path.abspath(sys.path[i])
+ ABSPATH = 0
+
+# Command-line entry point
+if __name__=='__main__':
+ os.environ[spyce.SPYCE_ENTRY] = 'cmd'
+ try:
+ exitcode=0
+ exitcode=spyceMain(cgimode=0)
+ except:
+ exitcode=1
+ traceback.print_exc()
+ sys.exit(exitcode)
+
diff --git a/system/python/spyce/spyceCompile.py b/system/python/spyce/spyceCompile.py
new file mode 100644
index 0000000000..b24319ae7a
--- /dev/null
+++ b/system/python/spyce/spyceCompile.py
@@ -0,0 +1,1111 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+#rimtodo:
+# - fix compaction (it assumed newlines parsed independently)
+# - active tags
+
+#try:
+# exec('import sre as re') # due to stack limitations of sre
+# # exec to be backwards compatible with Python 1.5
+#except:
+# import re
+import re # otherwise apache 2.0 pcre library conflicts
+ # we just can't win! either stack limits (sre), or
+ # library conflicts (pre)! :)
+
+from cStringIO import StringIO
+import sys, string, token, tokenize, os
+import spyceTag, spyceException, spyceUtil
+
+
+__doc__ = '''Compile Spyce files into Python code.'''
+
+##################################################
+# Special method names
+#
+
+SPYCE_CLASS = 'spyceImpl'
+SPYCE_INIT_FUNC = 'spyceStart'
+SPYCE_DESTROY_FUNC = 'spyceFinish'
+SPYCE_PROCESS_FUNC = 'spyceProcess'
+SPYCE_GLOBAL_CODE = '__SPYCE_GLOBAL_CODE_CONSTANT'
+SPYCE_WRAPPER = '_spyceWrapper'
+DEFAULT_CODEPOINT = [SPYCE_PROCESS_FUNC]
+
+##################################################
+# Dos-to-Unix linebreaks
+#
+
+# split a buffer into lines (regardless of terminators)
+def splitLines(buf):
+ lines=[]
+ f=StringIO(buf)
+ l=f.readline()
+ while len(l):
+ while l and l[-1] in ['\r', '\n']:
+ l=l[:-1]
+ lines.append(l)
+ l=f.readline()
+ return lines
+
+# encode document with LF
+def CRLF2LF(s):
+ return string.join(splitLines(s), '\n')+'\n'
+
+# encode document with CRLF
+def LF2CRLF(s):
+ return string.join(splitLines(s), '\r\n')+'\r\n'
+
+
+##################################################
+# Tokens
+#
+
+T_ESC = -2
+T_EOF = -1
+T_TEXT = 0
+T_EVAL = 1
+T_STMT = 2
+T_CHUNK = 3
+T_CHUNKG = 4
+T_DIRECT = 5
+T_LAMBDA = 6
+T_END = 7
+T_CMNT = 8
+T_END_CMNT = 9
+
+TOKENS = (
+ # in the order that they should be tested
+ # (i.e. usually longest first)
+ (T_ESC, r'\\\[\[', r'\\<%', r'\\\]\]', r'\\%>'), # escapes
+ (T_CHUNK, r'\[\[\\', r'<%\\'), # open chunk
+ (T_CHUNKG, r'\[\[\\\\', r'<%\\\\'), # open global chunk
+ (T_EVAL, r'\[\[=', r'<%='), # open eval
+ (T_DIRECT, r'\[\[\.', r'<%\.', r'<%@'), # open directive
+ (T_LAMBDA, r'\[\[spy', r'<%spy'), # open lambda
+ (T_CMNT, r'\[\[--', r'<%--'), # open comment
+ (T_END_CMNT, r'--\]\]', r'--%>'), # close comment
+ (T_STMT, r'\[\[', r'<%'), # open statement
+ (T_END, r'\]\]', r'%>'), # close
+)
+
+def genTokensRE(tokens):
+ regexp = []
+ typelookup = [None,]
+ for group in tokens:
+ type, matchstrings = group[0], group[1:]
+ for s in matchstrings:
+ regexp.append('(%s)' % s)
+ typelookup.append(type)
+ regexp = string.join(regexp, '|')
+ return re.compile(regexp, re.M), typelookup
+
+RE_TOKENS = None
+TOKEN_TYPES = None
+if not RE_TOKENS:
+ RE_TOKENS, TOKEN_TYPES = genTokensRE(TOKENS)
+
+def spyceTokenize(buf):
+ # scan using regexp
+ tokens = []
+ buflen = len(buf)
+ pos = 0
+ brow = bcol = erow = ecol = 0
+ while pos<buflen:
+ m = RE_TOKENS.search(buf, pos)
+ try:
+ mstart, mend = m.start(), m.end()
+ other, token = buf[pos:mstart], buf[mstart:mend]
+ if other:
+ tokens.append((T_TEXT, other, pos, mstart))
+ try:
+ type = TOKEN_TYPES[m.lastindex]
+ except AttributeError, e:
+ # Python 1.5 does not support lastindex
+ lastindex = 1
+ for x in m.groups():
+ if x: break
+ lastindex = lastindex + 1
+ type = TOKEN_TYPES[lastindex]
+ if type==T_ESC:
+ token = token[1:]
+ type = T_TEXT
+ tokens.append((type, token, mstart, mend))
+ pos = mend
+ except AttributeError, e:
+ # handle text before EOF...
+ other = buf[pos:]
+ if other:
+ tokens.append((T_TEXT, other, pos, buflen))
+ pos = buflen
+ # compute row, col
+ brow, bcol = 1, 0
+ tokens2 = []
+ for type, text, begin, end in tokens:
+ lines = string.split(text[:-1], '\n')
+ numlines = len(lines)
+ erow = brow + numlines - 1
+ ecol = bcol
+ if numlines>1: ecol = 0
+ ecol = ecol + len(lines[-1])
+ tokens2.append((type, text, (brow, bcol), (erow, ecol)))
+ if text[-1]=='\n':
+ brow = erow + 1
+ bcol = 0
+ else:
+ brow = erow
+ bcol = ecol + 1
+ return tokens2
+
+
+def spyceTokenize4Parse(buf):
+ # add eof and reverse (so that you can pop() tokens)
+ tokens = spyceTokenize(buf)
+ try:
+ _, _, _, end = tokens[-1]
+ except:
+ end = 0;
+ tokens.append((T_EOF, '<EOF>', end, end))
+ tokens.reverse()
+ return tokens
+
+def processMagic(buf):
+ if buf[:2]=='#!':
+ buf = string.join(string.split(buf, '\n')[1:], '\n')
+ return buf
+
+##################################################
+# Directives / Active Tags / Multi-line quotes
+#
+
+DIRECTIVE_NAME = re.compile('[a-zA-Z][-a-zA-Z0-9_:]*')
+DIRECTIVE_ATTR = re.compile(
+ r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
+ r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:;+*%?!&$\(\)_#=~]*))?')
+def parseDirective(text):
+ "Parse a Spyce directive into name and an attribute list."
+ attrs = {}
+ match = DIRECTIVE_NAME.match(text)
+ if not match: return None, {}
+ name = string.lower(text[:match.end()])
+ text = string.strip(text[match.end()+1:])
+ while text:
+ match = DIRECTIVE_ATTR.match(text)
+ if not match: break
+ attrname, rest, attrvalue = match.group(1, 2, 3)
+ if not rest: attrvalue = None
+ elif attrvalue[:1] == "'" == attrvalue[-1:] or \
+ attrvalue[:1] == '"' == attrvalue[-1:]:
+ attrvalue = attrvalue[1:-1]
+ attrs[string.lower(attrname)] = attrvalue
+ text = text[match.end()+1:]
+ return name, attrs
+
+RE_LIB_TAG = re.compile(r'''< # beginning of tag
+ (?P<end>/?) # ending tag
+ (?P<lib>[a-zA-Z][-.a-zA-Z0-9_]*): # lib name
+ (?P<name>[a-zA-Z][-.a-zA-Z0-9_]*) # tag name
+ (?P<attrs>(?:\s+ # attributes
+ (?:[a-zA-Z_][-.:a-zA-Z0-9_]* # attribute name
+ (?:\s*=\s* # value indicator
+ (?:'[^']*' # LITA-enclosed value
+ |"[^"]*" # LIT-enclosed value
+ |[^'">\s]+ # bare value
+ )
+ )?
+ )
+ )*)
+ \s* # trailing whitespace
+ (?P<single>/?) # single / unpaired tag
+ >''', re.VERBOSE) # end of tag
+def calcEndPos(begin, str):
+ if not str: raise 'empty string'
+ beginrow, begincol = begin
+ eol = 0
+ if str[-1]=='\n':
+ str = str[:-1]+' '
+ eol = 1
+ lines = string.split(str, '\n')
+ endrow = beginrow + len(lines)-1
+ if endrow!=beginrow:
+ begincol = 0
+ endcol = begincol + len(lines[-1]) - 1
+ beginrow, begincol = endrow, endcol + 1
+ if eol:
+ begincol = 0
+ beginrow = beginrow + 1
+ return (endrow, endcol), (beginrow, begincol)
+
+RE_MULTI_LINE_QUOTE_BEGIN = re.compile(r'r?'+"(''')|"+'(""")')
+def removeMultiLineQuotes(s):
+ def findMultiLineQuote(s):
+ quotelist = []
+ def eatToken(type, string, begin, end, _, quotelist=quotelist):
+ if type == token.STRING and RE_MULTI_LINE_QUOTE_BEGIN.match(string):
+ quotelist.append((string, begin,end))
+ tokenize.tokenize(StringIO(s).readline, eatToken)
+ return quotelist
+ def replaceRegionWithLine(s, begin, end, s2):
+ (beginrow, begincol), (endrow, endcol) = begin, end
+ beginrow, endrow = beginrow-1, endrow-1
+ s = string.split(s, '\n')
+ s1, s3 = s[:beginrow], s[endrow+1:]
+ s2 = s[beginrow][:begincol] + s2 + s[endrow][endcol:]
+ return string.join(s1 + [s2] + s3, '\n')
+ match = findMultiLineQuote(s)
+ offsets = {}
+ for _, (obr, _), (oer, _) in match:
+ offsets[obr] = oer - obr
+ while match:
+ s2, begin, end = match[0]
+ s = replaceRegionWithLine(s, begin, end, `eval(s2)`)
+ match = findMultiLineQuote(s)
+ return s, offsets
+
+##################################################
+# Pre-Python AST
+#
+
+# ast node types
+AST_PY = 0
+AST_PYEVAL = 1
+AST_TEXT = 2
+AST_COMPACT = 3
+
+# compacting modes
+COMPACT_OFF = 0
+COMPACT_LINE = 1
+COMPACT_SPACE = 2
+COMPACT_FULL = 3
+
+class ppyAST:
+ "Generate a pre-Python AST"
+ def __init__(self):
+ "Initialise parser data structures, AST, token handlers, ..."
+ # set up ast
+ self._code = {
+ 'elements': {},
+ 'leafs': [],
+ }
+ self._codepoint = self._code
+ self._codepointname = []
+ self._mods = []
+ self._taglibs = {}
+ def getCode(self):
+ return self._code
+ def getModules(self):
+ return self._mods
+ def getTaglibs(self):
+ return self._taglibs
+ # routines to navigate AST
+ def selectCodepoint(self, codepointname):
+ code = self._code
+ for point in codepointname:
+ code = code['elements'][point]
+ self._codepoint = code
+ self._codepointname = codepointname
+ def getCodepoint(self):
+ return self._codepointname
+ def descendCodepoint(self, codepointSuffix):
+ self._codepointname.append(codepointSuffix)
+ self.selectCodepoint(self._codepointname)
+ def ascendCodepoint(self):
+ suffix = self._codepointname.pop()
+ self.selectCodepoint(self._codepointname)
+ return suffix
+ # routines that modify the ast
+ def appendCodepoint(self, codepointSuffix, firstline, ref=None):
+ self._codepoint['elements'][codepointSuffix] = {
+ 'elements': {},
+ 'leafs': [],
+ }
+ self.descendCodepoint(codepointSuffix)
+ self.addCode(string.strip(firstline), ref) # note: firstline is not indented
+ def addCode(self, code, ref=None):
+ self._codepoint['leafs'].append((AST_PY, code, ref))
+ def addGlobalCode(self, code, ref=None):
+ codepoint = self.getCodepoint()
+ self.selectCodepoint([SPYCE_GLOBAL_CODE])
+ self.addCode(code+'\n', ref)
+ self.selectCodepoint(codepoint)
+ pass
+ def addEval(self, eval, ref=None):
+ self._codepoint['leafs'].append((AST_PYEVAL, eval, ref))
+ def addCodeIndented(self, code, ref=None, globalcode=0):
+ code, replacelist = removeMultiLineQuotes(code)
+ # funky hack: put in NULLs to preserve indentation
+ # NULLs don't appear in code, and the BraceConverter will
+ # turn them back into spaces. If we leave them as spaces,
+ # BraceConverter is just going to ignore them and pay attention
+ # only to the braces. (not the best compile-time performance!)
+ code = string.split(code, '\n')
+ code = map(lambda l: (len(l)-len(string.lstrip(l)), l), code)
+ code = map(lambda (indent, l): chr(0)*indent + l, code)
+ code.append('')
+ # split code lines
+ (brow, bcol), (erow, ecol), text, file = ref
+ row = brow
+ for l in code:
+ cbcol = 0
+ cecol = len(l)
+ if row==brow: cbcol = bcol
+ if row==erow: cecol = ecol
+ try: row2 = row + replacelist[row-brow+1]
+ except: row2 = row
+ ref = (row, cbcol), (row2, cecol), l, file
+ if globalcode: self.addGlobalCode(l, ref)
+ else: self.addCode(l, ref)
+ row = row2 + 1
+ def addText(self, text, ref=None):
+ self._codepoint['leafs'].append((AST_TEXT, text, ref))
+ def addCompact(self, compact, ref):
+ self._codepoint['leafs'].append((AST_COMPACT, compact, ref))
+ def addModule(self, modname, modfrom, modas):
+ self._mods.append((modname, modfrom, modas))
+ def addTaglib(self, libname, libfrom=None, libas=None):
+ if not libas: libas=libname
+ self._taglibs[libas] = libname, libfrom
+
+
+##################################################
+# Parse
+#
+
+class spyceParse:
+ def __init__(self, server, buf, filename, sig):
+ try:
+ # initialization
+ self._tagChecker = spyceTag.spyceTagChecker(server)
+ self._load_spylambda = 0
+ self._load_taglib = 0
+ self._curdir, self._curfile = os.getcwd(), '<string>'
+ if filename:
+ self._curdir, self._curfile = os.path.split(filename)
+ if not self._curdir:
+ self._curdir = os.getcwd()
+ # prime ast
+ self._ast = ppyAST()
+ self._ast.selectCodepoint([])
+ self._ast.appendCodepoint(SPYCE_GLOBAL_CODE, '')
+ self._ast.addGlobalCode('''
+try:
+ exec('from __future__ import nested_scopes')
+except: pass
+from spyceException import spyceDone, spyceRedirect, spyceRuntimeException
+ ''')
+ # define spyceProcess
+ self._ast.selectCodepoint([])
+ self._ast.appendCodepoint(SPYCE_PROCESS_FUNC, 'def '+SPYCE_PROCESS_FUNC+'('+sig+')')
+ # spyceProcess pre
+ self._ast.selectCodepoint(DEFAULT_CODEPOINT)
+ self._ast.addCode('try:{', None)
+ self._ast.addCode('pass', None)
+ # spyceProcess body
+ self._tokens = spyceTokenize4Parse(processMagic(buf))
+ self._tokenType = None
+ self.popToken()
+ self.processSpyce()
+ # spyceProcess post
+ self._ast.addCode('} except spyceDone: pass', None)
+ self._ast.addCode('except spyceRedirect: raise', None)
+ self._ast.addCode('except KeyboardInterrupt: raise', None)
+ self._ast.addCode('except:{ raise spyceRuntimeException(%s) }'%SPYCE_WRAPPER, None)
+ # post processing
+ if self._load_taglib: self._ast.addModule('taglib', None, None)
+ if self._load_spylambda: self._ast.addModule('spylambda', None, None)
+ self._tagChecker.finish()
+ except spyceException.spyceSyntaxError, e:
+ raise
+ if e.info:
+ begin, end, text, _ = e.info
+ e.info = begin, end, text, self._curfile
+ raise e
+ def info(self):
+ return self._ast.getCode(), self._ast.getModules()
+ def popToken(self):
+ if self._tokenType!=T_EOF:
+ self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd = self._tokens.pop()
+
+ def processSpyce(self):
+ while self._tokenType!=T_EOF:
+ [
+ self.processText, # T_TEXT
+ self.processEval, # T_EVAL
+ self.processStmt, # T_STMT
+ self.processChunk, # T_CHUNK
+ self.processGlobalChunk, # T_CHUNKG
+ self.processDirective, # T_DIRECT
+ self.processUnexpected, # T_LAMBDA
+ self.processUnexpected, # T_END
+ self.processComment, # T_CMNT
+ self.processUnexpected, # T_END_CMNT
+ ][self._tokenType]()
+ self.popToken()
+ def processComment(self):
+ # collect comment
+ self.popToken()
+ while self._tokenType not in [T_END_CMNT, T_EOF]:
+ self.popToken()
+ if self._tokenType==T_EOF:
+ self.processUnexpected()
+ def processText(self):
+ "Process HTML (possibly with some active tags)"
+ html, begin, end = self._tokenText, self._tokenBegin, self._tokenEnd
+ m = RE_LIB_TAG.search(html)
+ while m:
+ plain = html[:m.start()]
+ if plain:
+ plain_end, tag_begin = calcEndPos(begin, plain)
+ self._ast.addText(plain, (begin, plain_end, '<html string>', self._curfile))
+ else: tag_begin = begin
+ tag = m.group(0)
+ tag_end, begin = calcEndPos(tag_begin, tag)
+ self.processActiveTag(tag,
+ not not m.group('end'), m.group('lib'), m.group('name'),
+ m.group('attrs'), not m.group('single'),
+ tag_begin, tag_end)
+ html = html[m.end():]
+ m = RE_LIB_TAG.search(html)
+ self._ast.addText(html, (begin, end, '<html string>', self._curfile))
+ def processActiveTag(self, tag, tagend, taglib, tagname, tagattrs, tagpair, begin, end):
+ "Process HTML tags"
+ # make sure prefix belongs to loaded taglibrary
+ if not self._ast._taglibs.has_key(taglib):
+ self._ast.addText(tag, (begin, end, '<html string>', self._curfile))
+ return
+ # parse process tag attributes
+ _, tagattrs = parseDirective('x '+tagattrs)
+ # get tag class
+ tagclass = self._tagChecker.getTagClass(self._ast._taglibs[taglib],
+ tagname, (begin, end, tag, self._curfile))
+ # syntax check
+ if not tagend: # start tag
+ self._tagChecker.startTag(self._ast._taglibs[taglib],
+ tagname, tagattrs, tagpair, (begin, end, tag, self._curfile))
+ else: # end tag
+ self._tagChecker.endTag(self._ast._taglibs[taglib],
+ tagname, (begin, end, tag, self._curfile))
+ # add python code for active tag
+ if not tagend or not tagpair: # open or singleton tag
+ self._ast.addCode('try: { taglib.tagPush(%s, %s, %s, %s)' % (
+ repr(taglib), repr(tagname), repr(tagattrs), repr(tagpair)),
+ (begin, end, tag, self._curfile))
+ if tagclass.catches:
+ self._ast.addCode('try: {', (begin, end, tag, self._curfile))
+ if tagclass.conditional:
+ self._ast.addCode('if taglib.tagBegin(): {', (begin, end, tag, self._curfile))
+ else:
+ self._ast.addCode('taglib.tagBegin()', (begin, end, tag, self._curfile))
+ if tagclass.mustend:
+ self._ast.addCode('try: {', (begin, end, tag, self._curfile))
+ if tagclass.loops:
+ self._ast.addCode('while 1: {', (begin, end, tag, self._curfile))
+ if tagend or not tagpair: # close or singleton tag
+ if tagclass.loops:
+ self._ast.addCode('if not taglib.tagBody(): break }', (begin, end, tag, self._curfile))
+ else:
+ self._ast.addCode('taglib.tagBody()', (begin, end, tag, self._curfile))
+ if tagclass.mustend:
+ self._ast.addCode('} finally: taglib.tagEnd()', (begin, end, tag, self._curfile))
+ else:
+ self._ast.addCode('taglib.tagEnd()', (begin, end, tag, self._curfile))
+ if tagclass.conditional:
+ self._ast.addCode('}', (begin, end, tag, self._curfile))
+ if tagclass.catches:
+ self._ast.addCode('} except: taglib.tagCatch()', (begin, end, tag, self._curfile))
+ self._ast.addCode('} finally: taglib.tagPop()', (begin, end, tag, self._curfile))
+ def processEval(self):
+ # collect expression
+ begin = self._tokenBegin
+ self.popToken()
+ expr = ''
+ while self._tokenType not in [T_END, T_EOF]:
+ if self._tokenType==T_TEXT:
+ expr = expr + self._tokenText
+ elif self._tokenType==T_LAMBDA:
+ expr = expr + self.processLambda()
+ else: self.processUnexpected()
+ self.popToken()
+ expr = string.strip(expr)
+ if not expr: self.processUnexpected()
+ # add expression to ast
+ self._ast.addEval(expr, (begin, self._tokenEnd, '='+expr, self._curfile))
+ def processStmt(self):
+ # collect statement
+ self.popToken()
+ beginrow, begincol = self._tokenBegin
+ stmt = ''
+ while self._tokenType not in [T_END, T_EOF]:
+ if self._tokenType==T_TEXT:
+ stmt = stmt + self._tokenText
+ elif self._tokenType==T_LAMBDA:
+ stmt = stmt + self.processLambda()
+ else: self.processUnexpected()
+ endrow, endcol = self._tokenEnd
+ self.popToken()
+ if not string.strip(stmt): self.processUnexpected()
+ # add statement to ast, row-by-row
+ currow = beginrow
+ lines = string.split(stmt, '\n')
+ for l in lines:
+ if currow==beginrow: curcolbegin = begincol
+ else: curcolbegin = 0
+ if currow==endrow: curcolend = endcol
+ else: curcolend = len(l)
+ l = string.strip(l)
+ if l:
+ self._ast.addCode(l, ((currow, curcolbegin), (currow, curcolend), l, self._curfile))
+ currow = currow + 1
+ def processChunk(self, globalChunk=0):
+ # collect chunk
+ self.popToken()
+ begin = self._tokenBegin
+ chunk = ''
+ while self._tokenType not in [T_END, T_EOF]:
+ if self._tokenType==T_TEXT:
+ chunk = chunk + self._tokenText
+ elif self._tokenType==T_LAMBDA:
+ chunk = chunk + self.processLambda()
+ else: self.processUnexpected()
+ end = self._tokenEnd
+ self.popToken()
+ chunk = string.split(chunk, '\n')
+ # eliminate initial blank lines
+ brow, bcol = begin
+ while chunk and not string.strip(chunk[0]):
+ chunk = chunk[1:]
+ brow = brow + 1
+ bcol = 0
+ begin = brow, bcol
+ if not chunk: self.processUnexpected()
+ # outdent chunk based on first line
+ # note: modifies multi-line strings having more spaces than first line outdent
+ # by removing outdent number of spaces at the beginning of each line.
+ # -- difficult to deal with efficiently (without parsing python) so just
+ # don't do this!
+ outdent = len(chunk[0]) - len(string.lstrip(chunk[0]))
+ for i in range(len(chunk)):
+ if string.strip(chunk[i][:outdent]):
+ chunk[i] = ' '*outdent + chunk[i]
+ chunk = map(lambda l, outdent=outdent: l[outdent:], chunk)
+ chunk = string.join(chunk, '\n')
+ # add chunk block at ast
+ if chunk:
+ try:
+ self._ast.addCodeIndented(chunk, (begin, end, chunk, self._curfile), globalChunk)
+ except tokenize.TokenError, e:
+ msg, _ = e
+ raise spyceException.spyceSyntaxError(msg, (begin, end, chunk, self._curfile) )
+ def processGlobalChunk(self):
+ self.processChunk(1)
+ def processDirective(self):
+ # collect directive
+ begin = self._tokenBegin
+ self.popToken()
+ directive = ''
+ while self._tokenType not in [T_END, T_EOF]:
+ if self._tokenType==T_TEXT:
+ directive = directive + self._tokenText
+ else: self.processUnexpected()
+ end = self._tokenEnd
+ self.popToken()
+ directive = string.strip(directive)
+ if not directive: self.processUnexpected()
+ # process directives
+ name, attrs = parseDirective(directive)
+ if name=='compact':
+ compact_mode = COMPACT_FULL
+ if attrs.has_key('mode'):
+ mode = string.lower(attrs['mode'])
+ if mode=='off':
+ compact_mode = COMPACT_OFF
+ elif mode=='line':
+ compact_mode = COMPACT_LINE
+ elif mode=='space':
+ compact_mode = COMPACT_SPACE
+ elif mode=='full':
+ compact_mode = COMPACT_FULL
+ else:
+ raise spyceException.spyceSyntaxError('invalid compacting mode "%s" specified'%mode, (begin, end, directive, self._curfile))
+ self._ast.addCompact(compact_mode, (begin, end, '<spyce compact directive>', self._curfile))
+ elif name in ('module', 'import'):
+ if not attrs.has_key('name') and not attrs.has_key('names'):
+ raise spyceException.spyceSyntaxError('name or names attribute required', (begin, end, directive, self._curfile) )
+ if attrs.has_key('names'):
+ mod_names = filter(None, map(string.strip, string.split(attrs['names'],',')))
+ for mod_name in mod_names:
+ self._ast.addModule(mod_name, None, None)
+ self._ast.addCode('%s.init()'%mod_name, (begin, end, directive, self._curfile))
+ else:
+ mod_name = attrs['name']
+ mod_from = spyceUtil.extractValue(attrs, 'from')
+ mod_as = spyceUtil.extractValue(attrs, 'as')
+ mod_args = spyceUtil.extractValue(attrs, 'args', '')
+ if mod_as: theName=mod_as
+ else: theName=mod_name
+ self._ast.addModule(mod_name, mod_from, mod_as)
+ self._ast.addCode('%s.init(%s)'%(theName,mod_args), (begin, end, directive, self._curfile))
+ elif name in ('taglib',):
+ if not attrs.has_key('name') and not attrs.has_key('names'):
+ raise spyceException.spyceSyntaxError('name or names attribute required', (begin, end, directive, self._curfile) )
+ fullfile = os.path.join(self._curdir, self._curfile)
+ if attrs.has_key('names'):
+ taglib_names = filter(None, map(string.strip, string.split(attrs['names'],',')))
+ for taglib_name in taglib_names:
+ self._tagChecker.loadLib(taglib_name, None, None, fullfile, (begin, end, directive, self._curfile))
+ self._ast.addTaglib(taglib_name)
+ self._load_taglib = 1
+ self._ast.addCode('taglib.load(%s)'%repr(taglib_name), (begin, end, directive, self._curfile))
+ else:
+ taglib_name = attrs['name']
+ taglib_from = spyceUtil.extractValue(attrs, 'from')
+ taglib_as = spyceUtil.extractValue(attrs, 'as')
+ self._tagChecker.loadLib(taglib_name, taglib_from, taglib_as, fullfile, (begin, end, directive, self._curfile))
+ self._ast.addTaglib(taglib_name, taglib_from, taglib_as)
+ self._load_taglib = 1
+ self._ast.addCode('taglib.load(%s, %s, %s)'%(repr(taglib_name), repr(taglib_from), repr(taglib_as)), (begin, end, directive, self._curfile))
+ elif name=='include':
+ if not attrs.has_key('file'):
+ raise spyceException.spyceSyntaxError('file attribute missing', (begin, end, directive, self._curfile) )
+ filename = os.path.join(self._curdir, attrs['file'])
+ f = None
+ try:
+ try:
+ f = open(filename)
+ buf = f.read()
+ finally:
+ if f: f.close()
+ except KeyboardInterrupt: raise
+ except:
+ raise spyceException.spyceSyntaxError('unable to open included file: %s'%filename, (begin, end, directive, self._curfile) )
+ prev = (self._curdir, self._curfile, self._tokens,
+ self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd)
+ self._curdir, self._curfile = os.path.dirname(filename), filename
+ self._tokens = spyceTokenize4Parse(processMagic(buf))
+ self.popToken()
+ self.processSpyce()
+ (self._curdir, self._curfile, self._tokens,
+ self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd) = prev
+ else:
+ raise spyceException.spyceSyntaxError('invalid spyce directive', (begin, end, directive, self._curfile) )
+ def processLambda(self):
+ # collect lambda
+ self.popToken()
+ begin = self._tokenBegin
+ lamb = ''
+ depth = 1
+ while self._tokenType!=T_EOF:
+ if self._tokenType in [T_END,]:
+ depth = depth - 1
+ if not depth: break
+ lamb = lamb + self._tokenText
+ elif self._tokenType in [T_EVAL, T_STMT, T_CHUNK, T_CHUNKG, T_DIRECT, T_LAMBDA]:
+ depth = depth + 1
+ lamb = lamb + self._tokenText
+ elif self._tokenType==T_CMNT:
+ self.processComment()
+ else:
+ lamb = lamb + self._tokenText
+ end = self._tokenEnd
+ self.popToken()
+ # process lambda
+ lamb = string.split(lamb, ':')
+ try:
+ params = lamb[0]
+ memoize = 0
+ if params and params[0]=='!':
+ params = params[1:]
+ memoize = 1
+ lamb = string.join(lamb[1:],':')
+ except:
+ raise spyceException.spyceSyntaxError('invalid spyce lambda', (begin, end, lamb, self._curfile))
+ self._load_spylambda = 1
+ lamb = 'spylambda.define(%s,%s,%d)' % (`string.strip(params)`, `lamb`, memoize)
+ return lamb
+ def processUnexpected(self):
+ raise spyceException.spyceSyntaxError('unexpected token: "%s"'%self._tokenText,
+ (self._tokenBegin, self._tokenEnd, self._tokenText, self._curfile))
+
+##################################################
+# Peep-hole optimizer
+#
+
+class spyceOptimize:
+ def __init__(self, ast):
+ self.compaction(ast)
+ self.sideBySideWrites(ast)
+ #self.splitCodeLines(ast)
+ def splitCodeLines(self, ast):
+ elements, leafs = ast['elements'], ast['leafs']
+ for el in elements.keys():
+ self.splitCodeLines(elements[el])
+ if leafs:
+ i = 0
+ while i<len(leafs):
+ row = 1
+ type, text, ref = leafs[i]
+ if type == AST_PY and ref:
+ (brow, bcol), (erow, ecol), code, file = ref
+ lines = string.split(code, '\n')
+ if code==text and len(lines)>1:
+ del leafs[i]
+ row = brow
+ for l in lines:
+ cbcol = 0
+ cecol = len(l)
+ if row==brow: cbcol = bcol
+ if row==erow: becol = ecol
+ leafs.insert(i+(brow-row), (AST_PY, l, ((row, cbcol), (row, cecol), l, file)))
+ row = row + 1
+ i = i + row
+
+ def sideBySideWrites(self, ast):
+ elements, leafs = ast['elements'], ast['leafs']
+ for el in elements.keys():
+ self.sideBySideWrites(elements[el])
+ if leafs:
+ i = 0
+ while i+1<len(leafs):
+ type1, text1, ref1 = leafs[i]
+ type2, text2, ref2 = leafs[i+1]
+ file1 = None
+ file2 = None
+ if ref1:
+ _, _, _, file1 = ref1
+ if ref2:
+ _, _, _, file2 = ref2
+ if type1==AST_TEXT and type2==AST_TEXT and file1==file2:
+ text = text1 + text2
+ begin, _, orig, _ = ref1
+ _, end, _, _ = ref2
+ leafs[i] = AST_TEXT, text, (begin, end, orig, file1)
+ del leafs[i+1]
+ i = i - 1
+ i = i+1
+ def compaction(self, ast):
+ elements, leafs = ast['elements'], ast['leafs']
+ compact = COMPACT_LINE
+ for el in elements.keys():
+ self.compaction(elements[el])
+ if leafs:
+ i = 0
+ while i<len(leafs):
+ type, text, ref = leafs[i]
+ if type==AST_COMPACT:
+ compact = text
+ elif type==AST_TEXT:
+ # line compaction
+ if compact==COMPACT_LINE or compact==COMPACT_FULL:
+ # remove any trailing whitespace
+ text = string.split(text, '\n')
+ for j in range(len(text)-1):
+ text[j] = string.rstrip(text[j])
+ text = string.join(text, '\n')
+ # gobble the end of the line
+ ((row, _), _, _, file) = ref
+ rowtext = string.split(text, '\n')
+ if rowtext: rowtext = string.strip(rowtext[0])
+ crow = row ; cfile = file
+ j = i
+ while j and not rowtext:
+ j = j - 1
+ type2, text2, ref2 = leafs[j]
+ if ref2: (_, (crow, _), _, cfile) = ref2
+ if crow != row or file != cfile: break
+ if type2 == AST_TEXT:
+ text2 = string.split(text2, '\n')
+ if text2: text2 = text2[-1]
+ rowtext = rowtext + string.strip(text2)
+ elif type2 == AST_PYEVAL:
+ rowtext = 'x'
+ if not rowtext:
+ text = string.split(text, '\n')
+ if text and not string.strip(text[0]):
+ text = text[1:]
+ text = string.join(text, '\n')
+ # gobble beginning of the line
+ (_, (row, _), _, file) = ref
+ rowtext = string.split(text, '\n')
+ if rowtext: rowtext = string.strip(rowtext[-1])
+ crow = row ; cfile = file
+ j = i + 1
+ while j<len(leafs) and not rowtext:
+ type2, text2, ref2 = leafs[j]
+ if ref2: ((crow, _), _, _, cfile) = ref2
+ if crow != row or file != cfile: break
+ if type2 == AST_TEXT:
+ text2 = string.split(text2, '\n')
+ if text2: text2 = text2[0]
+ rowtext = rowtext + string.strip(text2)
+ elif type2 == AST_PYEVAL:
+ rowtext = 'x'
+ j = j + 1
+ if not rowtext:
+ text = string.split(text, '\n')
+ if text: text[-1] = string.strip(text[-1])
+ text = string.join(text, '\n')
+ # space compaction
+ if compact==COMPACT_SPACE or compact==COMPACT_FULL:
+ text = spyceUtil.spaceCompact(text)
+ # update text, if any
+ if text: leafs[i] = type, text, ref
+ else:
+ del leafs[i]
+ i = i -1
+ elif type in [AST_PY, AST_PYEVAL]:
+ pass
+ else:
+ raise 'error: unknown AST node type'
+ i = i + 1
+
+##################################################
+# Output classes
+#
+
+class LineWriter:
+ "Output class that counts lines written."
+ def __init__(self, f, initialLine = 1):
+ self.f = f
+ self.lineno = initialLine
+ def write(self, s):
+ self.f.write(s)
+ self.lineno = self.lineno + len(string.split(s,'\n'))-1
+ def writeln(self, s):
+ self.f.write(s+'\n')
+ def close(self):
+ self.f.close()
+
+class IndentingWriter:
+ "Output class that helps with indentation of code."
+ # Note: this writer is line-oriented.
+ def __init__(self, f, indentSize=2):
+ self._f = f
+ self._indentSize = indentSize
+ self._indent = 0
+ self._indentString = ' '*(self._indent*self._indentSize)
+ self._currentLine = ''
+ def close(self):
+ if self._indent > 0:
+ raise 'unmatched open brace'
+ self._f.close()
+ def indent(self):
+ self._indent = self._indent + 1
+ self._indentString = ' '*(self._indent*self._indentSize)
+ def outdent(self):
+ self._indent = self._indent - 1
+ if self._indent<0:
+ raise 'unmatched close brace'
+ self._indentString = ' '*(self._indent*self._indentSize)
+ def dumpLine(self, s):
+ self._f.write(self._indentString+s+'\n')
+ def write(self, s):
+ self._currentLine = self._currentLine + s
+ lines = string.split(self._currentLine, '\n')
+ for l in lines[:-1]:
+ self.dumpLine(l)
+ self._currentLine=lines[-1]
+ def writeln(self, s=''):
+ self.write(s+'\n')
+ # remaining methods are defined in terms of writeln(), indent(), outdent()
+ def pln(self, s=''):
+ self.writeln(s)
+ def pIln(self, s=''):
+ self.indent(); self.pln(s)
+ def plnI(self, s=''):
+ self.pln(s); self.indent()
+ def pOln(self, s=''):
+ self.outdent(); self.pln(s)
+ def plnO(self, s=''):
+ self.pln(s); self.outdent()
+ def pOlnI(self, s=''):
+ self.outdent(); self.pln(s); self.indent()
+ def pIlnO(self, s=''):
+ self.indent(); self.pln(s); self.outdent()
+
+##################################################
+# Print out Braced Python
+#
+
+class emitBracedPython:
+ def __init__(self, out, ast):
+ out = LineWriter(out)
+ self._spyceRefs = {}
+ # text compaction
+ self.compact = COMPACT_LINE
+ self._gobblelineNumber = 1
+ self._gobblelineText = ''
+ # do the deed!
+ self.emitSpyceRec(out, self._spyceRefs, None, ast['elements'], ast['leafs'][1:])
+ def getSpyceRefs(self):
+ return self._spyceRefs
+ def emitSpyceRec(self, out, spyceRefs, header, elements, leafs):
+ if header:
+ out.write(header+':{\n')
+ def processLevel(el, out=out, spyceRefs=spyceRefs, self=self):
+ self.emitSpyceRec(out, spyceRefs, el['leafs'][0][1], el['elements'], el['leafs'][1:])
+ try:
+ processLevel(elements[SPYCE_GLOBAL_CODE])
+ del elements[SPYCE_GLOBAL_CODE]
+ except KeyError: pass
+ for el in elements.keys():
+ processLevel(elements[el])
+ if leafs:
+ for type, text, ref in leafs:
+ line1 = out.lineno
+ if type==AST_TEXT:
+ out.write('response.writeStatic(%s)\n' % `text`)
+ elif type==AST_PY:
+ out.write(text+'\n')
+ elif type==AST_PYEVAL:
+ out.write('response.writeExpr(%s)\n' % text)
+ elif type==AST_COMPACT:
+ self.compact = text
+ else:
+ raise 'error: unknown AST node type'
+ line2 = out.lineno
+ if ref:
+ for l in range(line1, line2):
+ spyceRefs[l] = ref
+ if not leafs and not elements:
+ out.write('pass\n')
+ if header:
+ out.write('}\n')
+
+##################################################
+# Print out regular Python
+#
+
+class BraceConverter:
+ "Convert Python with braces into indented (normal) Python code."
+ def __init__(self, out):
+ self.out = IndentingWriter(out)
+ self.prevname = 0
+ self.prevstring = 0
+ self.dictlevel = 0
+ def emitToken(self, type, string):
+ if type==token.NAME:
+ if self.prevname: self.out.write(' ')
+ if self.prevstring: self.out.write(' ')
+ self.out.write(string)
+ elif type==token.STRING:
+ if self.prevname: self.out.write(' ')
+ string = `eval(string)` # get rid of multi-line strings
+ self.out.write(string)
+ elif type==token.NUMBER:
+ if self.prevname: self.out.write(' ')
+ self.out.write(string)
+ elif type==token.OP:
+ if string=='{':
+ if self.prevcolon and not self.dictlevel:
+ self.out.plnI()
+ else:
+ self.dictlevel = self.dictlevel + 1
+ self.out.write(string)
+ elif string=='}':
+ if not self.dictlevel:
+ self.out.plnO()
+ else:
+ self.dictlevel = self.dictlevel - 1
+ self.out.write(string)
+ else:
+ self.out.write(string)
+ elif type==token.ERRORTOKEN and string==chr(0):
+ self.out.write(' ')
+ else:
+ #print type, token.tok_name[type], `string`
+ self.out.write(string)
+ self.prevname = type==token.NAME
+ self.prevstring = type==token.STRING
+ self.prevcolon = type==token.OP and string==':'
+
+def emitPython(out, bracedPythonCode, spyceRefs):
+ out = LineWriter(out)
+ spyceRefs2 = {}
+ braceConv = BraceConverter(out)
+ def eatToken(type, string, begin, end, _, out=out, braceConv=braceConv, spyceRefs=spyceRefs, spyceRefs2=spyceRefs2):
+ try:
+ beginrow, _ = begin
+ line1 = out.lineno
+ braceConv.emitToken(type, string)
+ line2 = out.lineno
+ if spyceRefs.has_key(beginrow):
+ for l in range(line1, line2):
+ spyceRefs2[l] = spyceRefs[beginrow]
+ except:
+ raise spyceException.spyceSyntaxError(sys.exc_info()[0])
+ try:
+ tokenize.tokenize(StringIO(bracedPythonCode).readline, eatToken)
+ except tokenize.TokenError, e:
+ msg, (row, col) = e
+ raise spyceException.spyceSyntaxError(msg)
+ return spyceRefs2
+
+def calcRowCol(str, pos):
+ lines = string.split(str, '\n')
+ row = 1
+ while pos > len(lines[0]):
+ pos = pos - len(lines[0]) - 1
+ del lines[0]
+ row = row + 1
+ return row, pos
+
+RE_BRACES = re.compile('{|}')
+def checkBalancedParens(str, refs):
+ m = RE_BRACES.search(str)
+ stack = []
+ try:
+ while m:
+ if m.group(0)=='{': stack.append(m)
+ else: stack.pop()
+ m = RE_BRACES.search(str, m.end())
+ except IndexError:
+ row, _ = calcRowCol(str, m.start())
+ try: info = refs[row]
+ except KeyError: info =None
+ raise spyceException.spyceSyntaxError("unbalanced open brace '{'", info)
+ if stack:
+ m = stack[-1]
+ row, _ = calcRowCol(str, m.start())
+ try: info = refs[row]
+ except KeyError: info =None
+ raise spyceException.spyceSyntaxError("unbalanced close brace '}'", info)
+
+##############################################
+# Compile spyce files
+#
+
+def spyceCompile(buf, filename, sig, server):
+ # parse
+ ast, libs = spyceParse(server, CRLF2LF(buf), filename, sig).info()
+ # optimize the ast
+ spyceOptimize(ast)
+ # generate braced code
+ out = StringIO()
+ refs = emitBracedPython(out, ast).getSpyceRefs()
+ # then, generate regular python code
+ bracedPythonCode = out.getvalue()
+ checkBalancedParens(bracedPythonCode, refs)
+ out = StringIO()
+ refs = emitPython(out, bracedPythonCode, refs)
+ return out.getvalue(), refs, libs
+
+def test():
+ import spyce
+ f = open(sys.argv[1])
+ spycecode = f.read()
+ f.close()
+ tokens = spyceTokenize(processMagic(CRLF2LF(spycecode)))
+ for type, text, begin, end in tokens:
+ print '%s (%s, %s): %s' % (type, begin, end, `text`)
+ pythoncode, refs, libs = spyceCompile(spycecode, sys.argv[1], '', spyce.getServer())
+ print pythoncode
+
+if __name__ == '__main__':
+ test()
+
diff --git a/system/python/spyce/spyceConfig.py b/system/python/spyce/spyceConfig.py
new file mode 100644
index 0000000000..85c0c99359
--- /dev/null
+++ b/system/python/spyce/spyceConfig.py
@@ -0,0 +1,370 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import sys, os, string, ConfigParser
+import spyceException
+
+##################################################
+# Constants
+#
+
+SPYCE_CONCURRENCY_SINGLE = 0
+SPYCE_CONCURRENCY_FORK = 1
+SPYCE_CONCURRENCY_THREAD = 2
+
+SPYCE_CONFIG_LOCATIONS = [
+ '/etc',
+]
+SPYCE_CONFIG_FILENAME = 'spyce.conf'
+SPYCE_HOME = None
+
+##################################################
+# determine HOME (installation) directory
+#
+
+if SPYCE_HOME == None:
+ try:
+ SPYCE_HOME = os.path.abspath(os.path.dirname(sys.modules['spyceConfig'].__file__))
+ except:
+ for p in sys.path:
+ path = os.path.join(p, 'spyceConfig.py')
+ if os.path.exists(path):
+ SPYCE_HOME = os.path.abspath(p)
+ break
+ if not SPYCE_HOME:
+ raise 'unable to determine Spyce home directory'
+
+##################################################
+# Server configuration object
+#
+
+class spyceConfig:
+ def __init__(self,
+ file=None,
+ overide_path=None,
+ overide_import=None,
+ overide_error=None,
+ overide_pageerror=None,
+ overide_globals=None,
+ overide_debug=None,
+ default_concurrency=None, # no concurrency
+ overide_concurrency=None,
+ overide_cache=None,
+ default_www_root='.', # serve from current directory
+ overide_www_root=None,
+ default_www_port=80, # default HTTP
+ overide_www_port=None,
+ default_www_handler={
+ 'spy': 'spyce',
+ None: 'dump',
+ },
+ default_www_mime = [os.path.join(SPYCE_HOME,'spyce.mime')],
+ ):
+ self.file = file
+ self._procesed = 0
+ # config parameters
+ self.spyce_config = None # spyce configuration dictionary
+ self.spyce_path = None # spyce module search path
+ self.spyce_import = None # python modules loaded at startup
+ self.spyce_error = None # spyce engine-level error handler
+ self.spyce_pageerror = None # spyce page-level error handler
+ self.spyce_globals = {} # spyce engine globals dictionary
+ self.spyce_debug = None # spyce engine debug flag
+ self.spyce_concurrency = None # concurrency model
+ self.spyce_www_root = None # root directory for spyce web server
+ self.spyce_www_port = None # port used by spyce web server
+ self.spyce_www_mime = None # files with Apache-like mime type listings
+ self.spyce_www_handler = None # assign handlers by extension
+ # store overides and defaults
+ self.overide_path = overide_path
+ self.overide_import = overide_import
+ self.overide_error = overide_error
+ self.overide_pageerror = overide_pageerror
+ self.overide_globals = overide_globals
+ self.overide_debug = overide_debug
+ self.overide_concurrency = overide_concurrency
+ self.default_concurrency = default_concurrency
+ self.overide_cache = overide_cache
+ self.default_www_root = default_www_root
+ self.overide_www_root = overide_www_root
+ self.default_www_port = default_www_port
+ self.overide_www_port = overide_www_port
+ self.default_www_handler = default_www_handler
+ self.default_www_mime = default_www_mime
+ def process(self):
+ # process (order matters here!)
+ self.processConfigFile()
+ self.processSpycePath()
+ self.processSpyceDebug()
+ self.processSpyceGlobals()
+ self.processSpyceImport()
+ self.processSpyceError()
+ self.processSpyceConcurrency()
+ self.processSpyceCache()
+ self.processSpyceWWW()
+ # accessors
+ def getSpyceHome(self):
+ return SPYCE_HOME
+ def getSpycePath(self):
+ return self.spyce_path
+ def getSpyceImport(self):
+ return self.spyce_import
+ def getSpyceError(self):
+ return self.spyce_error
+ def getSpycePageError(self):
+ return self.spyce_pageerror
+ def getSpyceGlobals(self):
+ return self.spyce_globals
+ def getSpyceDebug(self):
+ return self.spyce_debug
+ def getSpyceConcurrency(self):
+ return self.spyce_concurrency
+ def getSpyceCache(self):
+ return self.spyce_cache
+ def getSpyceWWWRoot(self):
+ return self.spyce_www_root
+ def getSpyceWWWPort(self):
+ return self.spyce_www_port
+ def getSpyceWWWHandler(self):
+ return self.spyce_www_handler
+ def getSpyceWWWMime(self):
+ return self.spyce_www_mime
+ def __repr__(self):
+ return \
+'''path: %(path)s
+import: %(import)s
+error: %(error)s
+globals: %(globals)s
+debug: %(debug)s
+concurrency: %(concurrency)s
+cache: %(cache)s
+www root: %(wwwroot)s
+www port: %(wwwport)s
+www mime: %(wwwmime)s
+www handler: %(wwwhandler)s
+''' % {
+ 'path': self.spyce_path,
+ 'import': self.spyce_import,
+ 'error': self.spyce_error,
+ 'pageerror': self.spyce_pageerror,
+ 'globals': self.spyce_globals,
+ 'debug': self.spyce_debug,
+ 'concurrency': self.spyce_concurrency,
+ 'cache': self.spyce_cache,
+ 'wwwroot': self.spyce_www_root,
+ 'wwwport': self.spyce_www_port,
+ 'wwwmime': self.spyce_www_mime,
+ 'wwwhandler': self.spyce_www_handler,
+}
+
+ # configuration processing
+ def processConfigFile(self):
+ # config file
+ self.spyce_config = {}
+ if not self.file:
+ self.file = self.findConfigFile()
+ if self.file:
+ if not os.path.exists(self.file):
+ raise spyceException.spyceNotFound(self.file)
+ if not os.access(self.file, os.R_OK):
+ raise spyceException.spyceForbidden(self.file)
+ self.spyce_config = self.parseConfigFile()
+ def processSpycePath(self, mod_path=None):
+ def processSpycePathString(str, self=self):
+ dpath = filter(None, map(string.strip, string.split(str, os.pathsep)))
+ for i in range(len(dpath)):
+ dpath[i] = os.path.abspath(dpath[i])
+ self.spyce_path = self.spyce_path + dpath
+ sys.path = sys.path + dpath
+ def filterPath(path):
+ path2 = []
+ for p in path:
+ if p not in path2:
+ path2.append(p)
+ return path2
+ self.spyce_path = [
+ os.path.join(SPYCE_HOME, 'modules'),
+ os.path.join(SPYCE_HOME, 'tags'),
+ ]
+ if mod_path:
+ processSpycePathString(mod_path)
+ if self.spyce_config.has_key('path') and not self.overide_path:
+ processSpycePathString(self.spyce_config['path'])
+ if self.overide_path:
+ processSpycePathString(overide_path)
+ else:
+ if (self.spyce_config and not self.spyce_config.has_key('path')) and os.environ.has_key('SPYCE_PATH'):
+ processSpycePathString(os.environ['SPYCE_PATH'])
+ self.spyce_path = filterPath(self.spyce_path)
+ sys.path = filterPath(sys.path)
+ def processSpyceImport(self):
+ self.spyce_import = []
+ if self.spyce_config.has_key('import'):
+ new_import = filter(None, map(string.strip, string.split(self.spyce_config['import'], ',')))
+ self.spyce_import = self.spyce_import + new_import
+ if self.overide_import:
+ new_import = filter(None, map(string.strip, string.split(self.overide_import, ',')))
+ self.spyce_import = self.spyce_import + new_import
+ for i in self.spyce_import:
+ exec('import %s'%i)
+ def processSpyceError(self):
+ # server-level
+ self.spyce_error = 'error:serverHandler'
+ if self.spyce_config.has_key('error'):
+ self.spyce_error = self.spyce_config['error']
+ if self.overide_error:
+ self.spyce_error = self.overide_error
+ self.spyce_error = string.split(self.spyce_error, ':')
+ if len(self.spyce_error)==1:
+ raise 'invalid error handler specification (file:function)'
+ # page-level
+ self.spyce_pageerror = 'string:error:defaultErrorTemplate'
+ if self.spyce_config.has_key('pageerror'):
+ self.spyce_pageerror = self.spyce_config['pageerror']
+ if self.overide_pageerror:
+ self.spyce_pageerror = self.overide_pageerror
+ self.spyce_pageerror = string.split(self.spyce_pageerror, ':')
+ if (len(self.spyce_pageerror)<2 or self.spyce_pageerror[0] not in ('string', 'file')):
+ raise 'invalid pageerror handler specification ("string":module:variable, or ("file":file)'
+ def processSpyceGlobals(self):
+ self.spyce_globals.clear ()
+ if self.spyce_config.has_key('globals'):
+ for k in self.spyce_config['globals'].keys():
+ self.spyce_globals[k] = self.spyce_config['globals'][k]
+ if self.overide_globals:
+ for k in self.overide_globals.keys():
+ self.spyce_globals[k] = self.overide_globals[k]
+ for k in self.spyce_globals.keys():
+ self.spyce_globals[k] = eval(self.spyce_globals[k])
+ def processSpyceDebug(self):
+ self.spyce_debug = 0
+ if self.spyce_config.has_key('debug'):
+ self.spyce_debug = string.strip(string.lower(self.spyce_config['debug'])) not in ('0', 'false', 'no')
+ if self.overide_debug:
+ self.spyce_debug = 1
+ def processSpyceConcurrency(self):
+ self.spyce_concurrency = SPYCE_CONCURRENCY_SINGLE
+ if self.default_concurrency!=None:
+ self.spyce_concurrency = self.default_concurrency
+ if self.spyce_config.has_key('concurrency'):
+ self.spyce_concurrency = string.lower(self.spyce_config['concurrency'])
+ if self.spyce_concurrency in ('thread', 'threading'):
+ self.spyce_concurrency = SPYCE_CONCURRENCY_THREAD
+ elif self.spyce_concurrency in ('fork', 'forking'):
+ self.spyce_concurrency = SPYCE_CONCURRENCY_FORK
+ else:
+ self.spyce_concurrency = SPYCE_CONCURRENCY_SINGLE
+ if self.overide_concurrency!=None:
+ self.spyce_concurrency = self.overide_concurrency
+ def processSpyceCache(self):
+ cache = 'memory'
+ if self.spyce_config.has_key('cache'):
+ cache = self.spyce_config['cache']
+ cache = string.split(cache, ':')
+ self.spyce_cache = string.strip(string.lower(cache[0])), string.join(cache[1:], ':')
+ def processSpyceWWW(self):
+ # root
+ self.spyce_www_root = self.default_www_root
+ if self.spyce_config.has_key('www_root'):
+ self.spyce_www_root = self.spyce_config['www_root']
+ if self.overide_www_root!=None:
+ self.spyce_www_root = self.overide_www_root
+ # port
+ self.spyce_www_port = self.default_www_port
+ if self.spyce_config.has_key('www_port'):
+ self.spyce_www_port = int(self.spyce_config['www_port'])
+ if self.overide_www_port!=None:
+ self.spyce_www_port = int(self.overide_www_port)
+ # mime
+ self.spyce_www_mime = self.default_www_mime
+ if self.spyce_config.has_key('www_mime'):
+ mime = self.spyce_config['www_mime']
+ mime = map(string.strip, string.split(mime, ','))
+ self.spyce_www_mime = self.spyce_www_mime + mime
+ # handler
+ self.spyce_www_handler = self.default_www_handler
+ if self.spyce_config.has_key('www_handler'):
+ handler = self.spyce_config['www_handler']
+ for k in handler.keys():
+ self.spyce_www_handler[k] = handler[k]
+
+ # helpers
+ def findConfigFile(self):
+ locations = [SPYCE_HOME] + SPYCE_CONFIG_LOCATIONS
+ for l in locations:
+ p = os.path.join(l, SPYCE_CONFIG_FILENAME)
+ if os.path.exists(p):
+ return p
+ def parseConfigFile(self):
+ # initial defaults
+ path = None
+ load = None
+ error = None
+ pageerror = None
+ globals = None
+ debug = None
+ concurrency = None
+ cache = None
+ www_root = None
+ www_port = None
+ www_mime = None
+ www_handler = {}
+ cfg = ConfigParser.ConfigParser()
+ # parse
+ cfg.read(self.file)
+ if cfg.has_section('spyce'):
+ if 'path' in cfg.options('spyce'):
+ path = cfg.get('spyce', 'path')
+ if 'import' in cfg.options('spyce'):
+ load = cfg.get('spyce', 'import')
+ if 'error' in cfg.options('spyce'):
+ error = cfg.get('spyce', 'error')
+ if 'pageerror' in cfg.options('spyce'):
+ pageerror = cfg.get('spyce', 'pageerror')
+ if 'debug' in cfg.options('spyce'):
+ debug = cfg.get('spyce', 'debug')
+ if 'concurrency' in cfg.options('spyce'):
+ concurrency = cfg.get('spyce', 'concurrency')
+ if 'cache' in cfg.options('spyce'):
+ cache = cfg.get('spyce', 'cache')
+ if cfg.has_section('globals'):
+ globals = {}
+ for o in cfg.options('globals'):
+ if o=='__name__': continue
+ globals[o] = cfg.get('globals', o)
+ if cfg.has_section('www'):
+ for o in cfg.options('www'):
+ if o=='__name__': continue
+ if o=='root':
+ www_root = cfg.get('www', o)
+ continue
+ if o=='port':
+ www_port = cfg.get('www', o)
+ continue
+ if o=='mime':
+ www_mime = cfg.get('www', o)
+ continue
+ if o[:len('ext_')]=='ext_':
+ ext = o[len('ext_'):]
+ if not ext: ext = None
+ www_handler[ext] = cfg.get('www', o)
+ # results
+ config = {}
+ if path!=None: config['path'] = path
+ if load!=None: config['import'] = load
+ if error!=None: config['error'] = error
+ if pageerror!=None: config['pageerror'] = pageerror
+ if globals!=None: config['globals'] = globals
+ if debug!=None: config['debug'] = debug
+ if concurrency!=None: config['concurrency'] = concurrency
+ if cache!=None: config['cache'] = cache
+ if www_root!=None: config['www_root'] = www_root
+ if www_port!=None: config['www_port'] = www_port
+ if www_mime!=None: config['www_mime'] = www_mime
+ if www_handler!={}: config['www_handler'] = www_handler
+ return config
diff --git a/system/python/spyce/spyceException.py b/system/python/spyce/spyceException.py
new file mode 100644
index 0000000000..fdb62ff753
--- /dev/null
+++ b/system/python/spyce/spyceException.py
@@ -0,0 +1,116 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Various Spyce-related exceptions'''
+
+import sys, string
+import spyceCompile, spyceUtil
+
+##################################################
+# Syntax errors
+#
+
+class pythonSyntaxError:
+ "Generate string out of current pythonSyntaxError exception"
+ def __repr__(self):
+ return self.str
+ def __init__(self, spycewrap):
+ self.str = ''
+ type, error, _ = sys.exc_info()
+ if type is type(SyntaxError):
+ raise 'instantiate pythonSyntaxError only when SyntaxError raised: %s' % `type`
+ if spycewrap.getCodeRefs().has_key(error.lineno):
+ begin, end, text, filename = spycewrap.getCodeRefs()[error.lineno]
+ if begin[0]==end[0]:
+ linestr = str(begin[0])
+ else:
+ linestr = '%d-%d' % (begin[0], end[0])
+ self.str = 'Python syntax error at %s:%s - %s\n %s\n' % (filename, linestr, error.msg, text)
+ else:
+ self.str = spyceUtil.exceptionString()
+
+class spyceSyntaxError:
+ "Generate string out of current spyceSyntaxError exception"
+ def __init__(self, msg, info=None):
+ self.msg = msg
+ self.info = info
+ def __repr__(self):
+ s = 'Spyce syntax error'
+ if self.info:
+ (begin, _), (end, _), text, filename = self.info
+ if begin==end:
+ linestr = str(begin)
+ else:
+ linestr = '%d-%d' % (begin, end)
+ s = s + ' at %s:%s - %s\n %s\n' % (filename, linestr, self.msg, text)
+ else:
+ s = s + ': '+self.msg
+ return s
+
+##################################################
+# Runtime errors
+#
+
+class spyceRuntimeException:
+ "Generate string out of current SpyceException exception."
+ # useful fields: str, type, value, traceback, msg
+ def __repr__(self):
+ return self.str
+ def __init__(self, spycewrap=None):
+ import traceback, string
+ e1, e2, tb = sys.exc_info()
+ tb = traceback.extract_tb(tb)
+ self.str = ''
+ self.type, self.value, self.traceback = e1, e2, tb
+ if e1 == spyceRuntimeException:
+ self.msg = str(e2)
+ else:
+ self.msg = string.join(traceback.format_exception_only(e1, e2))
+ for i in range(len(tb)):
+ filename, lineno, funcname, text = tb[i]
+ if filename == '<string>' and spycewrap and spycewrap.getCodeRefs().has_key(lineno):
+ if funcname == spyceCompile.SPYCE_PROCESS_FUNC:
+ funcname = '(main)'
+ begin, end, text, filename = spycewrap.getCodeRefs()[lineno]
+ if begin[0]==end[0]:
+ lineno = str(begin[0])
+ else:
+ lineno = '%d-%d' % (begin[0], end[0])
+ lineno=str(lineno)
+ tb[i] = filename, lineno, funcname, text
+ for i in range(len(tb)):
+ self.str = self.str + ' %s:%s, in %s: \n %s\n' % tb[i]
+ self.str = self.str + self.msg
+
+class spyceNotFound:
+ "Exception class to signal that Spyce file does not exist."
+ def __init__(self, file):
+ self.file = file
+ def __repr__(self):
+ return 'spyceNotFound exception: could not find "%s"' % self.file
+
+class spyceForbidden:
+ "Exception class to signal that Spyce file has access problems."
+ def __init__(self, file):
+ self.file = file
+ def __repr__(self):
+ return 'spyceForbidden exception: could not read "%s"' % self.file
+
+##################################################
+# Special control-flow exceptions
+#
+
+class spyceRedirect:
+ "Exception class to signal an internal redirect."
+ def __init__(self, filename):
+ self.filename = filename
+
+class spyceDone:
+ "Exception class to immediately jump to the end of the spyceProcess method"
+ pass
+
diff --git a/system/python/spyce/spyceLock.py b/system/python/spyce/spyceLock.py
new file mode 100644
index 0000000000..61230b2187
--- /dev/null
+++ b/system/python/spyce/spyceLock.py
@@ -0,0 +1,120 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import os
+
+__doc__ = 'Spyce locking-related functions'
+
+##################################################
+# Generic lock
+#
+
+class genericLock:
+ def lock(self, block=1):
+ "return true, if lock acquired"
+ raise 'not implemented'
+ def unlock(self):
+ raise 'not implemented'
+ def locked(self):
+ raise 'not implemented'
+
+##################################################
+# Dummy lock
+#
+
+class dummyLock(genericLock):
+ def lock(self, block=1):
+ return 1
+ def unlock(self):
+ pass
+ def locked(self):
+ return 0
+
+##################################################
+# Thread locking
+#
+
+class threadLock(genericLock):
+ def __init__(self):
+ import thread
+ self._thelock = thread.allocate_lock()
+ def lock(self, block=1):
+ if block: return self._thelock.acquire()
+ else: return self._thelock.acquire(0)
+ def unlock(self):
+ return self._thelock.release()
+ def locked(self):
+ return self._thelock.locked()
+
+##################################################
+# File locking
+#
+
+# Adapted from portalocker.py, written by Jonathan Feinberg <jdf@pobox.com>
+# Used in rimap (http://rimap.sourceforge.net) before Spyce
+# Methods:
+# file_lock(file, flags)
+# file_unlock(file)
+# Constants: LOCK_EX, LOCK_SH, LOCK_NB
+# -- RB
+
+try:
+ if os.name=='nt':
+ import win32con, win32file, pywintypes
+ LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
+ LOCK_SH = 0 # the default
+ LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
+ # is there any reason not to reuse the following structure?
+ __overlapped = pywintypes.OVERLAPPED()
+ def file_lock(file, flags):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.LockFileEx(hfile, flags, 0, 0xffff0000L, __overlapped)
+ def file_unlock(file):
+ hfile = win32file._get_osfhandle(file.fileno())
+ win32file.UnlockFileEx(hfile, 0, 0xffff0000L, __overlapped)
+ elif os.name == 'posix':
+ import fcntl
+ LOCK_EX = fcntl.LOCK_EX
+ LOCK_SH = fcntl.LOCK_SH
+ LOCK_NB = fcntl.LOCK_NB
+ def file_lock(file, flags):
+ fcntl.flock(file.fileno(), flags)
+ def file_unlock(file):
+ fcntl.flock(file.fileno(), fcntl.LOCK_UN)
+ else:
+ raise 'locking not supported on this platform'
+except:
+ LOCK_EX = 0
+ LOCK_SH = 0
+ LOCK_NB = 0
+ # bring on the race conditions! :)
+ def file_lock(file, flags): pass
+ def file_unlock(file): pass
+
+class fileLock(genericLock):
+ f=name=None
+ def __init__(self, name):
+ self.name=name+'.lock'
+ self._locked = 0
+ def lock(self, block=1):
+ self.f=open(self.name, 'w')
+ if block: file_lock(self.f, LOCK_EX)
+ else: file_lock(self.f, LOCK_EX or LOCK_NB)
+ self._locked = 1
+ def unlock(self):
+ try:
+ if not self.f: return
+ file_unlock(self.f)
+ self.f.close()
+ os.unlink(self.name)
+ self.f=None
+ self._locked = 0
+ except: pass
+ def locked(self):
+ return self._locked
+
diff --git a/system/python/spyce/spyceModpy.py b/system/python/spyce/spyceModpy.py
new file mode 100644
index 0000000000..8d63dd1e59
--- /dev/null
+++ b/system/python/spyce/spyceModpy.py
@@ -0,0 +1,138 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import string, sys, os
+import spyce, spyceException, spyceUtil
+
+__doc__ = '''Apache mod_python-based Spyce entry point.'''
+
+##################################################
+# Request / response handlers
+#
+
+import _apache # fails when not running under apache
+from mod_python import apache
+
+class NoFlush:
+ "Elide flush calls"
+ def __init__(self, apacheRequest):
+ self.write = apacheRequest.write
+ def flush(self):
+ pass
+
+# make sure that both sets of classes have identical functionality
+class spyceModpyRequest(spyce.spyceRequest):
+ "Apache Spyce request object. (see spyce.spyceRequest)"
+ def __init__(self, apacheRequest):
+ spyce.spyceRequest.__init__(self)
+ self._in = apacheRequest
+ def env(self, name=None):
+ return spyceUtil.extractValue(self._in.subprocess_env, name)
+ def getHeader(self, type=None):
+ if type:
+ if self._in.headers_in.has_key(type):
+ return self._in.headers_in[type]
+ else: return self._in.headers_in
+ def getServerID(self):
+ return os.getpid()
+
+class spyceModpyResponse(spyce.spyceResponse):
+ "Apache Spyce response object. (see spyce.spyceResponse)"
+ def __init__(self, apacheRequest):
+ spyce.spyceResponse.__init__(self)
+ self.origout = apacheRequest
+ self.out = spyceUtil.BufferedOutput(NoFlush(apacheRequest))
+ self.isCTset = 0
+ self.headersSent = 0
+ self.returncode = self.origout.status = self.RETURN_OK
+ # functions (for performance)
+ self.write = self.out.write
+ self.writeErr = sys.stderr.write
+ def close(self):
+ self.flush()
+ #self.out.close()
+ def clear(self):
+ self.out.clear()
+ def sendHeaders(self):
+ if self.headersSent:
+ return
+ if not self.isCTset:
+ self.setContentType("text/html")
+ self.origout.send_http_header()
+ self.headersSent = 1
+ def clearHeaders(self):
+ if self.headersSent:
+ raise 'headers already sent'
+ for header in self.origout.headers_out.keys():
+ del self.origout.headers_out[header]
+ def setContentType(self, content_type):
+ if self.headersSent:
+ raise 'headers already sent'
+ self.origout.content_type = content_type
+ self.isCTset = 1
+ def setReturnCode(self, code):
+ if self.headersSent:
+ raise 'headers already sent'
+ self.returncode = self.origout.status = code
+ def addHeader(self, type, data, replace=0):
+ if self.headersSent:
+ raise 'headers already sent'
+ if replace:
+ self.origout.headers_out[type] = data
+ else:
+ self.origout.headers_out.add(type, data)
+ def flush(self, stopFlag=0):
+ if stopFlag: return
+ self.sendHeaders()
+ self.out.flush()
+ def unbuffer(self):
+ self.flush()
+ self.out.unbuffer()
+
+##################################################
+# Apache config
+#
+
+def getApacheConfig(apachereq, param, default=None):
+ "Return Apache mod_python configuration parameter."
+ try:
+ return apachereq.get_options()[param]
+ except:
+ return default
+
+##################################################
+# Apache entry point
+#
+
+CONFIG_FILE = None
+
+def spyceMain(apacheRequest):
+ "Apache entry point."
+ os.environ[spyce.SPYCE_ENTRY] = 'modpy'
+ apacheRequest.add_common_vars()
+ request = spyceModpyRequest(apacheRequest)
+ response = spyceModpyResponse(apacheRequest)
+ filename = apacheRequest.filename
+ global CONFIG_FILE
+ if CONFIG_FILE==None:
+ CONFIG_FILE = getApacheConfig(apacheRequest, 'SPYCE_CONFIG', None)
+ try:
+ result = spyce.spyceFileHandler(request, response, filename, config_file=CONFIG_FILE )
+ except (spyceException.spyceForbidden, spyceException.spyceNotFound), e:
+ response.clear()
+ response.setContentType('text/plain')
+ response.write(str(e)+'\n')
+ except:
+ response.clear()
+ response.setContentType('text/plain')
+ response.write(spyceUtil.exceptionString()+'\n')
+ try:
+ response.flush()
+ except: pass
+ return apache.OK
+
diff --git a/system/python/spyce/spyceModule.py b/system/python/spyce/spyceModule.py
new file mode 100644
index 0000000000..593996b7cd
--- /dev/null
+++ b/system/python/spyce/spyceModule.py
@@ -0,0 +1,55 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Spyce modules functionality.'''
+
+##################################################
+# Spyce module
+#
+
+class spyceModule:
+ "All Spyce module should subclass this."
+ def __init__(self, wrapper):
+ self._api = wrapper
+ def start(self):
+ pass
+ def finish(self, theError=None):
+ pass
+ def init(self, *args, **kwargs):
+ pass
+ def __repr__(self):
+ return 'no information, '+str(self.__class__)
+
+class spyceModulePlus(spyceModule):
+ def __init__(self, wrapper):
+ spyceModule.__init__(self, wrapper)
+ self.wrapper = self._api # deprecated
+ self.modules = moduleFinder(wrapper)
+ self.globals = wrapper.getGlobals()
+
+class moduleFinder:
+ def __init__(self, wrapper):
+ self._wrapper = wrapper
+ def __getattr__(self, name):
+ return self._wrapper.getModule(name)
+
+##################################################
+# Spyce module API
+#
+
+spyceModuleAPI = [ 'getFilename', 'getCode',
+ 'getCodeRefs', 'getModRefs',
+ 'getServerObject', 'getServerGlobals', 'getServerID',
+ 'getModules', 'getModule', 'setModule', 'getGlobals',
+ 'registerModuleCallback', 'unregisterModuleCallback',
+ 'getRequest', 'getResponse', 'setResponse',
+ 'registerResponseCallback', 'unregisterResponseCallback',
+ 'spyceString', 'spyceFile', 'spyceModule', 'spyceTaglib',
+ 'setStdout', 'getStdout',
+]
+
diff --git a/system/python/spyce/spyceTag.py b/system/python/spyce/spyceTag.py
new file mode 100644
index 0000000000..8cb0ec4395
--- /dev/null
+++ b/system/python/spyce/spyceTag.py
@@ -0,0 +1,260 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Spyce tags functionality.'''
+
+import string
+import spyceException, spyceModule
+
+##################################################
+# Spyce tag library
+#
+
+class spyceTagLibrary:
+ "All Spyce tag libraries should subclass this."
+ def __init__(self, prefix):
+ self._prefix = prefix
+ self._taghash = {}
+ for tag in self.tags:
+ self._taghash[tag.name] = tag
+ def getTag(self, name, attrs, paired, parent=None):
+ return self.getTagClass(name)(self._prefix, attrs, paired, parent)
+ def getTagClass(self, name):
+ return self._taghash[name]
+
+ # functions to override
+ tags = []
+ def start(self):
+ pass
+ def finish(self):
+ pass
+
+##################################################
+# Spyce tag
+#
+
+class spyceTag:
+ "All Spyce tags should subclass this."
+ def __init__(self, prefix, attrs, paired, parent=None):
+ "Initialize a tag; prefix = current library prefix"
+ self._prefix = prefix
+ self._attrs = attrs
+ self._pair = paired
+ self._parent = parent
+ self._out = None
+ self._context = None
+ self._buffered = 0
+ # setup tag environment (context and output stream)
+ def setOut(self, out):
+ "Set output stream"
+ self._out = out
+ def setContext(self, context):
+ "Set tag evaluation context"
+ self._context = context
+ def setBuffered(self, buffered):
+ "Set whether tag is running on a buffer wrt. enclosing scope"
+ self._buffered = buffered
+ # accessors
+ def getPrefix(self):
+ "Return tag prefix"
+ return self._prefix
+ def getAttributes(self):
+ "Get tag attributes."
+ return self._attrs
+ def getPaired(self):
+ "Return whether this is a paired or singleton tag."
+ return self._pair
+ def getParent(self, name=None):
+ "Get parent tag"
+ parent = self._parent
+ if name!=None:
+ while parent!=None:
+ if parent._prefix==self._prefix and parent.name==name: break;
+ parent = parent._parent
+ return parent
+ def getOut(self):
+ "Return output stream"
+ return self._out
+ def getContext(self):
+ return self._context
+ def getBuffered(self):
+ "Get whether tag is running on a buffer wrt. enclosing scope"
+ return self._buffered
+ # functions and fields to override
+ "The name of this tag!"
+ name = None
+ "Whether this tag wants to buffer its body processing"
+ buffer = 0
+ "Whether this tag want to conditionally perform body processing"
+ conditional = 0
+ "Whether this tag wants to possibly loop body processing"
+ loops = 0
+ "Whether this tag wants to handle exceptions"
+ catches = 0
+ "Whether end() must (even on exception) get called if begin() completes"
+ mustend = 0
+ def syntax(self):
+ "Check tag syntax"
+ pass
+ def begin(self, **kwargs):
+ "Process start tag; return true to process body (if conditional==1)"
+ return 1
+ def body(self, contents):
+ "Process tag body; return true to repeat (if loops==1)"
+ if contents:
+ self.getOut().write(contents)
+ return 0
+ def end(self):
+ "Process end tag"
+ pass
+ def catch(self, ex):
+ "Process any exception thrown by tag (if catches==1)"
+ raise
+
+class spyceTagPlus(spyceTag):
+ "An easier spyceTag class to work with..."
+ # tag context helpers
+ def contextSet(self, name, (exists, value)):
+ "Set a variable in the context"
+ prev = self.contextGet(name)
+ if exists: self._context[name] = value
+ else: del self._context[name]
+ return prev
+ def contextGet(self, name):
+ "Get a variable from the context"
+ try: return 1, self._context[name]
+ except KeyError: return 0, None
+ def contextEval(self, expr):
+ "Evaluate an expression within the context"
+ if expr and expr[0]=='=':
+ expr = eval(expr[1:], self._context)
+ return expr
+ def contextEvalAttrs(self, attrs):
+ "Evaluate attribute dictionary within context"
+ attrs2 = {}
+ for name in attrs.keys():
+ attrs2[name] = self.contextEval(attrs[name])
+ return attrs2
+ def contextGetModule(self, name):
+ "Return a Spyce module reference"
+ try: return self._context[name]
+ except KeyError:
+ return self._context['taglib']._api.getModule(name)
+
+ # tag syntax checking helpers
+ def syntaxExist(self, *must):
+ "Ensure that certain attributes exist"
+ for attr in must:
+ if not self._attrs.has_key(attr):
+ raise spyceTagSyntaxException('missing compulsory "%s" attribute' % attr)
+ def syntaxExistOr(self, *mustgroups):
+ "Ensure that one of a group of attributes must exist"
+ errors = []
+ success = 0
+ for must in mustgroups:
+ try:
+ if must==type(''): must = (must,)
+ self.apply(self.syntaxExist, must)
+ success = success + 1
+ except spyceTagSyntaxException, e:
+ errors.append(str(e))
+ if not success:
+ raise spyceTagSyntaxException(string.join(errors, ' OR '))
+ def syntaxExistOrEx(self, *mustgroups):
+ success = apply(self.syntaxExistOr, mustgroups)
+ if success > 1:
+ raise spyceTagSyntaxException('only one set of the following groups of tags are allowed: %s', string.join(map(repr, mustgroups), ', '))
+ def syntaxNonEmpty(self, *names):
+ for name in names:
+ try: value = self._attrs[name]
+ except KeyError: return
+ if not value:
+ raise spyceTagSyntaxException('attribute "%s" should not be empty', name)
+ def syntaxValidSet(self, name, validSet):
+ try: value = self._attrs[name]
+ except KeyError: return
+ if value not in validSet:
+ raise spyceTagSyntaxException('attribute "%s" should be one of: %s'% (name, string.join(validSet, ', ')))
+ def syntaxPairOnly(self):
+ "Ensure that this tag is paired i.e. open/close"
+ if not self._pair:
+ raise spyceTagSyntaxException('singleton tag not allowed')
+ def syntaxSingleOnly(self):
+ "Ensure that this tag is single i.e. <foo/>"
+ if self._pair:
+ raise spyceTagSyntaxException('paired tag not allowed')
+
+
+##################################################
+# Spyce tag syntax checking
+#
+
+class spyceTagChecker:
+ def __init__(self, server):
+ self._server = server
+ self._taglibs = {}
+ self._stack = []
+ def loadLib(self, libname, libfrom, libas, rel_file, info=None):
+ if not libas: libas = libname
+ try:
+ self._taglibs[(libname, libfrom)] = \
+ self._server.loadModule(libname, libfrom, rel_file)(libas)
+ except (SyntaxError, TypeError):
+ raise
+ except:
+ raise spyceException.spyceSyntaxError(
+ 'unable to load module: %s'%libname, info)
+ def getTag(self, (libname,libfrom), name, attrs, pair, info):
+ lib = self._taglibs[(libname, libfrom)]
+ try:
+ return lib.getTag(name, attrs, pair, None)
+ except:
+ raise spyceException.spyceSyntaxError(
+ 'unknown tag "%s:%s"'%(libname, name), info)
+ def getTagClass(self, (libname, libfrom), name, info):
+ lib = self._taglibs[(libname, libfrom)]
+ try:
+ return lib.getTagClass(name)
+ except:
+ raise spyceException.spyceSyntaxError(
+ 'unknown tag "%s:%s"'%(libname, name), info)
+ def startTag(self, (libname,libfrom), name, attrs, pair, info):
+ tag = self.getTag((libname, libfrom), name, attrs, pair, info)
+ try:
+ error = tag.syntax()
+ except spyceTagSyntaxException, e:
+ raise spyceException.spyceSyntaxError(str(e), info)
+ if error:
+ raise spyceException.spyceSyntaxError(error, info)
+ if pair:
+ self._stack.append( (libname, libfrom, name, info) )
+ def endTag(self, (libname,libfrom), name, info):
+ try:
+ libname1, libfrom1, name1, info1 = self._stack.pop()
+ except IndexError:
+ raise spyceException.spyceSyntaxError(
+ 'unmatched close tag', info)
+ if (libname1,libfrom1,name1) != (libname,libfrom,name):
+ raise spyceException.spyceSyntaxError(
+ 'unmatched close tag, expected <%s:%s>' % (libname1,name1), info)
+ def finish(self):
+ if self._stack:
+ libname, libfrom, name, info = self._stack.pop()
+ raise spyceException.spyceSyntaxError(
+ 'unmatched open tag', info)
+
+##################################################
+# Spyce tag syntax exception
+#
+
+class spyceTagSyntaxException:
+ def __init__(self, str):
+ self._str = str
+ def __repr__(self):
+ return self._str
+
diff --git a/system/python/spyce/spyceUtil.py b/system/python/spyce/spyceUtil.py
new file mode 100644
index 0000000000..2fc0a9e98b
--- /dev/null
+++ b/system/python/spyce/spyceUtil.py
@@ -0,0 +1,157 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import sys, re, string
+from cStringIO import StringIO
+
+__doc__ = '''Spyce utility functions'''
+
+##################################################
+# Current exception string
+#
+
+def exceptionString():
+ "Generate string out of current exception."
+ import traceback, string
+ ex=sys.exc_info()
+ ex=traceback.format_exception(ex[0], ex[1], ex[2])
+ ex=string.join(ex, '')
+ return ex
+
+##################################################
+# Return hashtable value, or entire hashtable
+#
+
+def extractValue(hash, key, default=None):
+ """Extract value from dictionary, if it exists.
+ If key is none, return entire dictionary"""
+ if key==None: return hash
+ if hash.has_key(key): return hash[key]
+ return default
+
+##################################################
+# Return hashtable value, or entire hashtable
+#
+
+RE_SPACE_REDUCE = re.compile('[ \t][ \t]+')
+RE_SPACE_NEWLINE_REDUCE = re.compile('\n\s+')
+def spaceCompact(text):
+ text = string.split(text, '\n')
+ text = map(lambda s: RE_SPACE_REDUCE.sub(' ', s), text)
+ text = string.join(text, '\n')
+ text = RE_SPACE_NEWLINE_REDUCE.sub('\n', text)
+ return text
+
+##################################################
+# Threading helpers
+#
+
+class ThreadedWriter:
+ '''Thread-safe writer'''
+ def __init__(self, o=None):
+ try: import thread,threading
+ except: raise 'threading not supported!'
+ self.__dict__['_currentThread'] = threading.currentThread
+ self.__dict__['_o'] = o
+ def setObject(self, o=None):
+ self._currentThread().threadOut = o
+ self._currentThread().threadWrite = o.write
+ def getObject(self):
+ try: return self._currentThread().threadOut
+ except AttributeError: return self._o
+ def clearObject(self):
+ try: del self._currentThread().threadOut
+ except AttributeError: pass
+ def write(self, s):
+ try: self._currentThread().threadWrite(s)
+ except AttributeError: self._o.write(s)
+ def close(self):
+ self.getObject().close()
+ def flush(self):
+ self.getObject().flush()
+ def __getattr__(self, name):
+ if name=='softspace': # performance
+ return self.getObject().softspace
+ return eval('self.getObject().%s'%name)
+ def __setattr__(self, name, value):
+ if name=='softspace': # performance
+ self.getObject().softspace = value
+ eval('self.getObject().%s=value'%name)
+ def __delattr__(self, name):
+ return eval('del self.getObject().%s'%name)
+
+##################################################
+# Output
+#
+
+class BufferedOutput:
+ "Buffered output stream."
+ def __init__(self, out):
+ self.buf = StringIO()
+ self.writeBuf = self.buf.write
+ self.out = out
+ self.closed = 0
+ def write(self, s):
+ if self.closed:
+ raise 'output stream closed'
+ self.writeBuf(s)
+ def clear(self):
+ if not self.buf:
+ raise 'stream is not buffered'
+ self.buf = StringIO()
+ self.writeBuf = self.buf.write
+ def flush(self, stopFlag=0):
+ if stopFlag: return
+ if self.buf and self.buf.getvalue():
+ self.out.write(self.buf.getvalue())
+ self.out.flush()
+ self.clear()
+ def close(self):
+ if self.closed:
+ raise 'output stream closed'
+ self.closed = 1
+ self.flush()
+ self.out.close()
+ def unbuffer(self):
+ "Turn this into a pass-through."
+ if self.buf:
+ self.flush()
+ self.buf = None
+ self.writeBuf = self.out.write
+ def getOut(self):
+ "Return underlying output stream."
+ return self.out
+
+
+class NoCloseOut:
+ def __init__(self, out):
+ self.out = out
+ self.write = self.out.write
+ self.flush = self.out.flush
+ def close(self):
+ pass
+ def getOut(self):
+ return self.out
+
+def panicOutput(response, s):
+ import cgi
+ # output to browser, if possible
+ try: response.clear()
+ except: pass
+ try:
+ response.write('<html><pre>\n')
+ response.write('Spyce Panic!<br>\n')
+ response.write(cgi.escape(s))
+ response.write('</pre></html>\n')
+ response.returncode = response.RETURN_OK
+ response.flush()
+ except: pass
+ # output to error log
+ sys.stderr.write(s)
+ sys.stderr.flush()
+ sys.exit(1)
diff --git a/system/python/spyce/spyceWWW.py b/system/python/spyce/spyceWWW.py
new file mode 100644
index 0000000000..0ba5d28b42
--- /dev/null
+++ b/system/python/spyce/spyceWWW.py
@@ -0,0 +1,324 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import sys, os, string, socket, BaseHTTPServer, SocketServer, cgi, stat, time
+import spyce, spyceConfig, spyceException, spyceCmd, spyceUtil
+
+__doc__ = '''Self-standing Spyce web server.'''
+
+LOG = 1
+
+def formatBytes(bytes):
+ bytes = float(bytes)
+ if bytes<=9999: return "%6.0f" % bytes
+ bytes = bytes / float(1024)
+ if bytes<=999: return "%5.1fK" % bytes
+ bytes = bytes / float(1024)
+ return "%5.1fM" % bytes
+
+##################################################
+# Request / response handlers
+#
+
+class spyceHTTPRequest(spyce.spyceRequest):
+ 'HTTP Spyce request object. (see spyce.spyceRequest)'
+ def __init__(self, httpdHandler, documentRoot):
+ spyce.spyceRequest.__init__(self)
+ self._in = httpdHandler.rfile
+ self._headers = httpdHandler.headers
+ self._httpdHandler = httpdHandler
+ self._documentRoot = documentRoot
+ self._env = None
+ def env(self, name=None):
+ if not self._env:
+ self._env = {
+ 'REMOTE_ADDR': self._httpdHandler.client_address[0],
+ 'REMOTE_PORT': self._httpdHandler.client_address[1],
+ 'GATEWAY_INTERFACE': "CGI/1.1",
+ 'REQUEST_METHOD': self._httpdHandler.command,
+ 'REQUEST_URI': self._httpdHandler.path,
+ 'PATH_INFO': self._httpdHandler.pathinfo,
+ 'SERVER_SOFTWARE': 'spyce/%s' % spyce.__version__,
+ 'SERVER_PROTOCOL': self._httpdHandler.request_version,
+ # 'SERVER_ADDR' ... '127.0.0.1'
+ # 'SERVER_PORT' ... '80'
+ # 'SERVER_NAME' ... 'mymachine.mydomain.com'
+ # 'SERVER_SIGNATURE' ... ' Apache/1.3.22 Server at mymachine.mydomain.com Port 80'
+ # 'SERVER_ADMIN'] ... 'rimon@acm.org'
+ 'DOCUMENT_ROOT': self._documentRoot,
+ 'QUERY_STRING':
+ string.join(string.split(self._httpdHandler.path, '?')[1:]) or '',
+ 'CONTENT_LENGTH': self.getHeader('Content-Length'),
+ 'CONTENT_TYPE': self.getHeader('Content-type'),
+ 'HTTP_USER_AGENT': self.getHeader('User-Agent'),
+ 'HTTP_ACCEPT': self.getHeader('Accept'),
+ 'HTTP_ACCEPT_ENCODING': self.getHeader('Accept-Encoding'),
+ 'HTTP_ACCEPT_LANGUAGE': self.getHeader('Accept-Language'),
+ 'HTTP_ACCEPT_CHARSET': self.getHeader('Accept-Charset'),
+ 'HTTP_COOKIE': self.getHeader('Cookie'),
+ 'HTTP_REFERER': self.getHeader('Referer'),
+ 'HTTP_HOST': self.getHeader('Host'),
+ 'HTTP_CONNECTION': self.getHeader('Connection'),
+ 'HTTP_KEEP_ALIVE': self.getHeader('Keep-alive'),
+ # From ASP
+ # AUTH_TYPE,
+ # APPL_PHYSICAL_PATH,
+ # REMOTE_HOST,
+ # SERVER_PROTOCOL,
+ # SERVER_SOFWARE
+ }
+ return spyceUtil.extractValue(self._env, name)
+ def getHeader(self, type=None):
+ if type: type=string.lower(type)
+ return spyceUtil.extractValue(self._headers.dict, type)
+ def getServerID(self):
+ return os.getpid()
+
+class spyceHTTPResponse(spyceCmd.spyceCmdlineResponse):
+ 'HTTP Spyce response object. (see spyce.spyceResponse)'
+ def __init__(self, httpdHandler):
+ self._httpheader = httpdHandler.request_version!='HTTP/0.9'
+ spyceCmd.spyceCmdlineResponse.__init__(self, spyceUtil.NoCloseOut(httpdHandler.wfile), sys.stdout, self._httpheader)
+ self._httpdHandler = httpdHandler
+ # incidentally, this a rather expensive operation!
+ if LOG:
+ httpdHandler.log_request()
+ def sendHeaders(self):
+ if self._httpheader and not self.headersSent:
+ resultText = spyceUtil.extractValue(self.RETURN_CODE, self.returncode)
+ self.origout.write('%s %s %s\n' % (self._httpdHandler.request_version, self.returncode, resultText))
+ spyceCmd.spyceCmdlineResponse.sendHeaders(self)
+ def close(self):
+ spyceCmd.spyceCmdlineResponse.close(self)
+ self._httpdHandler.request.close()
+
+
+##################################################
+# Spyce web server
+#
+
+class myHTTPhandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_GET(self):
+ try:
+ # parse pathinfo
+ pathinfo = os.path.normpath(string.split(self.path, '?')[0])
+ while pathinfo and (pathinfo[0]==os.sep or pathinfo[0:2]==os.pardir):
+ if pathinfo[0:len(os.sep)]==os.sep: pathinfo=pathinfo[len(os.sep):]
+ if pathinfo[0:len(os.pardir)]==os.pardir: pathinfo=pathinfo[len(os.pardir):]
+ self.pathinfo = "/"+pathinfo
+ # convert to path
+ path = os.path.join(self.server.documentRoot, pathinfo)
+ # directory listing
+ if os.path.isdir(path):
+ return self.handler_dir(path)
+ # search up path (path_info)
+ while len(path)>len(self.server.documentRoot) and not os.path.exists(path):
+ path, _ = os.path.split(path)
+ # for files (or links), find appropriate handler
+ if os.path.isfile(path) or os.path.islink(path):
+ _, ext = os.path.splitext(path)
+ if ext: ext = ext[1:]
+ try:
+ handler = self.server.handler[ext]
+ except:
+ handler = self.server.handler[None]
+ # process request
+ return handler(self, path)
+ # invalid path
+ self.send_error(404, "Invalid path")
+ return None
+ except IOError:
+ self.send_error(404, "Unexpected IOError")
+ return None
+ do_POST=do_GET
+ def handler_spyce(self, path):
+ # process spyce
+ request = spyceHTTPRequest(self, self.server.documentRoot)
+ response = spyceHTTPResponse(self)
+ result = spyce.spyceFileHandler(request, response, path)
+ response.close()
+ def handler_dump(self, path):
+ # process content to dump (with correct mime type)
+ f = None
+ try:
+ try:
+ f = open(path, 'rb')
+ except IOError:
+ self.send_error(404, "No permission to open file")
+ return None
+ try:
+ _, ext = os.path.splitext(path)
+ if ext: ext=ext[1:]
+ mimetype = self.server.mimeTable[ext]
+ except:
+ mimetype = "application/octet-stream"
+ self.send_response(200)
+ self.send_header("Content-type", mimetype)
+ self.end_headers()
+ self.wfile.write(f.read())
+ self.request.close()
+ finally:
+ try:
+ if f: f.close()
+ except: pass
+ def handler_dir(self, path):
+ # process directory
+ if(self.path[-1:]!='/'):
+ self.send_response(301)
+ self.send_header('Location', self.path+'/')
+ self.end_headers()
+ return
+ try:
+ list = os.listdir(path)
+ except os.error:
+ self.send_error(404, "Path does not exist")
+ return None
+ list.sort(lambda a, b: cmp(a.lower(), b.lower()))
+ def info(name, path=path):
+ fullname = os.path.join(path, name)
+ displayname = linkname = name = cgi.escape(name)
+ # Append / for directories or @ for symbolic links
+ if os.path.isdir(fullname):
+ displayname = name + "/"
+ linkname = name + "/"
+ elif os.path.islink(fullname):
+ displayname = name + "@"
+ statinfo = os.stat(fullname)
+ mtime = statinfo[stat.ST_MTIME]
+ size = statinfo[stat.ST_SIZE]
+ return linkname, displayname, mtime, size
+ list = map(info, list)
+
+ NAME_WIDTH = 30
+ output = '''
+<html><head>
+ <title>Index of %(title)s</title>
+</head>
+<body>
+<h1>Index of /%(title)s</h1>
+<pre> Name%(filler)s Date%(filler_date)s Size<hr/>''' % {
+ 'title' : self.pathinfo,
+ 'filler': ' '*(NAME_WIDTH-len('Name')),
+ 'filler_date': ' '*(len(time.asctime(time.localtime(0)))-len('Date')),
+ }
+
+ if list:
+ for link, display, mtime, size in list:
+ output = output + ' <a href="%(link)s">%(display)s</a>%(filler)s %(mtime)s %(size)s\n' % {
+ 'link': link,
+ 'display': display[:NAME_WIDTH],
+ 'link': link,
+ 'filler': ' '*(NAME_WIDTH-len(display)),
+ 'mtime': time.asctime(time.localtime(mtime)),
+ 'size': formatBytes(size),
+ }
+ else:
+ output = output + 'No files\n'
+
+ output = output[:-1] + '''<hr/></pre>
+<address>Spyce-WWW/%(version)s server</address>
+</body></html>
+''' % {
+ 'version' : spyce.__version__,
+ }
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+ self.wfile.write(output)
+
+def buildMimeTable(files):
+ mimetable = {}
+ for file in files:
+ try:
+ f = None
+ try:
+ f = open(file, 'r')
+ print "# MIME file: "+file
+ line = f.readline()
+ while line:
+ if line[0]=='#':
+ line = f.readline(); continue
+ line = string.strip(line)
+ if not line:
+ line = f.readline(); continue
+ line = string.replace(line, '\t', ' ')
+ items = filter(None, map(string.strip, string.split(line, ' ')))
+ mimetype, extensions = items[0], items[1:]
+ for ext in extensions:
+ mimetable[ext] = mimetype
+ line = f.readline()
+ except IOError: pass
+ finally:
+ try:
+ if f: f.close()
+ except: pass
+ return mimetable
+
+def buildHandlerTable(handler, server):
+ for ext in handler.keys():
+ handler[ext] = eval('server.handler_'+handler[ext])
+ return handler
+
+def spyceHTTPserver(port, root, config_file=None, daemon=None):
+ os.environ[spyce.SPYCE_ENTRY] = 'www'
+ spyceCmd.showVersion()
+ print '# Starting web server'
+ # test for threading support, if needed
+ try:
+ server = spyce.getServer(
+ config_file=config_file,
+ overide_www_port=port,
+ overide_www_root=root)
+ except (spyceException.spyceForbidden, spyceException.spyceNotFound), e:
+ print e
+ return
+ if server.concurrency==spyceConfig.SPYCE_CONCURRENCY_THREAD:
+ spyceUtil.ThreadedWriter() # will raise exception if 'import thread' fails
+ # determine type of server concurrency
+ serverSuperClass = {
+ spyceConfig.SPYCE_CONCURRENCY_SINGLE: SocketServer.TCPServer,
+ spyceConfig.SPYCE_CONCURRENCY_FORK: SocketServer.ForkingTCPServer,
+ spyceConfig.SPYCE_CONCURRENCY_THREAD: SocketServer.ThreadingTCPServer,
+ } [server.concurrency]
+ class sharedSocketServer(serverSuperClass):
+ def server_bind(self):
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ SocketServer.TCPServer.server_bind(self)
+ try:
+ # initialize server
+ try:
+ httpd = sharedSocketServer(('',server.config.getSpyceWWWPort()), myHTTPhandler)
+ print '# Server Port: %d' % server.config.getSpyceWWWPort()
+ httpd.documentRoot = os.path.abspath(server.config.getSpyceWWWRoot())
+ print '# Server Root: '+httpd.documentRoot
+ httpd.mimeTable = buildMimeTable(server.config.getSpyceWWWMime())
+ httpd.handler = buildHandlerTable(server.config.getSpyceWWWHandler(), myHTTPhandler)
+ except:
+ print 'Unable to start server on port %s' % server.config.getSpyceWWWPort()
+ return -1
+ # daemonize
+ if daemon:
+ print '# Daemonizing process.'
+ try:
+ spyceCmd.daemonize(pidfile=daemon)
+ except SystemExit: # expected
+ return 0
+ global LOG
+ LOG = 0
+ # process requests
+ print '# Ready.'
+ while 1:
+ try:
+ httpd.handle_request()
+ except KeyboardInterrupt: raise
+ except:
+ print 'Error: %s' % spyceUtil.exceptionString()
+ except KeyboardInterrupt:
+ print 'Break!'
+ return 0
+
diff --git a/system/python/spyce/spyceXbmc.py b/system/python/spyce/spyceXbmc.py
new file mode 100644
index 0000000000..6b807a7216
--- /dev/null
+++ b/system/python/spyce/spyceXbmc.py
@@ -0,0 +1,25 @@
+import sys, os
+sys.path.append(sys.executable + '\\spyce')
+from StringIO import StringIO
+import spyce, spyceCmd, string
+
+def ParseFile(file, env):
+ output = StringIO()
+ input = StringIO(env['QUERY_STRING'])
+ env['REQUEST_URI'] = "/" + string.replace(string.lstrip(file, "Q:\\web"), "\\", "/")
+ SPYCE_HOME = os.path.abspath(os.path.dirname(sys.modules['spyceXbmc'].__file__))
+ request = spyceCmd.spyceCmdlineRequest(input, env, file)
+ response = spyceCmd.spyceCmdlineResponse(output, sys.stderr, 1)
+ result = spyce.spyceFileHandler(request, response, file)
+ response.flush()
+ result = output.getvalue()
+ response.close()
+ return result
+
+if __name__ == '__main__':
+ file = 'docs\\examples\\hello.spy'
+ if os.access(file, os.F_OK):
+ print(ParseFile(file))
+ else:
+ print('file not found')
+
diff --git a/system/python/spyce/tree.py b/system/python/spyce/tree.py
new file mode 100644
index 0000000000..d4fb51f1e3
--- /dev/null
+++ b/system/python/spyce/tree.py
@@ -0,0 +1,70 @@
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+import string
+
+class tree:
+ def __init__(self, data):
+ self.data = data
+ self.parent = None
+ self.children = []
+ self.next = self.prev = None
+ self.depth = 0
+ def append(self, data):
+ node = tree(data)
+ self.children.append(node)
+ node.parent = self
+ node.depth = self.depth+1
+ return node
+ def delete(self):
+ for c in self.children:
+ c.delete()
+ if self.parent:
+ self.parent.children.remove(self)
+ self.parent = None
+ def __repr__(self):
+ return '%s [%s]' % (self.data, string.join(map(str, self.children),', '))
+ def postWalk(self, f):
+ for c in self.children:
+ c.postWalk(f)
+ f(self)
+ def preWalk(self, f):
+ f(self)
+ for c in self.children:
+ c.preWalk(f)
+ def computePreChain(self):
+ prev = [None]
+ def walker(node, prev=prev):
+ node.prev = prev[0]
+ if prev[0]:
+ node.prev.next = node
+ prev[0] = node
+ self.preWalk(walker)
+ def __cmp__(self, o):
+ try:
+ x = not self.data == o.data
+ if x: return x
+ x = not self.children == o.children
+ if x: return x
+ except:
+ return 1
+ return 0
+
+if __name__=='__main__':
+ root = tree('1')
+ n = root.append('1.1')
+ n.append('1.1.1')
+ n = root.append('1.2')
+ n.append('1.2.1')
+ root.computePreChain()
+ n = root
+ while(n):
+ print n.data
+ n = n.next
+ root.delete()
+
diff --git a/system/python/spyce/verchk.py b/system/python/spyce/verchk.py
new file mode 100755
index 0000000000..8ebfe8791f
--- /dev/null
+++ b/system/python/spyce/verchk.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+##################################################
+# SPYCE - Python-based HTML Scripting
+# Copyright (c) 2002 Rimon Barr.
+#
+# Refer to spyce.py
+# CVS: $Id$
+##################################################
+
+__doc__ = '''Version checking script.'''
+
+import sys, os
+
+REQUIRED = '1.5'
+
+def checkversion(required):
+ if int(sys.version[0])<int(required[0]) or \
+ (sys.version[0]==required[0] and int(sys.version[2])<int(required[2])):
+ return 0
+ return 1
+
+if __name__ == "__main__":
+ if not checkversion(REQUIRED):
+ print 'Python version '+REQUIRED+' required.'
+ sys.exit(-1)
+ if len(sys.argv)<2:
+ print 'Python version '+sys.version[:3]+' - OK'
+ else:
+ #sys.argv[1] = os.path.join(os.path.dirname(sys.argv[0]), sys.argv[1])
+ del sys.argv[0]
+ if not os.path.exists(sys.argv[0]):
+ print 'Script "'+sys.argv[0]+'" not found.'
+ sys.exit(-1)
+ execfile(sys.argv[0])
+