Merge lp:~barry/ubuntu/natty/cheetah/py27-compat into lp:ubuntu/natty/cheetah

Proposed by Barry Warsaw
Status: Merged
Merge reported by: Barry Warsaw
Merged at revision: not available
Proposed branch: lp:~barry/ubuntu/natty/cheetah/py27-compat
Merge into: lp:ubuntu/natty/cheetah
Diff against target: 43600 lines (+19910/-22763)
152 files modified
CHANGES (+91/-3)
Cheetah.egg-info/PKG-INFO (+47/-0)
Cheetah.egg-info/SOURCES.txt (+80/-0)
Cheetah.egg-info/dependency_links.txt (+1/-0)
Cheetah.egg-info/requires.txt (+1/-0)
Cheetah.egg-info/top_level.txt (+1/-0)
MANIFEST.in (+3/-3)
PKG-INFO (+7/-864)
README (+0/-51)
README.markdown (+51/-0)
SetupConfig.py (+44/-61)
SetupTools.py (+60/-29)
TODO (+6/-14)
bin/cheetah (+2/-2)
bin/cheetah-analyze (+6/-0)
bin/cheetah-compile (+2/-4)
cachedCompile.py (+0/-11)
cheetah-mem.py (+0/-22)
cheetah/CacheRegion.py (+136/-0)
cheetah/CacheStore.py (+106/-0)
cheetah/CheetahWrapper.py (+632/-0)
cheetah/Compiler.py (+2002/-0)
cheetah/DirectiveAnalyzer.py (+98/-0)
cheetah/Django.py (+16/-0)
cheetah/DummyTransaction.py (+108/-0)
cheetah/ErrorCatchers.py (+62/-0)
cheetah/FileUtils.py (+357/-0)
cheetah/Filters.py (+212/-0)
cheetah/ImportHooks.py (+129/-0)
cheetah/ImportManager.py (+541/-0)
cheetah/Macros/I18n.py (+67/-0)
cheetah/Macros/__init__.py (+1/-0)
cheetah/NameMapper.py (+366/-0)
cheetah/Parser.py (+2661/-0)
cheetah/Servlet.py (+48/-0)
cheetah/SettingsManager.py (+284/-0)
cheetah/SourceReader.py (+267/-0)
cheetah/Template.py (+1941/-0)
cheetah/TemplateCmdLineIface.py (+107/-0)
cheetah/Templates/SkeletonPage.py (+272/-0)
cheetah/Templates/SkeletonPage.tmpl (+44/-0)
cheetah/Templates/_SkeletonPage.py (+215/-0)
cheetah/Templates/__init__.py (+1/-0)
cheetah/Tests/Analyzer.py (+29/-0)
cheetah/Tests/CheetahWrapper.py (+579/-0)
cheetah/Tests/Cheps.py (+39/-0)
cheetah/Tests/Filters.py (+70/-0)
cheetah/Tests/Misc.py (+20/-0)
cheetah/Tests/NameMapper.py (+548/-0)
cheetah/Tests/Parser.py (+49/-0)
cheetah/Tests/Performance.py (+243/-0)
cheetah/Tests/Regressions.py (+247/-0)
cheetah/Tests/SyntaxAndOutput.py (+3253/-0)
cheetah/Tests/Template.py (+363/-0)
cheetah/Tests/Test.py (+53/-0)
cheetah/Tests/Unicode.py (+237/-0)
cheetah/Tests/__init__.py (+1/-0)
cheetah/Tests/xmlrunner.py (+381/-0)
cheetah/Tools/CGITemplate.py (+77/-0)
cheetah/Tools/MondoReport.py (+464/-0)
cheetah/Tools/MondoReportDoc.txt (+391/-0)
cheetah/Tools/RecursiveNull.py (+28/-0)
cheetah/Tools/SiteHierarchy.py (+166/-0)
cheetah/Tools/__init__.py (+8/-0)
cheetah/Tools/turbocheetah/__init__.py (+5/-0)
cheetah/Tools/turbocheetah/cheetahsupport.py (+110/-0)
cheetah/Tools/turbocheetah/tests/__init__.py (+1/-0)
cheetah/Tools/turbocheetah/tests/test_template.py (+66/-0)
cheetah/Unspecified.py (+9/-0)
cheetah/Utils/Indenter.py (+123/-0)
cheetah/Utils/Misc.py (+67/-0)
cheetah/Utils/WebInputMixin.py (+102/-0)
cheetah/Utils/__init__.py (+1/-0)
cheetah/Utils/htmlDecode.py (+14/-0)
cheetah/Utils/htmlEncode.py (+21/-0)
cheetah/Utils/statprof.py (+304/-0)
cheetah/Version.py (+58/-0)
cheetah/__init__.py (+20/-0)
cheetah/c/Cheetah.h (+47/-0)
cheetah/c/_namemapper.c (+494/-0)
cheetah/c/cheetah.h (+78/-0)
cheetah/convertTmplPathToModuleName.py (+20/-0)
closure.py (+0/-199)
debian/changelog (+7/-0)
debian/patches/02_clean_modules.patch (+0/-280)
debian/patches/02_fedora_cheetah_2.7_compat.patch (+36/-0)
debian/patches/03_ptyhon-2.6-warning-fix.patch (+0/-25)
debian/rules (+1/-1)
ez_setup.py (+0/-164)
foo.py (+0/-151)
setup.cfg (+5/-0)
src/CacheRegion.py (+0/-138)
src/CacheStore.py (+0/-108)
src/CheetahWrapper.py (+0/-589)
src/Compiler.py (+0/-2009)
src/DummyTransaction.py (+0/-58)
src/ErrorCatchers.py (+0/-63)
src/FileUtils.py (+0/-374)
src/Filters.py (+0/-176)
src/ImportHooks.py (+0/-139)
src/ImportManager.py (+0/-561)
src/Macros/I18n.py (+0/-67)
src/Macros/__init__.py (+0/-1)
src/NameMapper.py (+0/-355)
src/Parser.py (+0/-2599)
src/Servlet.py (+0/-126)
src/SettingsManager.py (+0/-620)
src/SourceReader.py (+0/-304)
src/Template.py (+0/-1856)
src/TemplateCmdLineIface.py (+0/-108)
src/Templates/SkeletonPage.py (+0/-273)
src/Templates/SkeletonPage.tmpl (+0/-44)
src/Templates/_SkeletonPage.py (+0/-216)
src/Templates/__init__.py (+0/-1)
src/Tests/CheetahWrapper.py (+0/-596)
src/Tests/FileRefresh.py (+0/-55)
src/Tests/NameMapper.py (+0/-539)
src/Tests/SyntaxAndOutput.py (+0/-3230)
src/Tests/Template.py (+0/-312)
src/Tests/Test.py (+0/-70)
src/Tests/__init__.py (+0/-1)
src/Tests/unittest_local_copy.py (+0/-977)
src/Tools/CGITemplate.py (+0/-78)
src/Tools/MondoReport.py (+0/-464)
src/Tools/MondoReportDoc.txt (+0/-391)
src/Tools/RecursiveNull.py (+0/-23)
src/Tools/SiteHierarchy.py (+0/-183)
src/Tools/__init__.py (+0/-8)
src/Tools/turbocheetah/__init__.py (+0/-5)
src/Tools/turbocheetah/cheetahsupport.py (+0/-110)
src/Tools/turbocheetah/tests/__init__.py (+0/-1)
src/Tools/turbocheetah/tests/test_template.py (+0/-66)
src/Unspecified.py (+0/-9)
src/Utils/Indenter.py (+0/-135)
src/Utils/Misc.py (+0/-82)
src/Utils/VerifyType.py (+0/-82)
src/Utils/WebInputMixin.py (+0/-103)
src/Utils/__init__.py (+0/-1)
src/Utils/htmlDecode.py (+0/-14)
src/Utils/htmlEncode.py (+0/-21)
src/Utils/memcache.py (+0/-625)
src/Utils/optik/__init__.py (+0/-32)
src/Utils/optik/errors.py (+0/-52)
src/Utils/optik/option.py (+0/-354)
src/Utils/optik/option_parser.py (+0/-667)
src/Version.py (+0/-58)
src/__init__.py (+0/-27)
src/_namemapper.c (+0/-490)
src/convertTmplPathToModuleName.py (+0/-15)
syntaxtest.py (+0/-15)
test2.2.py (+0/-2)
timingTests.py (+0/-201)
To merge this branch: bzr merge lp:~barry/ubuntu/natty/cheetah/py27-compat
Reviewer Review Type Date Requested Status
Clint Byrum Pending
Ubuntu branches Pending
Review via email: mp+38883@code.launchpad.net

Description of the change

Merges upstream 2.4.3 and adds Python 2.7 compatibility patch from the Fedora project.

These changes would still need to be pushed to Debian.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CHANGES'
--- CHANGES 2007-11-18 14:59:06 +0000
+++ CHANGES 2010-10-19 20:12:51 +0000
@@ -1,6 +1,94 @@
1Please initial your changes (there's a key at bottom) and add a date for each12.4.2 (February 8th, 2010)
2release2 - Fix issue where subclasses of Template failed to pick up attributes in the
3================================================================================3 searchlist
4 - Remove old/outdated bundled memcached python client
5 - Allow for #encoding directives to exist after a comment (i.e. not the first
6 line in a module)
7 - Remove support for WebWare servlets (which caused significant performance
8 slowdowns on Mac OS X)
9 - Old/stale code pruned in preparation for Python 3 support
10
112.4.1 (December 19th, 2009)
12 - --quiet flag added to `cheetah` to silence printing to stdout (abbeyj)
13 - Refactoring to minimize the amount of forked code for Python3 (rtyler)
14 - Template.compile() will no longer create class names with numerous leading
15 underscores (rtyler; reported by Kirill Uhanov)
16 - DirectiveAnalyzer (cheetah-analyze script) added to report directive usage in templates (rtyler)
17 - Older LaTeX docs converted to rst for Sphinx (rtyler)
18 - Prevent #raw blocks from evaluating $-placeholders and escaped strings (karmix0)
19 - New tests added to verify PSP behavior and other untested internals (rtyler)
20
212.4.0 (October 24th, 2009)
22 - Fix a major performance regression in Template.__init__()
23 - More graceful handling of unicode when calling .respond() to render a template
24 - Minor code updates
25 - Update the default filter (thanks mikeb!)
26
272.3.0 (October 24th, 2009) (loosely equivalent to 2.4.0)
28 - Fix a major performance regression in Template.__init__()
29 - More graceful handling of unicode when calling .respond() to render a template
30 - Minor code updates
31 - Update the default filter (thanks mikeb!)
32
332.2.2 (September 10th, 2009)
34 - Prevent _namemapper.c from segfaulting when PyImport_ImportModule fails for some reason (Bogdano Arendartchuk <debogdano@gmail.com>)
35 - Removal of the contrib/markdown module (in favor of a setuptools dependency)
36 - Default setup.py to use setuptools by default, failing that, fall back to distutils
37 - Improvements to setup.py to support building for Windows (thanks abbeyj!)
38 - Improvements to C-based NameMapper for Windows
39 - Fixes for a swath of unit tests on Windows
40 - Re-enabling the EOL tests (whoops)
41 - Fix for unicode/utf-8 dynamic compilation error (thanks mikeb!) (Test.Unicode.JBQ_UTF8_Test8)
42 - 0000010: [Templates] Failure to execute templates on Google App Engine (rtyler)
43 - 0000026: [Compiler] Support multiple inheritance (rtyler)
44
45
462.2.1 (June 1st, 2009)
47 - 0000020: [Templates] Builtin support for using Cheetah with Django (rtyler)
48 - 0000021: [Compiler] @static and @classmethod don't properly define the _filter local (rtyler)
49 - 0000023: [Compiler] Update Template super calls to use super() (rtyler)
50 - Update all references to communitycheetah.org to point back at cheetahtemplate.org
51
522.2.0 (May 17th, 2009)
53 - Switch all internal representations of template code to unicode objects instead of str() objects
54 - Convert unicode compiled template to an utf8 char buffer when writing to a file (Jean-Baptiste Quenot <jbq@caraldi.com>)
55 - 0000011: [Templates] Calling a function with arguments calls the function with None (rtyler)
56 - 0000015: [Tests] Resolve test failures in 'next' branch (rtyler)
57 - 0000019: [Templates] Properly warn when joining unicode and non-unicode objects in DummyTransaction (rtyler)
58
592.1.2 (May 5, 2009)
60 - 0000006: [Templates] Support @staticmethod and @classmethod (rtyler)
61
622.1.1 (April 16, 2009)
63 - Support __eq__() and __ne__() the way you might expect in src/Tools/RecursiveNull (patch suggested by Peter Warasin <peter@endian.com>)
64 - Applied patch to avoid hitting the filesystem to get the file modification time everytime a #include directive is processed (Jean-Baptiste Quenot <jbq@caraldi.com>)
65 - Applied patch to fix some annoying cases when Cheetah writes to stderr instead of propagating the exception (Jean-Baptiste Quenot <jbq@caraldi.com>)
66 - Added KDE editor support
67 - Applied patch to correct importHook behavior on Python 2.6 (reported/patched by Toshio Ernie Kuratomi <a.badger@gmail.com>)
68 - Correct unicode issue when calling/embedding unicode templates inside of other templtes (testcase Tests.Unicode.JPQ_UTF8_Test3. reported by Jean-Baptiste Quenot <jbq@caraldi.com>)
69 - Added --shbang option (e.g. "cheetah compile --shbang '#!/usr/bin/python2.6' ")
70 - Removed dependency on optik OptionParser in favor of builtin Python optparse module
71 - Introduction of the #transform directive for whole-document filtering
72 - Introduction of Cheetah.contrib.markdown and Cheetah.Filters.Markdown for outputting a markdown processed template (meant for #transform)
73 - Cheetah.Filters.CodeHighlighter, pygments-based code highlighting filter for use with #transform
74 - Addition of "useLegacyImportMode" compiler setting (defaulted to True) to allow for older (read: broken) import behavior
75
762.1.0.1 (March 27, 2009)
77 - Fix inline import issue introduced in v2.1.0
78
792.1.0 (March 16, 2009)
80 - Quiet DeprecationWarnings being printed to stderr when using Cheetah on Python 2.6 and up. Patch suggested by Satoru SATOH <satoru.satoh@gmail.com>
81 - Apply patch to support parallel compilation of templates courtesy of Evan Klitzke <evan@eklitzke.org>
82 - Corrected issue when __getattr__ calls on searchList objects raise exceptions (tyler@slide.com)
83 - make autocalling in valueForName correctly ignore newstyle classes and instances
84 that are callable, as it does for oldstyle classes and instances. Patch
85 from lucas@endian.com
86 [TR]
87 - made it possible to chain multiple decorators to a method #def [TR with
88 patch from Graham Dennis]
89 - fixed a bug in _eatMultiLineDef that Graham Dennis reported. [TR]
90 - fixed 'module.__init__() argument 1 must be string, not unicode' bug in
91 Template.py reported by Erwin Ambrosch [TR]
492
52.0.1 (Nov 16, 2007)932.0.1 (Nov 16, 2007)
6 - fixed a deadlock Christoph Zwerschke found in Cheetah.ImportHooks.94 - fixed a deadlock Christoph Zwerschke found in Cheetah.ImportHooks.
795
=== added directory 'Cheetah.egg-info'
=== added file 'Cheetah.egg-info/PKG-INFO'
--- Cheetah.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000
+++ Cheetah.egg-info/PKG-INFO 2010-10-19 20:12:51 +0000
@@ -0,0 +1,47 @@
1Metadata-Version: 1.0
2Name: Cheetah
3Version: 2.4.3
4Summary: Cheetah is a template engine and code generation tool.
5Home-page: http://www.cheetahtemplate.org/
6Author: R. Tyler Ballance
7Author-email: cheetahtemplate-discuss@lists.sf.net
8License: UNKNOWN
9Description: Cheetah is an open source template engine and code generation tool.
10
11 It can be used standalone or combined with other tools and frameworks. Web
12 development is its principle use, but Cheetah is very flexible and is also being
13 used to generate C++ game code, Java, sql, form emails and even Python code.
14
15 Documentation
16 ================================================================================
17 For a high-level introduction to Cheetah please refer to the User's Guide
18 at http://www.cheetahtemplate.org/learn.html
19
20 Mailing list
21 ================================================================================
22 cheetahtemplate-discuss@lists.sourceforge.net
23 Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
24
25 Credits
26 ================================================================================
27 http://www.cheetahtemplate.org/credits.html
28
29 Recent Changes
30 ================================================================================
31 See http://www.cheetahtemplate.org/CHANGES.txt for full details
32
33
34Platform: UNKNOWN
35Classifier: Development Status :: 5 - Production/Stable
36Classifier: Intended Audience :: Developers
37Classifier: Intended Audience :: System Administrators
38Classifier: License :: OSI Approved :: MIT License
39Classifier: Operating System :: OS Independent
40Classifier: Programming Language :: Python
41Classifier: Topic :: Internet :: WWW/HTTP
42Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
43Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
44Classifier: Topic :: Software Development :: Code Generators
45Classifier: Topic :: Software Development :: Libraries :: Python Modules
46Classifier: Topic :: Software Development :: User Interfaces
47Classifier: Topic :: Text Processing
048
=== added file 'Cheetah.egg-info/SOURCES.txt'
--- Cheetah.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
+++ Cheetah.egg-info/SOURCES.txt 2010-10-19 20:12:51 +0000
@@ -0,0 +1,80 @@
1CHANGES
2LICENSE
3MANIFEST.in
4README.markdown
5SetupConfig.py
6SetupTools.py
7TODO
8setup.py
9Cheetah.egg-info/PKG-INFO
10Cheetah.egg-info/SOURCES.txt
11Cheetah.egg-info/dependency_links.txt
12Cheetah.egg-info/requires.txt
13Cheetah.egg-info/top_level.txt
14bin/cheetah
15bin/cheetah-analyze
16bin/cheetah-compile
17cheetah/CacheRegion.py
18cheetah/CacheStore.py
19cheetah/CheetahWrapper.py
20cheetah/Compiler.py
21cheetah/DirectiveAnalyzer.py
22cheetah/Django.py
23cheetah/DummyTransaction.py
24cheetah/ErrorCatchers.py
25cheetah/FileUtils.py
26cheetah/Filters.py
27cheetah/ImportHooks.py
28cheetah/ImportManager.py
29cheetah/NameMapper.py
30cheetah/Parser.py
31cheetah/Servlet.py
32cheetah/SettingsManager.py
33cheetah/SourceReader.py
34cheetah/Template.py
35cheetah/TemplateCmdLineIface.py
36cheetah/Unspecified.py
37cheetah/Version.py
38cheetah/__init__.py
39cheetah/convertTmplPathToModuleName.py
40cheetah/Macros/I18n.py
41cheetah/Macros/__init__.py
42cheetah/Templates/SkeletonPage.py
43cheetah/Templates/SkeletonPage.tmpl
44cheetah/Templates/_SkeletonPage.py
45cheetah/Templates/__init__.py
46cheetah/Tests/Analyzer.py
47cheetah/Tests/CheetahWrapper.py
48cheetah/Tests/Cheps.py
49cheetah/Tests/Filters.py
50cheetah/Tests/Misc.py
51cheetah/Tests/NameMapper.py
52cheetah/Tests/Parser.py
53cheetah/Tests/Performance.py
54cheetah/Tests/Regressions.py
55cheetah/Tests/SyntaxAndOutput.py
56cheetah/Tests/Template.py
57cheetah/Tests/Test.py
58cheetah/Tests/Unicode.py
59cheetah/Tests/__init__.py
60cheetah/Tests/xmlrunner.py
61cheetah/Tools/CGITemplate.py
62cheetah/Tools/MondoReport.py
63cheetah/Tools/MondoReportDoc.txt
64cheetah/Tools/RecursiveNull.py
65cheetah/Tools/SiteHierarchy.py
66cheetah/Tools/__init__.py
67cheetah/Tools/turbocheetah/__init__.py
68cheetah/Tools/turbocheetah/cheetahsupport.py
69cheetah/Tools/turbocheetah/tests/__init__.py
70cheetah/Tools/turbocheetah/tests/test_template.py
71cheetah/Utils/Indenter.py
72cheetah/Utils/Misc.py
73cheetah/Utils/WebInputMixin.py
74cheetah/Utils/__init__.py
75cheetah/Utils/htmlDecode.py
76cheetah/Utils/htmlEncode.py
77cheetah/Utils/statprof.py
78cheetah/c/Cheetah.h
79cheetah/c/_namemapper.c
80cheetah/c/cheetah.h
0\ No newline at end of file81\ No newline at end of file
182
=== added file 'Cheetah.egg-info/dependency_links.txt'
--- Cheetah.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000
+++ Cheetah.egg-info/dependency_links.txt 2010-10-19 20:12:51 +0000
@@ -0,0 +1,1 @@
1
02
=== added file 'Cheetah.egg-info/requires.txt'
--- Cheetah.egg-info/requires.txt 1970-01-01 00:00:00 +0000
+++ Cheetah.egg-info/requires.txt 2010-10-19 20:12:51 +0000
@@ -0,0 +1,1 @@
1Markdown >= 2.0.1
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'Cheetah.egg-info/top_level.txt'
--- Cheetah.egg-info/top_level.txt 1970-01-01 00:00:00 +0000
+++ Cheetah.egg-info/top_level.txt 2010-10-19 20:12:51 +0000
@@ -0,0 +1,1 @@
1Cheetah
02
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2006-07-26 22:03:15 +0000
+++ MANIFEST.in 2010-10-19 20:12:51 +0000
@@ -1,7 +1,7 @@
1include MANIFEST.in *.py *.cfg TODO CHANGES LICENSE README examples docs bin1include MANIFEST.in *.py *.cfg TODO CHANGES LICENSE README.markdown examples docs bin
2recursive-include src *.py *.tmpl *.txt2recursive-include cheetah *.py *.tmpl *.txt *.h
3recursive-include bin *3recursive-include bin *
4recursive-include docs * 4recursive-include docs *
5recursive-include examples *5recursive-include examples *
6recursive-exclude src *.pyc *~ *.aux6recursive-exclude cheetah *.pyc *~ *.aux
7recursive-exclude docs *~ *.aux7recursive-exclude docs *~ *.aux
88
=== modified file 'PKG-INFO'
--- PKG-INFO 2007-11-18 14:59:06 +0000
+++ PKG-INFO 2010-10-19 20:12:51 +0000
@@ -1,9 +1,9 @@
1Metadata-Version: 1.01Metadata-Version: 1.0
2Name: Cheetah2Name: Cheetah
3Version: 2.0.13Version: 2.4.3
4Summary: Cheetah is a template engine and code generation tool.4Summary: Cheetah is a template engine and code generation tool.
5Home-page: http://www.CheetahTemplate.org/5Home-page: http://www.cheetahtemplate.org/
6Author: Tavis Rudd6Author: R. Tyler Ballance
7Author-email: cheetahtemplate-discuss@lists.sf.net7Author-email: cheetahtemplate-discuss@lists.sf.net
8License: UNKNOWN8License: UNKNOWN
9Description: Cheetah is an open source template engine and code generation tool.9Description: Cheetah is an open source template engine and code generation tool.
@@ -15,7 +15,7 @@
15 Documentation15 Documentation
16 ================================================================================16 ================================================================================
17 For a high-level introduction to Cheetah please refer to the User's Guide17 For a high-level introduction to Cheetah please refer to the User's Guide
18 at http://cheetahtemplate.org/learn.html18 at http://www.cheetahtemplate.org/learn.html
19 19
20 Mailing list20 Mailing list
21 ================================================================================21 ================================================================================
@@ -24,869 +24,12 @@
24 24
25 Credits25 Credits
26 ================================================================================26 ================================================================================
27 http://cheetahtemplate.org/credits.html27 http://www.cheetahtemplate.org/credits.html
28
29 Praise
30 ================================================================================
31 "I'm enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging
32 Technologies Group & director of Apache Software Foundation
33
34 "Give Cheetah a try. You won't regret it. ... Cheetah is a truly powerful
35 system. ... Cheetah is a serious contender for the 'best of breed' Python
36 templating." - Alex Martelli
37
38 "People with a strong PHP background absolutely love Cheetah for being Smarty,
39 but much, much better." - Marek Baczynski
40
41 "I am using Smarty and I know it very well, but compiled Cheetah Templates with
42 its inheritance approach is much powerful and easier to use than Smarty." -
43 Jaroslaw Zabiello
44
45 "There is no better solution than Cheetah" - Wilk
46
47 "A cheetah template can inherit from a python class, or a cheetah template, and
48 a Python class can inherit from a cheetah template. This brings the full power
49 of OO programming facilities to the templating system, and simply blows away
50 other templating systems" - Mike Meyer
51
52 "Cheetah has successfully been introduced as a replacement for the overweight
53 XSL Templates for code generation. Despite the power of XSL (and notably XPath
54 expressions), code generation is better suited to Cheetah as templates are much
55 easier to implement and manage." - The FEAR development team
56 (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573)
57
58 "I've used Cheetah quite a bit and it's a very good package" - Kevin Dangoor,
59 lead developer of TurboGears.
60 28
61 Recent Changes29 Recent Changes
62 ================================================================================30 ================================================================================
63 See http://cheetahtemplate.org/docs/CHANGES for full details.31 See http://www.cheetahtemplate.org/CHANGES.txt for full details
64 32
65 Please initial your changes (there's a key at bottom) and add a date for each
66 release
67 ================================================================================
68
69 2.0.1 (Nov 16, 2007)
70 - fixed a deadlock Christoph Zwerschke found in Cheetah.ImportHooks.
71 [TR]
72
73 2.0 (Oct 12, 2007)
74 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
75
76 - fixed exception handling issue in the C implemenation of NameMapper
77 [patch from Eric Huss]
78
79 - fixed filtering of #included subtemplates
80 [patch from Brian Bird]
81
82 See the release notes from 2.0b1-5 and 2.0rc1-8 for other changes since
83 Cheetah 1.0.
84
85
86 2.0rc8 (April 11, 2007)
87 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
88 Core Changes: [TR]
89
90 - added a '#unicode <encoding>' directive to indicate that the output of the
91 template should be a unicode string even if the template source is a
92 normal byte string.
93
94 - #unicode and #encoding are mutually exclusive. Use one or the other.
95 - #unicode must be on a line by itself.
96 - Strings in embedded code must be explictly marked as unicode if they
97 contain non-ascii chars:
98
99 #unicode latin-1
100 $f(u"<some non-ascii char>") ## right
101 $f("<some non-ascii char>") ## wrong
102
103 However, this works fine:
104
105 #unicode latin-1
106 blah blah <some non-ascii char> blah blah
107
108 - fixed several unicode bugs in the compiler.
109
110 - fixed some unicode issues in the standard filters.
111
112 - fixed a few minor bugs in code that never gets called. Thanks to
113 Alejandro Dubrovsky for pointing them out.
114
115 - make RawOrEncodedUnicode the baseclass of all filters and remove some
116 unused/redudant filters
117
118 - added new compiler setting 'addTimestampsToCompilerOutput'. See Brian
119 Bird's post about it. He stores his cheetah generated .py files in
120 subversion and needed to disable the timestamp code so svn wouldn't care
121 when he recompiles those .py modules.
122
123 - added the #super directive, which calls the method from the parent class
124 which has the same as the current #def or #block method.
125
126 #def foo
127 ... child output
128 #super ## includes output of super(<CurrentClass>, self).foo()
129 ... child output
130 #end def
131
132
133 #def bar(arg)
134 ... child output
135 #super(arg) ## includes output of super(<CurrentClass>, self).bar(arg)
136 ... child output
137 #end def
138
139 - added some unit tests for the new directives
140
141
142 2.0rc7 (July 4, 2006)
143 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
144 Core Changes: [TR]
145 - extended the #implements directive so an arguments list can be declared in
146 the same fashion as #def and #block.
147
148 - made the parser raise ParseError when $*placeholder, $*5*placeholder,
149 $(placeholder), etc. are found within expressions. They are only valid in
150 top-level text.
151
152 - tweaked the parser so it's possible to place a comment on the same line as
153 a directive without needing to explicitly close the directive first. This
154 works regardless of whether or not you added a colon.
155
156 self.verify("#if 1:\n$aStr\n#end if\n",
157 "blarg\n")
158
159 self.verify("#if 1: \n$aStr\n#end if\n",
160 "blarg\n")
161
162 self.verify("#if 1: ##comment \n$aStr\n#end if\n",
163 "blarg\n")
164
165 self.verify("#if 1 ##comment \n$aStr\n#end if\n",
166 "blarg\n")
167
168 Previously, that last test would have required an extra # to close the #if
169 directive before the comment directive started:
170 self.verify("#if 1 ###comment \n$aStr\n#end if\n",
171 "blarg\n")
172
173 Code that makes use of explicit directive close tokens immediately followed by
174 another directive will still work as expected:
175 #if test##for i in range(10)# foo $i#end for##end if
176
177 - safer handling of the baseclass arg to Template.compile(). It now does
178 the right thing if the user passes in an instance rather than a class.
179
180 ImportHooks: [TR]
181 - made it possible to specify a list of template filename extentions that are
182 looped through while searching for template modules. E.g.:
183 import Cheetah.ImportHooks
184 Cheetah.ImportHooks.install(templateFileExtensions=('.tmpl','.cheetah'))
185
186 Core changes by MO:
187 - Filters are now new-style classes.
188 - WebSafe and the other optional filters in Filters.py now use
189 RawOrEncodedUnicode instead of Filter as a base class. This allows them
190 to work with Unicode values containing non-ASCII characters.
191 User-written custom filters should inherit from
192 RawOrEncodedUnicode and call the superclass .filter() instead of str().
193 str() as of Python 2.4.2 still converts Unicode to string using
194 ASCII codec, which raises UnicodeEncodeError if it contains non-ASCII
195 characters.
196
197 2.0rc6 (Feb 4, 2006)
198 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
199 Core Changes: [TR]
200 - added a Cheetah version dependency check that raises an assertion if a
201 template was compiled with a previous version of Cheetah whose templates
202 must be recompiled.
203
204 - made the Cheetah compilation metadata accessible via class attributes in
205 addition to module globals
206
207 - major improvement to exception reporting in cases where bad Python syntax
208 slips past the Cheetah parser:
209 """
210 File "/usr/lib/python2.4/site-packages/Cheetah/Template.py", line 792, in compile
211 raise parseError
212 Cheetah.Parser.ParseError:
213
214 Error in the Python code which Cheetah generated for this template:
215 ================================================================================
216
217 invalid syntax (DynamicallyCompiledCheetahTemplate.py, line 86)
218
219 Line|Python Code
220 ----|-------------------------------------------------------------
221 84 |
222 85 | write('\n\n')
223 86 | for i an range(10): # generated from line 4, col 1
224 ^
225 87 | _v = i # '$i' on line 5, col 3
226 88 | if _v is not None: write(_filter(_v, rawExpr='$i')) # from line 5, col 3.
227 89 | write('\n')
228
229 ================================================================================
230
231 Here is the corresponding Cheetah code:
232
233 Line 4, column 1
234
235 Line|Cheetah Code
236 ----|-------------------------------------------------------------
237 2 |#compiler useNameMapper=False
238 3 |
239 4 |#for i an range(10)
240 ^
241 5 | $i
242 6 |#end for
243 7 |
244 """
245
246 2.0rc5 (Feb 3, 2006)
247 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
248 Core Changes: [TR]
249 - fixed a memory leak in Template.compile(), reported by Andrea Arcangeli
250 - simplified concurrency locking and compile caching in Template.compile()
251
252 The command line tool (CheetahWrapper.py):
253 - added new option --settings for supplying compiler settings
254 - added new option --templateAPIClass to replace the environment var
255 CHEETAH_TEMPLATE_CLASS lookup I added in 2.0b1
256
257 2.0rc4 (Jan 31, 2006)
258 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
259 Core Changes: [TR]
260 - fixed a typo-bug in the compile hashing code in Template.compile()
261 - improved the macros framework and made it possible to implement macros in
262 Python code so they can be shared between templates
263 - more work on the #i18n directive. It's now a macro directive.
264 - added new Cheetah.Macros package
265 - more tests
266
267 2.0rc3 (Jan 29, 2006)
268 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
269 Core Changes: [TR]
270 - added short-form single line versions of all directives that have an #end
271 tag, except for #errorCatcher:
272 #if, #else, #elif, #unless,
273 #for, #while, #repeat,
274 #try, #except, #finally,
275 #cache, #raw
276 #call, #capture
277
278 The #def and #block directives already had single-line versions.
279 #if cond: foo
280 #elif cond2: bar
281 #else: blarg
282
283 #for i, val in enumerate(vals): $i-$val
284
285 Note that if you accidentally leave a colon at the end of one of these
286 directives but nothing else follows it, aside from whitespace, the parser
287 will treat it as a normal multi-line directive.
288
289 The first leading space after the colon is discarded. Any additional
290 spaces will be included in the output.
291
292 Also note, if you use the short form versions of #if/#else/#elif you must
293 it for all three. The following is not valid:
294 #if cond: foo
295 #elif cond2
296 bar
297 #else: blarg
298
299 - added support for $!silentModePlaceholders
300 This is the same as quiet mode in Velocity:
301 http://jakarta.apache.org/velocity/docs/user-guide.html#Quiet%20Reference%20Notation
302
303 - added support for function/method @decorators. It also works with blocks.
304 As in vanilla Python, the @decorator statement must be followed with a
305 function/method definition (i.e. #def or #block).
306
307 #from xxx import aDecorator
308 ...
309 ...
310 #@aDecorator
311 #def func
312 foo
313 #end def
314
315 #@aDecorator
316 #def singleLineShortFormfunc: foo
317
318 #@aDecorator
319 #block func2
320 bar
321 #end block
322
323 - added a new callback hook 'handlerForExtendsDirective' to the compiler settings. It
324 can be used to customize the handling of #extends directives. The
325 callback can dynamically add import statements or rewrite the baseclass'
326 name if needed:
327 baseClassName = handler(compiler, baseClassName)
328 See the discussion on the mailing list on Jan 25th for more details.
329
330 - changed the default filter to the one that doesn't try to encode Unicode
331 It was 'EncodeUnicode' and is now 'RawOrEncodedUnicode'.
332
333 - added optional support for parsing whitespace between the directive start
334 token (#) and directive names, per Christophe Eymard's request. For the
335 argument behind this see the mailing list archives for Jan 29th. This is
336 off by default. You must turn it on using the compiler setting
337 allowWhitespaceAfterDirectiveStartToken=True
338
339 #for $something in $another
340 # for $somethin2 in $another2
341 blahblah $something in $something2
342 # end for
343 #end for
344
345 - made the handling of Template.compile()'s preprocessors arg simpler and
346 fixed a bug in it.
347
348 - fixed attribute name bug in the .compile() method (it affected the feature
349 that allows generated module files to be cached for better exception
350 tracebacks)
351
352 - refactored the #cache/CacheRegions code to support abitrary backend cache
353 data stores.
354
355 - added MemcachedCacheStore, which allows cache data to be stored in a
356 memcached backend. See http://www.linuxjournal.com/article/7451 and
357 http://www.danga.com/memcached/. This is only appropriate for systems
358 running many Python server processes that need to share cached data to
359 reduce memory requirements. Don't bother with this unless you actually
360 need it. If you have a limited number of Python server processes it is
361 much faster, simpler, and more secure to just cache in the memory of each
362 process.
363
364 KEEP MEMCACHED'S LIMITED SECURITY IN MIND!! It has no authentication or
365 encryption and will introduce a gaping hole in your defenses unless you
366 are careful. If you are caching sensitive data you should take measures
367 to ensure that a) untrusted local system users cannot connect to memcached
368 server, b) untrusted external servers cannot connect, and c) untrusted
369 users on trusted external servers cannot connect. Case (a) can be dealt
370 with via iptable's owner match module for one way to do this: "iptables -A
371 ... -m owner ..." Cases (b) and (c) can be handled by tunnelling
372 memcached network connections over stunnel and implementing stunnel
373 authentication with mandatory peer/client certs.
374
375 - some under-the-hood refactoring of the parser
376
377 - made it possible to add custom directives, or customize the
378 parsing/handling of existing ones, via the compiler settings
379 'directiveNamesAndParsers' and 'endDirectiveNamesAndHandlers'
380
381 - added a compile-time macro facility to Cheetah. These macros are very
382 similar to macros in Lisp:
383 http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html.
384
385 As with Lisp macros, they take source code (Cheetah source) as input and
386 return source code (again Cheetah source) as output. They are executed at
387 compile-time, just like in Lisp and C. The resultant code
388 gets executed at run-time.
389
390 The new #defmacro directive allows users to create macros inside the
391 source of their templates. Macros can also be provided via the compiler
392 setting 'macroDirectives'. The 'macroDirectives' setting allows you to
393 share common macros between templates.
394
395 The syntax for the opening tag of #defmacro is the same as for #def and
396 #block. It expects a macro name followed by an optional argument list in
397 brackets. A `src` argument is automatically added to the beginning of
398 every macro's argument list. The value of the `src` is the block of
399 input source code that is provided during a macro call (see below).
400
401 #defmacro <macroname>[(argspec)]
402 <macrobody>
403 #end defmacro
404
405 All of Cheetah's syntax is available for use inside macros, but the
406 placeholderStartToken is @ instead of $ and the
407 directiveStartToken/EndToken is % instead of #. Any syntax using the
408 standard $/# tokens will be treated as plain text and included in the output
409 of the macro.
410
411 Here are some examples:
412 #defmacro addHeaderFooter
413 header
414 @src
415 footer
416 #end defmacro
417
418 #defmacro addHeaderFooter(header='h', footer='f')
419 @header
420 @src
421 @footer
422 #end defmacro
423
424 There is a single-line short form like for other directives:
425
426 #defmacro addHeaderFooter: header @src footer
427 #defmacro addHeaderFooter(header='h', footer='f'): @header @src @footer
428
429 The syntax for calling a macro is similar to the simplest usage of the
430 #call directive:
431
432 #addHeaderFooter
433 Source $code to wrap
434 #end addHeaderFooter
435
436 #addHeaderFooter: Source $code to wrap
437
438 #addHeaderFooter header='header', footer='footer: Source $code to wrap
439
440
441 In Elisp you write
442 (defmacro inc (var)
443 (list 'setq var (list '1+ var)))
444 to define the macro `inc` and write
445 (inc x)
446 which expands to
447 (setq x (1+ x))
448
449 In Cheetah you'd write
450 #defmacro inc: #set @src +=1
451 #inc: $i
452 which expands to
453 #set $i += 1
454
455 print Template("""\
456 #defmacro inc: #set @src +=1
457 #set i = 1
458 #inc: $i
459 $i""").strip()==2
460
461 - fixed some bugs related to advanced usage of Template.compile(). These
462 were found via new unit tests. No one had actually run into them yet.
463
464 - added the initial bits of an #i18n directive. It has the same semantics
465 as
466 #call self.handleI18n
467 Some $var cheetah source
468 #end call
469 but has a simpler syntax:
470 #i18n
471 Some $var cheetah source
472 #end i18n
473
474 ## single-line short form:
475 #i18n: Some $var cheetah source
476
477 The method it calls, self.handleI18n, is just a stub at the moment, but it
478 will soon be a wrapper around gettext. It currently has one required
479 positional argument `message`. I anticipate supporting the following
480 optional arguments:
481
482 id = msgid in the translation catalog
483 domain = translation domain
484 source = source lang
485 target = a specific target lang
486 comment = a comment to the translation team
487
488 plural = the plural form of the message
489 n = a sized argument to distinguish between single and plural forms
490
491 #i18n is executed at runtime, but it can also be used in conjunction with
492 a Cheetah preprocessor or macro (see above) to support compile time
493 translation of strings that don't have to deal with plural forms.
494
495 - added Cheetah.Utils.htmlEncode and Cheetah.Utils.htmlDecode
496
497 - more docstring text
498
499 Unit tests: [TR]
500 - extended the caching tests
501 - added tests for the various calling styles of Template.compile()
502 - added copies of all the SyntaxAndOutput tests that use a template
503 baseclass other than `Template`. This ensures that all syntax & core
504 features work with 2.0's support for arbitrary baseclasses.
505 - added tests for all the new directives and the new single-line short forms
506
507 2.0rc2 (Jan 13th, 2006)
508 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
509 Core Changes: [TR]
510 - fixed some python 2.4isms that slipped in. All the tests pass with Python
511 2.2 now
512 - added lots more docstring content in the Template class
513 - made multiline comments gobble whitespace like other directives, per JJ's
514 request. The rather longwinded compiler setting
515 gobbleWhitespaceAroundMultiLineComments can be used to go back to the old
516 non-gobbling behaviour if needed.
517 - added #capture directive to complement the #call directive.
518 #call executes a region of Cheetah code and passes its output into a function call
519 #capture executes a region of Cheetah code and assigns its output to a variable
520 - extended the compile caching code in Template.compile so it works with the
521 'file' arg.
522 - added cacheModuleFilesForTracebacks and cacheDirForModuleFiles args to
523 Template.compile(). See the docstring for details.
524 - misc internal refactoring in the parser
525 - improved handling of keyword args in the __init__ method and fixed a
526 potential clash between the namespaces and searchList args
527
528 WWW: [TR]
529 - added the source for the new Cheetah website layout/content
530
531 2.0rc1 (Jan 10, 2006)
532 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
533 Core Changes: [TR]
534 - made it possible nest #filter directives
535 - added lots more docstring content in the Template class
536 - added Template.subclass() classmethod for quickly creating subclasses of
537 existing Cheetah template classes. It takes the same args as the
538 .compile() classmethod and returns a template that is a subclass of the
539 template .subclass() is called from:
540 T1 = Template.compile(' foo - $meth1 - bar\n#def meth1: this is T1.meth1')
541 T2 = T1.subclass('#implements meth1\n this is T2.meth1')
542
543 - added baseclass arg to Template.compile(). It simplifies the reuse of
544 dynamically compiled templates:
545 # example 1, quickly subclassing a normal Python class and using its
546 # __init__ call signature:
547 dictTemplate = Template.compile('hello $name from $caller', baseclass=dict)
548 print dictTemplate(name='world', caller='me')
549
550 # example 2, mixing a Cheetah method into a class definition:
551 class Foo(dict):
552 def meth1(self):
553 return 'foo'
554 def meth2(self):
555 return 'bar'
556 Foo = Template.compile('#implements meth3\nhello $name from $caller',
557 baseclass=Foo)
558 print Foo(name='world', caller='me')
559
560 A side-benefit is the possibility to use the same Cheetah source with
561 several baseclass, as the baseclass is orthogonal to the source code,
562 unlike the #extends directive.
563
564 - added 'namespaces' as an alias for 'searchList' in Template.__init__
565 - made it possible to pass in a single namespace to 'searchList', which will
566 automatically be converted into a list.
567 - fixed issue with buffering and use of #call when template is used as a
568 webkit servlet
569 - added Cheetah.Utils.htmlEncode and htmlDecode
570
571 The command line tool (CheetahWrapper.py):
572 - changed insertion order for the --env and --pickle options so they match the
573 commandline UI of the compiled template modules themselves [TR]
574
575 2.0b5 (Jan 7, 2006)
576 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
577 Core Changes: [TR]
578 - made Cheetah.Template a new-style class by inserting 'object' into its'
579 inheritance tree. Templates can now use super(), properties and all the
580 other goodies that come with new-style classes.
581 - removed the WebInputMixin by placing its one method directly in the
582 Template class.
583 - removed the SettingsManager Mixin. It wasn't being used by anything
584 anymore.
585 - added a framework for caching the results of compilations in
586 Template.compile(). This is on by default and protects against bad
587 performance issues that are due to programmers misguidedly compiling
588 templates inside tight loops. It also saves on memory usage.
589 - misc attr name changes to avoid namespace pollution
590 - more + improved docstrings
591 - replaced the oldstyle dynamic compile hacks with a wrapper around
592 Template.compile(). The old usage pattern Template(src) now benefits from
593 most of the recent changes.
594 Template(src).__class__ == Template.compile(src)
595 - removed all the extra imports required by oldstyle dynamic compile hacks
596 - converted the cheetah #include mechanism to newstyle compilation and made it
597 more flexible
598 - made the #include mechanism work with file objects in addition to file names
599 - made the handling of args to Template.compile() more flexible. You can now
600 provide defaults via class attributes.
601 - made preprocessors for Template.compile() work with file arguments
602 - added support for specifying a __metaclass__ on cheetah template classes
603 - refactored both the class and instance initialization processes
604 - improved the handling of __str__ in _assignRequiredMethodsToClass
605
606 The command line tool (CheetahWrapper.py): [TR]
607 - improved error output in CheetahWrapper
608 - switched fill command over to new style compile usage
609
610 Unit tests: [TR]
611 - fixed format string bug in unittest_local_copy.py
612
613 2.0b4 (Jan 6, 2006)
614 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
615 Core Changes: [TR]
616 - fixed up parsing of target lists in for loops. This was previously limited
617 to fairly simple target lists.
618 #for ($i, $j) in [('aa','bb'),('cc','dd')]
619 $i.upper,$j.upper
620 #end for"
621 #for (i, j) in [('aa','bb'),('cc','dd')]
622 $i.upper,$j.upper
623 #end for"
624 #for i,(j, k) in enumerate([('aa','bb'),('cc','dd')])
625 $j.upper,$k.upper
626 #end for"
627 - refactored the class initialization process
628 - improved handling of target lists in #set directive. This was previously
629 limited to fairly simple target lists.
630 #set i,j = [1,2] ... #set $i,$j = [1,2]
631 #set (i,j) = [1,2] ... #set ($i,$j) = [1,2]
632 #set i, (j,k) = [1,(2,3)] ... #set $i, ($j,$k) = [1,(2,3)]
633
634 - made it possible for the expressionFilter hooks to modify the code chunks
635 they are fed. Also documented the hooks in a docstring. Thus the hooks
636 can be used as preprocessors for expressions, 'restricted execution', or
637 even enforcement of style guidelines.
638
639 - removed cheetah junk from docstrings and placed it all in comments or
640 __moduleVars__. Per JJ's suggestion.
641
642 - made it possible to nest #cache directives to any level
643 - made it possible to nest #call directives to any level
644
645 Unit Tests [TR]
646 - extended tests for #for directive
647 - expanded tests for #set directive
648 - expanded tests for #call directive
649 - expanded tests for #cache directive
650 - added basic tests for the new $placeholder string expressions:
651 c'text $placeholder text'
652
653 2.0b3 (Jan 5, 2006)
654 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
655 Core Changes: [TR]
656 - added #yield statement
657 - added ability to create nested scopes/functions via nested #def statements
658 - added new #call directive and related #arg directive, per Ian Bicking's
659 suggestion.
660 - added new expression syntax c"text $placeholder text"
661
662 for those basic function calling cases where you just need to pass in a
663 small bit of cheetah output as an argument:
664
665 c'a string with $placeholders',
666 c'''a string with $placeholders''',
667 c"a string with $placeholders",
668 c"""a string with $placeholders"""
669
670 - They can't contain #directives, but accept any valid $placeholder syntax
671 except caching placeholders. Caching placeholders don't make any sense in
672 this context.
673 - They can be used *any* place where a python expression is expected.
674 - They can be nested to any depth.
675
676 $func(c'<li>$var1-$var2</li>')
677 $func(c'<li>$var1-$var2</li>', doSomething=True)
678 $func(content=c'<li>$var1-$var2</li>', doSomething=True)
679 $func(lambda x,y: c'<li>$x-$y</li>')
680 $func(callback=lambda x,y: c'<li>$x-$y</li>')
681 $func(lambda x,y: c'<li>$x-$y-$varInSearchList</li>')
682 $func(c'<li>$var1-$var2-$(var3*10)-$(94.3*58)</li>')
683 $func(c'<li>$var1-$var2-$func2(c"a nested expr $var99")</li>')
684 #if $cond then c'<li>$var1-$var2</li>' else c'<p>$var1-$var2</p>'
685 #def foo(arg1=c'$var1<span class="foo">$var2</span>'): blah $arg1 blah
686 $foo(c'$var1<i>$var2</i>')
687
688 - added preprocessor hooks to Template.compile()
689 can be used for partial completion or 'compile-time-caching'
690 ... more details and examples coming. It's very useful, but takes a bit
691 of explaining.
692 - added '#set module varName = expr' for adding module globals. JJ's suggestion
693 - improved generated docstring notes about cached vars
694 - fixed silly bug related to """ in docstring comments and statements like
695 this '#def foo: $str("""foo""")'. Reported by JJ.
696 - changed the handling of single-line defs so that
697 '#def xxx:<just whitespace>\n' will be treated as a multi-line #def.
698 The same applies to #block. There's a compiler setting to turn this off
699 if you really need empty single-line #def:'s.
700 JJ reported that this was causing great confusion with beginners.
701 - improved error message for unclosed directives, per Mike Orr's suggestion.
702 - added optional support for passing the trans arg to methods via **KWS rather
703 than trans=None. See the discussion on the mailing list Jan 4th (JJ's post) for
704 details. The purpose is to avoid a positional argument clash that
705 apparently is very confusing for beginners.
706
707 Note that any existing client code that passing the trans arg in
708 positionally rather than as a keyword will break as a result. WebKit
709 does this with the .respond method so I've kept the old style there.
710 You can also turn this new behaviour off by either manually including
711 the trans arg in your method signature (see the example below) or by
712 using the compiler setting 'useKWsDictArgForPassingTrans'=False.
713
714 #def manualOverride(arg1, trans=None)
715 foo $arg1
716 #end def
717
718 ImportHooks:
719 - made the ImportHook more robust against compilation errors during import [TR]
720
721 Install scripts: [TR]
722 - added optional support for pje's setuptools
723 - added cheeseshop classifiers
724 - removed out of date install instructions in __init__.py
725
726 Servlet Base Class For Webkit: [TR]
727 - disabled assignment of self.application (was a webware hack)
728
729 Unit Tests: [TR]
730 - unit tests for most of the new syntax elements
731 - tidied up some old tests
732 - misc refactoring
733
734 2.0b2 (Dec 30, 2005)
735 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
736
737 Core Changes:
738 - In previous versions of Cheetah tracebacks from exceptions that were raised
739 inside dynamically compiled Cheetah templates were opaque because
740 Python didn't have access to a python source file to use in the traceback:
741
742 File "xxxx.py", line 192, in getTextiledContent
743 content = str(template(searchList=searchList))
744 File "cheetah_yyyy.py", line 202, in __str__
745 File "cheetah_yyyy.py", line 187, in respond
746 File "cheetah_yyyy.py", line 139, in writeBody
747 ZeroDivisionError: integer division or modulo by zero
748
749 It is now possible to keep the generated source code from the python
750 classes returned by Template.compile() in a cache dir. Having these files
751 around allows Python to include the actual source lines in tracebacks and
752 makes them much easier to understand:
753
754 File "/usr/local/unsnarl/lib/python/us/ui/views/WikiPageRenderer.py", line 192, in getTextiledContent
755 content = str(template(searchList=searchList))
756 File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 202, in __str__
757 def __str__(self): return self.respond()
758 File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 187, in respond
759 self.writeBody(trans=trans)
760 File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 139, in writeBody
761 __v = 0/0 # $(0/0)
762 ZeroDivisionError: integer division or modulo by zero
763
764 This is turned off by default. To turn it on, do this:
765
766 class NiceTracebackTemplate(Template):
767 _CHEETAH_cacheModuleFilesForTracebacks = True
768 _CHEETAH_cacheDirForModuleFiles = '/tmp/CheetahCacheDir' # change to a dirname
769
770 templateClass = NiceTracebackTemplate.compile(src)
771
772 # or
773 templateClass = Template.compile(src,
774 cacheModuleFilesForTracebacks=True, cacheDirForModuleFiles='/tmp/CheetahCacheDir')
775
776
777 This only works with the new Template.compile(src) usage style!
778
779 Note, Cheetah generated modules that are compiled on the command line have
780 never been affected by this issue. [TR]
781
782 - added an extra comment per $placeholder to generated python code so it is
783 easier to grok. [TR]
784
785 2.0b1 (Dec 29, 2005)
786 !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!!
787
788 Core Changes:
789 - enabled use of any expression in ${placeholders}. See the examples I posted to
790 the email list on Dec 12th. All use cases of the #echo directive can now
791 be handled with ${placeholders}. This came from a suggestion by Mike
792 Orr. [TR]
793
794 - made it possible for templates to #extend (aka subclass) any arbitrary
795 baseclass, including Python's new style classes. You must either compile
796 your classes on the command line or use the new classmethod
797 Template.compile() as described below. The old Template(src) interface
798 still works, provided you don't try to use this new arbitrary baseclass
799 stuff. See my messages to the email list for more details. [TR]
800
801 - made it possible to create template classes dynamically, rather than just
802 instances. See the new classmethod Template.compile(). See my messages
803 to the email list for more details. [TR]
804
805 klass = Template.compile(src)
806
807 - made it easier to work with custom compiler settings, particularly from
808 the command line tool. You can now define a subclass of Template which
809 will compile your templates using custom compilerSettings, or even a
810 custom compiler class, without requiring you to manually pass in your
811 compilerSettings each time or define them in the template src itself via
812 the #compiler directive. You can make the command line tool use your
813 subclass by defining the environment variable CHEETAH_TEMPLATE_CLASS. It
814 should be in the form 'package.module:class'. See my messages
815 to the email list for more details. [TR]
816
817 - made it possible to pass the searchList in as an argument to #def'ined
818 methods. This makes all lookup that occur within the scope of that method
819 use the provided searchList rather than self._searchList. This does not
820 carry over to other methods called within the top method, unless they
821 explicitly accept the searchList in their signature AND you pass it to
822 them when calling them. This behaviour can be turned off with the
823 corresponding compilerSetting 'allowSearchListAsMethArg' [TR]
824
825 - added hooks for filtering / restricting dangerous stuff in cheetah source
826 code at compile time. These hooks can be used to enable Cheetah template
827 authoring by untrusted users. See my messages to the email list for more
828 details. Note, it filters expressions at parse/compile time, unlike Python's
829 old rexec module which restricted the Python environment at runtime. [TR]
830
831 # Here are the relevant compiler settings:
832 # use lower case keys here!!
833 'disabledDirectives':[], # list of directive keys, without the start token
834 'enabledDirectives':[], # list of directive keys, without the start token
835
836 'disabledDirectiveHooks':[], # callable(parser, directiveKey),
837 # called when a disabled directive is found, prior to raising an exception
838
839 'preparseDirectiveHooks':[], # callable(parser, directiveKey)
840 'postparseDirectiveHooks':[], # callable(parser, directiveKey)
841
842 'preparsePlaceholderHooks':[], # callable(parser)
843 'postparsePlaceholderHooks':[], # callable(parser)
844
845 'expressionFilterHooks':[],
846 # callable(parser, expr, exprType, rawExpr=None, startPos=None)
847 # exprType is the name of the directive, 'psp', or 'placeholder'.
848 #all lowercase
849
850 - added support for a short EOLSlurpToken to supplement the #slurp
851 directive. It's currently re.compile('#\s*\n') (i.e # followed by
852 arbitrary whitespace and a new line), but this is not set in stone. One
853 other suggestion was the backslash char, but I believe Python's own
854 interpretation of backslashes will lead to confusion. The compiler
855 setting 'EOLSlurpToken' controls this. You can turn it off completely by
856 setting 'EOLSlurpToken' to None. See the email list for more details. [TR]
857
858 - added '_CHEETAH_' prefix to all instance attribute names in compiled
859 templates. This is related to the arbitrary baseclass change. [TR]
860
861 - shifted instance attribute setup to _initCheetahAttributes() method. This
862 is related to the arbitrary baseclass change. [TR]
863
864 - made it possible to use full expressions in the #extends directive, rather
865 than just dotted names. This allows you to do things like this:
866
867 #from xx.TemplateRepository import getTemplateClass
868 #extends getTemplateClass('someName')
869
870 I don't expect this to be used much. I needed it for a wiki system in
871 which the baseclasses for the templates are dynamically compiled at run
872 time and are not available via simple imports. [TR]
873
874 - added compiler setting autoImportForExtendDirective=True, so this existing
875 default behaviour can be turned off when needed. [TR]
876
877 - fixed a bug in the parsing of single-line #def's and #block's when they
878 are enclosed within #if ... #end if. Reported by Marcin Gajda [TR]
879
880 - tweak to remove needless write('') calls in generated code [TR]
881
882 The command line tool (CheetahWrapper.py):
883 - added code to cleanup trailing slashes on path arguments (code originally
884 from Mike Orr) [TR]
885 - turned on the ImportHooks by default for the 'cheetah fill' command. See the
886 discussion on the email list [TR]
887
888 ImportHooks:
889 - fixed a name error bug in the ImportHooks [TR]
890 33
891Platform: UNKNOWN34Platform: UNKNOWN
892Classifier: Development Status :: 5 - Production/Stable35Classifier: Development Status :: 5 - Production/Stable
89336
=== removed file 'README'
--- README 2006-07-26 22:03:15 +0000
+++ README 1970-01-01 00:00:00 +0000
@@ -1,51 +0,0 @@
1Cheetah is an open source template engine and code generation tool.
2
3It can be used standalone or combined with other tools and frameworks. Web
4development is its principle use, but Cheetah is very flexible and is also being
5used to generate C++ game code, Java, sql, form emails and even Python code.
6
7Documentation
8================================================================================
9For a high-level introduction to Cheetah please refer to the User\'s Guide
10at http://cheetahtemplate.org/learn.html
11
12Mailing list
13================================================================================
14cheetahtemplate-discuss@lists.sourceforge.net
15Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
16
17Credits
18================================================================================
19http://cheetahtemplate.org/credits.html
20
21Praise
22================================================================================
23"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging
24Technologies Group & director of Apache Software Foundation
25
26"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful
27system. ... Cheetah is a serious contender for the 'best of breed' Python
28templating." - Alex Martelli
29
30"People with a strong PHP background absolutely love Cheetah for being Smarty,
31but much, much better." - Marek Baczynski
32
33"I am using Smarty and I know it very well, but compiled Cheetah Templates with
34its inheritance approach is much powerful and easier to use than Smarty." -
35Jaroslaw Zabiello
36
37"There is no better solution than Cheetah" - Wilk
38
39"A cheetah template can inherit from a python class, or a cheetah template, and
40a Python class can inherit from a cheetah template. This brings the full power
41of OO programming facilities to the templating system, and simply blows away
42other templating systems" - Mike Meyer
43
44"Cheetah has successfully been introduced as a replacement for the overweight
45XSL Templates for code generation. Despite the power of XSL (and notably XPath
46expressions), code generation is better suited to Cheetah as templates are much
47easier to implement and manage." - The FEAR development team
48 (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573)
49
50"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor,
51lead developer of TurboGears.
520
=== added file 'README.markdown'
--- README.markdown 1970-01-01 00:00:00 +0000
+++ README.markdown 2010-10-19 20:12:51 +0000
@@ -0,0 +1,51 @@
1Cheetah is an open source template engine and code generation tool.
2
3It can be used standalone or combined with other tools and frameworks. Web
4development is its principle use, but Cheetah is very flexible and is also being
5used to generate C++ game code, Java, sql, form emails and even Python code.
6
7Documentation
8================================================================================
9For a high-level introduction to Cheetah please refer to the User\'s Guide
10at http://cheetahtemplate.org/learn.html
11
12Mailing list
13================================================================================
14cheetahtemplate-discuss@lists.sourceforge.net
15Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
16
17Credits
18================================================================================
19http://cheetahtemplate.org/credits.html
20
21Praise
22================================================================================
23"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging
24Technologies Group & director of Apache Software Foundation
25
26"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful
27system. ... Cheetah is a serious contender for the 'best of breed' Python
28templating." - Alex Martelli
29
30"People with a strong PHP background absolutely love Cheetah for being Smarty,
31but much, much better." - Marek Baczynski
32
33"I am using Smarty and I know it very well, but compiled Cheetah Templates with
34its inheritance approach is much powerful and easier to use than Smarty." -
35Jaroslaw Zabiello
36
37"There is no better solution than Cheetah" - Wilk
38
39"A cheetah template can inherit from a python class, or a cheetah template, and
40a Python class can inherit from a cheetah template. This brings the full power
41of OO programming facilities to the templating system, and simply blows away
42other templating systems" - Mike Meyer
43
44"Cheetah has successfully been introduced as a replacement for the overweight
45XSL Templates for code generation. Despite the power of XSL (and notably XPath
46expressions), code generation is better suited to Cheetah as templates are much
47easier to implement and manage." - The FEAR development team
48 (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573)
49
50"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor,
51lead developer of TurboGears.
052
=== modified file 'SetupConfig.py'
--- SetupConfig.py 2006-07-26 22:03:15 +0000
+++ SetupConfig.py 2010-10-19 20:12:51 +0000
@@ -1,17 +1,18 @@
1#-------Main Package Settings-----------#1#-------Main Package Settings-----------#
2name = "Cheetah"2import sys
3from src.Version import Version as version3
4maintainer = "Tavis Rudd"4name = 'Cheetah'
5from cheetah.Version import Version as version
6maintainer = "R. Tyler Ballance"
5author = "Tavis Rudd"7author = "Tavis Rudd"
6author_email = "cheetahtemplate-discuss@lists.sf.net"8author_email = "cheetahtemplate-discuss@lists.sf.net"
7url = "http://www.CheetahTemplate.org/"9url = "http://www.cheetahtemplate.org/"
8packages = ['Cheetah',10packages = ['Cheetah',
9 'Cheetah.Macros', 11 'Cheetah.Macros',
10 'Cheetah.Templates',12 'Cheetah.Templates',
11 'Cheetah.Tests',13 'Cheetah.Tests',
12 'Cheetah.Tools',14 'Cheetah.Tools',
13 'Cheetah.Utils',15 'Cheetah.Utils',
14 'Cheetah.Utils.optik',
15 ]16 ]
16classifiers = [line.strip() for line in '''\17classifiers = [line.strip() for line in '''\
17 #Development Status :: 4 - Beta18 #Development Status :: 4 - Beta
@@ -28,30 +29,50 @@
28 Topic :: Software Development :: Libraries :: Python Modules29 Topic :: Software Development :: Libraries :: Python Modules
29 Topic :: Software Development :: User Interfaces30 Topic :: Software Development :: User Interfaces
30 Topic :: Text Processing'''.splitlines() if not line.strip().startswith('#')]31 Topic :: Text Processing'''.splitlines() if not line.strip().startswith('#')]
31del line
3232
33package_dir = {'Cheetah':'src'}33package_dir = {'Cheetah':'cheetah'}
3434
35import os35import os
36import os.path36import os.path
37from distutils.core import Extension37from distutils.core import Extension
3838
39## we only assume the presence of a c compiler on Posix systems, NT people will39ext_modules=[
40# have to enable this manually. 40 Extension("Cheetah._namemapper",
41if os.name == 'posix':41 [os.path.join('cheetah', 'c', '_namemapper.c')]),
42 ext_modules=[Extension("Cheetah._namemapper", [os.path.join("src" ,"_namemapper.c")]42 # Extension("Cheetah._verifytype",
43 )43 # [os.path.join('cheetah', 'c', '_verifytype.c')]),
44 ]44 # Extension("Cheetah._filters",
45else:45 # [os.path.join('cheetah', 'c', '_filters.c')]),
46 ext_modules=[]46 # Extension('Cheetah._template',
4747 # [os.path.join('cheetah', 'c', '_template.c')]),
48 ]
4849
49## Data Files and Scripts50## Data Files and Scripts
50scripts = ['bin/cheetah-compile',51scripts = ('bin/cheetah-compile',
51 'bin/cheetah',52 'bin/cheetah',
52 ]53 'bin/cheetah-analyze',
53data_files = ['recursive: src *.tmpl *.txt LICENSE README TODO CHANGES',54 )
54 ]55
56data_files = ['recursive: cheetah *.tmpl *.txt LICENSE README TODO CHANGES',]
57
58if not os.getenv('CHEETAH_INSTALL_WITHOUT_SETUPTOOLS'):
59 try:
60 from setuptools import setup
61 install_requires = [
62 "Markdown >= 2.0.1",
63 ]
64 if sys.platform == 'win32':
65 # use 'entry_points' instead of 'scripts'
66 del scripts
67 entry_points = {
68 'console_scripts': [
69 'cheetah = Cheetah.CheetahWrapper:_cheetah',
70 'cheetah-compile = Cheetah.CheetahWrapper:_cheetah_compile',
71 ]
72 }
73 except ImportError:
74 print('Not using setuptools, so we cannot install the Markdown dependency')
75
5576
56description = "Cheetah is a template engine and code generation tool."77description = "Cheetah is a template engine and code generation tool."
5778
@@ -64,7 +85,7 @@
64Documentation85Documentation
65================================================================================86================================================================================
66For a high-level introduction to Cheetah please refer to the User\'s Guide87For a high-level introduction to Cheetah please refer to the User\'s Guide
67at http://cheetahtemplate.org/learn.html88at http://www.cheetahtemplate.org/learn.html
6889
69Mailing list90Mailing list
70================================================================================91================================================================================
@@ -73,48 +94,10 @@
7394
74Credits95Credits
75================================================================================96================================================================================
76http://cheetahtemplate.org/credits.html97http://www.cheetahtemplate.org/credits.html
77
78Praise
79================================================================================
80"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging
81Technologies Group & director of Apache Software Foundation
82
83"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful
84system. ... Cheetah is a serious contender for the 'best of breed' Python
85templating." - Alex Martelli
86
87"People with a strong PHP background absolutely love Cheetah for being Smarty,
88but much, much better." - Marek Baczynski
89
90"I am using Smarty and I know it very well, but compiled Cheetah Templates with
91its inheritance approach is much powerful and easier to use than Smarty." -
92Jaroslaw Zabiello
93
94"There is no better solution than Cheetah" - Wilk
95
96"A cheetah template can inherit from a python class, or a cheetah template, and
97a Python class can inherit from a cheetah template. This brings the full power
98of OO programming facilities to the templating system, and simply blows away
99other templating systems" - Mike Meyer
100
101"Cheetah has successfully been introduced as a replacement for the overweight
102XSL Templates for code generation. Despite the power of XSL (and notably XPath
103expressions), code generation is better suited to Cheetah as templates are much
104easier to implement and manage." - The FEAR development team
105 (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573)
106
107"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor,
108lead developer of TurboGears.
10998
110Recent Changes99Recent Changes
111================================================================================100================================================================================
112See http://cheetahtemplate.org/docs/CHANGES for full details.101See http://www.cheetahtemplate.org/CHANGES.txt for full details
113102
114'''103'''
115try:
116 recentChanges = open('CHANGES').read().split('\n1.0')[0]
117 long_description += recentChanges
118 del recentChanges
119except:
120 pass
121104
=== modified file 'SetupTools.py'
--- SetupTools.py 2007-11-18 14:59:06 +0000
+++ SetupTools.py 2010-10-19 20:12:51 +0000
@@ -1,44 +1,61 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# $Id: SetupTools.py,v 1.9 2007/11/03 19:44:38 tavis_rudd Exp $
3"""Some tools for extending and working with distutils
4
5CREDITS: This module borrows code and ideas from M.A. Lemburg's excellent setup
6tools for the mxBase package.
7
8"""
9
10__author__ = "Tavis Rudd <tavis@damnsimple.com>"
11__version__ = "$Revision: 1.9 $"[11:-2]
12
13import os2import os
14from os import listdir3from os import listdir
15import os.path4import os.path
16from os.path import exists, isdir, isfile, join, splitext5from os.path import exists, isdir, isfile, join, splitext
6import sys
17import types7import types
18import glob8import glob
19import string9import string
20import traceback10import traceback
2111
22from distutils.core import setup12from distutils.core import setup
23if 'CHEETAH_USE_SETUPTOOLS' in os.environ:13if not os.getenv('CHEETAH_INSTALL_WITHOUT_SETUPTOOLS'):
24 # @@TR: Please note that this is for testing purposes only! PEAK setuptools
25 # is not required or recommended for installing Cheetah. Downstream
26 # package managers (linux distros, etc.) should *not* enable this.
27 try:14 try:
28 # use http://peak.telecommunity.com/DevCenter/setuptools if it's installed
29 # requires Py >=2.3
30 from setuptools import setup15 from setuptools import setup
31 except ImportError: 16 except ImportError:
32 from distutils.core import setup17 from distutils.core import setup
3318
34from distutils.core import Command19from distutils.core import Command
20from distutils.command.build_ext import build_ext
35from distutils.command.install_data import install_data21from distutils.command.install_data import install_data
22from distutils.errors import CCompilerError, DistutilsExecError, \
23 DistutilsPlatformError
3624
37#imports from Cheetah ...25#imports from Cheetah ...
38from src.FileUtils import findFiles26from cheetah.FileUtils import findFiles
27
28if sys.platform == 'win32' and sys.version_info > (2, 6):
29 # 2.6's distutils.msvc9compiler can raise an IOError when failing to
30 # find the compiler
31 ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError,
32 IOError)
33else:
34 ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError)
3935
40##################################################36##################################################
41## CLASSES ##37## CLASSES ##
38
39class BuildFailed(Exception):
40 pass
41
42class mod_build_ext(build_ext):
43 """A modified version of the distutils build_ext command that raises an
44 exception when building of the extension fails.
45 """
46
47 def run(self):
48 try:
49 build_ext.run(self)
50 except DistutilsPlatformError, x:
51 raise BuildFailed(x)
52
53 def build_extension(self, ext):
54 try:
55 build_ext.build_extension(self, ext)
56 except ext_errors, x:
57 raise BuildFailed(x)
58
42 59
43class mod_install_data(install_data):60class mod_install_data(install_data):
44 """A modified version of the disutils install_data command that allows data61 """A modified version of the disutils install_data command that allows data
@@ -60,8 +77,8 @@
60 data_files = self.get_inputs()77 data_files = self.get_inputs()
61 78
62 for entry in data_files:79 for entry in data_files:
63 if type(entry) != types.StringType:80 if not isinstance(entry, basestring):
64 raise ValueError, 'The entries in "data_files" must be strings'81 raise ValueError('The entries in "data_files" must be strings')
65 82
66 entry = string.join(string.split(entry, '/'), os.sep)83 entry = string.join(string.split(entry, '/'), os.sep)
67 # entry is a filename or glob pattern84 # entry is a filename or glob pattern
@@ -117,25 +134,39 @@
117 """134 """
118 # Build parameter dictionary135 # Build parameter dictionary
119 kws = {}136 kws = {}
137 newkws = {}
120 for configuration in configurations:138 for configuration in configurations:
121 kws.update(vars(configuration))139 kws.update(vars(configuration))
122 for name, value in kws.items():140 for name, value in kws.items():
123 if name[:1] == '_' or \141 if name[:1] == '_':
124 type(value) not in (types.StringType,142 continue
125 types.ListType,143 if not isinstance(value, (basestring, list, tuple, dict, int)):
126 types.TupleType,144 continue
127 types.DictType,145 newkws[name] = value
128 types.IntType,146 kws = newkws
129 ):
130 del kws[name]
131147
132 # Add setup extensions148 # Add setup extensions
133 cmdclasses = {149 cmdclasses = {
150 'build_ext': mod_build_ext,
134 'install_data': mod_install_data,151 'install_data': mod_install_data,
135 }152 }
136153
137 kws['cmdclass'] = cmdclasses154 kws['cmdclass'] = cmdclasses
138155
139 # Invoke distutils setup156 # Invoke distutils setup
140 apply(setup, (), kws)157 try:
158 setup(**kws)
159 except BuildFailed, x:
160 print("One or more C extensions failed to build.")
161 print("Details: %s" % x)
162 if os.environ.get('CHEETAH_C_EXTENSIONS_REQUIRED'):
163 raise x
164 print("Retrying without C extensions enabled.")
165
166 del kws['ext_modules']
167 setup(**kws)
168
169 print("One or more C extensions failed to build.")
170 print("Performance enhancements will not be available.")
171 print("Pure Python installation succeeded.")
141172
142173
=== modified file 'TODO'
--- TODO 2007-07-11 14:45:50 +0000
+++ TODO 2010-10-19 20:12:51 +0000
@@ -1,16 +1,9 @@
1Cheetah TODO list1NOTE: Please see http://bugs.cheetahtemplate.org
2-----------------2 for future feature requests/bugs/TODO
3* If you are working on a task please put your initials at the end of the3
4 description4
5* When a task is completed please remember to note it in the CHANGES file5===============================================================================
6* Unresolved bugs are listed in the BUGS file. Resolved bugs are be listed6===============================================================================
7 in the CHANGES file if the bug is considered significant enough and it
8 affected a released version of Cheetah.
9
10Required for Cheetah 2.0
11========================
12- Replace Optik with Python's optparse. Optik license has been removed from
13 Users' Guide.
147
15Desired for Cheetah 2.08Desired for Cheetah 2.0
16=======================9=======================
@@ -38,7 +31,6 @@
38 leak from one fill to the next.31 leak from one fill to the next.
3932
40- CheetahWrapper stuff: (MO)33- CheetahWrapper stuff: (MO)
41 * "cheetah compile --shbang '#!/usr/bin/python2.2'"
42 * "cheetah preview [options] [FILES]" print template-specific portion of main34 * "cheetah preview [options] [FILES]" print template-specific portion of main
43 method(s) to stdout, with line numbers based on the .py template module. 35 method(s) to stdout, with line numbers based on the .py template module.
44 Make a Template method to do the same thing, a la .generatedModuleCode().36 Make a Template method to do the same thing, a la .generatedModuleCode().
4537
=== modified file 'bin/cheetah'
--- bin/cheetah 2004-03-30 18:47:41 +0000
+++ bin/cheetah 2010-10-19 20:12:51 +0000
@@ -1,3 +1,3 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2from Cheetah.CheetahWrapper import CheetahWrapper2from Cheetah.CheetahWrapper import _cheetah
3CheetahWrapper().main()3_cheetah()
44
=== added file 'bin/cheetah-analyze'
--- bin/cheetah-analyze 1970-01-01 00:00:00 +0000
+++ bin/cheetah-analyze 2010-10-19 20:12:51 +0000
@@ -0,0 +1,6 @@
1#!/usr/bin/env python
2
3from Cheetah import DirectiveAnalyzer
4
5if __name__ == '__main__':
6 DirectiveAnalyzer.main()
07
=== modified file 'bin/cheetah-compile'
--- bin/cheetah-compile 2004-03-30 18:47:41 +0000
+++ bin/cheetah-compile 2010-10-19 20:12:51 +0000
@@ -1,5 +1,3 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2import sys2from Cheetah.CheetahWrapper import _cheetah_compile
3from Cheetah.CheetahWrapper import CheetahWrapper3_cheetah_compile()
4sys.argv.insert(1, "compile")
5CheetahWrapper().main()
64
=== removed file 'cachedCompile.py'
--- cachedCompile.py 2006-07-26 22:03:15 +0000
+++ cachedCompile.py 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1from Cheetah.Template import Template
2source = file('/home/tavis/cvs_working/Cheetah/src/Templates/SkeletonPage.tmpl').read()
3##klass = Template.compile(source,
4## cacheCompilationResults=1,
5## useCache=1,
6## )
7for i in range(2000):
8 klass = Template.compile(source,
9 cacheCompilationResults=0,
10 useCache=0,
11 )
120
=== added directory 'cheetah'
=== removed file 'cheetah-mem.py'
--- cheetah-mem.py 2006-07-26 22:03:15 +0000
+++ cheetah-mem.py 1970-01-01 00:00:00 +0000
@@ -1,22 +0,0 @@
1from Cheetah.Template import Template
2import gc
3
4src = open('/tmp/z.py').read()
5tclass = Template.compile(src)
6#t = Template(src)
7nr = 0
8while True:
9 #tclass = Template.compile(src)
10 #t = tclass()
11 t = Template(src)
12
13 output = t.respond()
14
15 nr += 1
16 if not nr % 10000:
17 print
18 #print 'collect'
19 #gc.collect()
20
21 print 'tclass id', id(t.__class__)
22 print 'cache size', len(Template._CHEETAH_compileCache.keys())
230
=== added file 'cheetah/CacheRegion.py'
--- cheetah/CacheRegion.py 1970-01-01 00:00:00 +0000
+++ cheetah/CacheRegion.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,136 @@
1# $Id: CacheRegion.py,v 1.3 2006/01/28 04:19:30 tavis_rudd Exp $
2'''
3Cache holder classes for Cheetah:
4
5Cache regions are defined using the #cache Cheetah directive. Each
6cache region can be viewed as a dictionary (keyed by cacheRegionID)
7handling at least one cache item (the default one). It's possible to add
8cacheItems in a region by using the `varyBy` #cache directive parameter as
9in the following example::
10 #def getArticle
11 this is the article content.
12 #end def
13
14 #cache varyBy=$getArticleID()
15 $getArticle($getArticleID())
16 #end cache
17
18The code above will generate a CacheRegion and add new cacheItem for each value
19of $getArticleID().
20'''
21
22try:
23 from hashlib import md5
24except ImportError:
25 from md5 import md5
26
27import time
28import Cheetah.CacheStore
29
30class CacheItem(object):
31 '''
32 A CacheItem is a container storing:
33
34 - cacheID (string)
35 - refreshTime (timestamp or None) : last time the cache was refreshed
36 - data (string) : the content of the cache
37 '''
38
39 def __init__(self, cacheItemID, cacheStore):
40 self._cacheItemID = cacheItemID
41 self._cacheStore = cacheStore
42 self._refreshTime = None
43 self._expiryTime = 0
44
45 def hasExpired(self):
46 return (self._expiryTime and time.time() > self._expiryTime)
47
48 def setExpiryTime(self, time):
49 self._expiryTime = time
50
51 def getExpiryTime(self):
52 return self._expiryTime
53
54 def setData(self, data):
55 self._refreshTime = time.time()
56 self._cacheStore.set(self._cacheItemID, data, self._expiryTime)
57
58 def getRefreshTime(self):
59 return self._refreshTime
60
61 def getData(self):
62 assert self._refreshTime
63 return self._cacheStore.get(self._cacheItemID)
64
65 def renderOutput(self):
66 """Can be overridden to implement edge-caching"""
67 return self.getData() or ""
68
69 def clear(self):
70 self._cacheStore.delete(self._cacheItemID)
71 self._refreshTime = None
72
73class _CacheDataStoreWrapper(object):
74 def __init__(self, dataStore, keyPrefix):
75 self._dataStore = dataStore
76 self._keyPrefix = keyPrefix
77
78 def get(self, key):
79 return self._dataStore.get(self._keyPrefix+key)
80
81 def delete(self, key):
82 self._dataStore.delete(self._keyPrefix+key)
83
84 def set(self, key, val, time=0):
85 self._dataStore.set(self._keyPrefix+key, val, time=time)
86
87class CacheRegion(object):
88 '''
89 A `CacheRegion` stores some `CacheItem` instances.
90
91 This implementation stores the data in the memory of the current process.
92 If you need a more advanced data store, create a cacheStore class that works
93 with Cheetah's CacheStore protocol and provide it as the cacheStore argument
94 to __init__. For example you could use
95 Cheetah.CacheStore.MemcachedCacheStore, a wrapper around the Python
96 memcached API (http://www.danga.com/memcached).
97 '''
98 _cacheItemClass = CacheItem
99
100 def __init__(self, regionID, templateCacheIdPrefix='', cacheStore=None):
101 self._isNew = True
102 self._regionID = regionID
103 self._templateCacheIdPrefix = templateCacheIdPrefix
104 if not cacheStore:
105 cacheStore = Cheetah.CacheStore.MemoryCacheStore()
106 self._cacheStore = cacheStore
107 self._wrappedCacheDataStore = _CacheDataStoreWrapper(
108 cacheStore, keyPrefix=templateCacheIdPrefix+':'+regionID+':')
109 self._cacheItems = {}
110
111 def isNew(self):
112 return self._isNew
113
114 def clear(self):
115 " drop all the caches stored in this cache region "
116 for cacheItemId in self._cacheItems.keys():
117 cacheItem = self._cacheItems[cacheItemId]
118 cacheItem.clear()
119 del self._cacheItems[cacheItemId]
120
121 def getCacheItem(self, cacheItemID):
122 """ Lazy access to a cacheItem
123
124 Try to find a cache in the stored caches. If it doesn't
125 exist, it's created.
126
127 Returns a `CacheItem` instance.
128 """
129 cacheItemID = md5(str(cacheItemID)).hexdigest()
130
131 if cacheItemID not in self._cacheItems:
132 cacheItem = self._cacheItemClass(
133 cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore)
134 self._cacheItems[cacheItemID] = cacheItem
135 self._isNew = False
136 return self._cacheItems[cacheItemID]
0137
=== added file 'cheetah/CacheStore.py'
--- cheetah/CacheStore.py 1970-01-01 00:00:00 +0000
+++ cheetah/CacheStore.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,106 @@
1'''
2Provides several CacheStore backends for Cheetah's caching framework. The
3methods provided by these classes have the same semantics as those in the
4python-memcached API, except for their return values:
5
6set(key, val, time=0)
7 set the value unconditionally
8add(key, val, time=0)
9 set only if the server doesn't already have this key
10replace(key, val, time=0)
11 set only if the server already have this key
12get(key, val)
13 returns val or raises a KeyError
14delete(key)
15 deletes or raises a KeyError
16'''
17import time
18
19class Error(Exception):
20 pass
21
22class AbstractCacheStore(object):
23
24 def set(self, key, val, time=None):
25 raise NotImplementedError
26
27 def add(self, key, val, time=None):
28 raise NotImplementedError
29
30 def replace(self, key, val, time=None):
31 raise NotImplementedError
32
33 def delete(self, key):
34 raise NotImplementedError
35
36 def get(self, key):
37 raise NotImplementedError
38
39class MemoryCacheStore(AbstractCacheStore):
40 def __init__(self):
41 self._data = {}
42
43 def set(self, key, val, time=0):
44 self._data[key] = (val, time)
45
46 def add(self, key, val, time=0):
47 if key in self._data:
48 raise Error('a value for key %r is already in the cache'%key)
49 self._data[key] = (val, time)
50
51 def replace(self, key, val, time=0):
52 if key in self._data:
53 raise Error('a value for key %r is already in the cache'%key)
54 self._data[key] = (val, time)
55
56 def delete(self, key):
57 del self._data[key]
58
59 def get(self, key):
60 (val, exptime) = self._data[key]
61 if exptime and time.time() > exptime:
62 del self._data[key]
63 raise KeyError(key)
64 else:
65 return val
66
67 def clear(self):
68 self._data.clear()
69
70class MemcachedCacheStore(AbstractCacheStore):
71 servers = ('127.0.0.1:11211')
72 def __init__(self, servers=None, debug=False):
73 if servers is None:
74 servers = self.servers
75 from memcache import Client as MemcachedClient
76 self._client = MemcachedClient(servers, debug)
77
78 def set(self, key, val, time=0):
79 self._client.set(key, val, time)
80
81 def add(self, key, val, time=0):
82 res = self._client.add(key, val, time)
83 if not res:
84 raise Error('a value for key %r is already in the cache'%key)
85 self._data[key] = (val, time)
86
87 def replace(self, key, val, time=0):
88 res = self._client.replace(key, val, time)
89 if not res:
90 raise Error('a value for key %r is already in the cache'%key)
91 self._data[key] = (val, time)
92
93 def delete(self, key):
94 res = self._client.delete(key, time=0)
95 if not res:
96 raise KeyError(key)
97
98 def get(self, key):
99 val = self._client.get(key)
100 if val is None:
101 raise KeyError(key)
102 else:
103 return val
104
105 def clear(self):
106 self._client.flush_all()
0107
=== added file 'cheetah/CheetahWrapper.py'
--- cheetah/CheetahWrapper.py 1970-01-01 00:00:00 +0000
+++ cheetah/CheetahWrapper.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,632 @@
1# $Id: CheetahWrapper.py,v 1.26 2007/10/02 01:22:04 tavis_rudd Exp $
2"""Cheetah command-line interface.
3
42002-09-03 MSO: Total rewrite.
52002-09-04 MSO: Bugfix, compile command was using wrong output ext.
62002-11-08 MSO: Another rewrite.
7
8Meta-Data
9================================================================================
10Author: Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>>
11Version: $Revision: 1.26 $
12Start Date: 2001/03/30
13Last Revision Date: $Date: 2007/10/02 01:22:04 $
14"""
15__author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>"
16__revision__ = "$Revision: 1.26 $"[11:-2]
17
18import getopt, glob, os, pprint, re, shutil, sys
19import cPickle as pickle
20from optparse import OptionParser
21
22from Cheetah.Version import Version
23from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS
24from Cheetah.Utils.Misc import mkdirsWithPyInitFiles
25
26optionDashesRE = re.compile( R"^-{1,2}" )
27moduleNameRE = re.compile( R"^[a-zA-Z_][a-zA-Z_0-9]*$" )
28
29def fprintfMessage(stream, format, *args):
30 if format[-1:] == '^':
31 format = format[:-1]
32 else:
33 format += '\n'
34 if args:
35 message = format % args
36 else:
37 message = format
38 stream.write(message)
39
40class Error(Exception):
41 pass
42
43
44class Bundle:
45 """Wrap the source, destination and backup paths in one neat little class.
46 Used by CheetahWrapper.getBundles().
47 """
48 def __init__(self, **kw):
49 self.__dict__.update(kw)
50
51 def __repr__(self):
52 return "<Bundle %r>" % self.__dict__
53
54
55##################################################
56## USAGE FUNCTION & MESSAGES
57
58def usage(usageMessage, errorMessage="", out=sys.stderr):
59 """Write help text, an optional error message, and abort the program.
60 """
61 out.write(WRAPPER_TOP)
62 out.write(usageMessage)
63 exitStatus = 0
64 if errorMessage:
65 out.write('\n')
66 out.write("*** USAGE ERROR ***: %s\n" % errorMessage)
67 exitStatus = 1
68 sys.exit(exitStatus)
69
70
71WRAPPER_TOP = """\
72 __ ____________ __
73 \ \/ \/ /
74 \/ * * \/ CHEETAH %(Version)s Command-Line Tool
75 \ | /
76 \ ==----== / by Tavis Rudd <tavis@damnsimple.com>
77 \__________/ and Mike Orr <sluggoster@gmail.com>
78
79""" % globals()
80
81
82HELP_PAGE1 = """\
83USAGE:
84------
85 cheetah compile [options] [FILES ...] : Compile template definitions
86 cheetah fill [options] [FILES ...] : Fill template definitions
87 cheetah help : Print this help message
88 cheetah options : Print options help message
89 cheetah test [options] : Run Cheetah's regression tests
90 : (same as for unittest)
91 cheetah version : Print Cheetah version number
92
93You may abbreviate the command to the first letter; e.g., 'h' == 'help'.
94If FILES is a single "-", read standard input and write standard output.
95Run "cheetah options" for the list of valid options.
96"""
97
98##################################################
99## CheetahWrapper CLASS
100
101class CheetahWrapper(object):
102 MAKE_BACKUPS = True
103 BACKUP_SUFFIX = ".bak"
104 _templateClass = None
105 _compilerSettings = None
106
107 def __init__(self):
108 self.progName = None
109 self.command = None
110 self.opts = None
111 self.pathArgs = None
112 self.sourceFiles = []
113 self.searchList = []
114 self.parser = None
115
116 ##################################################
117 ## MAIN ROUTINE
118
119 def main(self, argv=None):
120 """The main program controller."""
121
122 if argv is None:
123 argv = sys.argv
124
125 # Step 1: Determine the command and arguments.
126 try:
127 self.progName = progName = os.path.basename(argv[0])
128 self.command = command = optionDashesRE.sub("", argv[1])
129 if command == 'test':
130 self.testOpts = argv[2:]
131 else:
132 self.parseOpts(argv[2:])
133 except IndexError:
134 usage(HELP_PAGE1, "not enough command-line arguments")
135
136 # Step 2: Call the command
137 meths = (self.compile, self.fill, self.help, self.options,
138 self.test, self.version)
139 for meth in meths:
140 methName = meth.__name__
141 # Or meth.im_func.func_name
142 # Or meth.func_name (Python >= 2.1 only, sometimes works on 2.0)
143 methInitial = methName[0]
144 if command in (methName, methInitial):
145 sys.argv[0] += (" " + methName)
146 # @@MO: I don't necessarily agree sys.argv[0] should be
147 # modified.
148 meth()
149 return
150 # If none of the commands matched.
151 usage(HELP_PAGE1, "unknown command '%s'" % command)
152
153 def parseOpts(self, args):
154 C, D, W = self.chatter, self.debug, self.warn
155 self.isCompile = isCompile = self.command[0] == 'c'
156 defaultOext = isCompile and ".py" or ".html"
157 self.parser = OptionParser()
158 pao = self.parser.add_option
159 pao("--idir", action="store", dest="idir", default='', help='Input directory (defaults to current directory)')
160 pao("--odir", action="store", dest="odir", default="", help='Output directory (defaults to current directory)')
161 pao("--iext", action="store", dest="iext", default=".tmpl", help='File input extension (defaults: compile: .tmpl, fill: .tmpl)')
162 pao("--oext", action="store", dest="oext", default=defaultOext, help='File output extension (defaults: compile: .py, fill: .html)')
163 pao("-R", action="store_true", dest="recurse", default=False, help='Recurse through subdirectories looking for input files')
164 pao("--stdout", "-p", action="store_true", dest="stdout", default=False, help='Send output to stdout instead of writing to a file')
165 pao("--quiet", action="store_false", dest="verbose", default=True, help='Do not print informational messages to stdout')
166 pao("--debug", action="store_true", dest="debug", default=False, help='Print diagnostic/debug information to stderr')
167 pao("--env", action="store_true", dest="env", default=False, help='Pass the environment into the search list')
168 pao("--pickle", action="store", dest="pickle", default="", help='Unpickle FILE and pass it through in the search list')
169 pao("--flat", action="store_true", dest="flat", default=False, help='Do not build destination subdirectories')
170 pao("--nobackup", action="store_true", dest="nobackup", default=False, help='Do not make backup files when generating new ones')
171 pao("--settings", action="store", dest="compilerSettingsString", default=None, help='String of compiler settings to pass through, e.g. --settings="useNameMapper=False,useFilters=False"')
172 pao('--print-settings', action='store_true', dest='print_settings', help='Print out the list of available compiler settings')
173 pao("--templateAPIClass", action="store", dest="templateClassName", default=None, help='Name of a subclass of Cheetah.Template.Template to use for compilation, e.g. MyTemplateClass')
174 pao("--parallel", action="store", type="int", dest="parallel", default=1, help='Compile/fill templates in parallel, e.g. --parallel=4')
175 pao('--shbang', dest='shbang', default='#!/usr/bin/env python', help='Specify the shbang to place at the top of compiled templates, e.g. --shbang="#!/usr/bin/python2.6"')
176
177 opts, files = self.parser.parse_args(args)
178 self.opts = opts
179 if sys.platform == "win32":
180 new_files = []
181 for spec in files:
182 file_list = glob.glob(spec)
183 if file_list:
184 new_files.extend(file_list)
185 else:
186 new_files.append(spec)
187 files = new_files
188 self.pathArgs = files
189
190 D("""\
191cheetah compile %s
192Options are
193%s
194Files are %s""", args, pprint.pformat(vars(opts)), files)
195
196
197 if opts.print_settings:
198 print()
199 print('>> Available Cheetah compiler settings:')
200 from Cheetah.Compiler import _DEFAULT_COMPILER_SETTINGS
201 listing = _DEFAULT_COMPILER_SETTINGS
202 listing.sort(key=lambda l: l[0][0].lower())
203
204 for l in listing:
205 print('\t%s (default: "%s")\t%s' % l)
206 sys.exit(0)
207
208 #cleanup trailing path separators
209 seps = [sep for sep in [os.sep, os.altsep] if sep]
210 for attr in ['idir', 'odir']:
211 for sep in seps:
212 path = getattr(opts, attr, None)
213 if path and path.endswith(sep):
214 path = path[:-len(sep)]
215 setattr(opts, attr, path)
216 break
217
218 self._fixExts()
219 if opts.env:
220 self.searchList.insert(0, os.environ)
221 if opts.pickle:
222 f = open(opts.pickle, 'rb')
223 unpickled = pickle.load(f)
224 f.close()
225 self.searchList.insert(0, unpickled)
226
227 ##################################################
228 ## COMMAND METHODS
229
230 def compile(self):
231 self._compileOrFill()
232
233 def fill(self):
234 from Cheetah.ImportHooks import install
235 install()
236 self._compileOrFill()
237
238 def help(self):
239 usage(HELP_PAGE1, "", sys.stdout)
240
241 def options(self):
242 return self.parser.print_help()
243
244 def test(self):
245 # @@MO: Ugly kludge.
246 TEST_WRITE_FILENAME = 'cheetah_test_file_creation_ability.tmp'
247 try:
248 f = open(TEST_WRITE_FILENAME, 'w')
249 except:
250 sys.exit("""\
251Cannot run the tests because you don't have write permission in the current
252directory. The tests need to create temporary files. Change to a directory
253you do have write permission to and re-run the tests.""")
254 else:
255 f.close()
256 os.remove(TEST_WRITE_FILENAME)
257 # @@MO: End ugly kludge.
258 from Cheetah.Tests import Test
259 import unittest
260 verbosity = 1
261 if '-q' in self.testOpts:
262 verbosity = 0
263 if '-v' in self.testOpts:
264 verbosity = 2
265 runner = unittest.TextTestRunner(verbosity=verbosity)
266 runner.run(unittest.TestSuite(Test.suites))
267
268 def version(self):
269 print(Version)
270
271 # If you add a command, also add it to the 'meths' variable in main().
272
273 ##################################################
274 ## LOGGING METHODS
275
276 def chatter(self, format, *args):
277 """Print a verbose message to stdout. But don't if .opts.stdout is
278 true or .opts.verbose is false.
279 """
280 if self.opts.stdout or not self.opts.verbose:
281 return
282 fprintfMessage(sys.stdout, format, *args)
283
284
285 def debug(self, format, *args):
286 """Print a debugging message to stderr, but don't if .debug is
287 false.
288 """
289 if self.opts.debug:
290 fprintfMessage(sys.stderr, format, *args)
291
292 def warn(self, format, *args):
293 """Always print a warning message to stderr.
294 """
295 fprintfMessage(sys.stderr, format, *args)
296
297 def error(self, format, *args):
298 """Always print a warning message to stderr and exit with an error code.
299 """
300 fprintfMessage(sys.stderr, format, *args)
301 sys.exit(1)
302
303 ##################################################
304 ## HELPER METHODS
305
306
307 def _fixExts(self):
308 assert self.opts.oext, "oext is empty!"
309 iext, oext = self.opts.iext, self.opts.oext
310 if iext and not iext.startswith("."):
311 self.opts.iext = "." + iext
312 if oext and not oext.startswith("."):
313 self.opts.oext = "." + oext
314
315
316
317 def _compileOrFill(self):
318 C, D, W = self.chatter, self.debug, self.warn
319 opts, files = self.opts, self.pathArgs
320 if files == ["-"]:
321 self._compileOrFillStdin()
322 return
323 elif not files and opts.recurse:
324 which = opts.idir and "idir" or "current"
325 C("Drilling down recursively from %s directory.", which)
326 sourceFiles = []
327 dir = os.path.join(self.opts.idir, os.curdir)
328 os.path.walk(dir, self._expandSourceFilesWalk, sourceFiles)
329 elif not files:
330 usage(HELP_PAGE1, "Neither files nor -R specified!")
331 else:
332 sourceFiles = self._expandSourceFiles(files, opts.recurse, True)
333 sourceFiles = [os.path.normpath(x) for x in sourceFiles]
334 D("All source files found: %s", sourceFiles)
335 bundles = self._getBundles(sourceFiles)
336 D("All bundles: %s", pprint.pformat(bundles))
337 if self.opts.flat:
338 self._checkForCollisions(bundles)
339
340 # In parallel mode a new process is forked for each template
341 # compilation, out of a pool of size self.opts.parallel. This is not
342 # really optimal in all cases (e.g. probably wasteful for small
343 # templates), but seems to work well in real life for me.
344 #
345 # It also won't work for Windows users, but I'm not going to lose any
346 # sleep over that.
347 if self.opts.parallel > 1:
348 bad_child_exit = 0
349 pid_pool = set()
350
351 def child_wait():
352 pid, status = os.wait()
353 pid_pool.remove(pid)
354 return os.WEXITSTATUS(status)
355
356 while bundles:
357 b = bundles.pop()
358 pid = os.fork()
359 if pid:
360 pid_pool.add(pid)
361 else:
362 self._compileOrFillBundle(b)
363 sys.exit(0)
364
365 if len(pid_pool) == self.opts.parallel:
366 bad_child_exit = child_wait()
367 if bad_child_exit:
368 break
369
370 while pid_pool:
371 child_exit = child_wait()
372 if not bad_child_exit:
373 bad_child_exit = child_exit
374
375 if bad_child_exit:
376 sys.exit("Child process failed, exited with code %d" % bad_child_exit)
377
378 else:
379 for b in bundles:
380 self._compileOrFillBundle(b)
381
382 def _checkForCollisions(self, bundles):
383 """Check for multiple source paths writing to the same destination
384 path.
385 """
386 C, D, W = self.chatter, self.debug, self.warn
387 isError = False
388 dstSources = {}
389 for b in bundles:
390 if b.dst in dstSources:
391 dstSources[b.dst].append(b.src)
392 else:
393 dstSources[b.dst] = [b.src]
394 keys = sorted(dstSources.keys())
395 for dst in keys:
396 sources = dstSources[dst]
397 if len(sources) > 1:
398 isError = True
399 sources.sort()
400 fmt = "Collision: multiple source files %s map to one destination file %s"
401 W(fmt, sources, dst)
402 if isError:
403 what = self.isCompile and "Compilation" or "Filling"
404 sys.exit("%s aborted due to collisions" % what)
405
406
407 def _expandSourceFilesWalk(self, arg, dir, files):
408 """Recursion extension for .expandSourceFiles().
409 This method is a callback for os.path.walk().
410 'arg' is a list to which successful paths will be appended.
411 """
412 iext = self.opts.iext
413 for f in files:
414 path = os.path.join(dir, f)
415 if path.endswith(iext) and os.path.isfile(path):
416 arg.append(path)
417 elif os.path.islink(path) and os.path.isdir(path):
418 os.path.walk(path, self._expandSourceFilesWalk, arg)
419 # If is directory, do nothing; 'walk' will eventually get it.
420
421
422 def _expandSourceFiles(self, files, recurse, addIextIfMissing):
423 """Calculate source paths from 'files' by applying the
424 command-line options.
425 """
426 C, D, W = self.chatter, self.debug, self.warn
427 idir = self.opts.idir
428 iext = self.opts.iext
429 files = []
430 for f in self.pathArgs:
431 oldFilesLen = len(files)
432 D("Expanding %s", f)
433 path = os.path.join(idir, f)
434 pathWithExt = path + iext # May or may not be valid.
435 if os.path.isdir(path):
436 if recurse:
437 os.path.walk(path, self._expandSourceFilesWalk, files)
438 else:
439 raise Error("source file '%s' is a directory" % path)
440 elif os.path.isfile(path):
441 files.append(path)
442 elif (addIextIfMissing and not path.endswith(iext) and
443 os.path.isfile(pathWithExt)):
444 files.append(pathWithExt)
445 # Do not recurse directories discovered by iext appending.
446 elif os.path.exists(path):
447 W("Skipping source file '%s', not a plain file.", path)
448 else:
449 W("Skipping source file '%s', not found.", path)
450 if len(files) > oldFilesLen:
451 D(" ... found %s", files[oldFilesLen:])
452 return files
453
454
455 def _getBundles(self, sourceFiles):
456 flat = self.opts.flat
457 idir = self.opts.idir
458 iext = self.opts.iext
459 nobackup = self.opts.nobackup
460 odir = self.opts.odir
461 oext = self.opts.oext
462 idirSlash = idir + os.sep
463 bundles = []
464 for src in sourceFiles:
465 # 'base' is the subdirectory plus basename.
466 base = src
467 if idir and src.startswith(idirSlash):
468 base = src[len(idirSlash):]
469 if iext and base.endswith(iext):
470 base = base[:-len(iext)]
471 basename = os.path.basename(base)
472 if flat:
473 dst = os.path.join(odir, basename + oext)
474 else:
475 dbn = basename
476 if odir and base.startswith(os.sep):
477 odd = odir
478 while odd != '':
479 idx = base.find(odd)
480 if idx == 0:
481 dbn = base[len(odd):]
482 if dbn[0] == '/':
483 dbn = dbn[1:]
484 break
485 odd = os.path.dirname(odd)
486 if odd == '/':
487 break
488 dst = os.path.join(odir, dbn + oext)
489 else:
490 dst = os.path.join(odir, base + oext)
491 bak = dst + self.BACKUP_SUFFIX
492 b = Bundle(src=src, dst=dst, bak=bak, base=base, basename=basename)
493 bundles.append(b)
494 return bundles
495
496
497 def _getTemplateClass(self):
498 C, D, W = self.chatter, self.debug, self.warn
499 modname = None
500 if self._templateClass:
501 return self._templateClass
502
503 modname = self.opts.templateClassName
504
505 if not modname:
506 return Template
507 p = modname.rfind('.')
508 if ':' not in modname:
509 self.error('The value of option --templateAPIClass is invalid\n'
510 'It must be in the form "module:class", '
511 'e.g. "Cheetah.Template:Template"')
512
513 modname, classname = modname.split(':')
514
515 C('using --templateAPIClass=%s:%s'%(modname, classname))
516
517 if p >= 0:
518 mod = getattr(__import__(modname[:p], {}, {}, [modname[p+1:]]), modname[p+1:])
519 else:
520 mod = __import__(modname, {}, {}, [])
521
522 klass = getattr(mod, classname, None)
523 if klass:
524 self._templateClass = klass
525 return klass
526 else:
527 self.error('**Template class specified in option --templateAPIClass not found\n'
528 '**Falling back on Cheetah.Template:Template')
529
530
531 def _getCompilerSettings(self):
532 if self._compilerSettings:
533 return self._compilerSettings
534
535 def getkws(**kws):
536 return kws
537 if self.opts.compilerSettingsString:
538 try:
539 exec('settings = getkws(%s)'%self.opts.compilerSettingsString)
540 except:
541 self.error("There's an error in your --settings option."
542 "It must be valid Python syntax.\n"
543 +" --settings='%s'\n"%self.opts.compilerSettingsString
544 +" %s: %s"%sys.exc_info()[:2]
545 )
546
547 validKeys = DEFAULT_COMPILER_SETTINGS.keys()
548 if [k for k in settings.keys() if k not in validKeys]:
549 self.error(
550 'The --setting "%s" is not a valid compiler setting name.'%k)
551
552 self._compilerSettings = settings
553 return settings
554 else:
555 return {}
556
557 def _compileOrFillStdin(self):
558 TemplateClass = self._getTemplateClass()
559 compilerSettings = self._getCompilerSettings()
560 if self.isCompile:
561 pysrc = TemplateClass.compile(file=sys.stdin,
562 compilerSettings=compilerSettings,
563 returnAClass=False)
564 output = pysrc
565 else:
566 output = str(TemplateClass(file=sys.stdin, compilerSettings=compilerSettings))
567 sys.stdout.write(output)
568
569 def _compileOrFillBundle(self, b):
570 C, D, W = self.chatter, self.debug, self.warn
571 TemplateClass = self._getTemplateClass()
572 compilerSettings = self._getCompilerSettings()
573 src = b.src
574 dst = b.dst
575 base = b.base
576 basename = b.basename
577 dstDir = os.path.dirname(dst)
578 what = self.isCompile and "Compiling" or "Filling"
579 C("%s %s -> %s^", what, src, dst) # No trailing newline.
580 if os.path.exists(dst) and not self.opts.nobackup:
581 bak = b.bak
582 C(" (backup %s)", bak) # On same line as previous message.
583 else:
584 bak = None
585 C("")
586 if self.isCompile:
587 if not moduleNameRE.match(basename):
588 tup = basename, src
589 raise Error("""\
590%s: base name %s contains invalid characters. It must
591be named according to the same rules as Python modules.""" % tup)
592 pysrc = TemplateClass.compile(file=src, returnAClass=False,
593 moduleName=basename,
594 className=basename,
595 commandlineopts=self.opts,
596 compilerSettings=compilerSettings)
597 output = pysrc
598 else:
599 #output = str(TemplateClass(file=src, searchList=self.searchList))
600 tclass = TemplateClass.compile(file=src, compilerSettings=compilerSettings)
601 output = str(tclass(searchList=self.searchList))
602
603 if bak:
604 shutil.copyfile(dst, bak)
605 if dstDir and not os.path.exists(dstDir):
606 if self.isCompile:
607 mkdirsWithPyInitFiles(dstDir)
608 else:
609 os.makedirs(dstDir)
610 if self.opts.stdout:
611 sys.stdout.write(output)
612 else:
613 f = open(dst, 'w')
614 f.write(output)
615 f.close()
616
617
618# Called when invoked as `cheetah`
619def _cheetah():
620 CheetahWrapper().main()
621
622# Called when invoked as `cheetah-compile`
623def _cheetah_compile():
624 sys.argv.insert(1, "compile")
625 CheetahWrapper().main()
626
627
628##################################################
629## if run from the command line
630if __name__ == '__main__': CheetahWrapper().main()
631
632# vim: shiftwidth=4 tabstop=4 expandtab
0633
=== added file 'cheetah/Compiler.py'
--- cheetah/Compiler.py 1970-01-01 00:00:00 +0000
+++ cheetah/Compiler.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,2002 @@
1'''
2 Compiler classes for Cheetah:
3 ModuleCompiler aka 'Compiler'
4 ClassCompiler
5 MethodCompiler
6
7 If you are trying to grok this code start with ModuleCompiler.__init__,
8 ModuleCompiler.compile, and ModuleCompiler.__getattr__.
9'''
10
11import sys
12import os
13import os.path
14from os.path import getmtime, exists
15import re
16import types
17import time
18import random
19import warnings
20import copy
21
22from Cheetah.Version import Version, VersionTuple
23from Cheetah.SettingsManager import SettingsManager
24from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
25from Cheetah import ErrorCatchers
26from Cheetah import NameMapper
27from Cheetah.Parser import Parser, ParseError, specialVarRE, \
28 STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL, SET_MODULE, \
29 unicodeDirectiveRE, encodingDirectiveRE, escapedNewlineRE
30
31from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
32VFFSL=valueFromFrameOrSearchList
33VFSL=valueFromSearchList
34VFN=valueForName
35currentTime=time.time
36
37class Error(Exception): pass
38
39# Settings format: (key, default, docstring)
40_DEFAULT_COMPILER_SETTINGS = [
41 ('useNameMapper', True, 'Enable NameMapper for dotted notation and searchList support'),
42 ('useSearchList', True, 'Enable the searchList, requires useNameMapper=True, if disabled, first portion of the $variable is a global, builtin, or local variable that doesn\'t need looking up in the searchList'),
43 ('allowSearchListAsMethArg', True, ''),
44 ('useAutocalling', True, 'Detect and call callable objects in searchList, requires useNameMapper=True'),
45 ('useStackFrames', True, 'Used for NameMapper.valueFromFrameOrSearchList rather than NameMapper.valueFromSearchList'),
46 ('useErrorCatcher', False, 'Turn on the #errorCatcher directive for catching NameMapper errors, etc'),
47 ('alwaysFilterNone', True, 'Filter out None prior to calling the #filter'),
48 ('useFilters', True, 'If False, pass output through str()'),
49 ('includeRawExprInFilterArgs', True, ''),
50 ('useLegacyImportMode', True, 'All #import statements are relocated to the top of the generated Python module'),
51 ('prioritizeSearchListOverSelf', False, 'When iterating the searchList, look into the searchList passed into the initializer instead of Template members first'),
52
53 ('autoAssignDummyTransactionToSelf', False, ''),
54 ('useKWsDictArgForPassingTrans', True, ''),
55
56 ('commentOffset', 1, ''),
57 ('outputRowColComments', True, ''),
58 ('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'),
59 ('blockMarkerStart', ('\n<!-- START BLOCK: ', ' -->\n'), ''),
60 ('blockMarkerEnd', ('\n<!-- END BLOCK: ', ' -->\n'), ''),
61 ('defDocStrMsg', 'Autogenerated by Cheetah: The Python-Powered Template Engine', ''),
62 ('setup__str__method', False, ''),
63 ('mainMethodName', 'respond', ''),
64 ('mainMethodNameForSubclasses', 'writeBody', ''),
65 ('indentationStep', ' ' * 4, ''),
66 ('initialMethIndentLevel', 2, ''),
67 ('monitorSrcFile', False, ''),
68 ('outputMethodsBeforeAttributes', True, ''),
69 ('addTimestampsToCompilerOutput', True, ''),
70
71 ## Customizing the #extends directive
72 ('autoImportForExtendsDirective', True, ''),
73 ('handlerForExtendsDirective', None, ''),
74
75 ('disabledDirectives', [], 'List of directive keys to disable (without starting "#")'),
76 ('enabledDirectives', [], 'List of directive keys to enable (without starting "#")'),
77 ('disabledDirectiveHooks', [], 'callable(parser, directiveKey)'),
78 ('preparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
79 ('postparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
80 ('preparsePlaceholderHooks', [], 'callable(parser)'),
81 ('postparsePlaceholderHooks', [], 'callable(parser)'),
82 ('expressionFilterHooks', [], '''callable(parser, expr, exprType, rawExpr=None, startPos=None), exprType is the name of the directive, "psp" or "placeholder" The filters *must* return the expr or raise an expression, they can modify the expr if needed'''),
83 ('templateMetaclass', None, 'Strictly optional, only will work with new-style basecalsses as well'),
84 ('i18NFunctionName', 'self.i18n', ''),
85
86 ('cheetahVarStartToken', '$', ''),
87 ('commentStartToken', '##', ''),
88 ('multiLineCommentStartToken', '#*', ''),
89 ('multiLineCommentEndToken', '*#', ''),
90 ('gobbleWhitespaceAroundMultiLineComments', True, ''),
91 ('directiveStartToken', '#', ''),
92 ('directiveEndToken', '#', ''),
93 ('allowWhitespaceAfterDirectiveStartToken', False, ''),
94 ('PSPStartToken', '<%', ''),
95 ('PSPEndToken', '%>', ''),
96 ('EOLSlurpToken', '#', ''),
97 ('gettextTokens', ["_", "N_", "ngettext"], ''),
98 ('allowExpressionsInExtendsDirective', False, ''),
99 ('allowEmptySingleLineMethods', False, ''),
100 ('allowNestedDefScopes', True, ''),
101 ('allowPlaceholderFilterArgs', True, ''),
102]
103
104DEFAULT_COMPILER_SETTINGS = dict([(v[0], v[1]) for v in _DEFAULT_COMPILER_SETTINGS])
105
106
107
108class GenUtils(object):
109 """An abstract baseclass for the Compiler classes that provides methods that
110 perform generic utility functions or generate pieces of output code from
111 information passed in by the Parser baseclass. These methods don't do any
112 parsing themselves.
113 """
114
115 def genTimeInterval(self, timeString):
116 ##@@ TR: need to add some error handling here
117 if timeString[-1] == 's':
118 interval = float(timeString[:-1])
119 elif timeString[-1] == 'm':
120 interval = float(timeString[:-1])*60
121 elif timeString[-1] == 'h':
122 interval = float(timeString[:-1])*60*60
123 elif timeString[-1] == 'd':
124 interval = float(timeString[:-1])*60*60*24
125 elif timeString[-1] == 'w':
126 interval = float(timeString[:-1])*60*60*24*7
127 else: # default to minutes
128 interval = float(timeString)*60
129 return interval
130
131 def genCacheInfo(self, cacheTokenParts):
132 """Decipher a placeholder cachetoken
133 """
134 cacheInfo = {}
135 if cacheTokenParts['REFRESH_CACHE']:
136 cacheInfo['type'] = REFRESH_CACHE
137 cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval'])
138 elif cacheTokenParts['STATIC_CACHE']:
139 cacheInfo['type'] = STATIC_CACHE
140 return cacheInfo # is empty if no cache
141
142 def genCacheInfoFromArgList(self, argList):
143 cacheInfo = {'type':REFRESH_CACHE}
144 for key, val in argList:
145 if val[0] in '"\'':
146 val = val[1:-1]
147
148 if key == 'timer':
149 key = 'interval'
150 val = self.genTimeInterval(val)
151
152 cacheInfo[key] = val
153 return cacheInfo
154
155 def genCheetahVar(self, nameChunks, plain=False):
156 if nameChunks[0][0] in self.setting('gettextTokens'):
157 self.addGetTextVar(nameChunks)
158 if self.setting('useNameMapper') and not plain:
159 return self.genNameMapperVar(nameChunks)
160 else:
161 return self.genPlainVar(nameChunks)
162
163 def addGetTextVar(self, nameChunks):
164 """Output something that gettext can recognize.
165
166 This is a harmless side effect necessary to make gettext work when it
167 is scanning compiled templates for strings marked for translation.
168
169 @@TR: another marginally more efficient approach would be to put the
170 output in a dummy method that is never called.
171 """
172 # @@TR: this should be in the compiler not here
173 self.addChunk("if False:")
174 self.indent()
175 self.addChunk(self.genPlainVar(nameChunks[:]))
176 self.dedent()
177
178 def genPlainVar(self, nameChunks):
179 """Generate Python code for a Cheetah $var without using NameMapper
180 (Unified Dotted Notation with the SearchList).
181 """
182 nameChunks.reverse()
183 chunk = nameChunks.pop()
184 pythonCode = chunk[0] + chunk[2]
185 while nameChunks:
186 chunk = nameChunks.pop()
187 pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
188 return pythonCode
189
190 def genNameMapperVar(self, nameChunks):
191 """Generate valid Python code for a Cheetah $var, using NameMapper
192 (Unified Dotted Notation with the SearchList).
193
194 nameChunks = list of var subcomponents represented as tuples
195 [ (name,useAC,remainderOfExpr),
196 ]
197 where:
198 name = the dotted name base
199 useAC = where NameMapper should use autocalling on namemapperPart
200 remainderOfExpr = any arglist, index, or slice
201
202 If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
203 is False, otherwise it defaults to True. It is overridden by the global
204 setting 'useAutocalling' if this setting is False.
205
206 EXAMPLE
207 ------------------------------------------------------------------------
208 if the raw Cheetah Var is
209 $a.b.c[1].d().x.y.z
210
211 nameChunks is the list
212 [ ('a.b.c',True,'[1]'), # A
213 ('d',False,'()'), # B
214 ('x.y.z',True,''), # C
215 ]
216
217 When this method is fed the list above it returns
218 VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
219 which can be represented as
220 VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
221 where:
222 VFN = NameMapper.valueForName
223 VFFSL = NameMapper.valueFromFrameOrSearchList
224 VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
225 SL = self.searchList()
226 useAC = self.setting('useAutocalling') # True in this example
227
228 A = ('a.b.c',True,'[1]')
229 B = ('d',False,'()')
230 C = ('x.y.z',True,'')
231
232 C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
233 'd',False)(),
234 'x.y.z',True)
235 = VFN(B`, name='x.y.z', executeCallables=True)
236
237 B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
238 A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
239
240
241 Note, if the compiler setting useStackFrames=False (default is true)
242 then
243 A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
244 This option allows Cheetah to be used with Psyco, which doesn't support
245 stack frame introspection.
246 """
247 defaultUseAC = self.setting('useAutocalling')
248 useSearchList = self.setting('useSearchList')
249
250 nameChunks.reverse()
251 name, useAC, remainder = nameChunks.pop()
252
253 if not useSearchList:
254 firstDotIdx = name.find('.')
255 if firstDotIdx != -1 and firstDotIdx < len(name):
256 beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:]
257 pythonCode = ('VFN(' + beforeFirstDot +
258 ',"' + afterDot +
259 '",' + repr(defaultUseAC and useAC) + ')'
260 + remainder)
261 else:
262 pythonCode = name+remainder
263 elif self.setting('useStackFrames'):
264 pythonCode = ('VFFSL(SL,'
265 '"'+ name + '",'
266 + repr(defaultUseAC and useAC) + ')'
267 + remainder)
268 else:
269 pythonCode = ('VFSL([locals()]+SL+[globals(), builtin],'
270 '"'+ name + '",'
271 + repr(defaultUseAC and useAC) + ')'
272 + remainder)
273 ##
274 while nameChunks:
275 name, useAC, remainder = nameChunks.pop()
276 pythonCode = ('VFN(' + pythonCode +
277 ',"' + name +
278 '",' + repr(defaultUseAC and useAC) + ')'
279 + remainder)
280 return pythonCode
281
282##################################################
283## METHOD COMPILERS
284
285class MethodCompiler(GenUtils):
286 def __init__(self, methodName, classCompiler,
287 initialMethodComment=None,
288 decorators=None):
289 self._settingsManager = classCompiler
290 self._classCompiler = classCompiler
291 self._moduleCompiler = classCompiler._moduleCompiler
292 self._methodName = methodName
293 self._initialMethodComment = initialMethodComment
294 self._setupState()
295 self._decorators = decorators or []
296
297 def setting(self, key):
298 return self._settingsManager.setting(key)
299
300 def _setupState(self):
301 self._indent = self.setting('indentationStep')
302 self._indentLev = self.setting('initialMethIndentLevel')
303 self._pendingStrConstChunks = []
304 self._methodSignature = None
305 self._methodDef = None
306 self._docStringLines = []
307 self._methodBodyChunks = []
308
309 self._cacheRegionsStack = []
310 self._callRegionsStack = []
311 self._captureRegionsStack = []
312 self._filterRegionsStack = []
313
314 self._isErrorCatcherOn = False
315
316 self._hasReturnStatement = False
317 self._isGenerator = False
318
319
320 def cleanupState(self):
321 """Called by the containing class compiler instance
322 """
323 pass
324
325 def methodName(self):
326 return self._methodName
327
328 def setMethodName(self, name):
329 self._methodName = name
330
331 ## methods for managing indentation
332
333 def indentation(self):
334 return self._indent * self._indentLev
335
336 def indent(self):
337 self._indentLev +=1
338
339 def dedent(self):
340 if self._indentLev:
341 self._indentLev -=1
342 else:
343 raise Error('Attempt to dedent when the indentLev is 0')
344
345 ## methods for final code wrapping
346
347 def methodDef(self):
348 if self._methodDef:
349 return self._methodDef
350 else:
351 return self.wrapCode()
352
353 __str__ = methodDef
354 __unicode__ = methodDef
355
356 def wrapCode(self):
357 self.commitStrConst()
358 methodDefChunks = (
359 self.methodSignature(),
360 '\n',
361 self.docString(),
362 self.methodBody() )
363 methodDef = ''.join(methodDefChunks)
364 self._methodDef = methodDef
365 return methodDef
366
367 def methodSignature(self):
368 return self._indent + self._methodSignature + ':'
369
370 def setMethodSignature(self, signature):
371 self._methodSignature = signature
372
373 def methodBody(self):
374 return ''.join( self._methodBodyChunks )
375
376 def docString(self):
377 if not self._docStringLines:
378 return ''
379
380 ind = self._indent*2
381 docStr = (ind + '"""\n' + ind +
382 ('\n' + ind).join([ln.replace('"""', "'''") for ln in self._docStringLines]) +
383 '\n' + ind + '"""\n')
384 return docStr
385
386 ## methods for adding code
387 def addMethDocString(self, line):
388 self._docStringLines.append(line.replace('%', '%%'))
389
390 def addChunk(self, chunk):
391 self.commitStrConst()
392 chunk = "\n" + self.indentation() + chunk
393 self._methodBodyChunks.append(chunk)
394
395 def appendToPrevChunk(self, appendage):
396 self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage
397
398 def addWriteChunk(self, chunk):
399 self.addChunk('write(' + chunk + ')')
400
401 def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None):
402 if filterArgs is None:
403 filterArgs = ''
404 if self.setting('includeRawExprInFilterArgs') and rawExpr:
405 filterArgs += ', rawExpr=%s'%repr(rawExpr)
406
407 if self.setting('alwaysFilterNone'):
408 if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1:
409 self.addChunk("_v = %s # %r"%(chunk, rawExpr))
410 if lineCol:
411 self.appendToPrevChunk(' on line %s, col %s'%lineCol)
412 else:
413 self.addChunk("_v = %s"%chunk)
414
415 if self.setting('useFilters'):
416 self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
417 else:
418 self.addChunk("if _v is not None: write(str(_v))")
419 else:
420 if self.setting('useFilters'):
421 self.addChunk("write(_filter(%s%s))"%(chunk, filterArgs))
422 else:
423 self.addChunk("write(str(%s))"%chunk)
424
425 def _appendToPrevStrConst(self, strConst):
426 if self._pendingStrConstChunks:
427 self._pendingStrConstChunks.append(strConst)
428 else:
429 self._pendingStrConstChunks = [strConst]
430
431 def commitStrConst(self):
432 """Add the code for outputting the pending strConst without chopping off
433 any whitespace from it.
434 """
435 if not self._pendingStrConstChunks:
436 return
437
438 strConst = ''.join(self._pendingStrConstChunks)
439 self._pendingStrConstChunks = []
440 if not strConst:
441 return
442
443 reprstr = repr(strConst)
444 i = 0
445 out = []
446 if reprstr.startswith('u'):
447 i = 1
448 out = ['u']
449 body = escapedNewlineRE.sub('\\1\n', reprstr[i+1:-1])
450
451 if reprstr[i]=="'":
452 out.append("'''")
453 out.append(body)
454 out.append("'''")
455 else:
456 out.append('"""')
457 out.append(body)
458 out.append('"""')
459 self.addWriteChunk(''.join(out))
460
461 def handleWSBeforeDirective(self):
462 """Truncate the pending strCont to the beginning of the current line.
463 """
464 if self._pendingStrConstChunks:
465 src = self._pendingStrConstChunks[-1]
466 BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0)
467 if BOL < len(src):
468 self._pendingStrConstChunks[-1] = src[:BOL]
469
470
471
472 def isErrorCatcherOn(self):
473 return self._isErrorCatcherOn
474
475 def turnErrorCatcherOn(self):
476 self._isErrorCatcherOn = True
477
478 def turnErrorCatcherOff(self):
479 self._isErrorCatcherOn = False
480
481 # @@TR: consider merging the next two methods into one
482 def addStrConst(self, strConst):
483 self._appendToPrevStrConst(strConst)
484
485 def addRawText(self, text):
486 self.addStrConst(text)
487
488 def addMethComment(self, comm):
489 offSet = self.setting('commentOffset')
490 self.addChunk('#' + ' '*offSet + comm)
491
492 def addPlaceholder(self, expr, filterArgs, rawPlaceholder,
493 cacheTokenParts, lineCol,
494 silentMode=False):
495 cacheInfo = self.genCacheInfo(cacheTokenParts)
496 if cacheInfo:
497 cacheInfo['ID'] = repr(rawPlaceholder)[1:-1]
498 self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder)
499
500 if self.isErrorCatcherOn():
501 methodName = self._classCompiler.addErrorCatcherCall(
502 expr, rawCode=rawPlaceholder, lineCol=lineCol)
503 expr = 'self.' + methodName + '(localsDict=locals())'
504
505 if silentMode:
506 self.addChunk('try:')
507 self.indent()
508 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
509 self.dedent()
510 self.addChunk('except NotFound: pass')
511 else:
512 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
513
514 if self.setting('outputRowColComments'):
515 self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
516 if cacheInfo:
517 self.endCacheRegion()
518
519 def addSilent(self, expr):
520 self.addChunk( expr )
521
522 def addEcho(self, expr, rawExpr=None):
523 self.addFilteredChunk(expr, rawExpr=rawExpr)
524
525 def addSet(self, expr, exprComponents, setStyle):
526 if setStyle is SET_GLOBAL:
527 (LVALUE, OP, RVALUE) = (exprComponents.LVALUE,
528 exprComponents.OP,
529 exprComponents.RVALUE)
530 # we need to split the LVALUE to deal with globalSetVars
531 splitPos1 = LVALUE.find('.')
532 splitPos2 = LVALUE.find('[')
533 if splitPos1 > 0 and splitPos2==-1:
534 splitPos = splitPos1
535 elif splitPos1 > 0 and splitPos1 < max(splitPos2, 0):
536 splitPos = splitPos1
537 else:
538 splitPos = splitPos2
539
540 if splitPos >0:
541 primary = LVALUE[:splitPos]
542 secondary = LVALUE[splitPos:]
543 else:
544 primary = LVALUE
545 secondary = ''
546 LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
547 expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
548
549 if setStyle is SET_MODULE:
550 self._moduleCompiler.addModuleGlobal(expr)
551 else:
552 self.addChunk(expr)
553
554 def addInclude(self, sourceExpr, includeFrom, isRaw):
555 self.addChunk('self._handleCheetahInclude(' + sourceExpr +
556 ', trans=trans, ' +
557 'includeFrom="' + includeFrom + '", raw=' +
558 repr(isRaw) + ')')
559
560 def addWhile(self, expr, lineCol=None):
561 self.addIndentingDirective(expr, lineCol=lineCol)
562
563 def addFor(self, expr, lineCol=None):
564 self.addIndentingDirective(expr, lineCol=lineCol)
565
566 def addRepeat(self, expr, lineCol=None):
567 #the _repeatCount stuff here allows nesting of #repeat directives
568 self._repeatCount = getattr(self, "_repeatCount", -1) + 1
569 self.addFor('for __i%s in range(%s)' % (self._repeatCount, expr), lineCol=lineCol)
570
571 def addIndentingDirective(self, expr, lineCol=None):
572 if expr and not expr[-1] == ':':
573 expr = expr + ':'
574 self.addChunk( expr )
575 if lineCol:
576 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
577 self.indent()
578
579 def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
580 self.commitStrConst()
581 if dedent:
582 self.dedent()
583 if not expr[-1] == ':':
584 expr = expr + ':'
585
586 self.addChunk( expr )
587 if lineCol:
588 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
589 self.indent()
590
591 def addIf(self, expr, lineCol=None):
592 """For a full #if ... #end if directive
593 """
594 self.addIndentingDirective(expr, lineCol=lineCol)
595
596 def addOneLineIf(self, expr, lineCol=None):
597 """For a full #if ... #end if directive
598 """
599 self.addIndentingDirective(expr, lineCol=lineCol)
600
601 def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None):
602 """For a single-lie #if ... then .... else ... directive
603 <condition> then <trueExpr> else <falseExpr>
604 """
605 self.addIndentingDirective(conditionExpr, lineCol=lineCol)
606 self.addFilteredChunk(trueExpr)
607 self.dedent()
608 self.addIndentingDirective('else')
609 self.addFilteredChunk(falseExpr)
610 self.dedent()
611
612 def addElse(self, expr, dedent=True, lineCol=None):
613 expr = re.sub(r'else[ \f\t]+if', 'elif', expr)
614 self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
615
616 def addElif(self, expr, dedent=True, lineCol=None):
617 self.addElse(expr, dedent=dedent, lineCol=lineCol)
618
619 def addUnless(self, expr, lineCol=None):
620 self.addIf('if not (' + expr + ')')
621
622 def addClosure(self, functionName, argsList, parserComment):
623 argStringChunks = []
624 for arg in argsList:
625 chunk = arg[0]
626 if not arg[1] == None:
627 chunk += '=' + arg[1]
628 argStringChunks.append(chunk)
629 signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):"
630 self.addIndentingDirective(signature)
631 self.addChunk('#'+parserComment)
632
633 def addTry(self, expr, lineCol=None):
634 self.addIndentingDirective(expr, lineCol=lineCol)
635
636 def addExcept(self, expr, dedent=True, lineCol=None):
637 self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
638
639 def addFinally(self, expr, dedent=True, lineCol=None):
640 self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
641
642 def addReturn(self, expr):
643 assert not self._isGenerator
644 self.addChunk(expr)
645 self._hasReturnStatement = True
646
647 def addYield(self, expr):
648 assert not self._hasReturnStatement
649 self._isGenerator = True
650 if expr.replace('yield', '').strip():
651 self.addChunk(expr)
652 else:
653 self.addChunk('if _dummyTrans:')
654 self.indent()
655 self.addChunk('yield trans.response().getvalue()')
656 self.addChunk('trans = DummyTransaction()')
657 self.addChunk('write = trans.response().write')
658 self.dedent()
659 self.addChunk('else:')
660 self.indent()
661 self.addChunk(
662 'raise TypeError("This method cannot be called with a trans arg")')
663 self.dedent()
664
665
666 def addPass(self, expr):
667 self.addChunk(expr)
668
669 def addDel(self, expr):
670 self.addChunk(expr)
671
672 def addAssert(self, expr):
673 self.addChunk(expr)
674
675 def addRaise(self, expr):
676 self.addChunk(expr)
677
678 def addBreak(self, expr):
679 self.addChunk(expr)
680
681 def addContinue(self, expr):
682 self.addChunk(expr)
683
684 def addPSP(self, PSP):
685 self.commitStrConst()
686 autoIndent = False
687 if PSP[0] == '=':
688 PSP = PSP[1:]
689 if PSP:
690 self.addWriteChunk('_filter(' + PSP + ')')
691 return
692
693 elif PSP.lower() == 'end':
694 self.dedent()
695 return
696 elif PSP[-1] == '$':
697 autoIndent = True
698 PSP = PSP[:-1]
699 elif PSP[-1] == ':':
700 autoIndent = True
701
702 for line in PSP.splitlines():
703 self.addChunk(line)
704
705 if autoIndent:
706 self.indent()
707
708 def nextCacheID(self):
709 return ('_'+str(random.randrange(100, 999))
710 + str(random.randrange(10000, 99999)))
711
712 def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None):
713
714 # @@TR: we should add some runtime logging to this
715
716 ID = self.nextCacheID()
717 interval = cacheInfo.get('interval', None)
718 test = cacheInfo.get('test', None)
719 customID = cacheInfo.get('id', None)
720 if customID:
721 ID = customID
722 varyBy = cacheInfo.get('varyBy', repr(ID))
723 self._cacheRegionsStack.append(ID) # attrib of current methodCompiler
724
725 # @@TR: add this to a special class var as well
726 self.addChunk('')
727
728 self.addChunk('## START CACHE REGION: ID='+ID+
729 '. line %s, col %s'%lineCol + ' in the source.')
730
731 self.addChunk('_RECACHE_%(ID)s = False'%locals())
732 self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
733 + repr(ID)
734 + ', cacheInfo=%r'%cacheInfo
735 + ')')
736 self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
737 self.indent()
738 self.addChunk('_RECACHE_%(ID)s = True'%locals())
739 self.dedent()
740
741 self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
742 +varyBy+')')
743
744 self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
745 self.indent()
746 self.addChunk('_RECACHE_%(ID)s = True'%locals())
747 self.dedent()
748
749 if test:
750 self.addChunk('if ' + test + ':')
751 self.indent()
752 self.addChunk('_RECACHE_%(ID)s = True'%locals())
753 self.dedent()
754
755 self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
756 self.indent()
757 #self.addChunk('print "DEBUG"+"-"*50')
758 self.addChunk('try:')
759 self.indent()
760 self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
761 self.dedent()
762 self.addChunk('except KeyError:')
763 self.indent()
764 self.addChunk('_RECACHE_%(ID)s = True'%locals())
765 #self.addChunk('print "DEBUG"+"*"*50')
766 self.dedent()
767 self.addChunk('else:')
768 self.indent()
769 self.addWriteChunk('_output')
770 self.addChunk('del _output')
771 self.dedent()
772
773 self.dedent()
774
775 self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
776 self.indent()
777 self.addChunk('_orig_trans%(ID)s = trans'%locals())
778 self.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals())
779 self.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals())
780 if interval:
781 self.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals())
782 + str(interval) + ")")
783
784 def endCacheRegion(self):
785 ID = self._cacheRegionsStack.pop()
786 self.addChunk('trans = _orig_trans%(ID)s'%locals())
787 self.addChunk('write = trans.response().write')
788 self.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals())
789 self.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals())
790 self.addWriteChunk('_cacheData')
791 self.addChunk('del _cacheData')
792 self.addChunk('del _cacheCollector_%(ID)s'%locals())
793 self.addChunk('del _orig_trans%(ID)s'%locals())
794 self.dedent()
795 self.addChunk('## END CACHE REGION: '+ID)
796 self.addChunk('')
797
798 def nextCallRegionID(self):
799 return self.nextCacheID()
800
801 def startCallRegion(self, functionName, args, lineCol, regionTitle='CALL'):
802 class CallDetails(object):
803 pass
804 callDetails = CallDetails()
805 callDetails.ID = ID = self.nextCallRegionID()
806 callDetails.functionName = functionName
807 callDetails.args = args
808 callDetails.lineCol = lineCol
809 callDetails.usesKeywordArgs = False
810 self._callRegionsStack.append((ID, callDetails)) # attrib of current methodCompiler
811
812 self.addChunk('## START %(regionTitle)s REGION: '%locals()
813 +ID
814 +' of '+functionName
815 +' at line %s, col %s'%lineCol + ' in the source.')
816 self.addChunk('_orig_trans%(ID)s = trans'%locals())
817 self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
818 self.addChunk('self._CHEETAH__isBuffering = True')
819 self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
820 self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
821
822 def setCallArg(self, argName, lineCol):
823 ID, callDetails = self._callRegionsStack[-1]
824 argName = str(argName)
825 if callDetails.usesKeywordArgs:
826 self._endCallArg()
827 else:
828 callDetails.usesKeywordArgs = True
829 self.addChunk('_callKws%(ID)s = {}'%locals())
830 self.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals())
831 callDetails.currentArgname = argName
832
833 def _endCallArg(self):
834 ID, callDetails = self._callRegionsStack[-1]
835 currCallArg = callDetails.currentArgname
836 self.addChunk(('_callKws%(ID)s[%(currCallArg)r] ='
837 ' _callCollector%(ID)s.response().getvalue()')%locals())
838 self.addChunk('del _callCollector%(ID)s'%locals())
839 self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
840 self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
841
842 def endCallRegion(self, regionTitle='CALL'):
843 ID, callDetails = self._callRegionsStack[-1]
844 functionName, initialKwArgs, lineCol = (
845 callDetails.functionName, callDetails.args, callDetails.lineCol)
846
847 def reset(ID=ID):
848 self.addChunk('trans = _orig_trans%(ID)s'%locals())
849 self.addChunk('write = trans.response().write')
850 self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
851 self.addChunk('del _wasBuffering%(ID)s'%locals())
852 self.addChunk('del _orig_trans%(ID)s'%locals())
853
854 if not callDetails.usesKeywordArgs:
855 reset()
856 self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
857 self.addChunk('del _callCollector%(ID)s'%locals())
858 if initialKwArgs:
859 initialKwArgs = ', '+initialKwArgs
860 self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
861 self.addChunk('del _callArgVal%(ID)s'%locals())
862 else:
863 if initialKwArgs:
864 initialKwArgs = initialKwArgs+', '
865 self._endCallArg()
866 reset()
867 self.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals())
868 self.addChunk('del _callKws%(ID)s'%locals())
869 self.addChunk('## END %(regionTitle)s REGION: '%locals()
870 +ID
871 +' of '+functionName
872 +' at line %s, col %s'%lineCol + ' in the source.')
873 self.addChunk('')
874 self._callRegionsStack.pop() # attrib of current methodCompiler
875
876 def nextCaptureRegionID(self):
877 return self.nextCacheID()
878
879 def startCaptureRegion(self, assignTo, lineCol):
880 class CaptureDetails: pass
881 captureDetails = CaptureDetails()
882 captureDetails.ID = ID = self.nextCaptureRegionID()
883 captureDetails.assignTo = assignTo
884 captureDetails.lineCol = lineCol
885
886 self._captureRegionsStack.append((ID, captureDetails)) # attrib of current methodCompiler
887 self.addChunk('## START CAPTURE REGION: '+ID
888 +' '+assignTo
889 +' at line %s, col %s'%lineCol + ' in the source.')
890 self.addChunk('_orig_trans%(ID)s = trans'%locals())
891 self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
892 self.addChunk('self._CHEETAH__isBuffering = True')
893 self.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals())
894 self.addChunk('write = _captureCollector%(ID)s.response().write'%locals())
895
896 def endCaptureRegion(self):
897 ID, captureDetails = self._captureRegionsStack.pop()
898 assignTo, lineCol = (captureDetails.assignTo, captureDetails.lineCol)
899 self.addChunk('trans = _orig_trans%(ID)s'%locals())
900 self.addChunk('write = trans.response().write')
901 self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
902 self.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals())
903 self.addChunk('del _orig_trans%(ID)s'%locals())
904 self.addChunk('del _captureCollector%(ID)s'%locals())
905 self.addChunk('del _wasBuffering%(ID)s'%locals())
906
907 def setErrorCatcher(self, errorCatcherName):
908 self.turnErrorCatcherOn()
909
910 self.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName + '"):')
911 self.indent()
912 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
913 errorCatcherName + '"]')
914 self.dedent()
915 self.addChunk('else:')
916 self.indent()
917 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
918 + errorCatcherName + '"] = ErrorCatchers.'
919 + errorCatcherName + '(self)'
920 )
921 self.dedent()
922
923 def nextFilterRegionID(self):
924 return self.nextCacheID()
925
926 def setTransform(self, transformer, isKlass):
927 self.addChunk('trans = TransformerTransaction()')
928 self.addChunk('trans._response = trans.response()')
929 self.addChunk('trans._response._filter = %s' % transformer)
930 self.addChunk('write = trans._response.write')
931
932 def setFilter(self, theFilter, isKlass):
933 class FilterDetails:
934 pass
935 filterDetails = FilterDetails()
936 filterDetails.ID = ID = self.nextFilterRegionID()
937 filterDetails.theFilter = theFilter
938 filterDetails.isKlass = isKlass
939 self._filterRegionsStack.append((ID, filterDetails)) # attrib of current methodCompiler
940
941 self.addChunk('_orig_filter%(ID)s = _filter'%locals())
942 if isKlass:
943 self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() +
944 '(self).filter')
945 else:
946 if theFilter.lower() == 'none':
947 self.addChunk('_filter = self._CHEETAH__initialFilter')
948 else:
949 # is string representing the name of a builtin filter
950 self.addChunk('filterName = ' + repr(theFilter))
951 self.addChunk('if self._CHEETAH__filters.has_key("' + theFilter + '"):')
952 self.indent()
953 self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
954 self.dedent()
955 self.addChunk('else:')
956 self.indent()
957 self.addChunk('_filter = self._CHEETAH__currentFilter'
958 +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
959 + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
960 self.dedent()
961
962 def closeFilterBlock(self):
963 ID, filterDetails = self._filterRegionsStack.pop()
964 #self.addChunk('_filter = self._CHEETAH__initialFilter')
965 #self.addChunk('_filter = _orig_filter%(ID)s'%locals())
966 self.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals())
967
968class AutoMethodCompiler(MethodCompiler):
969
970 def _setupState(self):
971 MethodCompiler._setupState(self)
972 self._argStringList = [ ("self", None) ]
973 self._streamingEnabled = True
974 self._isClassMethod = None
975 self._isStaticMethod = None
976
977 def _useKWsDictArgForPassingTrans(self):
978 alreadyHasTransArg = [argname for argname, defval in self._argStringList
979 if argname=='trans']
980 return (self.methodName()!='respond'
981 and not alreadyHasTransArg
982 and self.setting('useKWsDictArgForPassingTrans'))
983
984 def isClassMethod(self):
985 if self._isClassMethod is None:
986 self._isClassMethod = '@classmethod' in self._decorators
987 return self._isClassMethod
988
989 def isStaticMethod(self):
990 if self._isStaticMethod is None:
991 self._isStaticMethod = '@staticmethod' in self._decorators
992 return self._isStaticMethod
993
994 def cleanupState(self):
995 MethodCompiler.cleanupState(self)
996 self.commitStrConst()
997 if self._cacheRegionsStack:
998 self.endCacheRegion()
999 if self._callRegionsStack:
1000 self.endCallRegion()
1001
1002 if self._streamingEnabled:
1003 kwargsName = None
1004 positionalArgsListName = None
1005 for argname, defval in self._argStringList:
1006 if argname.strip().startswith('**'):
1007 kwargsName = argname.strip().replace('**', '')
1008 break
1009 elif argname.strip().startswith('*'):
1010 positionalArgsListName = argname.strip().replace('*', '')
1011
1012 if not kwargsName and self._useKWsDictArgForPassingTrans():
1013 kwargsName = 'KWS'
1014 self.addMethArg('**KWS', None)
1015 self._kwargsName = kwargsName
1016
1017 if not self._useKWsDictArgForPassingTrans():
1018 if not kwargsName and not positionalArgsListName:
1019 self.addMethArg('trans', 'None')
1020 else:
1021 self._streamingEnabled = False
1022
1023 self._indentLev = self.setting('initialMethIndentLevel')
1024 mainBodyChunks = self._methodBodyChunks
1025 self._methodBodyChunks = []
1026 self._addAutoSetupCode()
1027 self._methodBodyChunks.extend(mainBodyChunks)
1028 self._addAutoCleanupCode()
1029
1030 def _addAutoSetupCode(self):
1031 if self._initialMethodComment:
1032 self.addChunk(self._initialMethodComment)
1033
1034 if self._streamingEnabled and not self.isClassMethod() and not self.isStaticMethod():
1035 if self._useKWsDictArgForPassingTrans() and self._kwargsName:
1036 self.addChunk('trans = %s.get("trans")'%self._kwargsName)
1037 self.addChunk('if (not trans and not self._CHEETAH__isBuffering'
1038 ' and not callable(self.transaction)):')
1039 self.indent()
1040 self.addChunk('trans = self.transaction'
1041 ' # is None unless self.awake() was called')
1042 self.dedent()
1043 self.addChunk('if not trans:')
1044 self.indent()
1045 self.addChunk('trans = DummyTransaction()')
1046 if self.setting('autoAssignDummyTransactionToSelf'):
1047 self.addChunk('self.transaction = trans')
1048 self.addChunk('_dummyTrans = True')
1049 self.dedent()
1050 self.addChunk('else: _dummyTrans = False')
1051 else:
1052 self.addChunk('trans = DummyTransaction()')
1053 self.addChunk('_dummyTrans = True')
1054 self.addChunk('write = trans.response().write')
1055 if self.setting('useNameMapper'):
1056 argNames = [arg[0] for arg in self._argStringList]
1057 allowSearchListAsMethArg = self.setting('allowSearchListAsMethArg')
1058 if allowSearchListAsMethArg and 'SL' in argNames:
1059 pass
1060 elif allowSearchListAsMethArg and 'searchList' in argNames:
1061 self.addChunk('SL = searchList')
1062 elif not self.isClassMethod() and not self.isStaticMethod():
1063 self.addChunk('SL = self._CHEETAH__searchList')
1064 else:
1065 self.addChunk('SL = [KWS]')
1066 if self.setting('useFilters'):
1067 if self.isClassMethod() or self.isStaticMethod():
1068 self.addChunk('_filter = lambda x, **kwargs: unicode(x)')
1069 else:
1070 self.addChunk('_filter = self._CHEETAH__currentFilter')
1071 self.addChunk('')
1072 self.addChunk("#" *40)
1073 self.addChunk('## START - generated method body')
1074 self.addChunk('')
1075
1076 def _addAutoCleanupCode(self):
1077 self.addChunk('')
1078 self.addChunk("#" *40)
1079 self.addChunk('## END - generated method body')
1080 self.addChunk('')
1081
1082 if not self._isGenerator:
1083 self.addStop()
1084 self.addChunk('')
1085
1086 def addStop(self, expr=None):
1087 self.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
1088
1089 def addMethArg(self, name, defVal=None):
1090 self._argStringList.append( (name, defVal) )
1091
1092 def methodSignature(self):
1093 argStringChunks = []
1094 for arg in self._argStringList:
1095 chunk = arg[0]
1096 if chunk == 'self' and self.isClassMethod():
1097 chunk = 'cls'
1098 if chunk == 'self' and self.isStaticMethod():
1099 # Skip the "self" method for @staticmethod decorators
1100 continue
1101 if not arg[1] == None:
1102 chunk += '=' + arg[1]
1103 argStringChunks.append(chunk)
1104 argString = (', ').join(argStringChunks)
1105
1106 output = []
1107 if self._decorators:
1108 output.append(''.join([self._indent + decorator + '\n'
1109 for decorator in self._decorators]))
1110 output.append(self._indent + "def "
1111 + self.methodName() + "(" +
1112 argString + "):\n\n")
1113 return ''.join(output)
1114
1115
1116##################################################
1117## CLASS COMPILERS
1118
1119_initMethod_initCheetah = """\
1120if not self._CHEETAH__instanceInitialized:
1121 cheetahKWArgs = {}
1122 allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
1123 for k,v in KWs.items():
1124 if k in allowedKWs: cheetahKWArgs[k] = v
1125 self._initCheetahInstance(**cheetahKWArgs)
1126""".replace('\n', '\n'+' '*8)
1127
1128class ClassCompiler(GenUtils):
1129 methodCompilerClass = AutoMethodCompiler
1130 methodCompilerClassForInit = MethodCompiler
1131
1132 def __init__(self, className, mainMethodName='respond',
1133 moduleCompiler=None,
1134 fileName=None,
1135 settingsManager=None):
1136
1137 self._settingsManager = settingsManager
1138 self._fileName = fileName
1139 self._className = className
1140 self._moduleCompiler = moduleCompiler
1141 self._mainMethodName = mainMethodName
1142 self._setupState()
1143 methodCompiler = self._spawnMethodCompiler(
1144 mainMethodName,
1145 initialMethodComment='## CHEETAH: main method generated for this template')
1146
1147 self._setActiveMethodCompiler(methodCompiler)
1148 if fileName and self.setting('monitorSrcFile'):
1149 self._addSourceFileMonitoring(fileName)
1150
1151 def setting(self, key):
1152 return self._settingsManager.setting(key)
1153
1154 def __getattr__(self, name):
1155 """Provide access to the methods and attributes of the MethodCompiler
1156 at the top of the activeMethods stack: one-way namespace sharing
1157
1158
1159 WARNING: Use .setMethods to assign the attributes of the MethodCompiler
1160 from the methods of this class!!! or you will be assigning to attributes
1161 of this object instead."""
1162
1163 if name in self.__dict__:
1164 return self.__dict__[name]
1165 elif hasattr(self.__class__, name):
1166 return getattr(self.__class__, name)
1167 elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name):
1168 return getattr(self._activeMethodsList[-1], name)
1169 else:
1170 raise AttributeError(name)
1171
1172 def _setupState(self):
1173 self._classDef = None
1174 self._decoratorsForNextMethod = []
1175 self._activeMethodsList = [] # stack while parsing/generating
1176 self._finishedMethodsList = [] # store by order
1177 self._methodsIndex = {} # store by name
1178 self._baseClass = 'Template'
1179 self._classDocStringLines = []
1180 # printed after methods in the gen class def:
1181 self._generatedAttribs = ['_CHEETAH__instanceInitialized = False']
1182 self._generatedAttribs.append('_CHEETAH_version = __CHEETAH_version__')
1183 self._generatedAttribs.append(
1184 '_CHEETAH_versionTuple = __CHEETAH_versionTuple__')
1185
1186 if self.setting('addTimestampsToCompilerOutput'):
1187 self._generatedAttribs.append('_CHEETAH_genTime = __CHEETAH_genTime__')
1188 self._generatedAttribs.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__')
1189
1190 self._generatedAttribs.append('_CHEETAH_src = __CHEETAH_src__')
1191 self._generatedAttribs.append(
1192 '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__')
1193
1194 if self.setting('templateMetaclass'):
1195 self._generatedAttribs.append('__metaclass__ = '+self.setting('templateMetaclass'))
1196 self._initMethChunks = []
1197 self._blockMetaData = {}
1198 self._errorCatcherCount = 0
1199 self._placeholderToErrorCatcherMap = {}
1200
1201 def cleanupState(self):
1202 while self._activeMethodsList:
1203 methCompiler = self._popActiveMethodCompiler()
1204 self._swallowMethodCompiler(methCompiler)
1205 self._setupInitMethod()
1206 if self._mainMethodName == 'respond':
1207 if self.setting('setup__str__method'):
1208 self._generatedAttribs.append('def __str__(self): return self.respond()')
1209 self.addAttribute('_mainCheetahMethod_for_' + self._className +
1210 '= ' + repr(self._mainMethodName) )
1211
1212 def _setupInitMethod(self):
1213 __init__ = self._spawnMethodCompiler('__init__',
1214 klass=self.methodCompilerClassForInit)
1215 __init__.setMethodSignature("def __init__(self, *args, **KWs)")
1216 __init__.addChunk('super(%s, self).__init__(*args, **KWs)' % self._className)
1217 __init__.addChunk(_initMethod_initCheetah % {'className' : self._className})
1218 for chunk in self._initMethChunks:
1219 __init__.addChunk(chunk)
1220 __init__.cleanupState()
1221 self._swallowMethodCompiler(__init__, pos=0)
1222
1223 def _addSourceFileMonitoring(self, fileName):
1224 # @@TR: this stuff needs auditing for Cheetah 2.0
1225 # the first bit is added to init
1226 self.addChunkToInit('self._filePath = ' + repr(fileName))
1227 self.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName)) )
1228
1229 # the rest is added to the main output method of the class ('mainMethod')
1230 self.addChunk('if exists(self._filePath) and ' +
1231 'getmtime(self._filePath) > self._fileMtime:')
1232 self.indent()
1233 self.addChunk('self._compile(file=self._filePath, moduleName='+self._className + ')')
1234 self.addChunk(
1235 'write(getattr(self, self._mainCheetahMethod_for_' + self._className +
1236 ')(trans=trans))')
1237 self.addStop()
1238 self.dedent()
1239
1240 def setClassName(self, name):
1241 self._className = name
1242
1243 def className(self):
1244 return self._className
1245
1246 def setBaseClass(self, baseClassName):
1247 self._baseClass = baseClassName
1248
1249 def setMainMethodName(self, methodName):
1250 if methodName == self._mainMethodName:
1251 return
1252 ## change the name in the methodCompiler and add new reference
1253 mainMethod = self._methodsIndex[self._mainMethodName]
1254 mainMethod.setMethodName(methodName)
1255 self._methodsIndex[methodName] = mainMethod
1256
1257 ## make sure that fileUpdate code still works properly:
1258 chunkToChange = ('write(self.' + self._mainMethodName + '(trans=trans))')
1259 chunks = mainMethod._methodBodyChunks
1260 if chunkToChange in chunks:
1261 for i in range(len(chunks)):
1262 if chunks[i] == chunkToChange:
1263 chunks[i] = ('write(self.' + methodName + '(trans=trans))')
1264 ## get rid of the old reference and update self._mainMethodName
1265 del self._methodsIndex[self._mainMethodName]
1266 self._mainMethodName = methodName
1267
1268 def setMainMethodArgs(self, argsList):
1269 mainMethodCompiler = self._methodsIndex[self._mainMethodName]
1270 for argName, defVal in argsList:
1271 mainMethodCompiler.addMethArg(argName, defVal)
1272
1273
1274 def _spawnMethodCompiler(self, methodName, klass=None,
1275 initialMethodComment=None):
1276 if klass is None:
1277 klass = self.methodCompilerClass
1278
1279 decorators = self._decoratorsForNextMethod or []
1280 self._decoratorsForNextMethod = []
1281 methodCompiler = klass(methodName, classCompiler=self,
1282 decorators=decorators,
1283 initialMethodComment=initialMethodComment)
1284 self._methodsIndex[methodName] = methodCompiler
1285 return methodCompiler
1286
1287 def _setActiveMethodCompiler(self, methodCompiler):
1288 self._activeMethodsList.append(methodCompiler)
1289
1290 def _getActiveMethodCompiler(self):
1291 return self._activeMethodsList[-1]
1292
1293 def _popActiveMethodCompiler(self):
1294 return self._activeMethodsList.pop()
1295
1296 def _swallowMethodCompiler(self, methodCompiler, pos=None):
1297 methodCompiler.cleanupState()
1298 if pos==None:
1299 self._finishedMethodsList.append( methodCompiler )
1300 else:
1301 self._finishedMethodsList.insert(pos, methodCompiler)
1302 return methodCompiler
1303
1304 def startMethodDef(self, methodName, argsList, parserComment):
1305 methodCompiler = self._spawnMethodCompiler(
1306 methodName, initialMethodComment=parserComment)
1307 self._setActiveMethodCompiler(methodCompiler)
1308 for argName, defVal in argsList:
1309 methodCompiler.addMethArg(argName, defVal)
1310
1311 def _finishedMethods(self):
1312 return self._finishedMethodsList
1313
1314 def addDecorator(self, decoratorExpr):
1315 """Set the decorator to be used with the next method in the source.
1316
1317 See _spawnMethodCompiler() and MethodCompiler for the details of how
1318 this is used.
1319 """
1320 self._decoratorsForNextMethod.append(decoratorExpr)
1321
1322 def addClassDocString(self, line):
1323 self._classDocStringLines.append( line.replace('%', '%%'))
1324
1325 def addChunkToInit(self, chunk):
1326 self._initMethChunks.append(chunk)
1327
1328 def addAttribute(self, attribExpr):
1329 ## first test to make sure that the user hasn't used any fancy Cheetah syntax
1330 # (placeholders, directives, etc.) inside the expression
1331 if attribExpr.find('VFN(') != -1 or attribExpr.find('VFFSL(') != -1:
1332 raise ParseError(self,
1333 'Invalid #attr directive.' +
1334 ' It should only contain simple Python literals.')
1335 ## now add the attribute
1336 self._generatedAttribs.append(attribExpr)
1337
1338 def addSuper(self, argsList, parserComment=None):
1339 className = self._className #self._baseClass
1340 methodName = self._getActiveMethodCompiler().methodName()
1341
1342 argStringChunks = []
1343 for arg in argsList:
1344 chunk = arg[0]
1345 if not arg[1] == None:
1346 chunk += '=' + arg[1]
1347 argStringChunks.append(chunk)
1348 argString = ','.join(argStringChunks)
1349
1350 self.addFilteredChunk(
1351 'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals())
1352
1353 def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''):
1354 if rawCode in self._placeholderToErrorCatcherMap:
1355 methodName = self._placeholderToErrorCatcherMap[rawCode]
1356 if not self.setting('outputRowColComments'):
1357 self._methodsIndex[methodName].addMethDocString(
1358 'plus at line %s, col %s'%lineCol)
1359 return methodName
1360
1361 self._errorCatcherCount += 1
1362 methodName = '__errorCatcher' + str(self._errorCatcherCount)
1363 self._placeholderToErrorCatcherMap[rawCode] = methodName
1364
1365 catcherMeth = self._spawnMethodCompiler(
1366 methodName,
1367 klass=MethodCompiler,
1368 initialMethodComment=('## CHEETAH: Generated from ' + rawCode +
1369 ' at line %s, col %s'%lineCol + '.')
1370 )
1371 catcherMeth.setMethodSignature('def ' + methodName +
1372 '(self, localsDict={})')
1373 # is this use of localsDict right?
1374 catcherMeth.addChunk('try:')
1375 catcherMeth.indent()
1376 catcherMeth.addChunk("return eval('''" + codeChunk +
1377 "''', globals(), localsDict)")
1378 catcherMeth.dedent()
1379 catcherMeth.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:')
1380 catcherMeth.indent()
1381 catcherMeth.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " +
1382 repr(codeChunk) + " , rawCode= " +
1383 repr(rawCode) + " , lineCol=" + str(lineCol) +")")
1384
1385 catcherMeth.cleanupState()
1386
1387 self._swallowMethodCompiler(catcherMeth)
1388 return methodName
1389
1390 def closeDef(self):
1391 self.commitStrConst()
1392 methCompiler = self._popActiveMethodCompiler()
1393 self._swallowMethodCompiler(methCompiler)
1394
1395 def closeBlock(self):
1396 self.commitStrConst()
1397 methCompiler = self._popActiveMethodCompiler()
1398 methodName = methCompiler.methodName()
1399 if self.setting('includeBlockMarkers'):
1400 endMarker = self.setting('blockMarkerEnd')
1401 methCompiler.addStrConst(endMarker[0] + methodName + endMarker[1])
1402 self._swallowMethodCompiler(methCompiler)
1403
1404 #metaData = self._blockMetaData[methodName]
1405 #rawDirective = metaData['raw']
1406 #lineCol = metaData['lineCol']
1407
1408 ## insert the code to call the block, caching if #cache directive is on
1409 codeChunk = 'self.' + methodName + '(trans=trans)'
1410 self.addChunk(codeChunk)
1411
1412 #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) )
1413 #if self.setting('outputRowColComments'):
1414 # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.')
1415
1416
1417 ## code wrapping methods
1418
1419 def classDef(self):
1420 if self._classDef:
1421 return self._classDef
1422 else:
1423 return self.wrapClassDef()
1424
1425 __str__ = classDef
1426 __unicode__ = classDef
1427
1428 def wrapClassDef(self):
1429 ind = self.setting('indentationStep')
1430 classDefChunks = [self.classSignature(),
1431 self.classDocstring(),
1432 ]
1433 def addMethods():
1434 classDefChunks.extend([
1435 ind + '#'*50,
1436 ind + '## CHEETAH GENERATED METHODS',
1437 '\n',
1438 self.methodDefs(),
1439 ])
1440 def addAttributes():
1441 classDefChunks.extend([
1442 ind + '#'*50,
1443 ind + '## CHEETAH GENERATED ATTRIBUTES',
1444 '\n',
1445 self.attributes(),
1446 ])
1447 if self.setting('outputMethodsBeforeAttributes'):
1448 addMethods()
1449 addAttributes()
1450 else:
1451 addAttributes()
1452 addMethods()
1453
1454 classDef = '\n'.join(classDefChunks)
1455 self._classDef = classDef
1456 return classDef
1457
1458
1459 def classSignature(self):
1460 return "class %s(%s):" % (self.className(), self._baseClass)
1461
1462 def classDocstring(self):
1463 if not self._classDocStringLines:
1464 return ''
1465 ind = self.setting('indentationStep')
1466 docStr = ('%(ind)s"""\n%(ind)s' +
1467 '\n%(ind)s'.join(self._classDocStringLines) +
1468 '\n%(ind)s"""\n'
1469 ) % {'ind':ind}
1470 return docStr
1471
1472 def methodDefs(self):
1473 methodDefs = [methGen.methodDef() for methGen in self._finishedMethods()]
1474 return '\n\n'.join(methodDefs)
1475
1476 def attributes(self):
1477 attribs = [self.setting('indentationStep') + str(attrib)
1478 for attrib in self._generatedAttribs ]
1479 return '\n\n'.join(attribs)
1480
1481class AutoClassCompiler(ClassCompiler):
1482 pass
1483
1484##################################################
1485## MODULE COMPILERS
1486
1487class ModuleCompiler(SettingsManager, GenUtils):
1488
1489 parserClass = Parser
1490 classCompilerClass = AutoClassCompiler
1491
1492 def __init__(self, source=None, file=None,
1493 moduleName='DynamicallyCompiledCheetahTemplate',
1494 mainClassName=None, # string
1495 mainMethodName=None, # string
1496 baseclassName=None, # string
1497 extraImportStatements=None, # list of strings
1498 settings=None # dict
1499 ):
1500 super(ModuleCompiler, self).__init__()
1501 if settings:
1502 self.updateSettings(settings)
1503 # disable useStackFrames if the C version of NameMapper isn't compiled
1504 # it's painfully slow in the Python version and bites Windows users all
1505 # the time:
1506 if not NameMapper.C_VERSION:
1507 if not sys.platform.startswith('java'):
1508 warnings.warn(
1509 "\nYou don't have the C version of NameMapper installed! "
1510 "I'm disabling Cheetah's useStackFrames option as it is "
1511 "painfully slow with the Python version of NameMapper. "
1512 "You should get a copy of Cheetah with the compiled C version of NameMapper."
1513 )
1514 self.setSetting('useStackFrames', False)
1515
1516 self._compiled = False
1517 self._moduleName = moduleName
1518 if not mainClassName:
1519 self._mainClassName = moduleName
1520 else:
1521 self._mainClassName = mainClassName
1522 self._mainMethodNameArg = mainMethodName
1523 if mainMethodName:
1524 self.setSetting('mainMethodName', mainMethodName)
1525 self._baseclassName = baseclassName
1526
1527 self._filePath = None
1528 self._fileMtime = None
1529
1530 if source and file:
1531 raise TypeError("Cannot compile from a source string AND file.")
1532 elif isinstance(file, basestring): # it's a filename.
1533 f = open(file) # Raises IOError.
1534 source = f.read()
1535 f.close()
1536 self._filePath = file
1537 self._fileMtime = os.path.getmtime(file)
1538 elif hasattr(file, 'read'):
1539 source = file.read() # Can't set filename or mtime--they're not accessible.
1540 elif file:
1541 raise TypeError("'file' argument must be a filename string or file-like object")
1542
1543 if self._filePath:
1544 self._fileDirName, self._fileBaseName = os.path.split(self._filePath)
1545 self._fileBaseNameRoot, self._fileBaseNameExt = os.path.splitext(self._fileBaseName)
1546
1547 if not isinstance(source, basestring):
1548 source = unicode(source)
1549 # by converting to string here we allow objects such as other Templates
1550 # to be passed in
1551
1552 # Handle the #indent directive by converting it to other directives.
1553 # (Over the long term we'll make it a real directive.)
1554 if source == "":
1555 warnings.warn("You supplied an empty string for the source!", )
1556
1557 else:
1558 unicodeMatch = unicodeDirectiveRE.search(source)
1559 encodingMatch = encodingDirectiveRE.search(source)
1560 if unicodeMatch:
1561 if encodingMatch:
1562 raise ParseError(
1563 self, "#encoding and #unicode are mutually exclusive! "
1564 "Use one or the other.")
1565 source = unicodeDirectiveRE.sub('', source)
1566 if isinstance(source, str):
1567 encoding = unicodeMatch.group(1) or 'ascii'
1568 source = unicode(source, encoding)
1569 elif encodingMatch:
1570 encodings = encodingMatch.groups()
1571 if len(encodings):
1572 encoding = encodings[0]
1573 source = source.decode(encoding)
1574 else:
1575 source = unicode(source)
1576
1577 if source.find('#indent') != -1: #@@TR: undocumented hack
1578 source = indentize(source)
1579
1580 self._parser = self.parserClass(source, filename=self._filePath, compiler=self)
1581 self._setupCompilerState()
1582
1583 def __getattr__(self, name):
1584 """Provide one-way access to the methods and attributes of the
1585 ClassCompiler, and thereby the MethodCompilers as well.
1586
1587 WARNING: Use .setMethods to assign the attributes of the ClassCompiler
1588 from the methods of this class!!! or you will be assigning to attributes
1589 of this object instead.
1590 """
1591 if name in self.__dict__:
1592 return self.__dict__[name]
1593 elif hasattr(self.__class__, name):
1594 return getattr(self.__class__, name)
1595 elif self._activeClassesList and hasattr(self._activeClassesList[-1], name):
1596 return getattr(self._activeClassesList[-1], name)
1597 else:
1598 raise AttributeError(name)
1599
1600 def _initializeSettings(self):
1601 self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS))
1602
1603 def _setupCompilerState(self):
1604 self._activeClassesList = []
1605 self._finishedClassesList = [] # listed by ordered
1606 self._finishedClassIndex = {} # listed by name
1607 self._moduleDef = None
1608 self._moduleShBang = '#!/usr/bin/env python'
1609 self._moduleEncoding = 'ascii'
1610 self._moduleEncodingStr = ''
1611 self._moduleHeaderLines = []
1612 self._moduleDocStringLines = []
1613 self._specialVars = {}
1614 self._importStatements = [
1615 "import sys",
1616 "import os",
1617 "import os.path",
1618 'try:',
1619 ' import builtins as builtin',
1620 'except ImportError:',
1621 ' import __builtin__ as builtin',
1622 "from os.path import getmtime, exists",
1623 "import time",
1624 "import types",
1625 "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion",
1626 "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple",
1627 "from Cheetah.Template import Template",
1628 "from Cheetah.DummyTransaction import *",
1629 "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList",
1630 "from Cheetah.CacheRegion import CacheRegion",
1631 "import Cheetah.Filters as Filters",
1632 "import Cheetah.ErrorCatchers as ErrorCatchers",
1633 ]
1634
1635 self._importedVarNames = ['sys',
1636 'os',
1637 'os.path',
1638 'time',
1639 'types',
1640 'Template',
1641 'DummyTransaction',
1642 'NotFound',
1643 'Filters',
1644 'ErrorCatchers',
1645 'CacheRegion',
1646 ]
1647
1648 self._moduleConstants = [
1649 "VFFSL=valueFromFrameOrSearchList",
1650 "VFSL=valueFromSearchList",
1651 "VFN=valueForName",
1652 "currentTime=time.time",
1653 ]
1654
1655 def compile(self):
1656 classCompiler = self._spawnClassCompiler(self._mainClassName)
1657 if self._baseclassName:
1658 classCompiler.setBaseClass(self._baseclassName)
1659 self._addActiveClassCompiler(classCompiler)
1660 self._parser.parse()
1661 self._swallowClassCompiler(self._popActiveClassCompiler())
1662 self._compiled = True
1663 self._parser.cleanup()
1664
1665 def _spawnClassCompiler(self, className, klass=None):
1666 if klass is None:
1667 klass = self.classCompilerClass
1668 classCompiler = klass(className,
1669 moduleCompiler=self,
1670 mainMethodName=self.setting('mainMethodName'),
1671 fileName=self._filePath,
1672 settingsManager=self,
1673 )
1674 return classCompiler
1675
1676 def _addActiveClassCompiler(self, classCompiler):
1677 self._activeClassesList.append(classCompiler)
1678
1679 def _getActiveClassCompiler(self):
1680 return self._activeClassesList[-1]
1681
1682 def _popActiveClassCompiler(self):
1683 return self._activeClassesList.pop()
1684
1685 def _swallowClassCompiler(self, classCompiler):
1686 classCompiler.cleanupState()
1687 self._finishedClassesList.append( classCompiler )
1688 self._finishedClassIndex[classCompiler.className()] = classCompiler
1689 return classCompiler
1690
1691 def _finishedClasses(self):
1692 return self._finishedClassesList
1693
1694 def importedVarNames(self):
1695 return self._importedVarNames
1696
1697 def addImportedVarNames(self, varNames, raw_statement=None):
1698 settings = self.settings()
1699 if not varNames:
1700 return
1701 if not settings.get('useLegacyImportMode'):
1702 if raw_statement and getattr(self, '_methodBodyChunks'):
1703 self.addChunk(raw_statement)
1704 else:
1705 self._importedVarNames.extend(varNames)
1706
1707 ## methods for adding stuff to the module and class definitions
1708
1709 def setBaseClass(self, baseClassName):
1710 if self._mainMethodNameArg:
1711 self.setMainMethodName(self._mainMethodNameArg)
1712 else:
1713 self.setMainMethodName(self.setting('mainMethodNameForSubclasses'))
1714
1715 if self.setting('handlerForExtendsDirective'):
1716 handler = self.setting('handlerForExtendsDirective')
1717 baseClassName = handler(compiler=self, baseClassName=baseClassName)
1718 self._getActiveClassCompiler().setBaseClass(baseClassName)
1719 elif (not self.setting('autoImportForExtendsDirective')
1720 or baseClassName=='object' or baseClassName in self.importedVarNames()):
1721 self._getActiveClassCompiler().setBaseClass(baseClassName)
1722 # no need to import
1723 else:
1724 ##################################################
1725 ## If the #extends directive contains a classname or modulename that isn't
1726 # in self.importedVarNames() already, we assume that we need to add
1727 # an implied 'from ModName import ClassName' where ModName == ClassName.
1728 # - This is the case in WebKit servlet modules.
1729 # - We also assume that the final . separates the classname from the
1730 # module name. This might break if people do something really fancy
1731 # with their dots and namespaces.
1732 baseclasses = baseClassName.split(',')
1733 for klass in baseclasses:
1734 chunks = klass.split('.')
1735 if len(chunks)==1:
1736 self._getActiveClassCompiler().setBaseClass(klass)
1737 if klass not in self.importedVarNames():
1738 modName = klass
1739 # we assume the class name to be the module name
1740 # and that it's not a builtin:
1741 importStatement = "from %s import %s" % (modName, klass)
1742 self.addImportStatement(importStatement)
1743 self.addImportedVarNames((klass,))
1744 else:
1745 needToAddImport = True
1746 modName = chunks[0]
1747 #print chunks, ':', self.importedVarNames()
1748 for chunk in chunks[1:-1]:
1749 if modName in self.importedVarNames():
1750 needToAddImport = False
1751 finalBaseClassName = klass.replace(modName+'.', '')
1752 self._getActiveClassCompiler().setBaseClass(finalBaseClassName)
1753 break
1754 else:
1755 modName += '.'+chunk
1756 if needToAddImport:
1757 modName, finalClassName = '.'.join(chunks[:-1]), chunks[-1]
1758 #if finalClassName != chunks[:-1][-1]:
1759 if finalClassName != chunks[-2]:
1760 # we assume the class name to be the module name
1761 modName = '.'.join(chunks)
1762 self._getActiveClassCompiler().setBaseClass(finalClassName)
1763 importStatement = "from %s import %s" % (modName, finalClassName)
1764 self.addImportStatement(importStatement)
1765 self.addImportedVarNames( [finalClassName,] )
1766
1767 def setCompilerSetting(self, key, valueExpr):
1768 self.setSetting(key, eval(valueExpr) )
1769 self._parser.configureParser()
1770
1771 def setCompilerSettings(self, keywords, settingsStr):
1772 KWs = keywords
1773 merge = True
1774 if 'nomerge' in KWs:
1775 merge = False
1776
1777 if 'reset' in KWs:
1778 # @@TR: this is actually caught by the parser at the moment.
1779 # subject to change in the future
1780 self._initializeSettings()
1781 self._parser.configureParser()
1782 return
1783 elif 'python' in KWs:
1784 settingsReader = self.updateSettingsFromPySrcStr
1785 # this comes from SettingsManager
1786 else:
1787 # this comes from SettingsManager
1788 settingsReader = self.updateSettingsFromConfigStr
1789
1790 settingsReader(settingsStr)
1791 self._parser.configureParser()
1792
1793 def setShBang(self, shBang):
1794 self._moduleShBang = shBang
1795
1796 def setModuleEncoding(self, encoding):
1797 self._moduleEncoding = encoding
1798
1799 def getModuleEncoding(self):
1800 return self._moduleEncoding
1801
1802 def addModuleHeader(self, line):
1803 """Adds a header comment to the top of the generated module.
1804 """
1805 self._moduleHeaderLines.append(line)
1806
1807 def addModuleDocString(self, line):
1808 """Adds a line to the generated module docstring.
1809 """
1810 self._moduleDocStringLines.append(line)
1811
1812 def addModuleGlobal(self, line):
1813 """Adds a line of global module code. It is inserted after the import
1814 statements and Cheetah default module constants.
1815 """
1816 self._moduleConstants.append(line)
1817
1818 def addSpecialVar(self, basename, contents, includeUnderscores=True):
1819 """Adds module __specialConstant__ to the module globals.
1820 """
1821 name = includeUnderscores and '__'+basename+'__' or basename
1822 self._specialVars[name] = contents.strip()
1823
1824 def addImportStatement(self, impStatement):
1825 settings = self.settings()
1826 if not self._methodBodyChunks or settings.get('useLegacyImportMode'):
1827 # In the case where we are importing inline in the middle of a source block
1828 # we don't want to inadvertantly import the module at the top of the file either
1829 self._importStatements.append(impStatement)
1830
1831 #@@TR 2005-01-01: there's almost certainly a cleaner way to do this!
1832 importVarNames = impStatement[impStatement.find('import') + len('import'):].split(',')
1833 importVarNames = [var.split()[-1] for var in importVarNames] # handles aliases
1834 importVarNames = [var for var in importVarNames if not var == '*']
1835 self.addImportedVarNames(importVarNames, raw_statement=impStatement) #used by #extend for auto-imports
1836
1837 def addAttribute(self, attribName, expr):
1838 self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr)
1839
1840 def addComment(self, comm):
1841 if re.match(r'#+$', comm): # skip bar comments
1842 return
1843
1844 specialVarMatch = specialVarRE.match(comm)
1845 if specialVarMatch:
1846 # @@TR: this is a bit hackish and is being replaced with
1847 # #set module varName = ...
1848 return self.addSpecialVar(specialVarMatch.group(1),
1849 comm[specialVarMatch.end():])
1850 elif comm.startswith('doc:'):
1851 addLine = self.addMethDocString
1852 comm = comm[len('doc:'):].strip()
1853 elif comm.startswith('doc-method:'):
1854 addLine = self.addMethDocString
1855 comm = comm[len('doc-method:'):].strip()
1856 elif comm.startswith('doc-module:'):
1857 addLine = self.addModuleDocString
1858 comm = comm[len('doc-module:'):].strip()
1859 elif comm.startswith('doc-class:'):
1860 addLine = self.addClassDocString
1861 comm = comm[len('doc-class:'):].strip()
1862 elif comm.startswith('header:'):
1863 addLine = self.addModuleHeader
1864 comm = comm[len('header:'):].strip()
1865 else:
1866 addLine = self.addMethComment
1867
1868 for line in comm.splitlines():
1869 addLine(line)
1870
1871 ## methods for module code wrapping
1872
1873 def getModuleCode(self):
1874 if not self._compiled:
1875 self.compile()
1876 if self._moduleDef:
1877 return self._moduleDef
1878 else:
1879 return self.wrapModuleDef()
1880
1881 __str__ = getModuleCode
1882
1883 def wrapModuleDef(self):
1884 self.addSpecialVar('CHEETAH_docstring', self.setting('defDocStrMsg'))
1885 self.addModuleGlobal('__CHEETAH_version__ = %r'%Version)
1886 self.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple,))
1887 if self.setting('addTimestampsToCompilerOutput'):
1888 self.addModuleGlobal('__CHEETAH_genTime__ = %r'%time.time())
1889 self.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self.timestamp())
1890 if self._filePath:
1891 timestamp = self.timestamp(self._fileMtime)
1892 self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath)
1893 self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp)
1894 else:
1895 self.addModuleGlobal('__CHEETAH_src__ = None')
1896 self.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
1897
1898 moduleDef = """%(header)s
1899%(docstring)s
1900
1901##################################################
1902## DEPENDENCIES
1903%(imports)s
1904
1905##################################################
1906## MODULE CONSTANTS
1907%(constants)s
1908%(specialVars)s
1909
1910if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
1911 raise AssertionError(
1912 'This template was compiled with Cheetah version'
1913 ' %%s. Templates compiled before version %%s must be recompiled.'%%(
1914 __CHEETAH_version__, RequiredCheetahVersion))
1915
1916##################################################
1917## CLASSES
1918
1919%(classes)s
1920
1921## END CLASS DEFINITION
1922
1923if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1924 templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1925 templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1926
1927%(footer)s
1928""" % {'header': self.moduleHeader(),
1929 'docstring': self.moduleDocstring(),
1930 'specialVars': self.specialVars(),
1931 'imports': self.importStatements(),
1932 'constants': self.moduleConstants(),
1933 'classes': self.classDefs(),
1934 'footer': self.moduleFooter(),
1935 'mainClassName': self._mainClassName,
1936 }
1937
1938 self._moduleDef = moduleDef
1939 return moduleDef
1940
1941 def timestamp(self, theTime=None):
1942 if not theTime:
1943 theTime = time.time()
1944 return time.asctime(time.localtime(theTime))
1945
1946 def moduleHeader(self):
1947 header = self._moduleShBang + '\n'
1948 header += self._moduleEncodingStr + '\n'
1949 if self._moduleHeaderLines:
1950 offSet = self.setting('commentOffset')
1951
1952 header += (
1953 '#' + ' '*offSet +
1954 ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n')
1955
1956 return header
1957
1958 def moduleDocstring(self):
1959 if not self._moduleDocStringLines:
1960 return ''
1961
1962 return ('"""' +
1963 '\n'.join(self._moduleDocStringLines) +
1964 '\n"""\n')
1965
1966 def specialVars(self):
1967 chunks = []
1968 theVars = self._specialVars
1969 keys = sorted(theVars.keys())
1970 for key in keys:
1971 chunks.append(key + ' = ' + repr(theVars[key]) )
1972 return '\n'.join(chunks)
1973
1974 def importStatements(self):
1975 return '\n'.join(self._importStatements)
1976
1977 def moduleConstants(self):
1978 return '\n'.join(self._moduleConstants)
1979
1980 def classDefs(self):
1981 classDefs = [klass.classDef() for klass in self._finishedClasses()]
1982 return '\n\n'.join(classDefs)
1983
1984 def moduleFooter(self):
1985 return """
1986# CHEETAH was developed by Tavis Rudd and Mike Orr
1987# with code, advice and input from many other volunteers.
1988# For more information visit http://www.CheetahTemplate.org/
1989
1990##################################################
1991## if run from command line:
1992if __name__ == '__main__':
1993 from Cheetah.TemplateCmdLineIface import CmdLineIface
1994 CmdLineIface(templateObj=%(className)s()).run()
1995
1996""" % {'className':self._mainClassName}
1997
1998
1999##################################################
2000## Make Compiler an alias for ModuleCompiler
2001
2002Compiler = ModuleCompiler
02003
=== added file 'cheetah/DirectiveAnalyzer.py'
--- cheetah/DirectiveAnalyzer.py 1970-01-01 00:00:00 +0000
+++ cheetah/DirectiveAnalyzer.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,98 @@
1#!/usr/bin/env python
2
3import os
4import pprint
5
6try:
7 from functools import reduce
8except ImportError:
9 # Assume we have reduce
10 pass
11
12from Cheetah import Parser
13from Cheetah import Compiler
14from Cheetah import Template
15
16class Analyzer(Parser.Parser):
17 def __init__(self, *args, **kwargs):
18 self.calls = {}
19 super(Analyzer, self).__init__(*args, **kwargs)
20
21 def eatDirective(self):
22 directive = self.matchDirective()
23 try:
24 self.calls[directive] += 1
25 except KeyError:
26 self.calls[directive] = 1
27 super(Analyzer, self).eatDirective()
28
29class AnalysisCompiler(Compiler.ModuleCompiler):
30 parserClass = Analyzer
31
32
33def analyze(source):
34 klass = Template.Template.compile(source, compilerClass=AnalysisCompiler)
35 return klass._CHEETAH_compilerInstance._parser.calls
36
37def main_file(f):
38 fd = open(f, 'r')
39 try:
40 print u'>>> Analyzing %s' % f
41 calls = analyze(fd.read())
42 return calls
43 finally:
44 fd.close()
45
46
47def _find_templates(directory, suffix):
48 for root, dirs, files in os.walk(directory):
49 for f in files:
50 if not f.endswith(suffix):
51 continue
52 yield root + os.path.sep + f
53
54def _analyze_templates(iterable):
55 for template in iterable:
56 yield main_file(template)
57
58def main_dir(opts):
59 results = _analyze_templates(_find_templates(opts.dir, opts.suffix))
60 totals = {}
61 for series in results:
62 if not series:
63 continue
64 for k, v in series.iteritems():
65 try:
66 totals[k] += v
67 except KeyError:
68 totals[k] = v
69 return totals
70
71
72def main():
73 from optparse import OptionParser
74 op = OptionParser()
75 op.add_option('-f', '--file', dest='file', default=None,
76 help='Specify a single file to analyze')
77 op.add_option('-d', '--dir', dest='dir', default=None,
78 help='Specify a directory of templates to analyze')
79 op.add_option('--suffix', default='tmpl', dest='suffix',
80 help='Specify a custom template file suffix for the -d option (default: "tmpl")')
81 opts, args = op.parse_args()
82
83 if not opts.file and not opts.dir:
84 op.print_help()
85 return
86
87 results = None
88 if opts.file:
89 results = main_file(opts.file)
90 if opts.dir:
91 results = main_dir(opts)
92
93 pprint.pprint(results)
94
95
96if __name__ == '__main__':
97 main()
98
099
=== added file 'cheetah/Django.py'
--- cheetah/Django.py 1970-01-01 00:00:00 +0000
+++ cheetah/Django.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,16 @@
1import Cheetah.Template
2
3def render(template_file, **kwargs):
4 '''
5 Cheetah.Django.render() takes the template filename
6 (the filename should be a file in your Django
7 TEMPLATE_DIRS)
8
9 Any additional keyword arguments are passed into the
10 template are propogated into the template's searchList
11 '''
12 import django.http
13 import django.template.loader
14 source, loader = django.template.loader.find_template_source(template_file)
15 t = Cheetah.Template.Template(source, searchList=[kwargs])
16 return django.http.HttpResponse(t.__str__())
017
=== added file 'cheetah/DummyTransaction.py'
--- cheetah/DummyTransaction.py 1970-01-01 00:00:00 +0000
+++ cheetah/DummyTransaction.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,108 @@
1
2'''
3Provides dummy Transaction and Response classes is used by Cheetah in place
4of real Webware transactions when the Template obj is not used directly as a
5Webware servlet.
6
7Warning: This may be deprecated in the future, please do not rely on any
8specific DummyTransaction or DummyResponse behavior
9'''
10
11import logging
12import types
13
14class DummyResponseFailure(Exception):
15 pass
16
17class DummyResponse(object):
18 '''
19 A dummy Response class is used by Cheetah in place of real Webware
20 Response objects when the Template obj is not used directly as a Webware
21 servlet
22 '''
23 def __init__(self):
24 self._outputChunks = []
25
26 def flush(self):
27 pass
28
29 def safeConvert(self, chunk):
30 # Exceptionally gross, but the safest way
31 # I've found to ensure I get a legit unicode object
32 if not chunk:
33 return u''
34 if isinstance(chunk, unicode):
35 return chunk
36 try:
37 return chunk.decode('utf-8', 'strict')
38 except UnicodeDecodeError:
39 try:
40 return chunk.decode('latin-1', 'strict')
41 except UnicodeDecodeError:
42 return chunk.decode('ascii', 'ignore')
43 except AttributeError:
44 return unicode(chunk, errors='ignore')
45 return chunk
46
47 def write(self, value):
48 self._outputChunks.append(value)
49
50 def writeln(self, txt):
51 write(txt)
52 write('\n')
53
54 def getvalue(self, outputChunks=None):
55 chunks = outputChunks or self._outputChunks
56 try:
57 return u''.join(chunks)
58 except UnicodeDecodeError, ex:
59 logging.debug('Trying to work around a UnicodeDecodeError in getvalue()')
60 logging.debug('...perhaps you could fix "%s" while you\'re debugging')
61 return ''.join((self.safeConvert(c) for c in chunks))
62
63 def writelines(self, *lines):
64 ## not used
65 [self.writeln(ln) for ln in lines]
66
67
68class DummyTransaction(object):
69 '''
70 A dummy Transaction class is used by Cheetah in place of real Webware
71 transactions when the Template obj is not used directly as a Webware
72 servlet.
73
74 It only provides a response object and method. All other methods and
75 attributes make no sense in this context.
76 '''
77 def __init__(self, *args, **kwargs):
78 self._response = None
79
80 def response(self, resp=None):
81 if self._response is None:
82 self._response = resp or DummyResponse()
83 return self._response
84
85
86class TransformerResponse(DummyResponse):
87 def __init__(self, *args, **kwargs):
88 super(TransformerResponse, self).__init__(*args, **kwargs)
89 self._filter = None
90
91 def getvalue(self, **kwargs):
92 output = super(TransformerResponse, self).getvalue(**kwargs)
93 if self._filter:
94 _filter = self._filter
95 if isinstance(_filter, type):
96 _filter = _filter()
97 return _filter.filter(output)
98 return output
99
100
101class TransformerTransaction(object):
102 def __init__(self, *args, **kwargs):
103 self._response = None
104 def response(self):
105 if self._response:
106 return self._response
107 return TransformerResponse()
108
0109
=== added file 'cheetah/ErrorCatchers.py'
--- cheetah/ErrorCatchers.py 1970-01-01 00:00:00 +0000
+++ cheetah/ErrorCatchers.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,62 @@
1# $Id: ErrorCatchers.py,v 1.7 2005/01/03 19:59:07 tavis_rudd Exp $
2"""ErrorCatcher class for Cheetah Templates
3
4Meta-Data
5================================================================================
6Author: Tavis Rudd <tavis@damnsimple.com>
7Version: $Revision: 1.7 $
8Start Date: 2001/08/01
9Last Revision Date: $Date: 2005/01/03 19:59:07 $
10"""
11__author__ = "Tavis Rudd <tavis@damnsimple.com>"
12__revision__ = "$Revision: 1.7 $"[11:-2]
13
14import time
15from Cheetah.NameMapper import NotFound
16
17class Error(Exception):
18 pass
19
20class ErrorCatcher:
21 _exceptionsToCatch = (NotFound,)
22
23 def __init__(self, templateObj):
24 pass
25
26 def exceptions(self):
27 return self._exceptionsToCatch
28
29 def warn(self, exc_val, code, rawCode, lineCol):
30 return rawCode
31## make an alias
32Echo = ErrorCatcher
33
34class BigEcho(ErrorCatcher):
35 def warn(self, exc_val, code, rawCode, lineCol):
36 return "="*15 + "&lt;" + rawCode + " could not be found&gt;" + "="*15
37
38class KeyError(ErrorCatcher):
39 def warn(self, exc_val, code, rawCode, lineCol):
40 raise KeyError("no '%s' in this Template Object's Search List" % rawCode)
41
42class ListErrors(ErrorCatcher):
43 """Accumulate a list of errors."""
44 _timeFormat = "%c"
45
46 def __init__(self, templateObj):
47 ErrorCatcher.__init__(self, templateObj)
48 self._errors = []
49
50 def warn(self, exc_val, code, rawCode, lineCol):
51 dict = locals().copy()
52 del dict['self']
53 dict['time'] = time.strftime(self._timeFormat,
54 time.localtime(time.time()))
55 self._errors.append(dict)
56 return rawCode
57
58 def listErrors(self):
59 """Return the list of errors."""
60 return self._errors
61
62
063
=== added file 'cheetah/FileUtils.py'
--- cheetah/FileUtils.py 1970-01-01 00:00:00 +0000
+++ cheetah/FileUtils.py 2010-10-19 20:12:51 +0000
@@ -0,0 +1,357 @@
1
2from glob import glob
3import os
4from os import listdir
5import os.path
6import re
7from tempfile import mktemp
8
9def _escapeRegexChars(txt,
10 escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
11 return escapeRE.sub(r'\\\1', txt)
12
13def findFiles(*args, **kw):
14 """Recursively find all the files matching a glob pattern.
15
16 This function is a wrapper around the FileFinder class. See its docstring
17 for details about the accepted arguments, etc."""
18
19 return FileFinder(*args, **kw).files()
20
21def replaceStrInFiles(files, theStr, repl):
22
23 """Replace all instances of 'theStr' with 'repl' for each file in the 'files'
24 list. Returns a dictionary with data about the matches found.
25
26 This is like string.replace() on a multi-file basis.
27
28 This function is a wrapper around the FindAndReplace class. See its
29 docstring for more details."""
30
31 pattern = _escapeRegexChars(theStr)
32 return FindAndReplace(files, pattern, repl).results()
33
34def replaceRegexInFiles(files, pattern, repl):
35
36 """Replace all instances of regex 'pattern' with 'repl' for each file in the
37 'files' list. Returns a dictionary with data about the matches found.
38
39 This is like re.sub on a multi-file basis.
40
41 This function is a wrapper around the FindAndReplace class. See its
42 docstring for more details."""
43
44 return FindAndReplace(files, pattern, repl).results()
45
46
47##################################################
48## CLASSES
49
50class FileFinder:
51
52 """Traverses a directory tree and finds all files in it that match one of
53 the specified glob patterns."""
54
55 def __init__(self, rootPath,
56 globPatterns=('*',),
57 ignoreBasenames=('CVS', '.svn'),
58 ignoreDirs=(),
59 ):
60
61 self._rootPath = rootPath
62 self._globPatterns = globPatterns
63 self._ignoreBasenames = ignoreBasenames
64 self._ignoreDirs = ignoreDirs
65 self._files = []
66
67 self.walkDirTree(rootPath)
68
69 def walkDirTree(self, dir='.',
70
71 listdir=os.listdir,
72 isdir=os.path.isdir,
73 join=os.path.join,
74 ):
75
76 """Recursively walk through a directory tree and find matching files."""
77 processDir = self.processDir
78 filterDir = self.filterDir
79
80 pendingDirs = [dir]
81 addDir = pendingDirs.append
82 getDir = pendingDirs.pop
83
84 while pendingDirs:
85 dir = getDir()
86 ## process this dir
87 processDir(dir)
88
89 ## and add sub-dirs
90 for baseName in listdir(dir):
91 fullPath = join(dir, baseName)
92 if isdir(fullPath):
93 if filterDir(baseName, fullPath):
94 addDir( fullPath )
95
96 def filterDir(self, baseName, fullPath):
97
98 """A hook for filtering out certain dirs. """
99
100 return not (baseName in self._ignoreBasenames or
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: