diff options
author | AlTheKiller <AlTheKiller@svn> | 2009-09-23 01:49:50 +0000 |
---|---|---|
committer | AlTheKiller <AlTheKiller@svn> | 2009-09-23 01:49:50 +0000 |
commit | 45285e8a9300cd754a760560640b75b09f98035e (patch) | |
tree | ad9f093885ad5c98e9dd4156674e7691c22ed0a2 /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')
60 files changed, 9286 insertions, 0 deletions
diff --git a/system/python/DLLs/_socket.pyd b/system/python/DLLs/_socket.pyd Binary files differnew file mode 100755 index 0000000000..330876876a --- /dev/null +++ b/system/python/DLLs/_socket.pyd diff --git a/system/python/DLLs/_ssl.pyd b/system/python/DLLs/_ssl.pyd Binary files differnew file mode 100755 index 0000000000..206953b598 --- /dev/null +++ b/system/python/DLLs/_ssl.pyd diff --git a/system/python/DLLs/bz2.pyd b/system/python/DLLs/bz2.pyd Binary files differnew file mode 100755 index 0000000000..dcf006ee61 --- /dev/null +++ b/system/python/DLLs/bz2.pyd diff --git a/system/python/DLLs/pyexpat.pyd b/system/python/DLLs/pyexpat.pyd Binary files differnew file mode 100755 index 0000000000..0132861751 --- /dev/null +++ b/system/python/DLLs/pyexpat.pyd diff --git a/system/python/DLLs/select.pyd b/system/python/DLLs/select.pyd Binary files differnew file mode 100755 index 0000000000..8a747d9e23 --- /dev/null +++ b/system/python/DLLs/select.pyd diff --git a/system/python/DLLs/unicodedata.pyd b/system/python/DLLs/unicodedata.pyd Binary files differnew file mode 100755 index 0000000000..0ca6ed0e21 --- /dev/null +++ b/system/python/DLLs/unicodedata.pyd diff --git a/system/python/DLLs/zlib.pyd b/system/python/DLLs/zlib.pyd Binary files differnew file mode 100755 index 0000000000..3066c14bc9 --- /dev/null +++ b/system/python/DLLs/zlib.pyd diff --git a/system/python/python24.dll b/system/python/python24.dll Binary files differnew file mode 100644 index 0000000000..91591b1f26 --- /dev/null +++ b/system/python/python24.dll diff --git a/system/python/python24.zlib b/system/python/python24.zlib Binary files differnew file mode 100644 index 0000000000..e1bb9c4101 --- /dev/null +++ b/system/python/python24.zlib 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): '"', chr(38): '&', chr(60): '<', chr(62): '>', + chr(160): ' ', chr(161): '¡', chr(162): '¢', chr(163): '£', + chr(164): '¤', chr(165): '¥', chr(166): '¦', chr(167): '§', + chr(168): '¨', chr(169): '©', chr(170): 'ª', chr(171): '«', + chr(172): '¬', chr(173): '­', chr(174): '®', chr(175): '¯', + chr(176): '°', chr(177): '±', chr(178): '²', chr(179): '³', + chr(180): '´', chr(181): 'µ', chr(182): '¶', chr(183): '·', + chr(184): '¸', chr(185): '¹', chr(186): 'º', chr(187): '»', + chr(188): '¼', chr(189): '½', chr(190): '¾', chr(191): '¿', + chr(192): 'À', chr(193): 'Á', chr(194): 'Â', chr(195): 'Ã', + chr(196): 'Ä', chr(197): 'Å', chr(198): 'Æ', chr(199): 'Ç', + chr(200): 'È', chr(201): 'É', chr(202): 'Ê', chr(203): 'Ë', + chr(204): 'Ì', chr(205): 'Í', chr(206): 'Î', chr(207): 'Ï', + chr(208): 'Ð', chr(209): 'Ñ', chr(210): 'Ò', chr(211): 'Ó', + chr(212): 'Ô', chr(213): 'Õ', chr(214): 'Ö', chr(215): '×', + chr(216): 'Ø', chr(217): 'Ù', chr(218): 'Ú', chr(219): 'Û', + chr(220): 'Ü', chr(221): 'Ý', chr(222): 'Þ', chr(223): 'ß', + chr(224): 'à', chr(225): 'á', chr(226): 'â', chr(227): 'ã', + chr(228): 'ä', chr(229): 'å', chr(230): 'æ', chr(231): 'ç', + chr(232): 'è', chr(233): 'é', chr(234): 'ê', chr(235): 'ë', + chr(236): 'ì', chr(237): 'í', chr(238): 'î', chr(239): 'ï', + chr(240): 'ð', chr(241): 'ñ', chr(242): 'ò', chr(243): 'ó', + chr(244): 'ô', chr(245): 'õ', chr(246): 'ö', chr(247): '÷', + chr(248): 'ø', chr(249): 'ù', chr(250): 'ú', chr(251): 'û', + chr(252): 'ü', chr(253): 'ý', chr(254): 'þ', chr(255): 'ÿ', +} +_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: ' ', 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]) + |