Merge lp:~barry/ubuntu/natty/cheetah/py27-compat into lp:ubuntu/natty/cheetah
- Natty (11.04)
- py27-compat
- Merge into natty
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Clint Byrum | Pending | ||
Ubuntu branches | Pending | ||
Review via email: mp+38883@code.launchpad.net |
Commit message
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
1 | === modified file 'CHANGES' |
2 | --- CHANGES 2007-11-18 14:59:06 +0000 |
3 | +++ CHANGES 2010-10-19 20:12:51 +0000 |
4 | @@ -1,6 +1,94 @@ |
5 | -Please initial your changes (there's a key at bottom) and add a date for each |
6 | -release |
7 | -================================================================================ |
8 | +2.4.2 (February 8th, 2010) |
9 | + - Fix issue where subclasses of Template failed to pick up attributes in the |
10 | + searchlist |
11 | + - Remove old/outdated bundled memcached python client |
12 | + - Allow for #encoding directives to exist after a comment (i.e. not the first |
13 | + line in a module) |
14 | + - Remove support for WebWare servlets (which caused significant performance |
15 | + slowdowns on Mac OS X) |
16 | + - Old/stale code pruned in preparation for Python 3 support |
17 | + |
18 | +2.4.1 (December 19th, 2009) |
19 | + - --quiet flag added to `cheetah` to silence printing to stdout (abbeyj) |
20 | + - Refactoring to minimize the amount of forked code for Python3 (rtyler) |
21 | + - Template.compile() will no longer create class names with numerous leading |
22 | + underscores (rtyler; reported by Kirill Uhanov) |
23 | + - DirectiveAnalyzer (cheetah-analyze script) added to report directive usage in templates (rtyler) |
24 | + - Older LaTeX docs converted to rst for Sphinx (rtyler) |
25 | + - Prevent #raw blocks from evaluating $-placeholders and escaped strings (karmix0) |
26 | + - New tests added to verify PSP behavior and other untested internals (rtyler) |
27 | + |
28 | +2.4.0 (October 24th, 2009) |
29 | + - Fix a major performance regression in Template.__init__() |
30 | + - More graceful handling of unicode when calling .respond() to render a template |
31 | + - Minor code updates |
32 | + - Update the default filter (thanks mikeb!) |
33 | + |
34 | +2.3.0 (October 24th, 2009) (loosely equivalent to 2.4.0) |
35 | + - Fix a major performance regression in Template.__init__() |
36 | + - More graceful handling of unicode when calling .respond() to render a template |
37 | + - Minor code updates |
38 | + - Update the default filter (thanks mikeb!) |
39 | + |
40 | +2.2.2 (September 10th, 2009) |
41 | + - Prevent _namemapper.c from segfaulting when PyImport_ImportModule fails for some reason (Bogdano Arendartchuk <debogdano@gmail.com>) |
42 | + - Removal of the contrib/markdown module (in favor of a setuptools dependency) |
43 | + - Default setup.py to use setuptools by default, failing that, fall back to distutils |
44 | + - Improvements to setup.py to support building for Windows (thanks abbeyj!) |
45 | + - Improvements to C-based NameMapper for Windows |
46 | + - Fixes for a swath of unit tests on Windows |
47 | + - Re-enabling the EOL tests (whoops) |
48 | + - Fix for unicode/utf-8 dynamic compilation error (thanks mikeb!) (Test.Unicode.JBQ_UTF8_Test8) |
49 | + - 0000010: [Templates] Failure to execute templates on Google App Engine (rtyler) |
50 | + - 0000026: [Compiler] Support multiple inheritance (rtyler) |
51 | + |
52 | + |
53 | +2.2.1 (June 1st, 2009) |
54 | + - 0000020: [Templates] Builtin support for using Cheetah with Django (rtyler) |
55 | + - 0000021: [Compiler] @static and @classmethod don't properly define the _filter local (rtyler) |
56 | + - 0000023: [Compiler] Update Template super calls to use super() (rtyler) |
57 | + - Update all references to communitycheetah.org to point back at cheetahtemplate.org |
58 | + |
59 | +2.2.0 (May 17th, 2009) |
60 | + - Switch all internal representations of template code to unicode objects instead of str() objects |
61 | + - Convert unicode compiled template to an utf8 char buffer when writing to a file (Jean-Baptiste Quenot <jbq@caraldi.com>) |
62 | + - 0000011: [Templates] Calling a function with arguments calls the function with None (rtyler) |
63 | + - 0000015: [Tests] Resolve test failures in 'next' branch (rtyler) |
64 | + - 0000019: [Templates] Properly warn when joining unicode and non-unicode objects in DummyTransaction (rtyler) |
65 | + |
66 | +2.1.2 (May 5, 2009) |
67 | + - 0000006: [Templates] Support @staticmethod and @classmethod (rtyler) |
68 | + |
69 | +2.1.1 (April 16, 2009) |
70 | + - Support __eq__() and __ne__() the way you might expect in src/Tools/RecursiveNull (patch suggested by Peter Warasin <peter@endian.com>) |
71 | + - 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>) |
72 | + - Applied patch to fix some annoying cases when Cheetah writes to stderr instead of propagating the exception (Jean-Baptiste Quenot <jbq@caraldi.com>) |
73 | + - Added KDE editor support |
74 | + - Applied patch to correct importHook behavior on Python 2.6 (reported/patched by Toshio Ernie Kuratomi <a.badger@gmail.com>) |
75 | + - 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>) |
76 | + - Added --shbang option (e.g. "cheetah compile --shbang '#!/usr/bin/python2.6' ") |
77 | + - Removed dependency on optik OptionParser in favor of builtin Python optparse module |
78 | + - Introduction of the #transform directive for whole-document filtering |
79 | + - Introduction of Cheetah.contrib.markdown and Cheetah.Filters.Markdown for outputting a markdown processed template (meant for #transform) |
80 | + - Cheetah.Filters.CodeHighlighter, pygments-based code highlighting filter for use with #transform |
81 | + - Addition of "useLegacyImportMode" compiler setting (defaulted to True) to allow for older (read: broken) import behavior |
82 | + |
83 | +2.1.0.1 (March 27, 2009) |
84 | + - Fix inline import issue introduced in v2.1.0 |
85 | + |
86 | +2.1.0 (March 16, 2009) |
87 | + - Quiet DeprecationWarnings being printed to stderr when using Cheetah on Python 2.6 and up. Patch suggested by Satoru SATOH <satoru.satoh@gmail.com> |
88 | + - Apply patch to support parallel compilation of templates courtesy of Evan Klitzke <evan@eklitzke.org> |
89 | + - Corrected issue when __getattr__ calls on searchList objects raise exceptions (tyler@slide.com) |
90 | + - make autocalling in valueForName correctly ignore newstyle classes and instances |
91 | + that are callable, as it does for oldstyle classes and instances. Patch |
92 | + from lucas@endian.com |
93 | + [TR] |
94 | + - made it possible to chain multiple decorators to a method #def [TR with |
95 | + patch from Graham Dennis] |
96 | + - fixed a bug in _eatMultiLineDef that Graham Dennis reported. [TR] |
97 | + - fixed 'module.__init__() argument 1 must be string, not unicode' bug in |
98 | + Template.py reported by Erwin Ambrosch [TR] |
99 | |
100 | 2.0.1 (Nov 16, 2007) |
101 | - fixed a deadlock Christoph Zwerschke found in Cheetah.ImportHooks. |
102 | |
103 | === added directory 'Cheetah.egg-info' |
104 | === added file 'Cheetah.egg-info/PKG-INFO' |
105 | --- Cheetah.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000 |
106 | +++ Cheetah.egg-info/PKG-INFO 2010-10-19 20:12:51 +0000 |
107 | @@ -0,0 +1,47 @@ |
108 | +Metadata-Version: 1.0 |
109 | +Name: Cheetah |
110 | +Version: 2.4.3 |
111 | +Summary: Cheetah is a template engine and code generation tool. |
112 | +Home-page: http://www.cheetahtemplate.org/ |
113 | +Author: R. Tyler Ballance |
114 | +Author-email: cheetahtemplate-discuss@lists.sf.net |
115 | +License: UNKNOWN |
116 | +Description: Cheetah is an open source template engine and code generation tool. |
117 | + |
118 | + It can be used standalone or combined with other tools and frameworks. Web |
119 | + development is its principle use, but Cheetah is very flexible and is also being |
120 | + used to generate C++ game code, Java, sql, form emails and even Python code. |
121 | + |
122 | + Documentation |
123 | + ================================================================================ |
124 | + For a high-level introduction to Cheetah please refer to the User's Guide |
125 | + at http://www.cheetahtemplate.org/learn.html |
126 | + |
127 | + Mailing list |
128 | + ================================================================================ |
129 | + cheetahtemplate-discuss@lists.sourceforge.net |
130 | + Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss |
131 | + |
132 | + Credits |
133 | + ================================================================================ |
134 | + http://www.cheetahtemplate.org/credits.html |
135 | + |
136 | + Recent Changes |
137 | + ================================================================================ |
138 | + See http://www.cheetahtemplate.org/CHANGES.txt for full details |
139 | + |
140 | + |
141 | +Platform: UNKNOWN |
142 | +Classifier: Development Status :: 5 - Production/Stable |
143 | +Classifier: Intended Audience :: Developers |
144 | +Classifier: Intended Audience :: System Administrators |
145 | +Classifier: License :: OSI Approved :: MIT License |
146 | +Classifier: Operating System :: OS Independent |
147 | +Classifier: Programming Language :: Python |
148 | +Classifier: Topic :: Internet :: WWW/HTTP |
149 | +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
150 | +Classifier: Topic :: Internet :: WWW/HTTP :: Site Management |
151 | +Classifier: Topic :: Software Development :: Code Generators |
152 | +Classifier: Topic :: Software Development :: Libraries :: Python Modules |
153 | +Classifier: Topic :: Software Development :: User Interfaces |
154 | +Classifier: Topic :: Text Processing |
155 | |
156 | === added file 'Cheetah.egg-info/SOURCES.txt' |
157 | --- Cheetah.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000 |
158 | +++ Cheetah.egg-info/SOURCES.txt 2010-10-19 20:12:51 +0000 |
159 | @@ -0,0 +1,80 @@ |
160 | +CHANGES |
161 | +LICENSE |
162 | +MANIFEST.in |
163 | +README.markdown |
164 | +SetupConfig.py |
165 | +SetupTools.py |
166 | +TODO |
167 | +setup.py |
168 | +Cheetah.egg-info/PKG-INFO |
169 | +Cheetah.egg-info/SOURCES.txt |
170 | +Cheetah.egg-info/dependency_links.txt |
171 | +Cheetah.egg-info/requires.txt |
172 | +Cheetah.egg-info/top_level.txt |
173 | +bin/cheetah |
174 | +bin/cheetah-analyze |
175 | +bin/cheetah-compile |
176 | +cheetah/CacheRegion.py |
177 | +cheetah/CacheStore.py |
178 | +cheetah/CheetahWrapper.py |
179 | +cheetah/Compiler.py |
180 | +cheetah/DirectiveAnalyzer.py |
181 | +cheetah/Django.py |
182 | +cheetah/DummyTransaction.py |
183 | +cheetah/ErrorCatchers.py |
184 | +cheetah/FileUtils.py |
185 | +cheetah/Filters.py |
186 | +cheetah/ImportHooks.py |
187 | +cheetah/ImportManager.py |
188 | +cheetah/NameMapper.py |
189 | +cheetah/Parser.py |
190 | +cheetah/Servlet.py |
191 | +cheetah/SettingsManager.py |
192 | +cheetah/SourceReader.py |
193 | +cheetah/Template.py |
194 | +cheetah/TemplateCmdLineIface.py |
195 | +cheetah/Unspecified.py |
196 | +cheetah/Version.py |
197 | +cheetah/__init__.py |
198 | +cheetah/convertTmplPathToModuleName.py |
199 | +cheetah/Macros/I18n.py |
200 | +cheetah/Macros/__init__.py |
201 | +cheetah/Templates/SkeletonPage.py |
202 | +cheetah/Templates/SkeletonPage.tmpl |
203 | +cheetah/Templates/_SkeletonPage.py |
204 | +cheetah/Templates/__init__.py |
205 | +cheetah/Tests/Analyzer.py |
206 | +cheetah/Tests/CheetahWrapper.py |
207 | +cheetah/Tests/Cheps.py |
208 | +cheetah/Tests/Filters.py |
209 | +cheetah/Tests/Misc.py |
210 | +cheetah/Tests/NameMapper.py |
211 | +cheetah/Tests/Parser.py |
212 | +cheetah/Tests/Performance.py |
213 | +cheetah/Tests/Regressions.py |
214 | +cheetah/Tests/SyntaxAndOutput.py |
215 | +cheetah/Tests/Template.py |
216 | +cheetah/Tests/Test.py |
217 | +cheetah/Tests/Unicode.py |
218 | +cheetah/Tests/__init__.py |
219 | +cheetah/Tests/xmlrunner.py |
220 | +cheetah/Tools/CGITemplate.py |
221 | +cheetah/Tools/MondoReport.py |
222 | +cheetah/Tools/MondoReportDoc.txt |
223 | +cheetah/Tools/RecursiveNull.py |
224 | +cheetah/Tools/SiteHierarchy.py |
225 | +cheetah/Tools/__init__.py |
226 | +cheetah/Tools/turbocheetah/__init__.py |
227 | +cheetah/Tools/turbocheetah/cheetahsupport.py |
228 | +cheetah/Tools/turbocheetah/tests/__init__.py |
229 | +cheetah/Tools/turbocheetah/tests/test_template.py |
230 | +cheetah/Utils/Indenter.py |
231 | +cheetah/Utils/Misc.py |
232 | +cheetah/Utils/WebInputMixin.py |
233 | +cheetah/Utils/__init__.py |
234 | +cheetah/Utils/htmlDecode.py |
235 | +cheetah/Utils/htmlEncode.py |
236 | +cheetah/Utils/statprof.py |
237 | +cheetah/c/Cheetah.h |
238 | +cheetah/c/_namemapper.c |
239 | +cheetah/c/cheetah.h |
240 | \ No newline at end of file |
241 | |
242 | === added file 'Cheetah.egg-info/dependency_links.txt' |
243 | --- Cheetah.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000 |
244 | +++ Cheetah.egg-info/dependency_links.txt 2010-10-19 20:12:51 +0000 |
245 | @@ -0,0 +1,1 @@ |
246 | + |
247 | |
248 | === added file 'Cheetah.egg-info/requires.txt' |
249 | --- Cheetah.egg-info/requires.txt 1970-01-01 00:00:00 +0000 |
250 | +++ Cheetah.egg-info/requires.txt 2010-10-19 20:12:51 +0000 |
251 | @@ -0,0 +1,1 @@ |
252 | +Markdown >= 2.0.1 |
253 | \ No newline at end of file |
254 | |
255 | === added file 'Cheetah.egg-info/top_level.txt' |
256 | --- Cheetah.egg-info/top_level.txt 1970-01-01 00:00:00 +0000 |
257 | +++ Cheetah.egg-info/top_level.txt 2010-10-19 20:12:51 +0000 |
258 | @@ -0,0 +1,1 @@ |
259 | +Cheetah |
260 | |
261 | === modified file 'MANIFEST.in' |
262 | --- MANIFEST.in 2006-07-26 22:03:15 +0000 |
263 | +++ MANIFEST.in 2010-10-19 20:12:51 +0000 |
264 | @@ -1,7 +1,7 @@ |
265 | -include MANIFEST.in *.py *.cfg TODO CHANGES LICENSE README examples docs bin |
266 | -recursive-include src *.py *.tmpl *.txt |
267 | +include MANIFEST.in *.py *.cfg TODO CHANGES LICENSE README.markdown examples docs bin |
268 | +recursive-include cheetah *.py *.tmpl *.txt *.h |
269 | recursive-include bin * |
270 | recursive-include docs * |
271 | recursive-include examples * |
272 | -recursive-exclude src *.pyc *~ *.aux |
273 | +recursive-exclude cheetah *.pyc *~ *.aux |
274 | recursive-exclude docs *~ *.aux |
275 | |
276 | === modified file 'PKG-INFO' |
277 | --- PKG-INFO 2007-11-18 14:59:06 +0000 |
278 | +++ PKG-INFO 2010-10-19 20:12:51 +0000 |
279 | @@ -1,9 +1,9 @@ |
280 | Metadata-Version: 1.0 |
281 | Name: Cheetah |
282 | -Version: 2.0.1 |
283 | +Version: 2.4.3 |
284 | Summary: Cheetah is a template engine and code generation tool. |
285 | -Home-page: http://www.CheetahTemplate.org/ |
286 | -Author: Tavis Rudd |
287 | +Home-page: http://www.cheetahtemplate.org/ |
288 | +Author: R. Tyler Ballance |
289 | Author-email: cheetahtemplate-discuss@lists.sf.net |
290 | License: UNKNOWN |
291 | Description: Cheetah is an open source template engine and code generation tool. |
292 | @@ -15,7 +15,7 @@ |
293 | Documentation |
294 | ================================================================================ |
295 | For a high-level introduction to Cheetah please refer to the User's Guide |
296 | - at http://cheetahtemplate.org/learn.html |
297 | + at http://www.cheetahtemplate.org/learn.html |
298 | |
299 | Mailing list |
300 | ================================================================================ |
301 | @@ -24,869 +24,12 @@ |
302 | |
303 | Credits |
304 | ================================================================================ |
305 | - http://cheetahtemplate.org/credits.html |
306 | - |
307 | - Praise |
308 | - ================================================================================ |
309 | - "I'm enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging |
310 | - Technologies Group & director of Apache Software Foundation |
311 | - |
312 | - "Give Cheetah a try. You won't regret it. ... Cheetah is a truly powerful |
313 | - system. ... Cheetah is a serious contender for the 'best of breed' Python |
314 | - templating." - Alex Martelli |
315 | - |
316 | - "People with a strong PHP background absolutely love Cheetah for being Smarty, |
317 | - but much, much better." - Marek Baczynski |
318 | - |
319 | - "I am using Smarty and I know it very well, but compiled Cheetah Templates with |
320 | - its inheritance approach is much powerful and easier to use than Smarty." - |
321 | - Jaroslaw Zabiello |
322 | - |
323 | - "There is no better solution than Cheetah" - Wilk |
324 | - |
325 | - "A cheetah template can inherit from a python class, or a cheetah template, and |
326 | - a Python class can inherit from a cheetah template. This brings the full power |
327 | - of OO programming facilities to the templating system, and simply blows away |
328 | - other templating systems" - Mike Meyer |
329 | - |
330 | - "Cheetah has successfully been introduced as a replacement for the overweight |
331 | - XSL Templates for code generation. Despite the power of XSL (and notably XPath |
332 | - expressions), code generation is better suited to Cheetah as templates are much |
333 | - easier to implement and manage." - The FEAR development team |
334 | - (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573) |
335 | - |
336 | - "I've used Cheetah quite a bit and it's a very good package" - Kevin Dangoor, |
337 | - lead developer of TurboGears. |
338 | + http://www.cheetahtemplate.org/credits.html |
339 | |
340 | Recent Changes |
341 | ================================================================================ |
342 | - See http://cheetahtemplate.org/docs/CHANGES for full details. |
343 | - |
344 | - Please initial your changes (there's a key at bottom) and add a date for each |
345 | - release |
346 | - ================================================================================ |
347 | - |
348 | - 2.0.1 (Nov 16, 2007) |
349 | - - fixed a deadlock Christoph Zwerschke found in Cheetah.ImportHooks. |
350 | - [TR] |
351 | - |
352 | - 2.0 (Oct 12, 2007) |
353 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
354 | - |
355 | - - fixed exception handling issue in the C implemenation of NameMapper |
356 | - [patch from Eric Huss] |
357 | - |
358 | - - fixed filtering of #included subtemplates |
359 | - [patch from Brian Bird] |
360 | - |
361 | - See the release notes from 2.0b1-5 and 2.0rc1-8 for other changes since |
362 | - Cheetah 1.0. |
363 | - |
364 | - |
365 | - 2.0rc8 (April 11, 2007) |
366 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
367 | - Core Changes: [TR] |
368 | - |
369 | - - added a '#unicode <encoding>' directive to indicate that the output of the |
370 | - template should be a unicode string even if the template source is a |
371 | - normal byte string. |
372 | - |
373 | - - #unicode and #encoding are mutually exclusive. Use one or the other. |
374 | - - #unicode must be on a line by itself. |
375 | - - Strings in embedded code must be explictly marked as unicode if they |
376 | - contain non-ascii chars: |
377 | - |
378 | - #unicode latin-1 |
379 | - $f(u"<some non-ascii char>") ## right |
380 | - $f("<some non-ascii char>") ## wrong |
381 | - |
382 | - However, this works fine: |
383 | - |
384 | - #unicode latin-1 |
385 | - blah blah <some non-ascii char> blah blah |
386 | - |
387 | - - fixed several unicode bugs in the compiler. |
388 | - |
389 | - - fixed some unicode issues in the standard filters. |
390 | - |
391 | - - fixed a few minor bugs in code that never gets called. Thanks to |
392 | - Alejandro Dubrovsky for pointing them out. |
393 | - |
394 | - - make RawOrEncodedUnicode the baseclass of all filters and remove some |
395 | - unused/redudant filters |
396 | - |
397 | - - added new compiler setting 'addTimestampsToCompilerOutput'. See Brian |
398 | - Bird's post about it. He stores his cheetah generated .py files in |
399 | - subversion and needed to disable the timestamp code so svn wouldn't care |
400 | - when he recompiles those .py modules. |
401 | - |
402 | - - added the #super directive, which calls the method from the parent class |
403 | - which has the same as the current #def or #block method. |
404 | - |
405 | - #def foo |
406 | - ... child output |
407 | - #super ## includes output of super(<CurrentClass>, self).foo() |
408 | - ... child output |
409 | - #end def |
410 | - |
411 | - |
412 | - #def bar(arg) |
413 | - ... child output |
414 | - #super(arg) ## includes output of super(<CurrentClass>, self).bar(arg) |
415 | - ... child output |
416 | - #end def |
417 | - |
418 | - - added some unit tests for the new directives |
419 | - |
420 | - |
421 | - 2.0rc7 (July 4, 2006) |
422 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
423 | - Core Changes: [TR] |
424 | - - extended the #implements directive so an arguments list can be declared in |
425 | - the same fashion as #def and #block. |
426 | - |
427 | - - made the parser raise ParseError when $*placeholder, $*5*placeholder, |
428 | - $(placeholder), etc. are found within expressions. They are only valid in |
429 | - top-level text. |
430 | - |
431 | - - tweaked the parser so it's possible to place a comment on the same line as |
432 | - a directive without needing to explicitly close the directive first. This |
433 | - works regardless of whether or not you added a colon. |
434 | - |
435 | - self.verify("#if 1:\n$aStr\n#end if\n", |
436 | - "blarg\n") |
437 | - |
438 | - self.verify("#if 1: \n$aStr\n#end if\n", |
439 | - "blarg\n") |
440 | - |
441 | - self.verify("#if 1: ##comment \n$aStr\n#end if\n", |
442 | - "blarg\n") |
443 | - |
444 | - self.verify("#if 1 ##comment \n$aStr\n#end if\n", |
445 | - "blarg\n") |
446 | - |
447 | - Previously, that last test would have required an extra # to close the #if |
448 | - directive before the comment directive started: |
449 | - self.verify("#if 1 ###comment \n$aStr\n#end if\n", |
450 | - "blarg\n") |
451 | - |
452 | - Code that makes use of explicit directive close tokens immediately followed by |
453 | - another directive will still work as expected: |
454 | - #if test##for i in range(10)# foo $i#end for##end if |
455 | - |
456 | - - safer handling of the baseclass arg to Template.compile(). It now does |
457 | - the right thing if the user passes in an instance rather than a class. |
458 | - |
459 | - ImportHooks: [TR] |
460 | - - made it possible to specify a list of template filename extentions that are |
461 | - looped through while searching for template modules. E.g.: |
462 | - import Cheetah.ImportHooks |
463 | - Cheetah.ImportHooks.install(templateFileExtensions=('.tmpl','.cheetah')) |
464 | - |
465 | - Core changes by MO: |
466 | - - Filters are now new-style classes. |
467 | - - WebSafe and the other optional filters in Filters.py now use |
468 | - RawOrEncodedUnicode instead of Filter as a base class. This allows them |
469 | - to work with Unicode values containing non-ASCII characters. |
470 | - User-written custom filters should inherit from |
471 | - RawOrEncodedUnicode and call the superclass .filter() instead of str(). |
472 | - str() as of Python 2.4.2 still converts Unicode to string using |
473 | - ASCII codec, which raises UnicodeEncodeError if it contains non-ASCII |
474 | - characters. |
475 | - |
476 | - 2.0rc6 (Feb 4, 2006) |
477 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
478 | - Core Changes: [TR] |
479 | - - added a Cheetah version dependency check that raises an assertion if a |
480 | - template was compiled with a previous version of Cheetah whose templates |
481 | - must be recompiled. |
482 | - |
483 | - - made the Cheetah compilation metadata accessible via class attributes in |
484 | - addition to module globals |
485 | - |
486 | - - major improvement to exception reporting in cases where bad Python syntax |
487 | - slips past the Cheetah parser: |
488 | - """ |
489 | - File "/usr/lib/python2.4/site-packages/Cheetah/Template.py", line 792, in compile |
490 | - raise parseError |
491 | - Cheetah.Parser.ParseError: |
492 | - |
493 | - Error in the Python code which Cheetah generated for this template: |
494 | - ================================================================================ |
495 | - |
496 | - invalid syntax (DynamicallyCompiledCheetahTemplate.py, line 86) |
497 | - |
498 | - Line|Python Code |
499 | - ----|------------------------------------------------------------- |
500 | - 84 | |
501 | - 85 | write('\n\n') |
502 | - 86 | for i an range(10): # generated from line 4, col 1 |
503 | - ^ |
504 | - 87 | _v = i # '$i' on line 5, col 3 |
505 | - 88 | if _v is not None: write(_filter(_v, rawExpr='$i')) # from line 5, col 3. |
506 | - 89 | write('\n') |
507 | - |
508 | - ================================================================================ |
509 | - |
510 | - Here is the corresponding Cheetah code: |
511 | - |
512 | - Line 4, column 1 |
513 | - |
514 | - Line|Cheetah Code |
515 | - ----|------------------------------------------------------------- |
516 | - 2 |#compiler useNameMapper=False |
517 | - 3 | |
518 | - 4 |#for i an range(10) |
519 | - ^ |
520 | - 5 | $i |
521 | - 6 |#end for |
522 | - 7 | |
523 | - """ |
524 | - |
525 | - 2.0rc5 (Feb 3, 2006) |
526 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
527 | - Core Changes: [TR] |
528 | - - fixed a memory leak in Template.compile(), reported by Andrea Arcangeli |
529 | - - simplified concurrency locking and compile caching in Template.compile() |
530 | - |
531 | - The command line tool (CheetahWrapper.py): |
532 | - - added new option --settings for supplying compiler settings |
533 | - - added new option --templateAPIClass to replace the environment var |
534 | - CHEETAH_TEMPLATE_CLASS lookup I added in 2.0b1 |
535 | - |
536 | - 2.0rc4 (Jan 31, 2006) |
537 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
538 | - Core Changes: [TR] |
539 | - - fixed a typo-bug in the compile hashing code in Template.compile() |
540 | - - improved the macros framework and made it possible to implement macros in |
541 | - Python code so they can be shared between templates |
542 | - - more work on the #i18n directive. It's now a macro directive. |
543 | - - added new Cheetah.Macros package |
544 | - - more tests |
545 | - |
546 | - 2.0rc3 (Jan 29, 2006) |
547 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
548 | - Core Changes: [TR] |
549 | - - added short-form single line versions of all directives that have an #end |
550 | - tag, except for #errorCatcher: |
551 | - #if, #else, #elif, #unless, |
552 | - #for, #while, #repeat, |
553 | - #try, #except, #finally, |
554 | - #cache, #raw |
555 | - #call, #capture |
556 | - |
557 | - The #def and #block directives already had single-line versions. |
558 | - #if cond: foo |
559 | - #elif cond2: bar |
560 | - #else: blarg |
561 | - |
562 | - #for i, val in enumerate(vals): $i-$val |
563 | - |
564 | - Note that if you accidentally leave a colon at the end of one of these |
565 | - directives but nothing else follows it, aside from whitespace, the parser |
566 | - will treat it as a normal multi-line directive. |
567 | - |
568 | - The first leading space after the colon is discarded. Any additional |
569 | - spaces will be included in the output. |
570 | - |
571 | - Also note, if you use the short form versions of #if/#else/#elif you must |
572 | - it for all three. The following is not valid: |
573 | - #if cond: foo |
574 | - #elif cond2 |
575 | - bar |
576 | - #else: blarg |
577 | - |
578 | - - added support for $!silentModePlaceholders |
579 | - This is the same as quiet mode in Velocity: |
580 | - http://jakarta.apache.org/velocity/docs/user-guide.html#Quiet%20Reference%20Notation |
581 | - |
582 | - - added support for function/method @decorators. It also works with blocks. |
583 | - As in vanilla Python, the @decorator statement must be followed with a |
584 | - function/method definition (i.e. #def or #block). |
585 | - |
586 | - #from xxx import aDecorator |
587 | - ... |
588 | - ... |
589 | - #@aDecorator |
590 | - #def func |
591 | - foo |
592 | - #end def |
593 | - |
594 | - #@aDecorator |
595 | - #def singleLineShortFormfunc: foo |
596 | - |
597 | - #@aDecorator |
598 | - #block func2 |
599 | - bar |
600 | - #end block |
601 | - |
602 | - - added a new callback hook 'handlerForExtendsDirective' to the compiler settings. It |
603 | - can be used to customize the handling of #extends directives. The |
604 | - callback can dynamically add import statements or rewrite the baseclass' |
605 | - name if needed: |
606 | - baseClassName = handler(compiler, baseClassName) |
607 | - See the discussion on the mailing list on Jan 25th for more details. |
608 | - |
609 | - - changed the default filter to the one that doesn't try to encode Unicode |
610 | - It was 'EncodeUnicode' and is now 'RawOrEncodedUnicode'. |
611 | - |
612 | - - added optional support for parsing whitespace between the directive start |
613 | - token (#) and directive names, per Christophe Eymard's request. For the |
614 | - argument behind this see the mailing list archives for Jan 29th. This is |
615 | - off by default. You must turn it on using the compiler setting |
616 | - allowWhitespaceAfterDirectiveStartToken=True |
617 | - |
618 | - #for $something in $another |
619 | - # for $somethin2 in $another2 |
620 | - blahblah $something in $something2 |
621 | - # end for |
622 | - #end for |
623 | - |
624 | - - made the handling of Template.compile()'s preprocessors arg simpler and |
625 | - fixed a bug in it. |
626 | - |
627 | - - fixed attribute name bug in the .compile() method (it affected the feature |
628 | - that allows generated module files to be cached for better exception |
629 | - tracebacks) |
630 | - |
631 | - - refactored the #cache/CacheRegions code to support abitrary backend cache |
632 | - data stores. |
633 | - |
634 | - - added MemcachedCacheStore, which allows cache data to be stored in a |
635 | - memcached backend. See http://www.linuxjournal.com/article/7451 and |
636 | - http://www.danga.com/memcached/. This is only appropriate for systems |
637 | - running many Python server processes that need to share cached data to |
638 | - reduce memory requirements. Don't bother with this unless you actually |
639 | - need it. If you have a limited number of Python server processes it is |
640 | - much faster, simpler, and more secure to just cache in the memory of each |
641 | - process. |
642 | - |
643 | - KEEP MEMCACHED'S LIMITED SECURITY IN MIND!! It has no authentication or |
644 | - encryption and will introduce a gaping hole in your defenses unless you |
645 | - are careful. If you are caching sensitive data you should take measures |
646 | - to ensure that a) untrusted local system users cannot connect to memcached |
647 | - server, b) untrusted external servers cannot connect, and c) untrusted |
648 | - users on trusted external servers cannot connect. Case (a) can be dealt |
649 | - with via iptable's owner match module for one way to do this: "iptables -A |
650 | - ... -m owner ..." Cases (b) and (c) can be handled by tunnelling |
651 | - memcached network connections over stunnel and implementing stunnel |
652 | - authentication with mandatory peer/client certs. |
653 | - |
654 | - - some under-the-hood refactoring of the parser |
655 | - |
656 | - - made it possible to add custom directives, or customize the |
657 | - parsing/handling of existing ones, via the compiler settings |
658 | - 'directiveNamesAndParsers' and 'endDirectiveNamesAndHandlers' |
659 | - |
660 | - - added a compile-time macro facility to Cheetah. These macros are very |
661 | - similar to macros in Lisp: |
662 | - http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html. |
663 | - |
664 | - As with Lisp macros, they take source code (Cheetah source) as input and |
665 | - return source code (again Cheetah source) as output. They are executed at |
666 | - compile-time, just like in Lisp and C. The resultant code |
667 | - gets executed at run-time. |
668 | - |
669 | - The new #defmacro directive allows users to create macros inside the |
670 | - source of their templates. Macros can also be provided via the compiler |
671 | - setting 'macroDirectives'. The 'macroDirectives' setting allows you to |
672 | - share common macros between templates. |
673 | - |
674 | - The syntax for the opening tag of #defmacro is the same as for #def and |
675 | - #block. It expects a macro name followed by an optional argument list in |
676 | - brackets. A `src` argument is automatically added to the beginning of |
677 | - every macro's argument list. The value of the `src` is the block of |
678 | - input source code that is provided during a macro call (see below). |
679 | - |
680 | - #defmacro <macroname>[(argspec)] |
681 | - <macrobody> |
682 | - #end defmacro |
683 | - |
684 | - All of Cheetah's syntax is available for use inside macros, but the |
685 | - placeholderStartToken is @ instead of $ and the |
686 | - directiveStartToken/EndToken is % instead of #. Any syntax using the |
687 | - standard $/# tokens will be treated as plain text and included in the output |
688 | - of the macro. |
689 | - |
690 | - Here are some examples: |
691 | - #defmacro addHeaderFooter |
692 | - header |
693 | - @src |
694 | - footer |
695 | - #end defmacro |
696 | - |
697 | - #defmacro addHeaderFooter(header='h', footer='f') |
698 | - @header |
699 | - @src |
700 | - @footer |
701 | - #end defmacro |
702 | - |
703 | - There is a single-line short form like for other directives: |
704 | - |
705 | - #defmacro addHeaderFooter: header @src footer |
706 | - #defmacro addHeaderFooter(header='h', footer='f'): @header @src @footer |
707 | - |
708 | - The syntax for calling a macro is similar to the simplest usage of the |
709 | - #call directive: |
710 | - |
711 | - #addHeaderFooter |
712 | - Source $code to wrap |
713 | - #end addHeaderFooter |
714 | - |
715 | - #addHeaderFooter: Source $code to wrap |
716 | - |
717 | - #addHeaderFooter header='header', footer='footer: Source $code to wrap |
718 | - |
719 | - |
720 | - In Elisp you write |
721 | - (defmacro inc (var) |
722 | - (list 'setq var (list '1+ var))) |
723 | - to define the macro `inc` and write |
724 | - (inc x) |
725 | - which expands to |
726 | - (setq x (1+ x)) |
727 | - |
728 | - In Cheetah you'd write |
729 | - #defmacro inc: #set @src +=1 |
730 | - #inc: $i |
731 | - which expands to |
732 | - #set $i += 1 |
733 | - |
734 | - print Template("""\ |
735 | - #defmacro inc: #set @src +=1 |
736 | - #set i = 1 |
737 | - #inc: $i |
738 | - $i""").strip()==2 |
739 | - |
740 | - - fixed some bugs related to advanced usage of Template.compile(). These |
741 | - were found via new unit tests. No one had actually run into them yet. |
742 | - |
743 | - - added the initial bits of an #i18n directive. It has the same semantics |
744 | - as |
745 | - #call self.handleI18n |
746 | - Some $var cheetah source |
747 | - #end call |
748 | - but has a simpler syntax: |
749 | - #i18n |
750 | - Some $var cheetah source |
751 | - #end i18n |
752 | - |
753 | - ## single-line short form: |
754 | - #i18n: Some $var cheetah source |
755 | - |
756 | - The method it calls, self.handleI18n, is just a stub at the moment, but it |
757 | - will soon be a wrapper around gettext. It currently has one required |
758 | - positional argument `message`. I anticipate supporting the following |
759 | - optional arguments: |
760 | - |
761 | - id = msgid in the translation catalog |
762 | - domain = translation domain |
763 | - source = source lang |
764 | - target = a specific target lang |
765 | - comment = a comment to the translation team |
766 | - |
767 | - plural = the plural form of the message |
768 | - n = a sized argument to distinguish between single and plural forms |
769 | - |
770 | - #i18n is executed at runtime, but it can also be used in conjunction with |
771 | - a Cheetah preprocessor or macro (see above) to support compile time |
772 | - translation of strings that don't have to deal with plural forms. |
773 | - |
774 | - - added Cheetah.Utils.htmlEncode and Cheetah.Utils.htmlDecode |
775 | - |
776 | - - more docstring text |
777 | - |
778 | - Unit tests: [TR] |
779 | - - extended the caching tests |
780 | - - added tests for the various calling styles of Template.compile() |
781 | - - added copies of all the SyntaxAndOutput tests that use a template |
782 | - baseclass other than `Template`. This ensures that all syntax & core |
783 | - features work with 2.0's support for arbitrary baseclasses. |
784 | - - added tests for all the new directives and the new single-line short forms |
785 | - |
786 | - 2.0rc2 (Jan 13th, 2006) |
787 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
788 | - Core Changes: [TR] |
789 | - - fixed some python 2.4isms that slipped in. All the tests pass with Python |
790 | - 2.2 now |
791 | - - added lots more docstring content in the Template class |
792 | - - made multiline comments gobble whitespace like other directives, per JJ's |
793 | - request. The rather longwinded compiler setting |
794 | - gobbleWhitespaceAroundMultiLineComments can be used to go back to the old |
795 | - non-gobbling behaviour if needed. |
796 | - - added #capture directive to complement the #call directive. |
797 | - #call executes a region of Cheetah code and passes its output into a function call |
798 | - #capture executes a region of Cheetah code and assigns its output to a variable |
799 | - - extended the compile caching code in Template.compile so it works with the |
800 | - 'file' arg. |
801 | - - added cacheModuleFilesForTracebacks and cacheDirForModuleFiles args to |
802 | - Template.compile(). See the docstring for details. |
803 | - - misc internal refactoring in the parser |
804 | - - improved handling of keyword args in the __init__ method and fixed a |
805 | - potential clash between the namespaces and searchList args |
806 | - |
807 | - WWW: [TR] |
808 | - - added the source for the new Cheetah website layout/content |
809 | - |
810 | - 2.0rc1 (Jan 10, 2006) |
811 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
812 | - Core Changes: [TR] |
813 | - - made it possible nest #filter directives |
814 | - - added lots more docstring content in the Template class |
815 | - - added Template.subclass() classmethod for quickly creating subclasses of |
816 | - existing Cheetah template classes. It takes the same args as the |
817 | - .compile() classmethod and returns a template that is a subclass of the |
818 | - template .subclass() is called from: |
819 | - T1 = Template.compile(' foo - $meth1 - bar\n#def meth1: this is T1.meth1') |
820 | - T2 = T1.subclass('#implements meth1\n this is T2.meth1') |
821 | - |
822 | - - added baseclass arg to Template.compile(). It simplifies the reuse of |
823 | - dynamically compiled templates: |
824 | - # example 1, quickly subclassing a normal Python class and using its |
825 | - # __init__ call signature: |
826 | - dictTemplate = Template.compile('hello $name from $caller', baseclass=dict) |
827 | - print dictTemplate(name='world', caller='me') |
828 | - |
829 | - # example 2, mixing a Cheetah method into a class definition: |
830 | - class Foo(dict): |
831 | - def meth1(self): |
832 | - return 'foo' |
833 | - def meth2(self): |
834 | - return 'bar' |
835 | - Foo = Template.compile('#implements meth3\nhello $name from $caller', |
836 | - baseclass=Foo) |
837 | - print Foo(name='world', caller='me') |
838 | - |
839 | - A side-benefit is the possibility to use the same Cheetah source with |
840 | - several baseclass, as the baseclass is orthogonal to the source code, |
841 | - unlike the #extends directive. |
842 | - |
843 | - - added 'namespaces' as an alias for 'searchList' in Template.__init__ |
844 | - - made it possible to pass in a single namespace to 'searchList', which will |
845 | - automatically be converted into a list. |
846 | - - fixed issue with buffering and use of #call when template is used as a |
847 | - webkit servlet |
848 | - - added Cheetah.Utils.htmlEncode and htmlDecode |
849 | - |
850 | - The command line tool (CheetahWrapper.py): |
851 | - - changed insertion order for the --env and --pickle options so they match the |
852 | - commandline UI of the compiled template modules themselves [TR] |
853 | - |
854 | - 2.0b5 (Jan 7, 2006) |
855 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
856 | - Core Changes: [TR] |
857 | - - made Cheetah.Template a new-style class by inserting 'object' into its' |
858 | - inheritance tree. Templates can now use super(), properties and all the |
859 | - other goodies that come with new-style classes. |
860 | - - removed the WebInputMixin by placing its one method directly in the |
861 | - Template class. |
862 | - - removed the SettingsManager Mixin. It wasn't being used by anything |
863 | - anymore. |
864 | - - added a framework for caching the results of compilations in |
865 | - Template.compile(). This is on by default and protects against bad |
866 | - performance issues that are due to programmers misguidedly compiling |
867 | - templates inside tight loops. It also saves on memory usage. |
868 | - - misc attr name changes to avoid namespace pollution |
869 | - - more + improved docstrings |
870 | - - replaced the oldstyle dynamic compile hacks with a wrapper around |
871 | - Template.compile(). The old usage pattern Template(src) now benefits from |
872 | - most of the recent changes. |
873 | - Template(src).__class__ == Template.compile(src) |
874 | - - removed all the extra imports required by oldstyle dynamic compile hacks |
875 | - - converted the cheetah #include mechanism to newstyle compilation and made it |
876 | - more flexible |
877 | - - made the #include mechanism work with file objects in addition to file names |
878 | - - made the handling of args to Template.compile() more flexible. You can now |
879 | - provide defaults via class attributes. |
880 | - - made preprocessors for Template.compile() work with file arguments |
881 | - - added support for specifying a __metaclass__ on cheetah template classes |
882 | - - refactored both the class and instance initialization processes |
883 | - - improved the handling of __str__ in _assignRequiredMethodsToClass |
884 | - |
885 | - The command line tool (CheetahWrapper.py): [TR] |
886 | - - improved error output in CheetahWrapper |
887 | - - switched fill command over to new style compile usage |
888 | - |
889 | - Unit tests: [TR] |
890 | - - fixed format string bug in unittest_local_copy.py |
891 | - |
892 | - 2.0b4 (Jan 6, 2006) |
893 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
894 | - Core Changes: [TR] |
895 | - - fixed up parsing of target lists in for loops. This was previously limited |
896 | - to fairly simple target lists. |
897 | - #for ($i, $j) in [('aa','bb'),('cc','dd')] |
898 | - $i.upper,$j.upper |
899 | - #end for" |
900 | - #for (i, j) in [('aa','bb'),('cc','dd')] |
901 | - $i.upper,$j.upper |
902 | - #end for" |
903 | - #for i,(j, k) in enumerate([('aa','bb'),('cc','dd')]) |
904 | - $j.upper,$k.upper |
905 | - #end for" |
906 | - - refactored the class initialization process |
907 | - - improved handling of target lists in #set directive. This was previously |
908 | - limited to fairly simple target lists. |
909 | - #set i,j = [1,2] ... #set $i,$j = [1,2] |
910 | - #set (i,j) = [1,2] ... #set ($i,$j) = [1,2] |
911 | - #set i, (j,k) = [1,(2,3)] ... #set $i, ($j,$k) = [1,(2,3)] |
912 | - |
913 | - - made it possible for the expressionFilter hooks to modify the code chunks |
914 | - they are fed. Also documented the hooks in a docstring. Thus the hooks |
915 | - can be used as preprocessors for expressions, 'restricted execution', or |
916 | - even enforcement of style guidelines. |
917 | - |
918 | - - removed cheetah junk from docstrings and placed it all in comments or |
919 | - __moduleVars__. Per JJ's suggestion. |
920 | - |
921 | - - made it possible to nest #cache directives to any level |
922 | - - made it possible to nest #call directives to any level |
923 | - |
924 | - Unit Tests [TR] |
925 | - - extended tests for #for directive |
926 | - - expanded tests for #set directive |
927 | - - expanded tests for #call directive |
928 | - - expanded tests for #cache directive |
929 | - - added basic tests for the new $placeholder string expressions: |
930 | - c'text $placeholder text' |
931 | - |
932 | - 2.0b3 (Jan 5, 2006) |
933 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
934 | - Core Changes: [TR] |
935 | - - added #yield statement |
936 | - - added ability to create nested scopes/functions via nested #def statements |
937 | - - added new #call directive and related #arg directive, per Ian Bicking's |
938 | - suggestion. |
939 | - - added new expression syntax c"text $placeholder text" |
940 | - |
941 | - for those basic function calling cases where you just need to pass in a |
942 | - small bit of cheetah output as an argument: |
943 | - |
944 | - c'a string with $placeholders', |
945 | - c'''a string with $placeholders''', |
946 | - c"a string with $placeholders", |
947 | - c"""a string with $placeholders""" |
948 | - |
949 | - - They can't contain #directives, but accept any valid $placeholder syntax |
950 | - except caching placeholders. Caching placeholders don't make any sense in |
951 | - this context. |
952 | - - They can be used *any* place where a python expression is expected. |
953 | - - They can be nested to any depth. |
954 | - |
955 | - $func(c'<li>$var1-$var2</li>') |
956 | - $func(c'<li>$var1-$var2</li>', doSomething=True) |
957 | - $func(content=c'<li>$var1-$var2</li>', doSomething=True) |
958 | - $func(lambda x,y: c'<li>$x-$y</li>') |
959 | - $func(callback=lambda x,y: c'<li>$x-$y</li>') |
960 | - $func(lambda x,y: c'<li>$x-$y-$varInSearchList</li>') |
961 | - $func(c'<li>$var1-$var2-$(var3*10)-$(94.3*58)</li>') |
962 | - $func(c'<li>$var1-$var2-$func2(c"a nested expr $var99")</li>') |
963 | - #if $cond then c'<li>$var1-$var2</li>' else c'<p>$var1-$var2</p>' |
964 | - #def foo(arg1=c'$var1<span class="foo">$var2</span>'): blah $arg1 blah |
965 | - $foo(c'$var1<i>$var2</i>') |
966 | - |
967 | - - added preprocessor hooks to Template.compile() |
968 | - can be used for partial completion or 'compile-time-caching' |
969 | - ... more details and examples coming. It's very useful, but takes a bit |
970 | - of explaining. |
971 | - - added '#set module varName = expr' for adding module globals. JJ's suggestion |
972 | - - improved generated docstring notes about cached vars |
973 | - - fixed silly bug related to """ in docstring comments and statements like |
974 | - this '#def foo: $str("""foo""")'. Reported by JJ. |
975 | - - changed the handling of single-line defs so that |
976 | - '#def xxx:<just whitespace>\n' will be treated as a multi-line #def. |
977 | - The same applies to #block. There's a compiler setting to turn this off |
978 | - if you really need empty single-line #def:'s. |
979 | - JJ reported that this was causing great confusion with beginners. |
980 | - - improved error message for unclosed directives, per Mike Orr's suggestion. |
981 | - - added optional support for passing the trans arg to methods via **KWS rather |
982 | - than trans=None. See the discussion on the mailing list Jan 4th (JJ's post) for |
983 | - details. The purpose is to avoid a positional argument clash that |
984 | - apparently is very confusing for beginners. |
985 | - |
986 | - Note that any existing client code that passing the trans arg in |
987 | - positionally rather than as a keyword will break as a result. WebKit |
988 | - does this with the .respond method so I've kept the old style there. |
989 | - You can also turn this new behaviour off by either manually including |
990 | - the trans arg in your method signature (see the example below) or by |
991 | - using the compiler setting 'useKWsDictArgForPassingTrans'=False. |
992 | - |
993 | - #def manualOverride(arg1, trans=None) |
994 | - foo $arg1 |
995 | - #end def |
996 | - |
997 | - ImportHooks: |
998 | - - made the ImportHook more robust against compilation errors during import [TR] |
999 | - |
1000 | - Install scripts: [TR] |
1001 | - - added optional support for pje's setuptools |
1002 | - - added cheeseshop classifiers |
1003 | - - removed out of date install instructions in __init__.py |
1004 | - |
1005 | - Servlet Base Class For Webkit: [TR] |
1006 | - - disabled assignment of self.application (was a webware hack) |
1007 | - |
1008 | - Unit Tests: [TR] |
1009 | - - unit tests for most of the new syntax elements |
1010 | - - tidied up some old tests |
1011 | - - misc refactoring |
1012 | - |
1013 | - 2.0b2 (Dec 30, 2005) |
1014 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
1015 | - |
1016 | - Core Changes: |
1017 | - - In previous versions of Cheetah tracebacks from exceptions that were raised |
1018 | - inside dynamically compiled Cheetah templates were opaque because |
1019 | - Python didn't have access to a python source file to use in the traceback: |
1020 | - |
1021 | - File "xxxx.py", line 192, in getTextiledContent |
1022 | - content = str(template(searchList=searchList)) |
1023 | - File "cheetah_yyyy.py", line 202, in __str__ |
1024 | - File "cheetah_yyyy.py", line 187, in respond |
1025 | - File "cheetah_yyyy.py", line 139, in writeBody |
1026 | - ZeroDivisionError: integer division or modulo by zero |
1027 | - |
1028 | - It is now possible to keep the generated source code from the python |
1029 | - classes returned by Template.compile() in a cache dir. Having these files |
1030 | - around allows Python to include the actual source lines in tracebacks and |
1031 | - makes them much easier to understand: |
1032 | - |
1033 | - File "/usr/local/unsnarl/lib/python/us/ui/views/WikiPageRenderer.py", line 192, in getTextiledContent |
1034 | - content = str(template(searchList=searchList)) |
1035 | - File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 202, in __str__ |
1036 | - def __str__(self): return self.respond() |
1037 | - File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 187, in respond |
1038 | - self.writeBody(trans=trans) |
1039 | - File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 139, in writeBody |
1040 | - __v = 0/0 # $(0/0) |
1041 | - ZeroDivisionError: integer division or modulo by zero |
1042 | - |
1043 | - This is turned off by default. To turn it on, do this: |
1044 | - |
1045 | - class NiceTracebackTemplate(Template): |
1046 | - _CHEETAH_cacheModuleFilesForTracebacks = True |
1047 | - _CHEETAH_cacheDirForModuleFiles = '/tmp/CheetahCacheDir' # change to a dirname |
1048 | - |
1049 | - templateClass = NiceTracebackTemplate.compile(src) |
1050 | - |
1051 | - # or |
1052 | - templateClass = Template.compile(src, |
1053 | - cacheModuleFilesForTracebacks=True, cacheDirForModuleFiles='/tmp/CheetahCacheDir') |
1054 | - |
1055 | - |
1056 | - This only works with the new Template.compile(src) usage style! |
1057 | - |
1058 | - Note, Cheetah generated modules that are compiled on the command line have |
1059 | - never been affected by this issue. [TR] |
1060 | - |
1061 | - - added an extra comment per $placeholder to generated python code so it is |
1062 | - easier to grok. [TR] |
1063 | - |
1064 | - 2.0b1 (Dec 29, 2005) |
1065 | - !!!THIS RELEASE REQUIRES RECOMPILATION OF ALL COMPILED CHEETAH TEMPLATES!!! |
1066 | - |
1067 | - Core Changes: |
1068 | - - enabled use of any expression in ${placeholders}. See the examples I posted to |
1069 | - the email list on Dec 12th. All use cases of the #echo directive can now |
1070 | - be handled with ${placeholders}. This came from a suggestion by Mike |
1071 | - Orr. [TR] |
1072 | - |
1073 | - - made it possible for templates to #extend (aka subclass) any arbitrary |
1074 | - baseclass, including Python's new style classes. You must either compile |
1075 | - your classes on the command line or use the new classmethod |
1076 | - Template.compile() as described below. The old Template(src) interface |
1077 | - still works, provided you don't try to use this new arbitrary baseclass |
1078 | - stuff. See my messages to the email list for more details. [TR] |
1079 | - |
1080 | - - made it possible to create template classes dynamically, rather than just |
1081 | - instances. See the new classmethod Template.compile(). See my messages |
1082 | - to the email list for more details. [TR] |
1083 | - |
1084 | - klass = Template.compile(src) |
1085 | - |
1086 | - - made it easier to work with custom compiler settings, particularly from |
1087 | - the command line tool. You can now define a subclass of Template which |
1088 | - will compile your templates using custom compilerSettings, or even a |
1089 | - custom compiler class, without requiring you to manually pass in your |
1090 | - compilerSettings each time or define them in the template src itself via |
1091 | - the #compiler directive. You can make the command line tool use your |
1092 | - subclass by defining the environment variable CHEETAH_TEMPLATE_CLASS. It |
1093 | - should be in the form 'package.module:class'. See my messages |
1094 | - to the email list for more details. [TR] |
1095 | - |
1096 | - - made it possible to pass the searchList in as an argument to #def'ined |
1097 | - methods. This makes all lookup that occur within the scope of that method |
1098 | - use the provided searchList rather than self._searchList. This does not |
1099 | - carry over to other methods called within the top method, unless they |
1100 | - explicitly accept the searchList in their signature AND you pass it to |
1101 | - them when calling them. This behaviour can be turned off with the |
1102 | - corresponding compilerSetting 'allowSearchListAsMethArg' [TR] |
1103 | - |
1104 | - - added hooks for filtering / restricting dangerous stuff in cheetah source |
1105 | - code at compile time. These hooks can be used to enable Cheetah template |
1106 | - authoring by untrusted users. See my messages to the email list for more |
1107 | - details. Note, it filters expressions at parse/compile time, unlike Python's |
1108 | - old rexec module which restricted the Python environment at runtime. [TR] |
1109 | - |
1110 | - # Here are the relevant compiler settings: |
1111 | - # use lower case keys here!! |
1112 | - 'disabledDirectives':[], # list of directive keys, without the start token |
1113 | - 'enabledDirectives':[], # list of directive keys, without the start token |
1114 | - |
1115 | - 'disabledDirectiveHooks':[], # callable(parser, directiveKey), |
1116 | - # called when a disabled directive is found, prior to raising an exception |
1117 | - |
1118 | - 'preparseDirectiveHooks':[], # callable(parser, directiveKey) |
1119 | - 'postparseDirectiveHooks':[], # callable(parser, directiveKey) |
1120 | - |
1121 | - 'preparsePlaceholderHooks':[], # callable(parser) |
1122 | - 'postparsePlaceholderHooks':[], # callable(parser) |
1123 | - |
1124 | - 'expressionFilterHooks':[], |
1125 | - # callable(parser, expr, exprType, rawExpr=None, startPos=None) |
1126 | - # exprType is the name of the directive, 'psp', or 'placeholder'. |
1127 | - #all lowercase |
1128 | - |
1129 | - - added support for a short EOLSlurpToken to supplement the #slurp |
1130 | - directive. It's currently re.compile('#\s*\n') (i.e # followed by |
1131 | - arbitrary whitespace and a new line), but this is not set in stone. One |
1132 | - other suggestion was the backslash char, but I believe Python's own |
1133 | - interpretation of backslashes will lead to confusion. The compiler |
1134 | - setting 'EOLSlurpToken' controls this. You can turn it off completely by |
1135 | - setting 'EOLSlurpToken' to None. See the email list for more details. [TR] |
1136 | - |
1137 | - - added '_CHEETAH_' prefix to all instance attribute names in compiled |
1138 | - templates. This is related to the arbitrary baseclass change. [TR] |
1139 | - |
1140 | - - shifted instance attribute setup to _initCheetahAttributes() method. This |
1141 | - is related to the arbitrary baseclass change. [TR] |
1142 | - |
1143 | - - made it possible to use full expressions in the #extends directive, rather |
1144 | - than just dotted names. This allows you to do things like this: |
1145 | - |
1146 | - #from xx.TemplateRepository import getTemplateClass |
1147 | - #extends getTemplateClass('someName') |
1148 | - |
1149 | - I don't expect this to be used much. I needed it for a wiki system in |
1150 | - which the baseclasses for the templates are dynamically compiled at run |
1151 | - time and are not available via simple imports. [TR] |
1152 | - |
1153 | - - added compiler setting autoImportForExtendDirective=True, so this existing |
1154 | - default behaviour can be turned off when needed. [TR] |
1155 | - |
1156 | - - fixed a bug in the parsing of single-line #def's and #block's when they |
1157 | - are enclosed within #if ... #end if. Reported by Marcin Gajda [TR] |
1158 | - |
1159 | - - tweak to remove needless write('') calls in generated code [TR] |
1160 | - |
1161 | - The command line tool (CheetahWrapper.py): |
1162 | - - added code to cleanup trailing slashes on path arguments (code originally |
1163 | - from Mike Orr) [TR] |
1164 | - - turned on the ImportHooks by default for the 'cheetah fill' command. See the |
1165 | - discussion on the email list [TR] |
1166 | - |
1167 | - ImportHooks: |
1168 | - - fixed a name error bug in the ImportHooks [TR] |
1169 | + See http://www.cheetahtemplate.org/CHANGES.txt for full details |
1170 | + |
1171 | |
1172 | Platform: UNKNOWN |
1173 | Classifier: Development Status :: 5 - Production/Stable |
1174 | |
1175 | === removed file 'README' |
1176 | --- README 2006-07-26 22:03:15 +0000 |
1177 | +++ README 1970-01-01 00:00:00 +0000 |
1178 | @@ -1,51 +0,0 @@ |
1179 | -Cheetah is an open source template engine and code generation tool. |
1180 | - |
1181 | -It can be used standalone or combined with other tools and frameworks. Web |
1182 | -development is its principle use, but Cheetah is very flexible and is also being |
1183 | -used to generate C++ game code, Java, sql, form emails and even Python code. |
1184 | - |
1185 | -Documentation |
1186 | -================================================================================ |
1187 | -For a high-level introduction to Cheetah please refer to the User\'s Guide |
1188 | -at http://cheetahtemplate.org/learn.html |
1189 | - |
1190 | -Mailing list |
1191 | -================================================================================ |
1192 | -cheetahtemplate-discuss@lists.sourceforge.net |
1193 | -Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss |
1194 | - |
1195 | -Credits |
1196 | -================================================================================ |
1197 | -http://cheetahtemplate.org/credits.html |
1198 | - |
1199 | -Praise |
1200 | -================================================================================ |
1201 | -"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging |
1202 | -Technologies Group & director of Apache Software Foundation |
1203 | - |
1204 | -"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful |
1205 | -system. ... Cheetah is a serious contender for the 'best of breed' Python |
1206 | -templating." - Alex Martelli |
1207 | - |
1208 | -"People with a strong PHP background absolutely love Cheetah for being Smarty, |
1209 | -but much, much better." - Marek Baczynski |
1210 | - |
1211 | -"I am using Smarty and I know it very well, but compiled Cheetah Templates with |
1212 | -its inheritance approach is much powerful and easier to use than Smarty." - |
1213 | -Jaroslaw Zabiello |
1214 | - |
1215 | -"There is no better solution than Cheetah" - Wilk |
1216 | - |
1217 | -"A cheetah template can inherit from a python class, or a cheetah template, and |
1218 | -a Python class can inherit from a cheetah template. This brings the full power |
1219 | -of OO programming facilities to the templating system, and simply blows away |
1220 | -other templating systems" - Mike Meyer |
1221 | - |
1222 | -"Cheetah has successfully been introduced as a replacement for the overweight |
1223 | -XSL Templates for code generation. Despite the power of XSL (and notably XPath |
1224 | -expressions), code generation is better suited to Cheetah as templates are much |
1225 | -easier to implement and manage." - The FEAR development team |
1226 | - (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573) |
1227 | - |
1228 | -"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor, |
1229 | -lead developer of TurboGears. |
1230 | |
1231 | === added file 'README.markdown' |
1232 | --- README.markdown 1970-01-01 00:00:00 +0000 |
1233 | +++ README.markdown 2010-10-19 20:12:51 +0000 |
1234 | @@ -0,0 +1,51 @@ |
1235 | +Cheetah is an open source template engine and code generation tool. |
1236 | + |
1237 | +It can be used standalone or combined with other tools and frameworks. Web |
1238 | +development is its principle use, but Cheetah is very flexible and is also being |
1239 | +used to generate C++ game code, Java, sql, form emails and even Python code. |
1240 | + |
1241 | +Documentation |
1242 | +================================================================================ |
1243 | +For a high-level introduction to Cheetah please refer to the User\'s Guide |
1244 | +at http://cheetahtemplate.org/learn.html |
1245 | + |
1246 | +Mailing list |
1247 | +================================================================================ |
1248 | +cheetahtemplate-discuss@lists.sourceforge.net |
1249 | +Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss |
1250 | + |
1251 | +Credits |
1252 | +================================================================================ |
1253 | +http://cheetahtemplate.org/credits.html |
1254 | + |
1255 | +Praise |
1256 | +================================================================================ |
1257 | +"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging |
1258 | +Technologies Group & director of Apache Software Foundation |
1259 | + |
1260 | +"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful |
1261 | +system. ... Cheetah is a serious contender for the 'best of breed' Python |
1262 | +templating." - Alex Martelli |
1263 | + |
1264 | +"People with a strong PHP background absolutely love Cheetah for being Smarty, |
1265 | +but much, much better." - Marek Baczynski |
1266 | + |
1267 | +"I am using Smarty and I know it very well, but compiled Cheetah Templates with |
1268 | +its inheritance approach is much powerful and easier to use than Smarty." - |
1269 | +Jaroslaw Zabiello |
1270 | + |
1271 | +"There is no better solution than Cheetah" - Wilk |
1272 | + |
1273 | +"A cheetah template can inherit from a python class, or a cheetah template, and |
1274 | +a Python class can inherit from a cheetah template. This brings the full power |
1275 | +of OO programming facilities to the templating system, and simply blows away |
1276 | +other templating systems" - Mike Meyer |
1277 | + |
1278 | +"Cheetah has successfully been introduced as a replacement for the overweight |
1279 | +XSL Templates for code generation. Despite the power of XSL (and notably XPath |
1280 | +expressions), code generation is better suited to Cheetah as templates are much |
1281 | +easier to implement and manage." - The FEAR development team |
1282 | + (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573) |
1283 | + |
1284 | +"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor, |
1285 | +lead developer of TurboGears. |
1286 | |
1287 | === modified file 'SetupConfig.py' |
1288 | --- SetupConfig.py 2006-07-26 22:03:15 +0000 |
1289 | +++ SetupConfig.py 2010-10-19 20:12:51 +0000 |
1290 | @@ -1,17 +1,18 @@ |
1291 | #-------Main Package Settings-----------# |
1292 | -name = "Cheetah" |
1293 | -from src.Version import Version as version |
1294 | -maintainer = "Tavis Rudd" |
1295 | +import sys |
1296 | + |
1297 | +name = 'Cheetah' |
1298 | +from cheetah.Version import Version as version |
1299 | +maintainer = "R. Tyler Ballance" |
1300 | author = "Tavis Rudd" |
1301 | author_email = "cheetahtemplate-discuss@lists.sf.net" |
1302 | -url = "http://www.CheetahTemplate.org/" |
1303 | +url = "http://www.cheetahtemplate.org/" |
1304 | packages = ['Cheetah', |
1305 | 'Cheetah.Macros', |
1306 | 'Cheetah.Templates', |
1307 | 'Cheetah.Tests', |
1308 | 'Cheetah.Tools', |
1309 | 'Cheetah.Utils', |
1310 | - 'Cheetah.Utils.optik', |
1311 | ] |
1312 | classifiers = [line.strip() for line in '''\ |
1313 | #Development Status :: 4 - Beta |
1314 | @@ -28,30 +29,50 @@ |
1315 | Topic :: Software Development :: Libraries :: Python Modules |
1316 | Topic :: Software Development :: User Interfaces |
1317 | Topic :: Text Processing'''.splitlines() if not line.strip().startswith('#')] |
1318 | -del line |
1319 | |
1320 | -package_dir = {'Cheetah':'src'} |
1321 | +package_dir = {'Cheetah':'cheetah'} |
1322 | |
1323 | import os |
1324 | import os.path |
1325 | from distutils.core import Extension |
1326 | |
1327 | -## we only assume the presence of a c compiler on Posix systems, NT people will |
1328 | -# have to enable this manually. |
1329 | -if os.name == 'posix': |
1330 | - ext_modules=[Extension("Cheetah._namemapper", [os.path.join("src" ,"_namemapper.c")] |
1331 | - ) |
1332 | - ] |
1333 | -else: |
1334 | - ext_modules=[] |
1335 | - |
1336 | +ext_modules=[ |
1337 | + Extension("Cheetah._namemapper", |
1338 | + [os.path.join('cheetah', 'c', '_namemapper.c')]), |
1339 | + # Extension("Cheetah._verifytype", |
1340 | + # [os.path.join('cheetah', 'c', '_verifytype.c')]), |
1341 | + # Extension("Cheetah._filters", |
1342 | + # [os.path.join('cheetah', 'c', '_filters.c')]), |
1343 | + # Extension('Cheetah._template', |
1344 | + # [os.path.join('cheetah', 'c', '_template.c')]), |
1345 | + ] |
1346 | |
1347 | ## Data Files and Scripts |
1348 | -scripts = ['bin/cheetah-compile', |
1349 | +scripts = ('bin/cheetah-compile', |
1350 | 'bin/cheetah', |
1351 | - ] |
1352 | -data_files = ['recursive: src *.tmpl *.txt LICENSE README TODO CHANGES', |
1353 | - ] |
1354 | + 'bin/cheetah-analyze', |
1355 | + ) |
1356 | + |
1357 | +data_files = ['recursive: cheetah *.tmpl *.txt LICENSE README TODO CHANGES',] |
1358 | + |
1359 | +if not os.getenv('CHEETAH_INSTALL_WITHOUT_SETUPTOOLS'): |
1360 | + try: |
1361 | + from setuptools import setup |
1362 | + install_requires = [ |
1363 | + "Markdown >= 2.0.1", |
1364 | + ] |
1365 | + if sys.platform == 'win32': |
1366 | + # use 'entry_points' instead of 'scripts' |
1367 | + del scripts |
1368 | + entry_points = { |
1369 | + 'console_scripts': [ |
1370 | + 'cheetah = Cheetah.CheetahWrapper:_cheetah', |
1371 | + 'cheetah-compile = Cheetah.CheetahWrapper:_cheetah_compile', |
1372 | + ] |
1373 | + } |
1374 | + except ImportError: |
1375 | + print('Not using setuptools, so we cannot install the Markdown dependency') |
1376 | + |
1377 | |
1378 | description = "Cheetah is a template engine and code generation tool." |
1379 | |
1380 | @@ -64,7 +85,7 @@ |
1381 | Documentation |
1382 | ================================================================================ |
1383 | For a high-level introduction to Cheetah please refer to the User\'s Guide |
1384 | -at http://cheetahtemplate.org/learn.html |
1385 | +at http://www.cheetahtemplate.org/learn.html |
1386 | |
1387 | Mailing list |
1388 | ================================================================================ |
1389 | @@ -73,48 +94,10 @@ |
1390 | |
1391 | Credits |
1392 | ================================================================================ |
1393 | -http://cheetahtemplate.org/credits.html |
1394 | - |
1395 | -Praise |
1396 | -================================================================================ |
1397 | -"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging |
1398 | -Technologies Group & director of Apache Software Foundation |
1399 | - |
1400 | -"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful |
1401 | -system. ... Cheetah is a serious contender for the 'best of breed' Python |
1402 | -templating." - Alex Martelli |
1403 | - |
1404 | -"People with a strong PHP background absolutely love Cheetah for being Smarty, |
1405 | -but much, much better." - Marek Baczynski |
1406 | - |
1407 | -"I am using Smarty and I know it very well, but compiled Cheetah Templates with |
1408 | -its inheritance approach is much powerful and easier to use than Smarty." - |
1409 | -Jaroslaw Zabiello |
1410 | - |
1411 | -"There is no better solution than Cheetah" - Wilk |
1412 | - |
1413 | -"A cheetah template can inherit from a python class, or a cheetah template, and |
1414 | -a Python class can inherit from a cheetah template. This brings the full power |
1415 | -of OO programming facilities to the templating system, and simply blows away |
1416 | -other templating systems" - Mike Meyer |
1417 | - |
1418 | -"Cheetah has successfully been introduced as a replacement for the overweight |
1419 | -XSL Templates for code generation. Despite the power of XSL (and notably XPath |
1420 | -expressions), code generation is better suited to Cheetah as templates are much |
1421 | -easier to implement and manage." - The FEAR development team |
1422 | - (http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573) |
1423 | - |
1424 | -"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor, |
1425 | -lead developer of TurboGears. |
1426 | +http://www.cheetahtemplate.org/credits.html |
1427 | |
1428 | Recent Changes |
1429 | ================================================================================ |
1430 | -See http://cheetahtemplate.org/docs/CHANGES for full details. |
1431 | +See http://www.cheetahtemplate.org/CHANGES.txt for full details |
1432 | |
1433 | ''' |
1434 | -try: |
1435 | - recentChanges = open('CHANGES').read().split('\n1.0')[0] |
1436 | - long_description += recentChanges |
1437 | - del recentChanges |
1438 | -except: |
1439 | - pass |
1440 | |
1441 | === modified file 'SetupTools.py' |
1442 | --- SetupTools.py 2007-11-18 14:59:06 +0000 |
1443 | +++ SetupTools.py 2010-10-19 20:12:51 +0000 |
1444 | @@ -1,44 +1,61 @@ |
1445 | #!/usr/bin/env python |
1446 | -# $Id: SetupTools.py,v 1.9 2007/11/03 19:44:38 tavis_rudd Exp $ |
1447 | -"""Some tools for extending and working with distutils |
1448 | - |
1449 | -CREDITS: This module borrows code and ideas from M.A. Lemburg's excellent setup |
1450 | -tools for the mxBase package. |
1451 | - |
1452 | -""" |
1453 | - |
1454 | -__author__ = "Tavis Rudd <tavis@damnsimple.com>" |
1455 | -__version__ = "$Revision: 1.9 $"[11:-2] |
1456 | - |
1457 | import os |
1458 | from os import listdir |
1459 | import os.path |
1460 | from os.path import exists, isdir, isfile, join, splitext |
1461 | +import sys |
1462 | import types |
1463 | import glob |
1464 | import string |
1465 | import traceback |
1466 | |
1467 | from distutils.core import setup |
1468 | -if 'CHEETAH_USE_SETUPTOOLS' in os.environ: |
1469 | - # @@TR: Please note that this is for testing purposes only! PEAK setuptools |
1470 | - # is not required or recommended for installing Cheetah. Downstream |
1471 | - # package managers (linux distros, etc.) should *not* enable this. |
1472 | +if not os.getenv('CHEETAH_INSTALL_WITHOUT_SETUPTOOLS'): |
1473 | try: |
1474 | - # use http://peak.telecommunity.com/DevCenter/setuptools if it's installed |
1475 | - # requires Py >=2.3 |
1476 | from setuptools import setup |
1477 | except ImportError: |
1478 | from distutils.core import setup |
1479 | |
1480 | from distutils.core import Command |
1481 | +from distutils.command.build_ext import build_ext |
1482 | from distutils.command.install_data import install_data |
1483 | +from distutils.errors import CCompilerError, DistutilsExecError, \ |
1484 | + DistutilsPlatformError |
1485 | |
1486 | #imports from Cheetah ... |
1487 | -from src.FileUtils import findFiles |
1488 | +from cheetah.FileUtils import findFiles |
1489 | + |
1490 | +if sys.platform == 'win32' and sys.version_info > (2, 6): |
1491 | + # 2.6's distutils.msvc9compiler can raise an IOError when failing to |
1492 | + # find the compiler |
1493 | + ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, |
1494 | + IOError) |
1495 | +else: |
1496 | + ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) |
1497 | |
1498 | ################################################## |
1499 | ## CLASSES ## |
1500 | + |
1501 | +class BuildFailed(Exception): |
1502 | + pass |
1503 | + |
1504 | +class mod_build_ext(build_ext): |
1505 | + """A modified version of the distutils build_ext command that raises an |
1506 | + exception when building of the extension fails. |
1507 | + """ |
1508 | + |
1509 | + def run(self): |
1510 | + try: |
1511 | + build_ext.run(self) |
1512 | + except DistutilsPlatformError, x: |
1513 | + raise BuildFailed(x) |
1514 | + |
1515 | + def build_extension(self, ext): |
1516 | + try: |
1517 | + build_ext.build_extension(self, ext) |
1518 | + except ext_errors, x: |
1519 | + raise BuildFailed(x) |
1520 | + |
1521 | |
1522 | class mod_install_data(install_data): |
1523 | """A modified version of the disutils install_data command that allows data |
1524 | @@ -60,8 +77,8 @@ |
1525 | data_files = self.get_inputs() |
1526 | |
1527 | for entry in data_files: |
1528 | - if type(entry) != types.StringType: |
1529 | - raise ValueError, 'The entries in "data_files" must be strings' |
1530 | + if not isinstance(entry, basestring): |
1531 | + raise ValueError('The entries in "data_files" must be strings') |
1532 | |
1533 | entry = string.join(string.split(entry, '/'), os.sep) |
1534 | # entry is a filename or glob pattern |
1535 | @@ -117,25 +134,39 @@ |
1536 | """ |
1537 | # Build parameter dictionary |
1538 | kws = {} |
1539 | + newkws = {} |
1540 | for configuration in configurations: |
1541 | kws.update(vars(configuration)) |
1542 | for name, value in kws.items(): |
1543 | - if name[:1] == '_' or \ |
1544 | - type(value) not in (types.StringType, |
1545 | - types.ListType, |
1546 | - types.TupleType, |
1547 | - types.DictType, |
1548 | - types.IntType, |
1549 | - ): |
1550 | - del kws[name] |
1551 | + if name[:1] == '_': |
1552 | + continue |
1553 | + if not isinstance(value, (basestring, list, tuple, dict, int)): |
1554 | + continue |
1555 | + newkws[name] = value |
1556 | + kws = newkws |
1557 | |
1558 | # Add setup extensions |
1559 | cmdclasses = { |
1560 | + 'build_ext': mod_build_ext, |
1561 | 'install_data': mod_install_data, |
1562 | } |
1563 | |
1564 | kws['cmdclass'] = cmdclasses |
1565 | |
1566 | # Invoke distutils setup |
1567 | - apply(setup, (), kws) |
1568 | + try: |
1569 | + setup(**kws) |
1570 | + except BuildFailed, x: |
1571 | + print("One or more C extensions failed to build.") |
1572 | + print("Details: %s" % x) |
1573 | + if os.environ.get('CHEETAH_C_EXTENSIONS_REQUIRED'): |
1574 | + raise x |
1575 | + print("Retrying without C extensions enabled.") |
1576 | + |
1577 | + del kws['ext_modules'] |
1578 | + setup(**kws) |
1579 | + |
1580 | + print("One or more C extensions failed to build.") |
1581 | + print("Performance enhancements will not be available.") |
1582 | + print("Pure Python installation succeeded.") |
1583 | |
1584 | |
1585 | === modified file 'TODO' |
1586 | --- TODO 2007-07-11 14:45:50 +0000 |
1587 | +++ TODO 2010-10-19 20:12:51 +0000 |
1588 | @@ -1,16 +1,9 @@ |
1589 | -Cheetah TODO list |
1590 | ------------------ |
1591 | -* If you are working on a task please put your initials at the end of the |
1592 | - description |
1593 | -* When a task is completed please remember to note it in the CHANGES file |
1594 | -* Unresolved bugs are listed in the BUGS file. Resolved bugs are be listed |
1595 | - in the CHANGES file if the bug is considered significant enough and it |
1596 | - affected a released version of Cheetah. |
1597 | - |
1598 | -Required for Cheetah 2.0 |
1599 | -======================== |
1600 | -- Replace Optik with Python's optparse. Optik license has been removed from |
1601 | - Users' Guide. |
1602 | +NOTE: Please see http://bugs.cheetahtemplate.org |
1603 | + for future feature requests/bugs/TODO |
1604 | + |
1605 | + |
1606 | +=============================================================================== |
1607 | +=============================================================================== |
1608 | |
1609 | Desired for Cheetah 2.0 |
1610 | ======================= |
1611 | @@ -38,7 +31,6 @@ |
1612 | leak from one fill to the next. |
1613 | |
1614 | - CheetahWrapper stuff: (MO) |
1615 | - * "cheetah compile --shbang '#!/usr/bin/python2.2'" |
1616 | * "cheetah preview [options] [FILES]" print template-specific portion of main |
1617 | method(s) to stdout, with line numbers based on the .py template module. |
1618 | Make a Template method to do the same thing, a la .generatedModuleCode(). |
1619 | |
1620 | === modified file 'bin/cheetah' |
1621 | --- bin/cheetah 2004-03-30 18:47:41 +0000 |
1622 | +++ bin/cheetah 2010-10-19 20:12:51 +0000 |
1623 | @@ -1,3 +1,3 @@ |
1624 | #!/usr/bin/env python |
1625 | -from Cheetah.CheetahWrapper import CheetahWrapper |
1626 | -CheetahWrapper().main() |
1627 | +from Cheetah.CheetahWrapper import _cheetah |
1628 | +_cheetah() |
1629 | |
1630 | === added file 'bin/cheetah-analyze' |
1631 | --- bin/cheetah-analyze 1970-01-01 00:00:00 +0000 |
1632 | +++ bin/cheetah-analyze 2010-10-19 20:12:51 +0000 |
1633 | @@ -0,0 +1,6 @@ |
1634 | +#!/usr/bin/env python |
1635 | + |
1636 | +from Cheetah import DirectiveAnalyzer |
1637 | + |
1638 | +if __name__ == '__main__': |
1639 | + DirectiveAnalyzer.main() |
1640 | |
1641 | === modified file 'bin/cheetah-compile' |
1642 | --- bin/cheetah-compile 2004-03-30 18:47:41 +0000 |
1643 | +++ bin/cheetah-compile 2010-10-19 20:12:51 +0000 |
1644 | @@ -1,5 +1,3 @@ |
1645 | #!/usr/bin/env python |
1646 | -import sys |
1647 | -from Cheetah.CheetahWrapper import CheetahWrapper |
1648 | -sys.argv.insert(1, "compile") |
1649 | -CheetahWrapper().main() |
1650 | +from Cheetah.CheetahWrapper import _cheetah_compile |
1651 | +_cheetah_compile() |
1652 | |
1653 | === removed file 'cachedCompile.py' |
1654 | --- cachedCompile.py 2006-07-26 22:03:15 +0000 |
1655 | +++ cachedCompile.py 1970-01-01 00:00:00 +0000 |
1656 | @@ -1,11 +0,0 @@ |
1657 | -from Cheetah.Template import Template |
1658 | -source = file('/home/tavis/cvs_working/Cheetah/src/Templates/SkeletonPage.tmpl').read() |
1659 | -##klass = Template.compile(source, |
1660 | -## cacheCompilationResults=1, |
1661 | -## useCache=1, |
1662 | -## ) |
1663 | -for i in range(2000): |
1664 | - klass = Template.compile(source, |
1665 | - cacheCompilationResults=0, |
1666 | - useCache=0, |
1667 | - ) |
1668 | |
1669 | === added directory 'cheetah' |
1670 | === removed file 'cheetah-mem.py' |
1671 | --- cheetah-mem.py 2006-07-26 22:03:15 +0000 |
1672 | +++ cheetah-mem.py 1970-01-01 00:00:00 +0000 |
1673 | @@ -1,22 +0,0 @@ |
1674 | -from Cheetah.Template import Template |
1675 | -import gc |
1676 | - |
1677 | -src = open('/tmp/z.py').read() |
1678 | -tclass = Template.compile(src) |
1679 | -#t = Template(src) |
1680 | -nr = 0 |
1681 | -while True: |
1682 | - #tclass = Template.compile(src) |
1683 | - #t = tclass() |
1684 | - t = Template(src) |
1685 | - |
1686 | - output = t.respond() |
1687 | - |
1688 | - nr += 1 |
1689 | - if not nr % 10000: |
1690 | |
1691 | - #print 'collect' |
1692 | - #gc.collect() |
1693 | - |
1694 | - print 'tclass id', id(t.__class__) |
1695 | - print 'cache size', len(Template._CHEETAH_compileCache.keys()) |
1696 | |
1697 | === added file 'cheetah/CacheRegion.py' |
1698 | --- cheetah/CacheRegion.py 1970-01-01 00:00:00 +0000 |
1699 | +++ cheetah/CacheRegion.py 2010-10-19 20:12:51 +0000 |
1700 | @@ -0,0 +1,136 @@ |
1701 | +# $Id: CacheRegion.py,v 1.3 2006/01/28 04:19:30 tavis_rudd Exp $ |
1702 | +''' |
1703 | +Cache holder classes for Cheetah: |
1704 | + |
1705 | +Cache regions are defined using the #cache Cheetah directive. Each |
1706 | +cache region can be viewed as a dictionary (keyed by cacheRegionID) |
1707 | +handling at least one cache item (the default one). It's possible to add |
1708 | +cacheItems in a region by using the `varyBy` #cache directive parameter as |
1709 | +in the following example:: |
1710 | + #def getArticle |
1711 | + this is the article content. |
1712 | + #end def |
1713 | + |
1714 | + #cache varyBy=$getArticleID() |
1715 | + $getArticle($getArticleID()) |
1716 | + #end cache |
1717 | + |
1718 | +The code above will generate a CacheRegion and add new cacheItem for each value |
1719 | +of $getArticleID(). |
1720 | +''' |
1721 | + |
1722 | +try: |
1723 | + from hashlib import md5 |
1724 | +except ImportError: |
1725 | + from md5 import md5 |
1726 | + |
1727 | +import time |
1728 | +import Cheetah.CacheStore |
1729 | + |
1730 | +class CacheItem(object): |
1731 | + ''' |
1732 | + A CacheItem is a container storing: |
1733 | + |
1734 | + - cacheID (string) |
1735 | + - refreshTime (timestamp or None) : last time the cache was refreshed |
1736 | + - data (string) : the content of the cache |
1737 | + ''' |
1738 | + |
1739 | + def __init__(self, cacheItemID, cacheStore): |
1740 | + self._cacheItemID = cacheItemID |
1741 | + self._cacheStore = cacheStore |
1742 | + self._refreshTime = None |
1743 | + self._expiryTime = 0 |
1744 | + |
1745 | + def hasExpired(self): |
1746 | + return (self._expiryTime and time.time() > self._expiryTime) |
1747 | + |
1748 | + def setExpiryTime(self, time): |
1749 | + self._expiryTime = time |
1750 | + |
1751 | + def getExpiryTime(self): |
1752 | + return self._expiryTime |
1753 | + |
1754 | + def setData(self, data): |
1755 | + self._refreshTime = time.time() |
1756 | + self._cacheStore.set(self._cacheItemID, data, self._expiryTime) |
1757 | + |
1758 | + def getRefreshTime(self): |
1759 | + return self._refreshTime |
1760 | + |
1761 | + def getData(self): |
1762 | + assert self._refreshTime |
1763 | + return self._cacheStore.get(self._cacheItemID) |
1764 | + |
1765 | + def renderOutput(self): |
1766 | + """Can be overridden to implement edge-caching""" |
1767 | + return self.getData() or "" |
1768 | + |
1769 | + def clear(self): |
1770 | + self._cacheStore.delete(self._cacheItemID) |
1771 | + self._refreshTime = None |
1772 | + |
1773 | +class _CacheDataStoreWrapper(object): |
1774 | + def __init__(self, dataStore, keyPrefix): |
1775 | + self._dataStore = dataStore |
1776 | + self._keyPrefix = keyPrefix |
1777 | + |
1778 | + def get(self, key): |
1779 | + return self._dataStore.get(self._keyPrefix+key) |
1780 | + |
1781 | + def delete(self, key): |
1782 | + self._dataStore.delete(self._keyPrefix+key) |
1783 | + |
1784 | + def set(self, key, val, time=0): |
1785 | + self._dataStore.set(self._keyPrefix+key, val, time=time) |
1786 | + |
1787 | +class CacheRegion(object): |
1788 | + ''' |
1789 | + A `CacheRegion` stores some `CacheItem` instances. |
1790 | + |
1791 | + This implementation stores the data in the memory of the current process. |
1792 | + If you need a more advanced data store, create a cacheStore class that works |
1793 | + with Cheetah's CacheStore protocol and provide it as the cacheStore argument |
1794 | + to __init__. For example you could use |
1795 | + Cheetah.CacheStore.MemcachedCacheStore, a wrapper around the Python |
1796 | + memcached API (http://www.danga.com/memcached). |
1797 | + ''' |
1798 | + _cacheItemClass = CacheItem |
1799 | + |
1800 | + def __init__(self, regionID, templateCacheIdPrefix='', cacheStore=None): |
1801 | + self._isNew = True |
1802 | + self._regionID = regionID |
1803 | + self._templateCacheIdPrefix = templateCacheIdPrefix |
1804 | + if not cacheStore: |
1805 | + cacheStore = Cheetah.CacheStore.MemoryCacheStore() |
1806 | + self._cacheStore = cacheStore |
1807 | + self._wrappedCacheDataStore = _CacheDataStoreWrapper( |
1808 | + cacheStore, keyPrefix=templateCacheIdPrefix+':'+regionID+':') |
1809 | + self._cacheItems = {} |
1810 | + |
1811 | + def isNew(self): |
1812 | + return self._isNew |
1813 | + |
1814 | + def clear(self): |
1815 | + " drop all the caches stored in this cache region " |
1816 | + for cacheItemId in self._cacheItems.keys(): |
1817 | + cacheItem = self._cacheItems[cacheItemId] |
1818 | + cacheItem.clear() |
1819 | + del self._cacheItems[cacheItemId] |
1820 | + |
1821 | + def getCacheItem(self, cacheItemID): |
1822 | + """ Lazy access to a cacheItem |
1823 | + |
1824 | + Try to find a cache in the stored caches. If it doesn't |
1825 | + exist, it's created. |
1826 | + |
1827 | + Returns a `CacheItem` instance. |
1828 | + """ |
1829 | + cacheItemID = md5(str(cacheItemID)).hexdigest() |
1830 | + |
1831 | + if cacheItemID not in self._cacheItems: |
1832 | + cacheItem = self._cacheItemClass( |
1833 | + cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore) |
1834 | + self._cacheItems[cacheItemID] = cacheItem |
1835 | + self._isNew = False |
1836 | + return self._cacheItems[cacheItemID] |
1837 | |
1838 | === added file 'cheetah/CacheStore.py' |
1839 | --- cheetah/CacheStore.py 1970-01-01 00:00:00 +0000 |
1840 | +++ cheetah/CacheStore.py 2010-10-19 20:12:51 +0000 |
1841 | @@ -0,0 +1,106 @@ |
1842 | +''' |
1843 | +Provides several CacheStore backends for Cheetah's caching framework. The |
1844 | +methods provided by these classes have the same semantics as those in the |
1845 | +python-memcached API, except for their return values: |
1846 | + |
1847 | +set(key, val, time=0) |
1848 | + set the value unconditionally |
1849 | +add(key, val, time=0) |
1850 | + set only if the server doesn't already have this key |
1851 | +replace(key, val, time=0) |
1852 | + set only if the server already have this key |
1853 | +get(key, val) |
1854 | + returns val or raises a KeyError |
1855 | +delete(key) |
1856 | + deletes or raises a KeyError |
1857 | +''' |
1858 | +import time |
1859 | + |
1860 | +class Error(Exception): |
1861 | + pass |
1862 | + |
1863 | +class AbstractCacheStore(object): |
1864 | + |
1865 | + def set(self, key, val, time=None): |
1866 | + raise NotImplementedError |
1867 | + |
1868 | + def add(self, key, val, time=None): |
1869 | + raise NotImplementedError |
1870 | + |
1871 | + def replace(self, key, val, time=None): |
1872 | + raise NotImplementedError |
1873 | + |
1874 | + def delete(self, key): |
1875 | + raise NotImplementedError |
1876 | + |
1877 | + def get(self, key): |
1878 | + raise NotImplementedError |
1879 | + |
1880 | +class MemoryCacheStore(AbstractCacheStore): |
1881 | + def __init__(self): |
1882 | + self._data = {} |
1883 | + |
1884 | + def set(self, key, val, time=0): |
1885 | + self._data[key] = (val, time) |
1886 | + |
1887 | + def add(self, key, val, time=0): |
1888 | + if key in self._data: |
1889 | + raise Error('a value for key %r is already in the cache'%key) |
1890 | + self._data[key] = (val, time) |
1891 | + |
1892 | + def replace(self, key, val, time=0): |
1893 | + if key in self._data: |
1894 | + raise Error('a value for key %r is already in the cache'%key) |
1895 | + self._data[key] = (val, time) |
1896 | + |
1897 | + def delete(self, key): |
1898 | + del self._data[key] |
1899 | + |
1900 | + def get(self, key): |
1901 | + (val, exptime) = self._data[key] |
1902 | + if exptime and time.time() > exptime: |
1903 | + del self._data[key] |
1904 | + raise KeyError(key) |
1905 | + else: |
1906 | + return val |
1907 | + |
1908 | + def clear(self): |
1909 | + self._data.clear() |
1910 | + |
1911 | +class MemcachedCacheStore(AbstractCacheStore): |
1912 | + servers = ('127.0.0.1:11211') |
1913 | + def __init__(self, servers=None, debug=False): |
1914 | + if servers is None: |
1915 | + servers = self.servers |
1916 | + from memcache import Client as MemcachedClient |
1917 | + self._client = MemcachedClient(servers, debug) |
1918 | + |
1919 | + def set(self, key, val, time=0): |
1920 | + self._client.set(key, val, time) |
1921 | + |
1922 | + def add(self, key, val, time=0): |
1923 | + res = self._client.add(key, val, time) |
1924 | + if not res: |
1925 | + raise Error('a value for key %r is already in the cache'%key) |
1926 | + self._data[key] = (val, time) |
1927 | + |
1928 | + def replace(self, key, val, time=0): |
1929 | + res = self._client.replace(key, val, time) |
1930 | + if not res: |
1931 | + raise Error('a value for key %r is already in the cache'%key) |
1932 | + self._data[key] = (val, time) |
1933 | + |
1934 | + def delete(self, key): |
1935 | + res = self._client.delete(key, time=0) |
1936 | + if not res: |
1937 | + raise KeyError(key) |
1938 | + |
1939 | + def get(self, key): |
1940 | + val = self._client.get(key) |
1941 | + if val is None: |
1942 | + raise KeyError(key) |
1943 | + else: |
1944 | + return val |
1945 | + |
1946 | + def clear(self): |
1947 | + self._client.flush_all() |
1948 | |
1949 | === added file 'cheetah/CheetahWrapper.py' |
1950 | --- cheetah/CheetahWrapper.py 1970-01-01 00:00:00 +0000 |
1951 | +++ cheetah/CheetahWrapper.py 2010-10-19 20:12:51 +0000 |
1952 | @@ -0,0 +1,632 @@ |
1953 | +# $Id: CheetahWrapper.py,v 1.26 2007/10/02 01:22:04 tavis_rudd Exp $ |
1954 | +"""Cheetah command-line interface. |
1955 | + |
1956 | +2002-09-03 MSO: Total rewrite. |
1957 | +2002-09-04 MSO: Bugfix, compile command was using wrong output ext. |
1958 | +2002-11-08 MSO: Another rewrite. |
1959 | + |
1960 | +Meta-Data |
1961 | +================================================================================ |
1962 | +Author: Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>> |
1963 | +Version: $Revision: 1.26 $ |
1964 | +Start Date: 2001/03/30 |
1965 | +Last Revision Date: $Date: 2007/10/02 01:22:04 $ |
1966 | +""" |
1967 | +__author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>" |
1968 | +__revision__ = "$Revision: 1.26 $"[11:-2] |
1969 | + |
1970 | +import getopt, glob, os, pprint, re, shutil, sys |
1971 | +import cPickle as pickle |
1972 | +from optparse import OptionParser |
1973 | + |
1974 | +from Cheetah.Version import Version |
1975 | +from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS |
1976 | +from Cheetah.Utils.Misc import mkdirsWithPyInitFiles |
1977 | + |
1978 | +optionDashesRE = re.compile( R"^-{1,2}" ) |
1979 | +moduleNameRE = re.compile( R"^[a-zA-Z_][a-zA-Z_0-9]*$" ) |
1980 | + |
1981 | +def fprintfMessage(stream, format, *args): |
1982 | + if format[-1:] == '^': |
1983 | + format = format[:-1] |
1984 | + else: |
1985 | + format += '\n' |
1986 | + if args: |
1987 | + message = format % args |
1988 | + else: |
1989 | + message = format |
1990 | + stream.write(message) |
1991 | + |
1992 | +class Error(Exception): |
1993 | + pass |
1994 | + |
1995 | + |
1996 | +class Bundle: |
1997 | + """Wrap the source, destination and backup paths in one neat little class. |
1998 | + Used by CheetahWrapper.getBundles(). |
1999 | + """ |
2000 | + def __init__(self, **kw): |
2001 | + self.__dict__.update(kw) |
2002 | + |
2003 | + def __repr__(self): |
2004 | + return "<Bundle %r>" % self.__dict__ |
2005 | + |
2006 | + |
2007 | +################################################## |
2008 | +## USAGE FUNCTION & MESSAGES |
2009 | + |
2010 | +def usage(usageMessage, errorMessage="", out=sys.stderr): |
2011 | + """Write help text, an optional error message, and abort the program. |
2012 | + """ |
2013 | + out.write(WRAPPER_TOP) |
2014 | + out.write(usageMessage) |
2015 | + exitStatus = 0 |
2016 | + if errorMessage: |
2017 | + out.write('\n') |
2018 | + out.write("*** USAGE ERROR ***: %s\n" % errorMessage) |
2019 | + exitStatus = 1 |
2020 | + sys.exit(exitStatus) |
2021 | + |
2022 | + |
2023 | +WRAPPER_TOP = """\ |
2024 | + __ ____________ __ |
2025 | + \ \/ \/ / |
2026 | + \/ * * \/ CHEETAH %(Version)s Command-Line Tool |
2027 | + \ | / |
2028 | + \ ==----== / by Tavis Rudd <tavis@damnsimple.com> |
2029 | + \__________/ and Mike Orr <sluggoster@gmail.com> |
2030 | + |
2031 | +""" % globals() |
2032 | + |
2033 | + |
2034 | +HELP_PAGE1 = """\ |
2035 | +USAGE: |
2036 | +------ |
2037 | + cheetah compile [options] [FILES ...] : Compile template definitions |
2038 | + cheetah fill [options] [FILES ...] : Fill template definitions |
2039 | + cheetah help : Print this help message |
2040 | + cheetah options : Print options help message |
2041 | + cheetah test [options] : Run Cheetah's regression tests |
2042 | + : (same as for unittest) |
2043 | + cheetah version : Print Cheetah version number |
2044 | + |
2045 | +You may abbreviate the command to the first letter; e.g., 'h' == 'help'. |
2046 | +If FILES is a single "-", read standard input and write standard output. |
2047 | +Run "cheetah options" for the list of valid options. |
2048 | +""" |
2049 | + |
2050 | +################################################## |
2051 | +## CheetahWrapper CLASS |
2052 | + |
2053 | +class CheetahWrapper(object): |
2054 | + MAKE_BACKUPS = True |
2055 | + BACKUP_SUFFIX = ".bak" |
2056 | + _templateClass = None |
2057 | + _compilerSettings = None |
2058 | + |
2059 | + def __init__(self): |
2060 | + self.progName = None |
2061 | + self.command = None |
2062 | + self.opts = None |
2063 | + self.pathArgs = None |
2064 | + self.sourceFiles = [] |
2065 | + self.searchList = [] |
2066 | + self.parser = None |
2067 | + |
2068 | + ################################################## |
2069 | + ## MAIN ROUTINE |
2070 | + |
2071 | + def main(self, argv=None): |
2072 | + """The main program controller.""" |
2073 | + |
2074 | + if argv is None: |
2075 | + argv = sys.argv |
2076 | + |
2077 | + # Step 1: Determine the command and arguments. |
2078 | + try: |
2079 | + self.progName = progName = os.path.basename(argv[0]) |
2080 | + self.command = command = optionDashesRE.sub("", argv[1]) |
2081 | + if command == 'test': |
2082 | + self.testOpts = argv[2:] |
2083 | + else: |
2084 | + self.parseOpts(argv[2:]) |
2085 | + except IndexError: |
2086 | + usage(HELP_PAGE1, "not enough command-line arguments") |
2087 | + |
2088 | + # Step 2: Call the command |
2089 | + meths = (self.compile, self.fill, self.help, self.options, |
2090 | + self.test, self.version) |
2091 | + for meth in meths: |
2092 | + methName = meth.__name__ |
2093 | + # Or meth.im_func.func_name |
2094 | + # Or meth.func_name (Python >= 2.1 only, sometimes works on 2.0) |
2095 | + methInitial = methName[0] |
2096 | + if command in (methName, methInitial): |
2097 | + sys.argv[0] += (" " + methName) |
2098 | + # @@MO: I don't necessarily agree sys.argv[0] should be |
2099 | + # modified. |
2100 | + meth() |
2101 | + return |
2102 | + # If none of the commands matched. |
2103 | + usage(HELP_PAGE1, "unknown command '%s'" % command) |
2104 | + |
2105 | + def parseOpts(self, args): |
2106 | + C, D, W = self.chatter, self.debug, self.warn |
2107 | + self.isCompile = isCompile = self.command[0] == 'c' |
2108 | + defaultOext = isCompile and ".py" or ".html" |
2109 | + self.parser = OptionParser() |
2110 | + pao = self.parser.add_option |
2111 | + pao("--idir", action="store", dest="idir", default='', help='Input directory (defaults to current directory)') |
2112 | + pao("--odir", action="store", dest="odir", default="", help='Output directory (defaults to current directory)') |
2113 | + pao("--iext", action="store", dest="iext", default=".tmpl", help='File input extension (defaults: compile: .tmpl, fill: .tmpl)') |
2114 | + pao("--oext", action="store", dest="oext", default=defaultOext, help='File output extension (defaults: compile: .py, fill: .html)') |
2115 | + pao("-R", action="store_true", dest="recurse", default=False, help='Recurse through subdirectories looking for input files') |
2116 | + pao("--stdout", "-p", action="store_true", dest="stdout", default=False, help='Send output to stdout instead of writing to a file') |
2117 | + pao("--quiet", action="store_false", dest="verbose", default=True, help='Do not print informational messages to stdout') |
2118 | + pao("--debug", action="store_true", dest="debug", default=False, help='Print diagnostic/debug information to stderr') |
2119 | + pao("--env", action="store_true", dest="env", default=False, help='Pass the environment into the search list') |
2120 | + pao("--pickle", action="store", dest="pickle", default="", help='Unpickle FILE and pass it through in the search list') |
2121 | + pao("--flat", action="store_true", dest="flat", default=False, help='Do not build destination subdirectories') |
2122 | + pao("--nobackup", action="store_true", dest="nobackup", default=False, help='Do not make backup files when generating new ones') |
2123 | + pao("--settings", action="store", dest="compilerSettingsString", default=None, help='String of compiler settings to pass through, e.g. --settings="useNameMapper=False,useFilters=False"') |
2124 | + pao('--print-settings', action='store_true', dest='print_settings', help='Print out the list of available compiler settings') |
2125 | + pao("--templateAPIClass", action="store", dest="templateClassName", default=None, help='Name of a subclass of Cheetah.Template.Template to use for compilation, e.g. MyTemplateClass') |
2126 | + pao("--parallel", action="store", type="int", dest="parallel", default=1, help='Compile/fill templates in parallel, e.g. --parallel=4') |
2127 | + 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"') |
2128 | + |
2129 | + opts, files = self.parser.parse_args(args) |
2130 | + self.opts = opts |
2131 | + if sys.platform == "win32": |
2132 | + new_files = [] |
2133 | + for spec in files: |
2134 | + file_list = glob.glob(spec) |
2135 | + if file_list: |
2136 | + new_files.extend(file_list) |
2137 | + else: |
2138 | + new_files.append(spec) |
2139 | + files = new_files |
2140 | + self.pathArgs = files |
2141 | + |
2142 | + D("""\ |
2143 | +cheetah compile %s |
2144 | +Options are |
2145 | +%s |
2146 | +Files are %s""", args, pprint.pformat(vars(opts)), files) |
2147 | + |
2148 | + |
2149 | + if opts.print_settings: |
2150 | + print() |
2151 | + print('>> Available Cheetah compiler settings:') |
2152 | + from Cheetah.Compiler import _DEFAULT_COMPILER_SETTINGS |
2153 | + listing = _DEFAULT_COMPILER_SETTINGS |
2154 | + listing.sort(key=lambda l: l[0][0].lower()) |
2155 | + |
2156 | + for l in listing: |
2157 | + print('\t%s (default: "%s")\t%s' % l) |
2158 | + sys.exit(0) |
2159 | + |
2160 | + #cleanup trailing path separators |
2161 | + seps = [sep for sep in [os.sep, os.altsep] if sep] |
2162 | + for attr in ['idir', 'odir']: |
2163 | + for sep in seps: |
2164 | + path = getattr(opts, attr, None) |
2165 | + if path and path.endswith(sep): |
2166 | + path = path[:-len(sep)] |
2167 | + setattr(opts, attr, path) |
2168 | + break |
2169 | + |
2170 | + self._fixExts() |
2171 | + if opts.env: |
2172 | + self.searchList.insert(0, os.environ) |
2173 | + if opts.pickle: |
2174 | + f = open(opts.pickle, 'rb') |
2175 | + unpickled = pickle.load(f) |
2176 | + f.close() |
2177 | + self.searchList.insert(0, unpickled) |
2178 | + |
2179 | + ################################################## |
2180 | + ## COMMAND METHODS |
2181 | + |
2182 | + def compile(self): |
2183 | + self._compileOrFill() |
2184 | + |
2185 | + def fill(self): |
2186 | + from Cheetah.ImportHooks import install |
2187 | + install() |
2188 | + self._compileOrFill() |
2189 | + |
2190 | + def help(self): |
2191 | + usage(HELP_PAGE1, "", sys.stdout) |
2192 | + |
2193 | + def options(self): |
2194 | + return self.parser.print_help() |
2195 | + |
2196 | + def test(self): |
2197 | + # @@MO: Ugly kludge. |
2198 | + TEST_WRITE_FILENAME = 'cheetah_test_file_creation_ability.tmp' |
2199 | + try: |
2200 | + f = open(TEST_WRITE_FILENAME, 'w') |
2201 | + except: |
2202 | + sys.exit("""\ |
2203 | +Cannot run the tests because you don't have write permission in the current |
2204 | +directory. The tests need to create temporary files. Change to a directory |
2205 | +you do have write permission to and re-run the tests.""") |
2206 | + else: |
2207 | + f.close() |
2208 | + os.remove(TEST_WRITE_FILENAME) |
2209 | + # @@MO: End ugly kludge. |
2210 | + from Cheetah.Tests import Test |
2211 | + import unittest |
2212 | + verbosity = 1 |
2213 | + if '-q' in self.testOpts: |
2214 | + verbosity = 0 |
2215 | + if '-v' in self.testOpts: |
2216 | + verbosity = 2 |
2217 | + runner = unittest.TextTestRunner(verbosity=verbosity) |
2218 | + runner.run(unittest.TestSuite(Test.suites)) |
2219 | + |
2220 | + def version(self): |
2221 | + print(Version) |
2222 | + |
2223 | + # If you add a command, also add it to the 'meths' variable in main(). |
2224 | + |
2225 | + ################################################## |
2226 | + ## LOGGING METHODS |
2227 | + |
2228 | + def chatter(self, format, *args): |
2229 | + """Print a verbose message to stdout. But don't if .opts.stdout is |
2230 | + true or .opts.verbose is false. |
2231 | + """ |
2232 | + if self.opts.stdout or not self.opts.verbose: |
2233 | + return |
2234 | + fprintfMessage(sys.stdout, format, *args) |
2235 | + |
2236 | + |
2237 | + def debug(self, format, *args): |
2238 | + """Print a debugging message to stderr, but don't if .debug is |
2239 | + false. |
2240 | + """ |
2241 | + if self.opts.debug: |
2242 | + fprintfMessage(sys.stderr, format, *args) |
2243 | + |
2244 | + def warn(self, format, *args): |
2245 | + """Always print a warning message to stderr. |
2246 | + """ |
2247 | + fprintfMessage(sys.stderr, format, *args) |
2248 | + |
2249 | + def error(self, format, *args): |
2250 | + """Always print a warning message to stderr and exit with an error code. |
2251 | + """ |
2252 | + fprintfMessage(sys.stderr, format, *args) |
2253 | + sys.exit(1) |
2254 | + |
2255 | + ################################################## |
2256 | + ## HELPER METHODS |
2257 | + |
2258 | + |
2259 | + def _fixExts(self): |
2260 | + assert self.opts.oext, "oext is empty!" |
2261 | + iext, oext = self.opts.iext, self.opts.oext |
2262 | + if iext and not iext.startswith("."): |
2263 | + self.opts.iext = "." + iext |
2264 | + if oext and not oext.startswith("."): |
2265 | + self.opts.oext = "." + oext |
2266 | + |
2267 | + |
2268 | + |
2269 | + def _compileOrFill(self): |
2270 | + C, D, W = self.chatter, self.debug, self.warn |
2271 | + opts, files = self.opts, self.pathArgs |
2272 | + if files == ["-"]: |
2273 | + self._compileOrFillStdin() |
2274 | + return |
2275 | + elif not files and opts.recurse: |
2276 | + which = opts.idir and "idir" or "current" |
2277 | + C("Drilling down recursively from %s directory.", which) |
2278 | + sourceFiles = [] |
2279 | + dir = os.path.join(self.opts.idir, os.curdir) |
2280 | + os.path.walk(dir, self._expandSourceFilesWalk, sourceFiles) |
2281 | + elif not files: |
2282 | + usage(HELP_PAGE1, "Neither files nor -R specified!") |
2283 | + else: |
2284 | + sourceFiles = self._expandSourceFiles(files, opts.recurse, True) |
2285 | + sourceFiles = [os.path.normpath(x) for x in sourceFiles] |
2286 | + D("All source files found: %s", sourceFiles) |
2287 | + bundles = self._getBundles(sourceFiles) |
2288 | + D("All bundles: %s", pprint.pformat(bundles)) |
2289 | + if self.opts.flat: |
2290 | + self._checkForCollisions(bundles) |
2291 | + |
2292 | + # In parallel mode a new process is forked for each template |
2293 | + # compilation, out of a pool of size self.opts.parallel. This is not |
2294 | + # really optimal in all cases (e.g. probably wasteful for small |
2295 | + # templates), but seems to work well in real life for me. |
2296 | + # |
2297 | + # It also won't work for Windows users, but I'm not going to lose any |
2298 | + # sleep over that. |
2299 | + if self.opts.parallel > 1: |
2300 | + bad_child_exit = 0 |
2301 | + pid_pool = set() |
2302 | + |
2303 | + def child_wait(): |
2304 | + pid, status = os.wait() |
2305 | + pid_pool.remove(pid) |
2306 | + return os.WEXITSTATUS(status) |
2307 | + |
2308 | + while bundles: |
2309 | + b = bundles.pop() |
2310 | + pid = os.fork() |
2311 | + if pid: |
2312 | + pid_pool.add(pid) |
2313 | + else: |
2314 | + self._compileOrFillBundle(b) |
2315 | + sys.exit(0) |
2316 | + |
2317 | + if len(pid_pool) == self.opts.parallel: |
2318 | + bad_child_exit = child_wait() |
2319 | + if bad_child_exit: |
2320 | + break |
2321 | + |
2322 | + while pid_pool: |
2323 | + child_exit = child_wait() |
2324 | + if not bad_child_exit: |
2325 | + bad_child_exit = child_exit |
2326 | + |
2327 | + if bad_child_exit: |
2328 | + sys.exit("Child process failed, exited with code %d" % bad_child_exit) |
2329 | + |
2330 | + else: |
2331 | + for b in bundles: |
2332 | + self._compileOrFillBundle(b) |
2333 | + |
2334 | + def _checkForCollisions(self, bundles): |
2335 | + """Check for multiple source paths writing to the same destination |
2336 | + path. |
2337 | + """ |
2338 | + C, D, W = self.chatter, self.debug, self.warn |
2339 | + isError = False |
2340 | + dstSources = {} |
2341 | + for b in bundles: |
2342 | + if b.dst in dstSources: |
2343 | + dstSources[b.dst].append(b.src) |
2344 | + else: |
2345 | + dstSources[b.dst] = [b.src] |
2346 | + keys = sorted(dstSources.keys()) |
2347 | + for dst in keys: |
2348 | + sources = dstSources[dst] |
2349 | + if len(sources) > 1: |
2350 | + isError = True |
2351 | + sources.sort() |
2352 | + fmt = "Collision: multiple source files %s map to one destination file %s" |
2353 | + W(fmt, sources, dst) |
2354 | + if isError: |
2355 | + what = self.isCompile and "Compilation" or "Filling" |
2356 | + sys.exit("%s aborted due to collisions" % what) |
2357 | + |
2358 | + |
2359 | + def _expandSourceFilesWalk(self, arg, dir, files): |
2360 | + """Recursion extension for .expandSourceFiles(). |
2361 | + This method is a callback for os.path.walk(). |
2362 | + 'arg' is a list to which successful paths will be appended. |
2363 | + """ |
2364 | + iext = self.opts.iext |
2365 | + for f in files: |
2366 | + path = os.path.join(dir, f) |
2367 | + if path.endswith(iext) and os.path.isfile(path): |
2368 | + arg.append(path) |
2369 | + elif os.path.islink(path) and os.path.isdir(path): |
2370 | + os.path.walk(path, self._expandSourceFilesWalk, arg) |
2371 | + # If is directory, do nothing; 'walk' will eventually get it. |
2372 | + |
2373 | + |
2374 | + def _expandSourceFiles(self, files, recurse, addIextIfMissing): |
2375 | + """Calculate source paths from 'files' by applying the |
2376 | + command-line options. |
2377 | + """ |
2378 | + C, D, W = self.chatter, self.debug, self.warn |
2379 | + idir = self.opts.idir |
2380 | + iext = self.opts.iext |
2381 | + files = [] |
2382 | + for f in self.pathArgs: |
2383 | + oldFilesLen = len(files) |
2384 | + D("Expanding %s", f) |
2385 | + path = os.path.join(idir, f) |
2386 | + pathWithExt = path + iext # May or may not be valid. |
2387 | + if os.path.isdir(path): |
2388 | + if recurse: |
2389 | + os.path.walk(path, self._expandSourceFilesWalk, files) |
2390 | + else: |
2391 | + raise Error("source file '%s' is a directory" % path) |
2392 | + elif os.path.isfile(path): |
2393 | + files.append(path) |
2394 | + elif (addIextIfMissing and not path.endswith(iext) and |
2395 | + os.path.isfile(pathWithExt)): |
2396 | + files.append(pathWithExt) |
2397 | + # Do not recurse directories discovered by iext appending. |
2398 | + elif os.path.exists(path): |
2399 | + W("Skipping source file '%s', not a plain file.", path) |
2400 | + else: |
2401 | + W("Skipping source file '%s', not found.", path) |
2402 | + if len(files) > oldFilesLen: |
2403 | + D(" ... found %s", files[oldFilesLen:]) |
2404 | + return files |
2405 | + |
2406 | + |
2407 | + def _getBundles(self, sourceFiles): |
2408 | + flat = self.opts.flat |
2409 | + idir = self.opts.idir |
2410 | + iext = self.opts.iext |
2411 | + nobackup = self.opts.nobackup |
2412 | + odir = self.opts.odir |
2413 | + oext = self.opts.oext |
2414 | + idirSlash = idir + os.sep |
2415 | + bundles = [] |
2416 | + for src in sourceFiles: |
2417 | + # 'base' is the subdirectory plus basename. |
2418 | + base = src |
2419 | + if idir and src.startswith(idirSlash): |
2420 | + base = src[len(idirSlash):] |
2421 | + if iext and base.endswith(iext): |
2422 | + base = base[:-len(iext)] |
2423 | + basename = os.path.basename(base) |
2424 | + if flat: |
2425 | + dst = os.path.join(odir, basename + oext) |
2426 | + else: |
2427 | + dbn = basename |
2428 | + if odir and base.startswith(os.sep): |
2429 | + odd = odir |
2430 | + while odd != '': |
2431 | + idx = base.find(odd) |
2432 | + if idx == 0: |
2433 | + dbn = base[len(odd):] |
2434 | + if dbn[0] == '/': |
2435 | + dbn = dbn[1:] |
2436 | + break |
2437 | + odd = os.path.dirname(odd) |
2438 | + if odd == '/': |
2439 | + break |
2440 | + dst = os.path.join(odir, dbn + oext) |
2441 | + else: |
2442 | + dst = os.path.join(odir, base + oext) |
2443 | + bak = dst + self.BACKUP_SUFFIX |
2444 | + b = Bundle(src=src, dst=dst, bak=bak, base=base, basename=basename) |
2445 | + bundles.append(b) |
2446 | + return bundles |
2447 | + |
2448 | + |
2449 | + def _getTemplateClass(self): |
2450 | + C, D, W = self.chatter, self.debug, self.warn |
2451 | + modname = None |
2452 | + if self._templateClass: |
2453 | + return self._templateClass |
2454 | + |
2455 | + modname = self.opts.templateClassName |
2456 | + |
2457 | + if not modname: |
2458 | + return Template |
2459 | + p = modname.rfind('.') |
2460 | + if ':' not in modname: |
2461 | + self.error('The value of option --templateAPIClass is invalid\n' |
2462 | + 'It must be in the form "module:class", ' |
2463 | + 'e.g. "Cheetah.Template:Template"') |
2464 | + |
2465 | + modname, classname = modname.split(':') |
2466 | + |
2467 | + C('using --templateAPIClass=%s:%s'%(modname, classname)) |
2468 | + |
2469 | + if p >= 0: |
2470 | + mod = getattr(__import__(modname[:p], {}, {}, [modname[p+1:]]), modname[p+1:]) |
2471 | + else: |
2472 | + mod = __import__(modname, {}, {}, []) |
2473 | + |
2474 | + klass = getattr(mod, classname, None) |
2475 | + if klass: |
2476 | + self._templateClass = klass |
2477 | + return klass |
2478 | + else: |
2479 | + self.error('**Template class specified in option --templateAPIClass not found\n' |
2480 | + '**Falling back on Cheetah.Template:Template') |
2481 | + |
2482 | + |
2483 | + def _getCompilerSettings(self): |
2484 | + if self._compilerSettings: |
2485 | + return self._compilerSettings |
2486 | + |
2487 | + def getkws(**kws): |
2488 | + return kws |
2489 | + if self.opts.compilerSettingsString: |
2490 | + try: |
2491 | + exec('settings = getkws(%s)'%self.opts.compilerSettingsString) |
2492 | + except: |
2493 | + self.error("There's an error in your --settings option." |
2494 | + "It must be valid Python syntax.\n" |
2495 | + +" --settings='%s'\n"%self.opts.compilerSettingsString |
2496 | + +" %s: %s"%sys.exc_info()[:2] |
2497 | + ) |
2498 | + |
2499 | + validKeys = DEFAULT_COMPILER_SETTINGS.keys() |
2500 | + if [k for k in settings.keys() if k not in validKeys]: |
2501 | + self.error( |
2502 | + 'The --setting "%s" is not a valid compiler setting name.'%k) |
2503 | + |
2504 | + self._compilerSettings = settings |
2505 | + return settings |
2506 | + else: |
2507 | + return {} |
2508 | + |
2509 | + def _compileOrFillStdin(self): |
2510 | + TemplateClass = self._getTemplateClass() |
2511 | + compilerSettings = self._getCompilerSettings() |
2512 | + if self.isCompile: |
2513 | + pysrc = TemplateClass.compile(file=sys.stdin, |
2514 | + compilerSettings=compilerSettings, |
2515 | + returnAClass=False) |
2516 | + output = pysrc |
2517 | + else: |
2518 | + output = str(TemplateClass(file=sys.stdin, compilerSettings=compilerSettings)) |
2519 | + sys.stdout.write(output) |
2520 | + |
2521 | + def _compileOrFillBundle(self, b): |
2522 | + C, D, W = self.chatter, self.debug, self.warn |
2523 | + TemplateClass = self._getTemplateClass() |
2524 | + compilerSettings = self._getCompilerSettings() |
2525 | + src = b.src |
2526 | + dst = b.dst |
2527 | + base = b.base |
2528 | + basename = b.basename |
2529 | + dstDir = os.path.dirname(dst) |
2530 | + what = self.isCompile and "Compiling" or "Filling" |
2531 | + C("%s %s -> %s^", what, src, dst) # No trailing newline. |
2532 | + if os.path.exists(dst) and not self.opts.nobackup: |
2533 | + bak = b.bak |
2534 | + C(" (backup %s)", bak) # On same line as previous message. |
2535 | + else: |
2536 | + bak = None |
2537 | + C("") |
2538 | + if self.isCompile: |
2539 | + if not moduleNameRE.match(basename): |
2540 | + tup = basename, src |
2541 | + raise Error("""\ |
2542 | +%s: base name %s contains invalid characters. It must |
2543 | +be named according to the same rules as Python modules.""" % tup) |
2544 | + pysrc = TemplateClass.compile(file=src, returnAClass=False, |
2545 | + moduleName=basename, |
2546 | + className=basename, |
2547 | + commandlineopts=self.opts, |
2548 | + compilerSettings=compilerSettings) |
2549 | + output = pysrc |
2550 | + else: |
2551 | + #output = str(TemplateClass(file=src, searchList=self.searchList)) |
2552 | + tclass = TemplateClass.compile(file=src, compilerSettings=compilerSettings) |
2553 | + output = str(tclass(searchList=self.searchList)) |
2554 | + |
2555 | + if bak: |
2556 | + shutil.copyfile(dst, bak) |
2557 | + if dstDir and not os.path.exists(dstDir): |
2558 | + if self.isCompile: |
2559 | + mkdirsWithPyInitFiles(dstDir) |
2560 | + else: |
2561 | + os.makedirs(dstDir) |
2562 | + if self.opts.stdout: |
2563 | + sys.stdout.write(output) |
2564 | + else: |
2565 | + f = open(dst, 'w') |
2566 | + f.write(output) |
2567 | + f.close() |
2568 | + |
2569 | + |
2570 | +# Called when invoked as `cheetah` |
2571 | +def _cheetah(): |
2572 | + CheetahWrapper().main() |
2573 | + |
2574 | +# Called when invoked as `cheetah-compile` |
2575 | +def _cheetah_compile(): |
2576 | + sys.argv.insert(1, "compile") |
2577 | + CheetahWrapper().main() |
2578 | + |
2579 | + |
2580 | +################################################## |
2581 | +## if run from the command line |
2582 | +if __name__ == '__main__': CheetahWrapper().main() |
2583 | + |
2584 | +# vim: shiftwidth=4 tabstop=4 expandtab |
2585 | |
2586 | === added file 'cheetah/Compiler.py' |
2587 | --- cheetah/Compiler.py 1970-01-01 00:00:00 +0000 |
2588 | +++ cheetah/Compiler.py 2010-10-19 20:12:51 +0000 |
2589 | @@ -0,0 +1,2002 @@ |
2590 | +''' |
2591 | + Compiler classes for Cheetah: |
2592 | + ModuleCompiler aka 'Compiler' |
2593 | + ClassCompiler |
2594 | + MethodCompiler |
2595 | + |
2596 | + If you are trying to grok this code start with ModuleCompiler.__init__, |
2597 | + ModuleCompiler.compile, and ModuleCompiler.__getattr__. |
2598 | +''' |
2599 | + |
2600 | +import sys |
2601 | +import os |
2602 | +import os.path |
2603 | +from os.path import getmtime, exists |
2604 | +import re |
2605 | +import types |
2606 | +import time |
2607 | +import random |
2608 | +import warnings |
2609 | +import copy |
2610 | + |
2611 | +from Cheetah.Version import Version, VersionTuple |
2612 | +from Cheetah.SettingsManager import SettingsManager |
2613 | +from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor |
2614 | +from Cheetah import ErrorCatchers |
2615 | +from Cheetah import NameMapper |
2616 | +from Cheetah.Parser import Parser, ParseError, specialVarRE, \ |
2617 | + STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL, SET_MODULE, \ |
2618 | + unicodeDirectiveRE, encodingDirectiveRE, escapedNewlineRE |
2619 | + |
2620 | +from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList |
2621 | +VFFSL=valueFromFrameOrSearchList |
2622 | +VFSL=valueFromSearchList |
2623 | +VFN=valueForName |
2624 | +currentTime=time.time |
2625 | + |
2626 | +class Error(Exception): pass |
2627 | + |
2628 | +# Settings format: (key, default, docstring) |
2629 | +_DEFAULT_COMPILER_SETTINGS = [ |
2630 | + ('useNameMapper', True, 'Enable NameMapper for dotted notation and searchList support'), |
2631 | + ('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'), |
2632 | + ('allowSearchListAsMethArg', True, ''), |
2633 | + ('useAutocalling', True, 'Detect and call callable objects in searchList, requires useNameMapper=True'), |
2634 | + ('useStackFrames', True, 'Used for NameMapper.valueFromFrameOrSearchList rather than NameMapper.valueFromSearchList'), |
2635 | + ('useErrorCatcher', False, 'Turn on the #errorCatcher directive for catching NameMapper errors, etc'), |
2636 | + ('alwaysFilterNone', True, 'Filter out None prior to calling the #filter'), |
2637 | + ('useFilters', True, 'If False, pass output through str()'), |
2638 | + ('includeRawExprInFilterArgs', True, ''), |
2639 | + ('useLegacyImportMode', True, 'All #import statements are relocated to the top of the generated Python module'), |
2640 | + ('prioritizeSearchListOverSelf', False, 'When iterating the searchList, look into the searchList passed into the initializer instead of Template members first'), |
2641 | + |
2642 | + ('autoAssignDummyTransactionToSelf', False, ''), |
2643 | + ('useKWsDictArgForPassingTrans', True, ''), |
2644 | + |
2645 | + ('commentOffset', 1, ''), |
2646 | + ('outputRowColComments', True, ''), |
2647 | + ('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'), |
2648 | + ('blockMarkerStart', ('\n<!-- START BLOCK: ', ' -->\n'), ''), |
2649 | + ('blockMarkerEnd', ('\n<!-- END BLOCK: ', ' -->\n'), ''), |
2650 | + ('defDocStrMsg', 'Autogenerated by Cheetah: The Python-Powered Template Engine', ''), |
2651 | + ('setup__str__method', False, ''), |
2652 | + ('mainMethodName', 'respond', ''), |
2653 | + ('mainMethodNameForSubclasses', 'writeBody', ''), |
2654 | + ('indentationStep', ' ' * 4, ''), |
2655 | + ('initialMethIndentLevel', 2, ''), |
2656 | + ('monitorSrcFile', False, ''), |
2657 | + ('outputMethodsBeforeAttributes', True, ''), |
2658 | + ('addTimestampsToCompilerOutput', True, ''), |
2659 | + |
2660 | + ## Customizing the #extends directive |
2661 | + ('autoImportForExtendsDirective', True, ''), |
2662 | + ('handlerForExtendsDirective', None, ''), |
2663 | + |
2664 | + ('disabledDirectives', [], 'List of directive keys to disable (without starting "#")'), |
2665 | + ('enabledDirectives', [], 'List of directive keys to enable (without starting "#")'), |
2666 | + ('disabledDirectiveHooks', [], 'callable(parser, directiveKey)'), |
2667 | + ('preparseDirectiveHooks', [], 'callable(parser, directiveKey)'), |
2668 | + ('postparseDirectiveHooks', [], 'callable(parser, directiveKey)'), |
2669 | + ('preparsePlaceholderHooks', [], 'callable(parser)'), |
2670 | + ('postparsePlaceholderHooks', [], 'callable(parser)'), |
2671 | + ('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'''), |
2672 | + ('templateMetaclass', None, 'Strictly optional, only will work with new-style basecalsses as well'), |
2673 | + ('i18NFunctionName', 'self.i18n', ''), |
2674 | + |
2675 | + ('cheetahVarStartToken', '$', ''), |
2676 | + ('commentStartToken', '##', ''), |
2677 | + ('multiLineCommentStartToken', '#*', ''), |
2678 | + ('multiLineCommentEndToken', '*#', ''), |
2679 | + ('gobbleWhitespaceAroundMultiLineComments', True, ''), |
2680 | + ('directiveStartToken', '#', ''), |
2681 | + ('directiveEndToken', '#', ''), |
2682 | + ('allowWhitespaceAfterDirectiveStartToken', False, ''), |
2683 | + ('PSPStartToken', '<%', ''), |
2684 | + ('PSPEndToken', '%>', ''), |
2685 | + ('EOLSlurpToken', '#', ''), |
2686 | + ('gettextTokens', ["_", "N_", "ngettext"], ''), |
2687 | + ('allowExpressionsInExtendsDirective', False, ''), |
2688 | + ('allowEmptySingleLineMethods', False, ''), |
2689 | + ('allowNestedDefScopes', True, ''), |
2690 | + ('allowPlaceholderFilterArgs', True, ''), |
2691 | +] |
2692 | + |
2693 | +DEFAULT_COMPILER_SETTINGS = dict([(v[0], v[1]) for v in _DEFAULT_COMPILER_SETTINGS]) |
2694 | + |
2695 | + |
2696 | + |
2697 | +class GenUtils(object): |
2698 | + """An abstract baseclass for the Compiler classes that provides methods that |
2699 | + perform generic utility functions or generate pieces of output code from |
2700 | + information passed in by the Parser baseclass. These methods don't do any |
2701 | + parsing themselves. |
2702 | + """ |
2703 | + |
2704 | + def genTimeInterval(self, timeString): |
2705 | + ##@@ TR: need to add some error handling here |
2706 | + if timeString[-1] == 's': |
2707 | + interval = float(timeString[:-1]) |
2708 | + elif timeString[-1] == 'm': |
2709 | + interval = float(timeString[:-1])*60 |
2710 | + elif timeString[-1] == 'h': |
2711 | + interval = float(timeString[:-1])*60*60 |
2712 | + elif timeString[-1] == 'd': |
2713 | + interval = float(timeString[:-1])*60*60*24 |
2714 | + elif timeString[-1] == 'w': |
2715 | + interval = float(timeString[:-1])*60*60*24*7 |
2716 | + else: # default to minutes |
2717 | + interval = float(timeString)*60 |
2718 | + return interval |
2719 | + |
2720 | + def genCacheInfo(self, cacheTokenParts): |
2721 | + """Decipher a placeholder cachetoken |
2722 | + """ |
2723 | + cacheInfo = {} |
2724 | + if cacheTokenParts['REFRESH_CACHE']: |
2725 | + cacheInfo['type'] = REFRESH_CACHE |
2726 | + cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval']) |
2727 | + elif cacheTokenParts['STATIC_CACHE']: |
2728 | + cacheInfo['type'] = STATIC_CACHE |
2729 | + return cacheInfo # is empty if no cache |
2730 | + |
2731 | + def genCacheInfoFromArgList(self, argList): |
2732 | + cacheInfo = {'type':REFRESH_CACHE} |
2733 | + for key, val in argList: |
2734 | + if val[0] in '"\'': |
2735 | + val = val[1:-1] |
2736 | + |
2737 | + if key == 'timer': |
2738 | + key = 'interval' |
2739 | + val = self.genTimeInterval(val) |
2740 | + |
2741 | + cacheInfo[key] = val |
2742 | + return cacheInfo |
2743 | + |
2744 | + def genCheetahVar(self, nameChunks, plain=False): |
2745 | + if nameChunks[0][0] in self.setting('gettextTokens'): |
2746 | + self.addGetTextVar(nameChunks) |
2747 | + if self.setting('useNameMapper') and not plain: |
2748 | + return self.genNameMapperVar(nameChunks) |
2749 | + else: |
2750 | + return self.genPlainVar(nameChunks) |
2751 | + |
2752 | + def addGetTextVar(self, nameChunks): |
2753 | + """Output something that gettext can recognize. |
2754 | + |
2755 | + This is a harmless side effect necessary to make gettext work when it |
2756 | + is scanning compiled templates for strings marked for translation. |
2757 | + |
2758 | + @@TR: another marginally more efficient approach would be to put the |
2759 | + output in a dummy method that is never called. |
2760 | + """ |
2761 | + # @@TR: this should be in the compiler not here |
2762 | + self.addChunk("if False:") |
2763 | + self.indent() |
2764 | + self.addChunk(self.genPlainVar(nameChunks[:])) |
2765 | + self.dedent() |
2766 | + |
2767 | + def genPlainVar(self, nameChunks): |
2768 | + """Generate Python code for a Cheetah $var without using NameMapper |
2769 | + (Unified Dotted Notation with the SearchList). |
2770 | + """ |
2771 | + nameChunks.reverse() |
2772 | + chunk = nameChunks.pop() |
2773 | + pythonCode = chunk[0] + chunk[2] |
2774 | + while nameChunks: |
2775 | + chunk = nameChunks.pop() |
2776 | + pythonCode = (pythonCode + '.' + chunk[0] + chunk[2]) |
2777 | + return pythonCode |
2778 | + |
2779 | + def genNameMapperVar(self, nameChunks): |
2780 | + """Generate valid Python code for a Cheetah $var, using NameMapper |
2781 | + (Unified Dotted Notation with the SearchList). |
2782 | + |
2783 | + nameChunks = list of var subcomponents represented as tuples |
2784 | + [ (name,useAC,remainderOfExpr), |
2785 | + ] |
2786 | + where: |
2787 | + name = the dotted name base |
2788 | + useAC = where NameMapper should use autocalling on namemapperPart |
2789 | + remainderOfExpr = any arglist, index, or slice |
2790 | + |
2791 | + If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC |
2792 | + is False, otherwise it defaults to True. It is overridden by the global |
2793 | + setting 'useAutocalling' if this setting is False. |
2794 | + |
2795 | + EXAMPLE |
2796 | + ------------------------------------------------------------------------ |
2797 | + if the raw Cheetah Var is |
2798 | + $a.b.c[1].d().x.y.z |
2799 | + |
2800 | + nameChunks is the list |
2801 | + [ ('a.b.c',True,'[1]'), # A |
2802 | + ('d',False,'()'), # B |
2803 | + ('x.y.z',True,''), # C |
2804 | + ] |
2805 | + |
2806 | + When this method is fed the list above it returns |
2807 | + VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True) |
2808 | + which can be represented as |
2809 | + VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2] |
2810 | + where: |
2811 | + VFN = NameMapper.valueForName |
2812 | + VFFSL = NameMapper.valueFromFrameOrSearchList |
2813 | + VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL |
2814 | + SL = self.searchList() |
2815 | + useAC = self.setting('useAutocalling') # True in this example |
2816 | + |
2817 | + A = ('a.b.c',True,'[1]') |
2818 | + B = ('d',False,'()') |
2819 | + C = ('x.y.z',True,'') |
2820 | + |
2821 | + C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1], |
2822 | + 'd',False)(), |
2823 | + 'x.y.z',True) |
2824 | + = VFN(B`, name='x.y.z', executeCallables=True) |
2825 | + |
2826 | + B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2] |
2827 | + A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2] |
2828 | + |
2829 | + |
2830 | + Note, if the compiler setting useStackFrames=False (default is true) |
2831 | + then |
2832 | + A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2] |
2833 | + This option allows Cheetah to be used with Psyco, which doesn't support |
2834 | + stack frame introspection. |
2835 | + """ |
2836 | + defaultUseAC = self.setting('useAutocalling') |
2837 | + useSearchList = self.setting('useSearchList') |
2838 | + |
2839 | + nameChunks.reverse() |
2840 | + name, useAC, remainder = nameChunks.pop() |
2841 | + |
2842 | + if not useSearchList: |
2843 | + firstDotIdx = name.find('.') |
2844 | + if firstDotIdx != -1 and firstDotIdx < len(name): |
2845 | + beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:] |
2846 | + pythonCode = ('VFN(' + beforeFirstDot + |
2847 | + ',"' + afterDot + |
2848 | + '",' + repr(defaultUseAC and useAC) + ')' |
2849 | + + remainder) |
2850 | + else: |
2851 | + pythonCode = name+remainder |
2852 | + elif self.setting('useStackFrames'): |
2853 | + pythonCode = ('VFFSL(SL,' |
2854 | + '"'+ name + '",' |
2855 | + + repr(defaultUseAC and useAC) + ')' |
2856 | + + remainder) |
2857 | + else: |
2858 | + pythonCode = ('VFSL([locals()]+SL+[globals(), builtin],' |
2859 | + '"'+ name + '",' |
2860 | + + repr(defaultUseAC and useAC) + ')' |
2861 | + + remainder) |
2862 | + ## |
2863 | + while nameChunks: |
2864 | + name, useAC, remainder = nameChunks.pop() |
2865 | + pythonCode = ('VFN(' + pythonCode + |
2866 | + ',"' + name + |
2867 | + '",' + repr(defaultUseAC and useAC) + ')' |
2868 | + + remainder) |
2869 | + return pythonCode |
2870 | + |
2871 | +################################################## |
2872 | +## METHOD COMPILERS |
2873 | + |
2874 | +class MethodCompiler(GenUtils): |
2875 | + def __init__(self, methodName, classCompiler, |
2876 | + initialMethodComment=None, |
2877 | + decorators=None): |
2878 | + self._settingsManager = classCompiler |
2879 | + self._classCompiler = classCompiler |
2880 | + self._moduleCompiler = classCompiler._moduleCompiler |
2881 | + self._methodName = methodName |
2882 | + self._initialMethodComment = initialMethodComment |
2883 | + self._setupState() |
2884 | + self._decorators = decorators or [] |
2885 | + |
2886 | + def setting(self, key): |
2887 | + return self._settingsManager.setting(key) |
2888 | + |
2889 | + def _setupState(self): |
2890 | + self._indent = self.setting('indentationStep') |
2891 | + self._indentLev = self.setting('initialMethIndentLevel') |
2892 | + self._pendingStrConstChunks = [] |
2893 | + self._methodSignature = None |
2894 | + self._methodDef = None |
2895 | + self._docStringLines = [] |
2896 | + self._methodBodyChunks = [] |
2897 | + |
2898 | + self._cacheRegionsStack = [] |
2899 | + self._callRegionsStack = [] |
2900 | + self._captureRegionsStack = [] |
2901 | + self._filterRegionsStack = [] |
2902 | + |
2903 | + self._isErrorCatcherOn = False |
2904 | + |
2905 | + self._hasReturnStatement = False |
2906 | + self._isGenerator = False |
2907 | + |
2908 | + |
2909 | + def cleanupState(self): |
2910 | + """Called by the containing class compiler instance |
2911 | + """ |
2912 | + pass |
2913 | + |
2914 | + def methodName(self): |
2915 | + return self._methodName |
2916 | + |
2917 | + def setMethodName(self, name): |
2918 | + self._methodName = name |
2919 | + |
2920 | + ## methods for managing indentation |
2921 | + |
2922 | + def indentation(self): |
2923 | + return self._indent * self._indentLev |
2924 | + |
2925 | + def indent(self): |
2926 | + self._indentLev +=1 |
2927 | + |
2928 | + def dedent(self): |
2929 | + if self._indentLev: |
2930 | + self._indentLev -=1 |
2931 | + else: |
2932 | + raise Error('Attempt to dedent when the indentLev is 0') |
2933 | + |
2934 | + ## methods for final code wrapping |
2935 | + |
2936 | + def methodDef(self): |
2937 | + if self._methodDef: |
2938 | + return self._methodDef |
2939 | + else: |
2940 | + return self.wrapCode() |
2941 | + |
2942 | + __str__ = methodDef |
2943 | + __unicode__ = methodDef |
2944 | + |
2945 | + def wrapCode(self): |
2946 | + self.commitStrConst() |
2947 | + methodDefChunks = ( |
2948 | + self.methodSignature(), |
2949 | + '\n', |
2950 | + self.docString(), |
2951 | + self.methodBody() ) |
2952 | + methodDef = ''.join(methodDefChunks) |
2953 | + self._methodDef = methodDef |
2954 | + return methodDef |
2955 | + |
2956 | + def methodSignature(self): |
2957 | + return self._indent + self._methodSignature + ':' |
2958 | + |
2959 | + def setMethodSignature(self, signature): |
2960 | + self._methodSignature = signature |
2961 | + |
2962 | + def methodBody(self): |
2963 | + return ''.join( self._methodBodyChunks ) |
2964 | + |
2965 | + def docString(self): |
2966 | + if not self._docStringLines: |
2967 | + return '' |
2968 | + |
2969 | + ind = self._indent*2 |
2970 | + docStr = (ind + '"""\n' + ind + |
2971 | + ('\n' + ind).join([ln.replace('"""', "'''") for ln in self._docStringLines]) + |
2972 | + '\n' + ind + '"""\n') |
2973 | + return docStr |
2974 | + |
2975 | + ## methods for adding code |
2976 | + def addMethDocString(self, line): |
2977 | + self._docStringLines.append(line.replace('%', '%%')) |
2978 | + |
2979 | + def addChunk(self, chunk): |
2980 | + self.commitStrConst() |
2981 | + chunk = "\n" + self.indentation() + chunk |
2982 | + self._methodBodyChunks.append(chunk) |
2983 | + |
2984 | + def appendToPrevChunk(self, appendage): |
2985 | + self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage |
2986 | + |
2987 | + def addWriteChunk(self, chunk): |
2988 | + self.addChunk('write(' + chunk + ')') |
2989 | + |
2990 | + def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None): |
2991 | + if filterArgs is None: |
2992 | + filterArgs = '' |
2993 | + if self.setting('includeRawExprInFilterArgs') and rawExpr: |
2994 | + filterArgs += ', rawExpr=%s'%repr(rawExpr) |
2995 | + |
2996 | + if self.setting('alwaysFilterNone'): |
2997 | + if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1: |
2998 | + self.addChunk("_v = %s # %r"%(chunk, rawExpr)) |
2999 | + if lineCol: |
3000 | + self.appendToPrevChunk(' on line %s, col %s'%lineCol) |
3001 | + else: |
3002 | + self.addChunk("_v = %s"%chunk) |
3003 | + |
3004 | + if self.setting('useFilters'): |
3005 | + self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs) |
3006 | + else: |
3007 | + self.addChunk("if _v is not None: write(str(_v))") |
3008 | + else: |
3009 | + if self.setting('useFilters'): |
3010 | + self.addChunk("write(_filter(%s%s))"%(chunk, filterArgs)) |
3011 | + else: |
3012 | + self.addChunk("write(str(%s))"%chunk) |
3013 | + |
3014 | + def _appendToPrevStrConst(self, strConst): |
3015 | + if self._pendingStrConstChunks: |
3016 | + self._pendingStrConstChunks.append(strConst) |
3017 | + else: |
3018 | + self._pendingStrConstChunks = [strConst] |
3019 | + |
3020 | + def commitStrConst(self): |
3021 | + """Add the code for outputting the pending strConst without chopping off |
3022 | + any whitespace from it. |
3023 | + """ |
3024 | + if not self._pendingStrConstChunks: |
3025 | + return |
3026 | + |
3027 | + strConst = ''.join(self._pendingStrConstChunks) |
3028 | + self._pendingStrConstChunks = [] |
3029 | + if not strConst: |
3030 | + return |
3031 | + |
3032 | + reprstr = repr(strConst) |
3033 | + i = 0 |
3034 | + out = [] |
3035 | + if reprstr.startswith('u'): |
3036 | + i = 1 |
3037 | + out = ['u'] |
3038 | + body = escapedNewlineRE.sub('\\1\n', reprstr[i+1:-1]) |
3039 | + |
3040 | + if reprstr[i]=="'": |
3041 | + out.append("'''") |
3042 | + out.append(body) |
3043 | + out.append("'''") |
3044 | + else: |
3045 | + out.append('"""') |
3046 | + out.append(body) |
3047 | + out.append('"""') |
3048 | + self.addWriteChunk(''.join(out)) |
3049 | + |
3050 | + def handleWSBeforeDirective(self): |
3051 | + """Truncate the pending strCont to the beginning of the current line. |
3052 | + """ |
3053 | + if self._pendingStrConstChunks: |
3054 | + src = self._pendingStrConstChunks[-1] |
3055 | + BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0) |
3056 | + if BOL < len(src): |
3057 | + self._pendingStrConstChunks[-1] = src[:BOL] |
3058 | + |
3059 | + |
3060 | + |
3061 | + def isErrorCatcherOn(self): |
3062 | + return self._isErrorCatcherOn |
3063 | + |
3064 | + def turnErrorCatcherOn(self): |
3065 | + self._isErrorCatcherOn = True |
3066 | + |
3067 | + def turnErrorCatcherOff(self): |
3068 | + self._isErrorCatcherOn = False |
3069 | + |
3070 | + # @@TR: consider merging the next two methods into one |
3071 | + def addStrConst(self, strConst): |
3072 | + self._appendToPrevStrConst(strConst) |
3073 | + |
3074 | + def addRawText(self, text): |
3075 | + self.addStrConst(text) |
3076 | + |
3077 | + def addMethComment(self, comm): |
3078 | + offSet = self.setting('commentOffset') |
3079 | + self.addChunk('#' + ' '*offSet + comm) |
3080 | + |
3081 | + def addPlaceholder(self, expr, filterArgs, rawPlaceholder, |
3082 | + cacheTokenParts, lineCol, |
3083 | + silentMode=False): |
3084 | + cacheInfo = self.genCacheInfo(cacheTokenParts) |
3085 | + if cacheInfo: |
3086 | + cacheInfo['ID'] = repr(rawPlaceholder)[1:-1] |
3087 | + self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder) |
3088 | + |
3089 | + if self.isErrorCatcherOn(): |
3090 | + methodName = self._classCompiler.addErrorCatcherCall( |
3091 | + expr, rawCode=rawPlaceholder, lineCol=lineCol) |
3092 | + expr = 'self.' + methodName + '(localsDict=locals())' |
3093 | + |
3094 | + if silentMode: |
3095 | + self.addChunk('try:') |
3096 | + self.indent() |
3097 | + self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol) |
3098 | + self.dedent() |
3099 | + self.addChunk('except NotFound: pass') |
3100 | + else: |
3101 | + self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol) |
3102 | + |
3103 | + if self.setting('outputRowColComments'): |
3104 | + self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.') |
3105 | + if cacheInfo: |
3106 | + self.endCacheRegion() |
3107 | + |
3108 | + def addSilent(self, expr): |
3109 | + self.addChunk( expr ) |
3110 | + |
3111 | + def addEcho(self, expr, rawExpr=None): |
3112 | + self.addFilteredChunk(expr, rawExpr=rawExpr) |
3113 | + |
3114 | + def addSet(self, expr, exprComponents, setStyle): |
3115 | + if setStyle is SET_GLOBAL: |
3116 | + (LVALUE, OP, RVALUE) = (exprComponents.LVALUE, |
3117 | + exprComponents.OP, |
3118 | + exprComponents.RVALUE) |
3119 | + # we need to split the LVALUE to deal with globalSetVars |
3120 | + splitPos1 = LVALUE.find('.') |
3121 | + splitPos2 = LVALUE.find('[') |
3122 | + if splitPos1 > 0 and splitPos2==-1: |
3123 | + splitPos = splitPos1 |
3124 | + elif splitPos1 > 0 and splitPos1 < max(splitPos2, 0): |
3125 | + splitPos = splitPos1 |
3126 | + else: |
3127 | + splitPos = splitPos2 |
3128 | + |
3129 | + if splitPos >0: |
3130 | + primary = LVALUE[:splitPos] |
3131 | + secondary = LVALUE[splitPos:] |
3132 | + else: |
3133 | + primary = LVALUE |
3134 | + secondary = '' |
3135 | + LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary |
3136 | + expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip() |
3137 | + |
3138 | + if setStyle is SET_MODULE: |
3139 | + self._moduleCompiler.addModuleGlobal(expr) |
3140 | + else: |
3141 | + self.addChunk(expr) |
3142 | + |
3143 | + def addInclude(self, sourceExpr, includeFrom, isRaw): |
3144 | + self.addChunk('self._handleCheetahInclude(' + sourceExpr + |
3145 | + ', trans=trans, ' + |
3146 | + 'includeFrom="' + includeFrom + '", raw=' + |
3147 | + repr(isRaw) + ')') |
3148 | + |
3149 | + def addWhile(self, expr, lineCol=None): |
3150 | + self.addIndentingDirective(expr, lineCol=lineCol) |
3151 | + |
3152 | + def addFor(self, expr, lineCol=None): |
3153 | + self.addIndentingDirective(expr, lineCol=lineCol) |
3154 | + |
3155 | + def addRepeat(self, expr, lineCol=None): |
3156 | + #the _repeatCount stuff here allows nesting of #repeat directives |
3157 | + self._repeatCount = getattr(self, "_repeatCount", -1) + 1 |
3158 | + self.addFor('for __i%s in range(%s)' % (self._repeatCount, expr), lineCol=lineCol) |
3159 | + |
3160 | + def addIndentingDirective(self, expr, lineCol=None): |
3161 | + if expr and not expr[-1] == ':': |
3162 | + expr = expr + ':' |
3163 | + self.addChunk( expr ) |
3164 | + if lineCol: |
3165 | + self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol ) |
3166 | + self.indent() |
3167 | + |
3168 | + def addReIndentingDirective(self, expr, dedent=True, lineCol=None): |
3169 | + self.commitStrConst() |
3170 | + if dedent: |
3171 | + self.dedent() |
3172 | + if not expr[-1] == ':': |
3173 | + expr = expr + ':' |
3174 | + |
3175 | + self.addChunk( expr ) |
3176 | + if lineCol: |
3177 | + self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol ) |
3178 | + self.indent() |
3179 | + |
3180 | + def addIf(self, expr, lineCol=None): |
3181 | + """For a full #if ... #end if directive |
3182 | + """ |
3183 | + self.addIndentingDirective(expr, lineCol=lineCol) |
3184 | + |
3185 | + def addOneLineIf(self, expr, lineCol=None): |
3186 | + """For a full #if ... #end if directive |
3187 | + """ |
3188 | + self.addIndentingDirective(expr, lineCol=lineCol) |
3189 | + |
3190 | + def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None): |
3191 | + """For a single-lie #if ... then .... else ... directive |
3192 | + <condition> then <trueExpr> else <falseExpr> |
3193 | + """ |
3194 | + self.addIndentingDirective(conditionExpr, lineCol=lineCol) |
3195 | + self.addFilteredChunk(trueExpr) |
3196 | + self.dedent() |
3197 | + self.addIndentingDirective('else') |
3198 | + self.addFilteredChunk(falseExpr) |
3199 | + self.dedent() |
3200 | + |
3201 | + def addElse(self, expr, dedent=True, lineCol=None): |
3202 | + expr = re.sub(r'else[ \f\t]+if', 'elif', expr) |
3203 | + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) |
3204 | + |
3205 | + def addElif(self, expr, dedent=True, lineCol=None): |
3206 | + self.addElse(expr, dedent=dedent, lineCol=lineCol) |
3207 | + |
3208 | + def addUnless(self, expr, lineCol=None): |
3209 | + self.addIf('if not (' + expr + ')') |
3210 | + |
3211 | + def addClosure(self, functionName, argsList, parserComment): |
3212 | + argStringChunks = [] |
3213 | + for arg in argsList: |
3214 | + chunk = arg[0] |
3215 | + if not arg[1] == None: |
3216 | + chunk += '=' + arg[1] |
3217 | + argStringChunks.append(chunk) |
3218 | + signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):" |
3219 | + self.addIndentingDirective(signature) |
3220 | + self.addChunk('#'+parserComment) |
3221 | + |
3222 | + def addTry(self, expr, lineCol=None): |
3223 | + self.addIndentingDirective(expr, lineCol=lineCol) |
3224 | + |
3225 | + def addExcept(self, expr, dedent=True, lineCol=None): |
3226 | + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) |
3227 | + |
3228 | + def addFinally(self, expr, dedent=True, lineCol=None): |
3229 | + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) |
3230 | + |
3231 | + def addReturn(self, expr): |
3232 | + assert not self._isGenerator |
3233 | + self.addChunk(expr) |
3234 | + self._hasReturnStatement = True |
3235 | + |
3236 | + def addYield(self, expr): |
3237 | + assert not self._hasReturnStatement |
3238 | + self._isGenerator = True |
3239 | + if expr.replace('yield', '').strip(): |
3240 | + self.addChunk(expr) |
3241 | + else: |
3242 | + self.addChunk('if _dummyTrans:') |
3243 | + self.indent() |
3244 | + self.addChunk('yield trans.response().getvalue()') |
3245 | + self.addChunk('trans = DummyTransaction()') |
3246 | + self.addChunk('write = trans.response().write') |
3247 | + self.dedent() |
3248 | + self.addChunk('else:') |
3249 | + self.indent() |
3250 | + self.addChunk( |
3251 | + 'raise TypeError("This method cannot be called with a trans arg")') |
3252 | + self.dedent() |
3253 | + |
3254 | + |
3255 | + def addPass(self, expr): |
3256 | + self.addChunk(expr) |
3257 | + |
3258 | + def addDel(self, expr): |
3259 | + self.addChunk(expr) |
3260 | + |
3261 | + def addAssert(self, expr): |
3262 | + self.addChunk(expr) |
3263 | + |
3264 | + def addRaise(self, expr): |
3265 | + self.addChunk(expr) |
3266 | + |
3267 | + def addBreak(self, expr): |
3268 | + self.addChunk(expr) |
3269 | + |
3270 | + def addContinue(self, expr): |
3271 | + self.addChunk(expr) |
3272 | + |
3273 | + def addPSP(self, PSP): |
3274 | + self.commitStrConst() |
3275 | + autoIndent = False |
3276 | + if PSP[0] == '=': |
3277 | + PSP = PSP[1:] |
3278 | + if PSP: |
3279 | + self.addWriteChunk('_filter(' + PSP + ')') |
3280 | + return |
3281 | + |
3282 | + elif PSP.lower() == 'end': |
3283 | + self.dedent() |
3284 | + return |
3285 | + elif PSP[-1] == '$': |
3286 | + autoIndent = True |
3287 | + PSP = PSP[:-1] |
3288 | + elif PSP[-1] == ':': |
3289 | + autoIndent = True |
3290 | + |
3291 | + for line in PSP.splitlines(): |
3292 | + self.addChunk(line) |
3293 | + |
3294 | + if autoIndent: |
3295 | + self.indent() |
3296 | + |
3297 | + def nextCacheID(self): |
3298 | + return ('_'+str(random.randrange(100, 999)) |
3299 | + + str(random.randrange(10000, 99999))) |
3300 | + |
3301 | + def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None): |
3302 | + |
3303 | + # @@TR: we should add some runtime logging to this |
3304 | + |
3305 | + ID = self.nextCacheID() |
3306 | + interval = cacheInfo.get('interval', None) |
3307 | + test = cacheInfo.get('test', None) |
3308 | + customID = cacheInfo.get('id', None) |
3309 | + if customID: |
3310 | + ID = customID |
3311 | + varyBy = cacheInfo.get('varyBy', repr(ID)) |
3312 | + self._cacheRegionsStack.append(ID) # attrib of current methodCompiler |
3313 | + |
3314 | + # @@TR: add this to a special class var as well |
3315 | + self.addChunk('') |
3316 | + |
3317 | + self.addChunk('## START CACHE REGION: ID='+ID+ |
3318 | + '. line %s, col %s'%lineCol + ' in the source.') |
3319 | + |
3320 | + self.addChunk('_RECACHE_%(ID)s = False'%locals()) |
3321 | + self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals() |
3322 | + + repr(ID) |
3323 | + + ', cacheInfo=%r'%cacheInfo |
3324 | + + ')') |
3325 | + self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals()) |
3326 | + self.indent() |
3327 | + self.addChunk('_RECACHE_%(ID)s = True'%locals()) |
3328 | + self.dedent() |
3329 | + |
3330 | + self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals() |
3331 | + +varyBy+')') |
3332 | + |
3333 | + self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals()) |
3334 | + self.indent() |
3335 | + self.addChunk('_RECACHE_%(ID)s = True'%locals()) |
3336 | + self.dedent() |
3337 | + |
3338 | + if test: |
3339 | + self.addChunk('if ' + test + ':') |
3340 | + self.indent() |
3341 | + self.addChunk('_RECACHE_%(ID)s = True'%locals()) |
3342 | + self.dedent() |
3343 | + |
3344 | + self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals()) |
3345 | + self.indent() |
3346 | + #self.addChunk('print "DEBUG"+"-"*50') |
3347 | + self.addChunk('try:') |
3348 | + self.indent() |
3349 | + self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals()) |
3350 | + self.dedent() |
3351 | + self.addChunk('except KeyError:') |
3352 | + self.indent() |
3353 | + self.addChunk('_RECACHE_%(ID)s = True'%locals()) |
3354 | + #self.addChunk('print "DEBUG"+"*"*50') |
3355 | + self.dedent() |
3356 | + self.addChunk('else:') |
3357 | + self.indent() |
3358 | + self.addWriteChunk('_output') |
3359 | + self.addChunk('del _output') |
3360 | + self.dedent() |
3361 | + |
3362 | + self.dedent() |
3363 | + |
3364 | + self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals()) |
3365 | + self.indent() |
3366 | + self.addChunk('_orig_trans%(ID)s = trans'%locals()) |
3367 | + self.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals()) |
3368 | + self.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals()) |
3369 | + if interval: |
3370 | + self.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals()) |
3371 | + + str(interval) + ")") |
3372 | + |
3373 | + def endCacheRegion(self): |
3374 | + ID = self._cacheRegionsStack.pop() |
3375 | + self.addChunk('trans = _orig_trans%(ID)s'%locals()) |
3376 | + self.addChunk('write = trans.response().write') |
3377 | + self.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals()) |
3378 | + self.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals()) |
3379 | + self.addWriteChunk('_cacheData') |
3380 | + self.addChunk('del _cacheData') |
3381 | + self.addChunk('del _cacheCollector_%(ID)s'%locals()) |
3382 | + self.addChunk('del _orig_trans%(ID)s'%locals()) |
3383 | + self.dedent() |
3384 | + self.addChunk('## END CACHE REGION: '+ID) |
3385 | + self.addChunk('') |
3386 | + |
3387 | + def nextCallRegionID(self): |
3388 | + return self.nextCacheID() |
3389 | + |
3390 | + def startCallRegion(self, functionName, args, lineCol, regionTitle='CALL'): |
3391 | + class CallDetails(object): |
3392 | + pass |
3393 | + callDetails = CallDetails() |
3394 | + callDetails.ID = ID = self.nextCallRegionID() |
3395 | + callDetails.functionName = functionName |
3396 | + callDetails.args = args |
3397 | + callDetails.lineCol = lineCol |
3398 | + callDetails.usesKeywordArgs = False |
3399 | + self._callRegionsStack.append((ID, callDetails)) # attrib of current methodCompiler |
3400 | + |
3401 | + self.addChunk('## START %(regionTitle)s REGION: '%locals() |
3402 | + +ID |
3403 | + +' of '+functionName |
3404 | + +' at line %s, col %s'%lineCol + ' in the source.') |
3405 | + self.addChunk('_orig_trans%(ID)s = trans'%locals()) |
3406 | + self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals()) |
3407 | + self.addChunk('self._CHEETAH__isBuffering = True') |
3408 | + self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals()) |
3409 | + self.addChunk('write = _callCollector%(ID)s.response().write'%locals()) |
3410 | + |
3411 | + def setCallArg(self, argName, lineCol): |
3412 | + ID, callDetails = self._callRegionsStack[-1] |
3413 | + argName = str(argName) |
3414 | + if callDetails.usesKeywordArgs: |
3415 | + self._endCallArg() |
3416 | + else: |
3417 | + callDetails.usesKeywordArgs = True |
3418 | + self.addChunk('_callKws%(ID)s = {}'%locals()) |
3419 | + self.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals()) |
3420 | + callDetails.currentArgname = argName |
3421 | + |
3422 | + def _endCallArg(self): |
3423 | + ID, callDetails = self._callRegionsStack[-1] |
3424 | + currCallArg = callDetails.currentArgname |
3425 | + self.addChunk(('_callKws%(ID)s[%(currCallArg)r] =' |
3426 | + ' _callCollector%(ID)s.response().getvalue()')%locals()) |
3427 | + self.addChunk('del _callCollector%(ID)s'%locals()) |
3428 | + self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals()) |
3429 | + self.addChunk('write = _callCollector%(ID)s.response().write'%locals()) |
3430 | + |
3431 | + def endCallRegion(self, regionTitle='CALL'): |
3432 | + ID, callDetails = self._callRegionsStack[-1] |
3433 | + functionName, initialKwArgs, lineCol = ( |
3434 | + callDetails.functionName, callDetails.args, callDetails.lineCol) |
3435 | + |
3436 | + def reset(ID=ID): |
3437 | + self.addChunk('trans = _orig_trans%(ID)s'%locals()) |
3438 | + self.addChunk('write = trans.response().write') |
3439 | + self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals()) |
3440 | + self.addChunk('del _wasBuffering%(ID)s'%locals()) |
3441 | + self.addChunk('del _orig_trans%(ID)s'%locals()) |
3442 | + |
3443 | + if not callDetails.usesKeywordArgs: |
3444 | + reset() |
3445 | + self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals()) |
3446 | + self.addChunk('del _callCollector%(ID)s'%locals()) |
3447 | + if initialKwArgs: |
3448 | + initialKwArgs = ', '+initialKwArgs |
3449 | + self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals()) |
3450 | + self.addChunk('del _callArgVal%(ID)s'%locals()) |
3451 | + else: |
3452 | + if initialKwArgs: |
3453 | + initialKwArgs = initialKwArgs+', ' |
3454 | + self._endCallArg() |
3455 | + reset() |
3456 | + self.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals()) |
3457 | + self.addChunk('del _callKws%(ID)s'%locals()) |
3458 | + self.addChunk('## END %(regionTitle)s REGION: '%locals() |
3459 | + +ID |
3460 | + +' of '+functionName |
3461 | + +' at line %s, col %s'%lineCol + ' in the source.') |
3462 | + self.addChunk('') |
3463 | + self._callRegionsStack.pop() # attrib of current methodCompiler |
3464 | + |
3465 | + def nextCaptureRegionID(self): |
3466 | + return self.nextCacheID() |
3467 | + |
3468 | + def startCaptureRegion(self, assignTo, lineCol): |
3469 | + class CaptureDetails: pass |
3470 | + captureDetails = CaptureDetails() |
3471 | + captureDetails.ID = ID = self.nextCaptureRegionID() |
3472 | + captureDetails.assignTo = assignTo |
3473 | + captureDetails.lineCol = lineCol |
3474 | + |
3475 | + self._captureRegionsStack.append((ID, captureDetails)) # attrib of current methodCompiler |
3476 | + self.addChunk('## START CAPTURE REGION: '+ID |
3477 | + +' '+assignTo |
3478 | + +' at line %s, col %s'%lineCol + ' in the source.') |
3479 | + self.addChunk('_orig_trans%(ID)s = trans'%locals()) |
3480 | + self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals()) |
3481 | + self.addChunk('self._CHEETAH__isBuffering = True') |
3482 | + self.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals()) |
3483 | + self.addChunk('write = _captureCollector%(ID)s.response().write'%locals()) |
3484 | + |
3485 | + def endCaptureRegion(self): |
3486 | + ID, captureDetails = self._captureRegionsStack.pop() |
3487 | + assignTo, lineCol = (captureDetails.assignTo, captureDetails.lineCol) |
3488 | + self.addChunk('trans = _orig_trans%(ID)s'%locals()) |
3489 | + self.addChunk('write = trans.response().write') |
3490 | + self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals()) |
3491 | + self.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals()) |
3492 | + self.addChunk('del _orig_trans%(ID)s'%locals()) |
3493 | + self.addChunk('del _captureCollector%(ID)s'%locals()) |
3494 | + self.addChunk('del _wasBuffering%(ID)s'%locals()) |
3495 | + |
3496 | + def setErrorCatcher(self, errorCatcherName): |
3497 | + self.turnErrorCatcherOn() |
3498 | + |
3499 | + self.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName + '"):') |
3500 | + self.indent() |
3501 | + self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' + |
3502 | + errorCatcherName + '"]') |
3503 | + self.dedent() |
3504 | + self.addChunk('else:') |
3505 | + self.indent() |
3506 | + self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' |
3507 | + + errorCatcherName + '"] = ErrorCatchers.' |
3508 | + + errorCatcherName + '(self)' |
3509 | + ) |
3510 | + self.dedent() |
3511 | + |
3512 | + def nextFilterRegionID(self): |
3513 | + return self.nextCacheID() |
3514 | + |
3515 | + def setTransform(self, transformer, isKlass): |
3516 | + self.addChunk('trans = TransformerTransaction()') |
3517 | + self.addChunk('trans._response = trans.response()') |
3518 | + self.addChunk('trans._response._filter = %s' % transformer) |
3519 | + self.addChunk('write = trans._response.write') |
3520 | + |
3521 | + def setFilter(self, theFilter, isKlass): |
3522 | + class FilterDetails: |
3523 | + pass |
3524 | + filterDetails = FilterDetails() |
3525 | + filterDetails.ID = ID = self.nextFilterRegionID() |
3526 | + filterDetails.theFilter = theFilter |
3527 | + filterDetails.isKlass = isKlass |
3528 | + self._filterRegionsStack.append((ID, filterDetails)) # attrib of current methodCompiler |
3529 | + |
3530 | + self.addChunk('_orig_filter%(ID)s = _filter'%locals()) |
3531 | + if isKlass: |
3532 | + self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() + |
3533 | + '(self).filter') |
3534 | + else: |
3535 | + if theFilter.lower() == 'none': |
3536 | + self.addChunk('_filter = self._CHEETAH__initialFilter') |
3537 | + else: |
3538 | + # is string representing the name of a builtin filter |
3539 | + self.addChunk('filterName = ' + repr(theFilter)) |
3540 | + self.addChunk('if self._CHEETAH__filters.has_key("' + theFilter + '"):') |
3541 | + self.indent() |
3542 | + self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]') |
3543 | + self.dedent() |
3544 | + self.addChunk('else:') |
3545 | + self.indent() |
3546 | + self.addChunk('_filter = self._CHEETAH__currentFilter' |
3547 | + +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = ' |
3548 | + + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter') |
3549 | + self.dedent() |
3550 | + |
3551 | + def closeFilterBlock(self): |
3552 | + ID, filterDetails = self._filterRegionsStack.pop() |
3553 | + #self.addChunk('_filter = self._CHEETAH__initialFilter') |
3554 | + #self.addChunk('_filter = _orig_filter%(ID)s'%locals()) |
3555 | + self.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals()) |
3556 | + |
3557 | +class AutoMethodCompiler(MethodCompiler): |
3558 | + |
3559 | + def _setupState(self): |
3560 | + MethodCompiler._setupState(self) |
3561 | + self._argStringList = [ ("self", None) ] |
3562 | + self._streamingEnabled = True |
3563 | + self._isClassMethod = None |
3564 | + self._isStaticMethod = None |
3565 | + |
3566 | + def _useKWsDictArgForPassingTrans(self): |
3567 | + alreadyHasTransArg = [argname for argname, defval in self._argStringList |
3568 | + if argname=='trans'] |
3569 | + return (self.methodName()!='respond' |
3570 | + and not alreadyHasTransArg |
3571 | + and self.setting('useKWsDictArgForPassingTrans')) |
3572 | + |
3573 | + def isClassMethod(self): |
3574 | + if self._isClassMethod is None: |
3575 | + self._isClassMethod = '@classmethod' in self._decorators |
3576 | + return self._isClassMethod |
3577 | + |
3578 | + def isStaticMethod(self): |
3579 | + if self._isStaticMethod is None: |
3580 | + self._isStaticMethod = '@staticmethod' in self._decorators |
3581 | + return self._isStaticMethod |
3582 | + |
3583 | + def cleanupState(self): |
3584 | + MethodCompiler.cleanupState(self) |
3585 | + self.commitStrConst() |
3586 | + if self._cacheRegionsStack: |
3587 | + self.endCacheRegion() |
3588 | + if self._callRegionsStack: |
3589 | + self.endCallRegion() |
3590 | + |
3591 | + if self._streamingEnabled: |
3592 | + kwargsName = None |
3593 | + positionalArgsListName = None |
3594 | + for argname, defval in self._argStringList: |
3595 | + if argname.strip().startswith('**'): |
3596 | + kwargsName = argname.strip().replace('**', '') |
3597 | + break |
3598 | + elif argname.strip().startswith('*'): |
3599 | + positionalArgsListName = argname.strip().replace('*', '') |
3600 | + |
3601 | + if not kwargsName and self._useKWsDictArgForPassingTrans(): |
3602 | + kwargsName = 'KWS' |
3603 | + self.addMethArg('**KWS', None) |
3604 | + self._kwargsName = kwargsName |
3605 | + |
3606 | + if not self._useKWsDictArgForPassingTrans(): |
3607 | + if not kwargsName and not positionalArgsListName: |
3608 | + self.addMethArg('trans', 'None') |
3609 | + else: |
3610 | + self._streamingEnabled = False |
3611 | + |
3612 | + self._indentLev = self.setting('initialMethIndentLevel') |
3613 | + mainBodyChunks = self._methodBodyChunks |
3614 | + self._methodBodyChunks = [] |
3615 | + self._addAutoSetupCode() |
3616 | + self._methodBodyChunks.extend(mainBodyChunks) |
3617 | + self._addAutoCleanupCode() |
3618 | + |
3619 | + def _addAutoSetupCode(self): |
3620 | + if self._initialMethodComment: |
3621 | + self.addChunk(self._initialMethodComment) |
3622 | + |
3623 | + if self._streamingEnabled and not self.isClassMethod() and not self.isStaticMethod(): |
3624 | + if self._useKWsDictArgForPassingTrans() and self._kwargsName: |
3625 | + self.addChunk('trans = %s.get("trans")'%self._kwargsName) |
3626 | + self.addChunk('if (not trans and not self._CHEETAH__isBuffering' |
3627 | + ' and not callable(self.transaction)):') |
3628 | + self.indent() |
3629 | + self.addChunk('trans = self.transaction' |
3630 | + ' # is None unless self.awake() was called') |
3631 | + self.dedent() |
3632 | + self.addChunk('if not trans:') |
3633 | + self.indent() |
3634 | + self.addChunk('trans = DummyTransaction()') |
3635 | + if self.setting('autoAssignDummyTransactionToSelf'): |
3636 | + self.addChunk('self.transaction = trans') |
3637 | + self.addChunk('_dummyTrans = True') |
3638 | + self.dedent() |
3639 | + self.addChunk('else: _dummyTrans = False') |
3640 | + else: |
3641 | + self.addChunk('trans = DummyTransaction()') |
3642 | + self.addChunk('_dummyTrans = True') |
3643 | + self.addChunk('write = trans.response().write') |
3644 | + if self.setting('useNameMapper'): |
3645 | + argNames = [arg[0] for arg in self._argStringList] |
3646 | + allowSearchListAsMethArg = self.setting('allowSearchListAsMethArg') |
3647 | + if allowSearchListAsMethArg and 'SL' in argNames: |
3648 | + pass |
3649 | + elif allowSearchListAsMethArg and 'searchList' in argNames: |
3650 | + self.addChunk('SL = searchList') |
3651 | + elif not self.isClassMethod() and not self.isStaticMethod(): |
3652 | + self.addChunk('SL = self._CHEETAH__searchList') |
3653 | + else: |
3654 | + self.addChunk('SL = [KWS]') |
3655 | + if self.setting('useFilters'): |
3656 | + if self.isClassMethod() or self.isStaticMethod(): |
3657 | + self.addChunk('_filter = lambda x, **kwargs: unicode(x)') |
3658 | + else: |
3659 | + self.addChunk('_filter = self._CHEETAH__currentFilter') |
3660 | + self.addChunk('') |
3661 | + self.addChunk("#" *40) |
3662 | + self.addChunk('## START - generated method body') |
3663 | + self.addChunk('') |
3664 | + |
3665 | + def _addAutoCleanupCode(self): |
3666 | + self.addChunk('') |
3667 | + self.addChunk("#" *40) |
3668 | + self.addChunk('## END - generated method body') |
3669 | + self.addChunk('') |
3670 | + |
3671 | + if not self._isGenerator: |
3672 | + self.addStop() |
3673 | + self.addChunk('') |
3674 | + |
3675 | + def addStop(self, expr=None): |
3676 | + self.addChunk('return _dummyTrans and trans.response().getvalue() or ""') |
3677 | + |
3678 | + def addMethArg(self, name, defVal=None): |
3679 | + self._argStringList.append( (name, defVal) ) |
3680 | + |
3681 | + def methodSignature(self): |
3682 | + argStringChunks = [] |
3683 | + for arg in self._argStringList: |
3684 | + chunk = arg[0] |
3685 | + if chunk == 'self' and self.isClassMethod(): |
3686 | + chunk = 'cls' |
3687 | + if chunk == 'self' and self.isStaticMethod(): |
3688 | + # Skip the "self" method for @staticmethod decorators |
3689 | + continue |
3690 | + if not arg[1] == None: |
3691 | + chunk += '=' + arg[1] |
3692 | + argStringChunks.append(chunk) |
3693 | + argString = (', ').join(argStringChunks) |
3694 | + |
3695 | + output = [] |
3696 | + if self._decorators: |
3697 | + output.append(''.join([self._indent + decorator + '\n' |
3698 | + for decorator in self._decorators])) |
3699 | + output.append(self._indent + "def " |
3700 | + + self.methodName() + "(" + |
3701 | + argString + "):\n\n") |
3702 | + return ''.join(output) |
3703 | + |
3704 | + |
3705 | +################################################## |
3706 | +## CLASS COMPILERS |
3707 | + |
3708 | +_initMethod_initCheetah = """\ |
3709 | +if not self._CHEETAH__instanceInitialized: |
3710 | + cheetahKWArgs = {} |
3711 | + allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split() |
3712 | + for k,v in KWs.items(): |
3713 | + if k in allowedKWs: cheetahKWArgs[k] = v |
3714 | + self._initCheetahInstance(**cheetahKWArgs) |
3715 | +""".replace('\n', '\n'+' '*8) |
3716 | + |
3717 | +class ClassCompiler(GenUtils): |
3718 | + methodCompilerClass = AutoMethodCompiler |
3719 | + methodCompilerClassForInit = MethodCompiler |
3720 | + |
3721 | + def __init__(self, className, mainMethodName='respond', |
3722 | + moduleCompiler=None, |
3723 | + fileName=None, |
3724 | + settingsManager=None): |
3725 | + |
3726 | + self._settingsManager = settingsManager |
3727 | + self._fileName = fileName |
3728 | + self._className = className |
3729 | + self._moduleCompiler = moduleCompiler |
3730 | + self._mainMethodName = mainMethodName |
3731 | + self._setupState() |
3732 | + methodCompiler = self._spawnMethodCompiler( |
3733 | + mainMethodName, |
3734 | + initialMethodComment='## CHEETAH: main method generated for this template') |
3735 | + |
3736 | + self._setActiveMethodCompiler(methodCompiler) |
3737 | + if fileName and self.setting('monitorSrcFile'): |
3738 | + self._addSourceFileMonitoring(fileName) |
3739 | + |
3740 | + def setting(self, key): |
3741 | + return self._settingsManager.setting(key) |
3742 | + |
3743 | + def __getattr__(self, name): |
3744 | + """Provide access to the methods and attributes of the MethodCompiler |
3745 | + at the top of the activeMethods stack: one-way namespace sharing |
3746 | + |
3747 | + |
3748 | + WARNING: Use .setMethods to assign the attributes of the MethodCompiler |
3749 | + from the methods of this class!!! or you will be assigning to attributes |
3750 | + of this object instead.""" |
3751 | + |
3752 | + if name in self.__dict__: |
3753 | + return self.__dict__[name] |
3754 | + elif hasattr(self.__class__, name): |
3755 | + return getattr(self.__class__, name) |
3756 | + elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name): |
3757 | + return getattr(self._activeMethodsList[-1], name) |
3758 | + else: |
3759 | + raise AttributeError(name) |
3760 | + |
3761 | + def _setupState(self): |
3762 | + self._classDef = None |
3763 | + self._decoratorsForNextMethod = [] |
3764 | + self._activeMethodsList = [] # stack while parsing/generating |
3765 | + self._finishedMethodsList = [] # store by order |
3766 | + self._methodsIndex = {} # store by name |
3767 | + self._baseClass = 'Template' |
3768 | + self._classDocStringLines = [] |
3769 | + # printed after methods in the gen class def: |
3770 | + self._generatedAttribs = ['_CHEETAH__instanceInitialized = False'] |
3771 | + self._generatedAttribs.append('_CHEETAH_version = __CHEETAH_version__') |
3772 | + self._generatedAttribs.append( |
3773 | + '_CHEETAH_versionTuple = __CHEETAH_versionTuple__') |
3774 | + |
3775 | + if self.setting('addTimestampsToCompilerOutput'): |
3776 | + self._generatedAttribs.append('_CHEETAH_genTime = __CHEETAH_genTime__') |
3777 | + self._generatedAttribs.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__') |
3778 | + |
3779 | + self._generatedAttribs.append('_CHEETAH_src = __CHEETAH_src__') |
3780 | + self._generatedAttribs.append( |
3781 | + '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__') |
3782 | + |
3783 | + if self.setting('templateMetaclass'): |
3784 | + self._generatedAttribs.append('__metaclass__ = '+self.setting('templateMetaclass')) |
3785 | + self._initMethChunks = [] |
3786 | + self._blockMetaData = {} |
3787 | + self._errorCatcherCount = 0 |
3788 | + self._placeholderToErrorCatcherMap = {} |
3789 | + |
3790 | + def cleanupState(self): |
3791 | + while self._activeMethodsList: |
3792 | + methCompiler = self._popActiveMethodCompiler() |
3793 | + self._swallowMethodCompiler(methCompiler) |
3794 | + self._setupInitMethod() |
3795 | + if self._mainMethodName == 'respond': |
3796 | + if self.setting('setup__str__method'): |
3797 | + self._generatedAttribs.append('def __str__(self): return self.respond()') |
3798 | + self.addAttribute('_mainCheetahMethod_for_' + self._className + |
3799 | + '= ' + repr(self._mainMethodName) ) |
3800 | + |
3801 | + def _setupInitMethod(self): |
3802 | + __init__ = self._spawnMethodCompiler('__init__', |
3803 | + klass=self.methodCompilerClassForInit) |
3804 | + __init__.setMethodSignature("def __init__(self, *args, **KWs)") |
3805 | + __init__.addChunk('super(%s, self).__init__(*args, **KWs)' % self._className) |
3806 | + __init__.addChunk(_initMethod_initCheetah % {'className' : self._className}) |
3807 | + for chunk in self._initMethChunks: |
3808 | + __init__.addChunk(chunk) |
3809 | + __init__.cleanupState() |
3810 | + self._swallowMethodCompiler(__init__, pos=0) |
3811 | + |
3812 | + def _addSourceFileMonitoring(self, fileName): |
3813 | + # @@TR: this stuff needs auditing for Cheetah 2.0 |
3814 | + # the first bit is added to init |
3815 | + self.addChunkToInit('self._filePath = ' + repr(fileName)) |
3816 | + self.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName)) ) |
3817 | + |
3818 | + # the rest is added to the main output method of the class ('mainMethod') |
3819 | + self.addChunk('if exists(self._filePath) and ' + |
3820 | + 'getmtime(self._filePath) > self._fileMtime:') |
3821 | + self.indent() |
3822 | + self.addChunk('self._compile(file=self._filePath, moduleName='+self._className + ')') |
3823 | + self.addChunk( |
3824 | + 'write(getattr(self, self._mainCheetahMethod_for_' + self._className + |
3825 | + ')(trans=trans))') |
3826 | + self.addStop() |
3827 | + self.dedent() |
3828 | + |
3829 | + def setClassName(self, name): |
3830 | + self._className = name |
3831 | + |
3832 | + def className(self): |
3833 | + return self._className |
3834 | + |
3835 | + def setBaseClass(self, baseClassName): |
3836 | + self._baseClass = baseClassName |
3837 | + |
3838 | + def setMainMethodName(self, methodName): |
3839 | + if methodName == self._mainMethodName: |
3840 | + return |
3841 | + ## change the name in the methodCompiler and add new reference |
3842 | + mainMethod = self._methodsIndex[self._mainMethodName] |
3843 | + mainMethod.setMethodName(methodName) |
3844 | + self._methodsIndex[methodName] = mainMethod |
3845 | + |
3846 | + ## make sure that fileUpdate code still works properly: |
3847 | + chunkToChange = ('write(self.' + self._mainMethodName + '(trans=trans))') |
3848 | + chunks = mainMethod._methodBodyChunks |
3849 | + if chunkToChange in chunks: |
3850 | + for i in range(len(chunks)): |
3851 | + if chunks[i] == chunkToChange: |
3852 | + chunks[i] = ('write(self.' + methodName + '(trans=trans))') |
3853 | + ## get rid of the old reference and update self._mainMethodName |
3854 | + del self._methodsIndex[self._mainMethodName] |
3855 | + self._mainMethodName = methodName |
3856 | + |
3857 | + def setMainMethodArgs(self, argsList): |
3858 | + mainMethodCompiler = self._methodsIndex[self._mainMethodName] |
3859 | + for argName, defVal in argsList: |
3860 | + mainMethodCompiler.addMethArg(argName, defVal) |
3861 | + |
3862 | + |
3863 | + def _spawnMethodCompiler(self, methodName, klass=None, |
3864 | + initialMethodComment=None): |
3865 | + if klass is None: |
3866 | + klass = self.methodCompilerClass |
3867 | + |
3868 | + decorators = self._decoratorsForNextMethod or [] |
3869 | + self._decoratorsForNextMethod = [] |
3870 | + methodCompiler = klass(methodName, classCompiler=self, |
3871 | + decorators=decorators, |
3872 | + initialMethodComment=initialMethodComment) |
3873 | + self._methodsIndex[methodName] = methodCompiler |
3874 | + return methodCompiler |
3875 | + |
3876 | + def _setActiveMethodCompiler(self, methodCompiler): |
3877 | + self._activeMethodsList.append(methodCompiler) |
3878 | + |
3879 | + def _getActiveMethodCompiler(self): |
3880 | + return self._activeMethodsList[-1] |
3881 | + |
3882 | + def _popActiveMethodCompiler(self): |
3883 | + return self._activeMethodsList.pop() |
3884 | + |
3885 | + def _swallowMethodCompiler(self, methodCompiler, pos=None): |
3886 | + methodCompiler.cleanupState() |
3887 | + if pos==None: |
3888 | + self._finishedMethodsList.append( methodCompiler ) |
3889 | + else: |
3890 | + self._finishedMethodsList.insert(pos, methodCompiler) |
3891 | + return methodCompiler |
3892 | + |
3893 | + def startMethodDef(self, methodName, argsList, parserComment): |
3894 | + methodCompiler = self._spawnMethodCompiler( |
3895 | + methodName, initialMethodComment=parserComment) |
3896 | + self._setActiveMethodCompiler(methodCompiler) |
3897 | + for argName, defVal in argsList: |
3898 | + methodCompiler.addMethArg(argName, defVal) |
3899 | + |
3900 | + def _finishedMethods(self): |
3901 | + return self._finishedMethodsList |
3902 | + |
3903 | + def addDecorator(self, decoratorExpr): |
3904 | + """Set the decorator to be used with the next method in the source. |
3905 | + |
3906 | + See _spawnMethodCompiler() and MethodCompiler for the details of how |
3907 | + this is used. |
3908 | + """ |
3909 | + self._decoratorsForNextMethod.append(decoratorExpr) |
3910 | + |
3911 | + def addClassDocString(self, line): |
3912 | + self._classDocStringLines.append( line.replace('%', '%%')) |
3913 | + |
3914 | + def addChunkToInit(self, chunk): |
3915 | + self._initMethChunks.append(chunk) |
3916 | + |
3917 | + def addAttribute(self, attribExpr): |
3918 | + ## first test to make sure that the user hasn't used any fancy Cheetah syntax |
3919 | + # (placeholders, directives, etc.) inside the expression |
3920 | + if attribExpr.find('VFN(') != -1 or attribExpr.find('VFFSL(') != -1: |
3921 | + raise ParseError(self, |
3922 | + 'Invalid #attr directive.' + |
3923 | + ' It should only contain simple Python literals.') |
3924 | + ## now add the attribute |
3925 | + self._generatedAttribs.append(attribExpr) |
3926 | + |
3927 | + def addSuper(self, argsList, parserComment=None): |
3928 | + className = self._className #self._baseClass |
3929 | + methodName = self._getActiveMethodCompiler().methodName() |
3930 | + |
3931 | + argStringChunks = [] |
3932 | + for arg in argsList: |
3933 | + chunk = arg[0] |
3934 | + if not arg[1] == None: |
3935 | + chunk += '=' + arg[1] |
3936 | + argStringChunks.append(chunk) |
3937 | + argString = ','.join(argStringChunks) |
3938 | + |
3939 | + self.addFilteredChunk( |
3940 | + 'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals()) |
3941 | + |
3942 | + def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''): |
3943 | + if rawCode in self._placeholderToErrorCatcherMap: |
3944 | + methodName = self._placeholderToErrorCatcherMap[rawCode] |
3945 | + if not self.setting('outputRowColComments'): |
3946 | + self._methodsIndex[methodName].addMethDocString( |
3947 | + 'plus at line %s, col %s'%lineCol) |
3948 | + return methodName |
3949 | + |
3950 | + self._errorCatcherCount += 1 |
3951 | + methodName = '__errorCatcher' + str(self._errorCatcherCount) |
3952 | + self._placeholderToErrorCatcherMap[rawCode] = methodName |
3953 | + |
3954 | + catcherMeth = self._spawnMethodCompiler( |
3955 | + methodName, |
3956 | + klass=MethodCompiler, |
3957 | + initialMethodComment=('## CHEETAH: Generated from ' + rawCode + |
3958 | + ' at line %s, col %s'%lineCol + '.') |
3959 | + ) |
3960 | + catcherMeth.setMethodSignature('def ' + methodName + |
3961 | + '(self, localsDict={})') |
3962 | + # is this use of localsDict right? |
3963 | + catcherMeth.addChunk('try:') |
3964 | + catcherMeth.indent() |
3965 | + catcherMeth.addChunk("return eval('''" + codeChunk + |
3966 | + "''', globals(), localsDict)") |
3967 | + catcherMeth.dedent() |
3968 | + catcherMeth.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:') |
3969 | + catcherMeth.indent() |
3970 | + catcherMeth.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " + |
3971 | + repr(codeChunk) + " , rawCode= " + |
3972 | + repr(rawCode) + " , lineCol=" + str(lineCol) +")") |
3973 | + |
3974 | + catcherMeth.cleanupState() |
3975 | + |
3976 | + self._swallowMethodCompiler(catcherMeth) |
3977 | + return methodName |
3978 | + |
3979 | + def closeDef(self): |
3980 | + self.commitStrConst() |
3981 | + methCompiler = self._popActiveMethodCompiler() |
3982 | + self._swallowMethodCompiler(methCompiler) |
3983 | + |
3984 | + def closeBlock(self): |
3985 | + self.commitStrConst() |
3986 | + methCompiler = self._popActiveMethodCompiler() |
3987 | + methodName = methCompiler.methodName() |
3988 | + if self.setting('includeBlockMarkers'): |
3989 | + endMarker = self.setting('blockMarkerEnd') |
3990 | + methCompiler.addStrConst(endMarker[0] + methodName + endMarker[1]) |
3991 | + self._swallowMethodCompiler(methCompiler) |
3992 | + |
3993 | + #metaData = self._blockMetaData[methodName] |
3994 | + #rawDirective = metaData['raw'] |
3995 | + #lineCol = metaData['lineCol'] |
3996 | + |
3997 | + ## insert the code to call the block, caching if #cache directive is on |
3998 | + codeChunk = 'self.' + methodName + '(trans=trans)' |
3999 | + self.addChunk(codeChunk) |
4000 | + |
4001 | + #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) ) |
4002 | + #if self.setting('outputRowColComments'): |
4003 | + # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.') |
4004 | + |
4005 | + |
4006 | + ## code wrapping methods |
4007 | + |
4008 | + def classDef(self): |
4009 | + if self._classDef: |
4010 | + return self._classDef |
4011 | + else: |
4012 | + return self.wrapClassDef() |
4013 | + |
4014 | + __str__ = classDef |
4015 | + __unicode__ = classDef |
4016 | + |
4017 | + def wrapClassDef(self): |
4018 | + ind = self.setting('indentationStep') |
4019 | + classDefChunks = [self.classSignature(), |
4020 | + self.classDocstring(), |
4021 | + ] |
4022 | + def addMethods(): |
4023 | + classDefChunks.extend([ |
4024 | + ind + '#'*50, |
4025 | + ind + '## CHEETAH GENERATED METHODS', |
4026 | + '\n', |
4027 | + self.methodDefs(), |
4028 | + ]) |
4029 | + def addAttributes(): |
4030 | + classDefChunks.extend([ |
4031 | + ind + '#'*50, |
4032 | + ind + '## CHEETAH GENERATED ATTRIBUTES', |
4033 | + '\n', |
4034 | + self.attributes(), |
4035 | + ]) |
4036 | + if self.setting('outputMethodsBeforeAttributes'): |
4037 | + addMethods() |
4038 | + addAttributes() |
4039 | + else: |
4040 | + addAttributes() |
4041 | + addMethods() |
4042 | + |
4043 | + classDef = '\n'.join(classDefChunks) |
4044 | + self._classDef = classDef |
4045 | + return classDef |
4046 | + |
4047 | + |
4048 | + def classSignature(self): |
4049 | + return "class %s(%s):" % (self.className(), self._baseClass) |
4050 | + |
4051 | + def classDocstring(self): |
4052 | + if not self._classDocStringLines: |
4053 | + return '' |
4054 | + ind = self.setting('indentationStep') |
4055 | + docStr = ('%(ind)s"""\n%(ind)s' + |
4056 | + '\n%(ind)s'.join(self._classDocStringLines) + |
4057 | + '\n%(ind)s"""\n' |
4058 | + ) % {'ind':ind} |
4059 | + return docStr |
4060 | + |
4061 | + def methodDefs(self): |
4062 | + methodDefs = [methGen.methodDef() for methGen in self._finishedMethods()] |
4063 | + return '\n\n'.join(methodDefs) |
4064 | + |
4065 | + def attributes(self): |
4066 | + attribs = [self.setting('indentationStep') + str(attrib) |
4067 | + for attrib in self._generatedAttribs ] |
4068 | + return '\n\n'.join(attribs) |
4069 | + |
4070 | +class AutoClassCompiler(ClassCompiler): |
4071 | + pass |
4072 | + |
4073 | +################################################## |
4074 | +## MODULE COMPILERS |
4075 | + |
4076 | +class ModuleCompiler(SettingsManager, GenUtils): |
4077 | + |
4078 | + parserClass = Parser |
4079 | + classCompilerClass = AutoClassCompiler |
4080 | + |
4081 | + def __init__(self, source=None, file=None, |
4082 | + moduleName='DynamicallyCompiledCheetahTemplate', |
4083 | + mainClassName=None, # string |
4084 | + mainMethodName=None, # string |
4085 | + baseclassName=None, # string |
4086 | + extraImportStatements=None, # list of strings |
4087 | + settings=None # dict |
4088 | + ): |
4089 | + super(ModuleCompiler, self).__init__() |
4090 | + if settings: |
4091 | + self.updateSettings(settings) |
4092 | + # disable useStackFrames if the C version of NameMapper isn't compiled |
4093 | + # it's painfully slow in the Python version and bites Windows users all |
4094 | + # the time: |
4095 | + if not NameMapper.C_VERSION: |
4096 | + if not sys.platform.startswith('java'): |
4097 | + warnings.warn( |
4098 | + "\nYou don't have the C version of NameMapper installed! " |
4099 | + "I'm disabling Cheetah's useStackFrames option as it is " |
4100 | + "painfully slow with the Python version of NameMapper. " |
4101 | + "You should get a copy of Cheetah with the compiled C version of NameMapper." |
4102 | + ) |
4103 | + self.setSetting('useStackFrames', False) |
4104 | + |
4105 | + self._compiled = False |
4106 | + self._moduleName = moduleName |
4107 | + if not mainClassName: |
4108 | + self._mainClassName = moduleName |
4109 | + else: |
4110 | + self._mainClassName = mainClassName |
4111 | + self._mainMethodNameArg = mainMethodName |
4112 | + if mainMethodName: |
4113 | + self.setSetting('mainMethodName', mainMethodName) |
4114 | + self._baseclassName = baseclassName |
4115 | + |
4116 | + self._filePath = None |
4117 | + self._fileMtime = None |
4118 | + |
4119 | + if source and file: |
4120 | + raise TypeError("Cannot compile from a source string AND file.") |
4121 | + elif isinstance(file, basestring): # it's a filename. |
4122 | + f = open(file) # Raises IOError. |
4123 | + source = f.read() |
4124 | + f.close() |
4125 | + self._filePath = file |
4126 | + self._fileMtime = os.path.getmtime(file) |
4127 | + elif hasattr(file, 'read'): |
4128 | + source = file.read() # Can't set filename or mtime--they're not accessible. |
4129 | + elif file: |
4130 | + raise TypeError("'file' argument must be a filename string or file-like object") |
4131 | + |
4132 | + if self._filePath: |
4133 | + self._fileDirName, self._fileBaseName = os.path.split(self._filePath) |
4134 | + self._fileBaseNameRoot, self._fileBaseNameExt = os.path.splitext(self._fileBaseName) |
4135 | + |
4136 | + if not isinstance(source, basestring): |
4137 | + source = unicode(source) |
4138 | + # by converting to string here we allow objects such as other Templates |
4139 | + # to be passed in |
4140 | + |
4141 | + # Handle the #indent directive by converting it to other directives. |
4142 | + # (Over the long term we'll make it a real directive.) |
4143 | + if source == "": |
4144 | + warnings.warn("You supplied an empty string for the source!", ) |
4145 | + |
4146 | + else: |
4147 | + unicodeMatch = unicodeDirectiveRE.search(source) |
4148 | + encodingMatch = encodingDirectiveRE.search(source) |
4149 | + if unicodeMatch: |
4150 | + if encodingMatch: |
4151 | + raise ParseError( |
4152 | + self, "#encoding and #unicode are mutually exclusive! " |
4153 | + "Use one or the other.") |
4154 | + source = unicodeDirectiveRE.sub('', source) |
4155 | + if isinstance(source, str): |
4156 | + encoding = unicodeMatch.group(1) or 'ascii' |
4157 | + source = unicode(source, encoding) |
4158 | + elif encodingMatch: |
4159 | + encodings = encodingMatch.groups() |
4160 | + if len(encodings): |
4161 | + encoding = encodings[0] |
4162 | + source = source.decode(encoding) |
4163 | + else: |
4164 | + source = unicode(source) |
4165 | + |
4166 | + if source.find('#indent') != -1: #@@TR: undocumented hack |
4167 | + source = indentize(source) |
4168 | + |
4169 | + self._parser = self.parserClass(source, filename=self._filePath, compiler=self) |
4170 | + self._setupCompilerState() |
4171 | + |
4172 | + def __getattr__(self, name): |
4173 | + """Provide one-way access to the methods and attributes of the |
4174 | + ClassCompiler, and thereby the MethodCompilers as well. |
4175 | + |
4176 | + WARNING: Use .setMethods to assign the attributes of the ClassCompiler |
4177 | + from the methods of this class!!! or you will be assigning to attributes |
4178 | + of this object instead. |
4179 | + """ |
4180 | + if name in self.__dict__: |
4181 | + return self.__dict__[name] |
4182 | + elif hasattr(self.__class__, name): |
4183 | + return getattr(self.__class__, name) |
4184 | + elif self._activeClassesList and hasattr(self._activeClassesList[-1], name): |
4185 | + return getattr(self._activeClassesList[-1], name) |
4186 | + else: |
4187 | + raise AttributeError(name) |
4188 | + |
4189 | + def _initializeSettings(self): |
4190 | + self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS)) |
4191 | + |
4192 | + def _setupCompilerState(self): |
4193 | + self._activeClassesList = [] |
4194 | + self._finishedClassesList = [] # listed by ordered |
4195 | + self._finishedClassIndex = {} # listed by name |
4196 | + self._moduleDef = None |
4197 | + self._moduleShBang = '#!/usr/bin/env python' |
4198 | + self._moduleEncoding = 'ascii' |
4199 | + self._moduleEncodingStr = '' |
4200 | + self._moduleHeaderLines = [] |
4201 | + self._moduleDocStringLines = [] |
4202 | + self._specialVars = {} |
4203 | + self._importStatements = [ |
4204 | + "import sys", |
4205 | + "import os", |
4206 | + "import os.path", |
4207 | + 'try:', |
4208 | + ' import builtins as builtin', |
4209 | + 'except ImportError:', |
4210 | + ' import __builtin__ as builtin', |
4211 | + "from os.path import getmtime, exists", |
4212 | + "import time", |
4213 | + "import types", |
4214 | + "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion", |
4215 | + "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple", |
4216 | + "from Cheetah.Template import Template", |
4217 | + "from Cheetah.DummyTransaction import *", |
4218 | + "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList", |
4219 | + "from Cheetah.CacheRegion import CacheRegion", |
4220 | + "import Cheetah.Filters as Filters", |
4221 | + "import Cheetah.ErrorCatchers as ErrorCatchers", |
4222 | + ] |
4223 | + |
4224 | + self._importedVarNames = ['sys', |
4225 | + 'os', |
4226 | + 'os.path', |
4227 | + 'time', |
4228 | + 'types', |
4229 | + 'Template', |
4230 | + 'DummyTransaction', |
4231 | + 'NotFound', |
4232 | + 'Filters', |
4233 | + 'ErrorCatchers', |
4234 | + 'CacheRegion', |
4235 | + ] |
4236 | + |
4237 | + self._moduleConstants = [ |
4238 | + "VFFSL=valueFromFrameOrSearchList", |
4239 | + "VFSL=valueFromSearchList", |
4240 | + "VFN=valueForName", |
4241 | + "currentTime=time.time", |
4242 | + ] |
4243 | + |
4244 | + def compile(self): |
4245 | + classCompiler = self._spawnClassCompiler(self._mainClassName) |
4246 | + if self._baseclassName: |
4247 | + classCompiler.setBaseClass(self._baseclassName) |
4248 | + self._addActiveClassCompiler(classCompiler) |
4249 | + self._parser.parse() |
4250 | + self._swallowClassCompiler(self._popActiveClassCompiler()) |
4251 | + self._compiled = True |
4252 | + self._parser.cleanup() |
4253 | + |
4254 | + def _spawnClassCompiler(self, className, klass=None): |
4255 | + if klass is None: |
4256 | + klass = self.classCompilerClass |
4257 | + classCompiler = klass(className, |
4258 | + moduleCompiler=self, |
4259 | + mainMethodName=self.setting('mainMethodName'), |
4260 | + fileName=self._filePath, |
4261 | + settingsManager=self, |
4262 | + ) |
4263 | + return classCompiler |
4264 | + |
4265 | + def _addActiveClassCompiler(self, classCompiler): |
4266 | + self._activeClassesList.append(classCompiler) |
4267 | + |
4268 | + def _getActiveClassCompiler(self): |
4269 | + return self._activeClassesList[-1] |
4270 | + |
4271 | + def _popActiveClassCompiler(self): |
4272 | + return self._activeClassesList.pop() |
4273 | + |
4274 | + def _swallowClassCompiler(self, classCompiler): |
4275 | + classCompiler.cleanupState() |
4276 | + self._finishedClassesList.append( classCompiler ) |
4277 | + self._finishedClassIndex[classCompiler.className()] = classCompiler |
4278 | + return classCompiler |
4279 | + |
4280 | + def _finishedClasses(self): |
4281 | + return self._finishedClassesList |
4282 | + |
4283 | + def importedVarNames(self): |
4284 | + return self._importedVarNames |
4285 | + |
4286 | + def addImportedVarNames(self, varNames, raw_statement=None): |
4287 | + settings = self.settings() |
4288 | + if not varNames: |
4289 | + return |
4290 | + if not settings.get('useLegacyImportMode'): |
4291 | + if raw_statement and getattr(self, '_methodBodyChunks'): |
4292 | + self.addChunk(raw_statement) |
4293 | + else: |
4294 | + self._importedVarNames.extend(varNames) |
4295 | + |
4296 | + ## methods for adding stuff to the module and class definitions |
4297 | + |
4298 | + def setBaseClass(self, baseClassName): |
4299 | + if self._mainMethodNameArg: |
4300 | + self.setMainMethodName(self._mainMethodNameArg) |
4301 | + else: |
4302 | + self.setMainMethodName(self.setting('mainMethodNameForSubclasses')) |
4303 | + |
4304 | + if self.setting('handlerForExtendsDirective'): |
4305 | + handler = self.setting('handlerForExtendsDirective') |
4306 | + baseClassName = handler(compiler=self, baseClassName=baseClassName) |
4307 | + self._getActiveClassCompiler().setBaseClass(baseClassName) |
4308 | + elif (not self.setting('autoImportForExtendsDirective') |
4309 | + or baseClassName=='object' or baseClassName in self.importedVarNames()): |
4310 | + self._getActiveClassCompiler().setBaseClass(baseClassName) |
4311 | + # no need to import |
4312 | + else: |
4313 | + ################################################## |
4314 | + ## If the #extends directive contains a classname or modulename that isn't |
4315 | + # in self.importedVarNames() already, we assume that we need to add |
4316 | + # an implied 'from ModName import ClassName' where ModName == ClassName. |
4317 | + # - This is the case in WebKit servlet modules. |
4318 | + # - We also assume that the final . separates the classname from the |
4319 | + # module name. This might break if people do something really fancy |
4320 | + # with their dots and namespaces. |
4321 | + baseclasses = baseClassName.split(',') |
4322 | + for klass in baseclasses: |
4323 | + chunks = klass.split('.') |
4324 | + if len(chunks)==1: |
4325 | + self._getActiveClassCompiler().setBaseClass(klass) |
4326 | + if klass not in self.importedVarNames(): |
4327 | + modName = klass |
4328 | + # we assume the class name to be the module name |
4329 | + # and that it's not a builtin: |
4330 | + importStatement = "from %s import %s" % (modName, klass) |
4331 | + self.addImportStatement(importStatement) |
4332 | + self.addImportedVarNames((klass,)) |
4333 | + else: |
4334 | + needToAddImport = True |
4335 | + modName = chunks[0] |
4336 | + #print chunks, ':', self.importedVarNames() |
4337 | + for chunk in chunks[1:-1]: |
4338 | + if modName in self.importedVarNames(): |
4339 | + needToAddImport = False |
4340 | + finalBaseClassName = klass.replace(modName+'.', '') |
4341 | + self._getActiveClassCompiler().setBaseClass(finalBaseClassName) |
4342 | + break |
4343 | + else: |
4344 | + modName += '.'+chunk |
4345 | + if needToAddImport: |
4346 | + modName, finalClassName = '.'.join(chunks[:-1]), chunks[-1] |
4347 | + #if finalClassName != chunks[:-1][-1]: |
4348 | + if finalClassName != chunks[-2]: |
4349 | + # we assume the class name to be the module name |
4350 | + modName = '.'.join(chunks) |
4351 | + self._getActiveClassCompiler().setBaseClass(finalClassName) |
4352 | + importStatement = "from %s import %s" % (modName, finalClassName) |
4353 | + self.addImportStatement(importStatement) |
4354 | + self.addImportedVarNames( [finalClassName,] ) |
4355 | + |
4356 | + def setCompilerSetting(self, key, valueExpr): |
4357 | + self.setSetting(key, eval(valueExpr) ) |
4358 | + self._parser.configureParser() |
4359 | + |
4360 | + def setCompilerSettings(self, keywords, settingsStr): |
4361 | + KWs = keywords |
4362 | + merge = True |
4363 | + if 'nomerge' in KWs: |
4364 | + merge = False |
4365 | + |
4366 | + if 'reset' in KWs: |
4367 | + # @@TR: this is actually caught by the parser at the moment. |
4368 | + # subject to change in the future |
4369 | + self._initializeSettings() |
4370 | + self._parser.configureParser() |
4371 | + return |
4372 | + elif 'python' in KWs: |
4373 | + settingsReader = self.updateSettingsFromPySrcStr |
4374 | + # this comes from SettingsManager |
4375 | + else: |
4376 | + # this comes from SettingsManager |
4377 | + settingsReader = self.updateSettingsFromConfigStr |
4378 | + |
4379 | + settingsReader(settingsStr) |
4380 | + self._parser.configureParser() |
4381 | + |
4382 | + def setShBang(self, shBang): |
4383 | + self._moduleShBang = shBang |
4384 | + |
4385 | + def setModuleEncoding(self, encoding): |
4386 | + self._moduleEncoding = encoding |
4387 | + |
4388 | + def getModuleEncoding(self): |
4389 | + return self._moduleEncoding |
4390 | + |
4391 | + def addModuleHeader(self, line): |
4392 | + """Adds a header comment to the top of the generated module. |
4393 | + """ |
4394 | + self._moduleHeaderLines.append(line) |
4395 | + |
4396 | + def addModuleDocString(self, line): |
4397 | + """Adds a line to the generated module docstring. |
4398 | + """ |
4399 | + self._moduleDocStringLines.append(line) |
4400 | + |
4401 | + def addModuleGlobal(self, line): |
4402 | + """Adds a line of global module code. It is inserted after the import |
4403 | + statements and Cheetah default module constants. |
4404 | + """ |
4405 | + self._moduleConstants.append(line) |
4406 | + |
4407 | + def addSpecialVar(self, basename, contents, includeUnderscores=True): |
4408 | + """Adds module __specialConstant__ to the module globals. |
4409 | + """ |
4410 | + name = includeUnderscores and '__'+basename+'__' or basename |
4411 | + self._specialVars[name] = contents.strip() |
4412 | + |
4413 | + def addImportStatement(self, impStatement): |
4414 | + settings = self.settings() |
4415 | + if not self._methodBodyChunks or settings.get('useLegacyImportMode'): |
4416 | + # In the case where we are importing inline in the middle of a source block |
4417 | + # we don't want to inadvertantly import the module at the top of the file either |
4418 | + self._importStatements.append(impStatement) |
4419 | + |
4420 | + #@@TR 2005-01-01: there's almost certainly a cleaner way to do this! |
4421 | + importVarNames = impStatement[impStatement.find('import') + len('import'):].split(',') |
4422 | + importVarNames = [var.split()[-1] for var in importVarNames] # handles aliases |
4423 | + importVarNames = [var for var in importVarNames if not var == '*'] |
4424 | + self.addImportedVarNames(importVarNames, raw_statement=impStatement) #used by #extend for auto-imports |
4425 | + |
4426 | + def addAttribute(self, attribName, expr): |
4427 | + self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr) |
4428 | + |
4429 | + def addComment(self, comm): |
4430 | + if re.match(r'#+$', comm): # skip bar comments |
4431 | + return |
4432 | + |
4433 | + specialVarMatch = specialVarRE.match(comm) |
4434 | + if specialVarMatch: |
4435 | + # @@TR: this is a bit hackish and is being replaced with |
4436 | + # #set module varName = ... |
4437 | + return self.addSpecialVar(specialVarMatch.group(1), |
4438 | + comm[specialVarMatch.end():]) |
4439 | + elif comm.startswith('doc:'): |
4440 | + addLine = self.addMethDocString |
4441 | + comm = comm[len('doc:'):].strip() |
4442 | + elif comm.startswith('doc-method:'): |
4443 | + addLine = self.addMethDocString |
4444 | + comm = comm[len('doc-method:'):].strip() |
4445 | + elif comm.startswith('doc-module:'): |
4446 | + addLine = self.addModuleDocString |
4447 | + comm = comm[len('doc-module:'):].strip() |
4448 | + elif comm.startswith('doc-class:'): |
4449 | + addLine = self.addClassDocString |
4450 | + comm = comm[len('doc-class:'):].strip() |
4451 | + elif comm.startswith('header:'): |
4452 | + addLine = self.addModuleHeader |
4453 | + comm = comm[len('header:'):].strip() |
4454 | + else: |
4455 | + addLine = self.addMethComment |
4456 | + |
4457 | + for line in comm.splitlines(): |
4458 | + addLine(line) |
4459 | + |
4460 | + ## methods for module code wrapping |
4461 | + |
4462 | + def getModuleCode(self): |
4463 | + if not self._compiled: |
4464 | + self.compile() |
4465 | + if self._moduleDef: |
4466 | + return self._moduleDef |
4467 | + else: |
4468 | + return self.wrapModuleDef() |
4469 | + |
4470 | + __str__ = getModuleCode |
4471 | + |
4472 | + def wrapModuleDef(self): |
4473 | + self.addSpecialVar('CHEETAH_docstring', self.setting('defDocStrMsg')) |
4474 | + self.addModuleGlobal('__CHEETAH_version__ = %r'%Version) |
4475 | + self.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple,)) |
4476 | + if self.setting('addTimestampsToCompilerOutput'): |
4477 | + self.addModuleGlobal('__CHEETAH_genTime__ = %r'%time.time()) |
4478 | + self.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self.timestamp()) |
4479 | + if self._filePath: |
4480 | + timestamp = self.timestamp(self._fileMtime) |
4481 | + self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath) |
4482 | + self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp) |
4483 | + else: |
4484 | + self.addModuleGlobal('__CHEETAH_src__ = None') |
4485 | + self.addModuleGlobal('__CHEETAH_srcLastModified__ = None') |
4486 | + |
4487 | + moduleDef = """%(header)s |
4488 | +%(docstring)s |
4489 | + |
4490 | +################################################## |
4491 | +## DEPENDENCIES |
4492 | +%(imports)s |
4493 | + |
4494 | +################################################## |
4495 | +## MODULE CONSTANTS |
4496 | +%(constants)s |
4497 | +%(specialVars)s |
4498 | + |
4499 | +if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: |
4500 | + raise AssertionError( |
4501 | + 'This template was compiled with Cheetah version' |
4502 | + ' %%s. Templates compiled before version %%s must be recompiled.'%%( |
4503 | + __CHEETAH_version__, RequiredCheetahVersion)) |
4504 | + |
4505 | +################################################## |
4506 | +## CLASSES |
4507 | + |
4508 | +%(classes)s |
4509 | + |
4510 | +## END CLASS DEFINITION |
4511 | + |
4512 | +if not hasattr(%(mainClassName)s, '_initCheetahAttributes'): |
4513 | + templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template) |
4514 | + templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s) |
4515 | + |
4516 | +%(footer)s |
4517 | +""" % {'header': self.moduleHeader(), |
4518 | + 'docstring': self.moduleDocstring(), |
4519 | + 'specialVars': self.specialVars(), |
4520 | + 'imports': self.importStatements(), |
4521 | + 'constants': self.moduleConstants(), |
4522 | + 'classes': self.classDefs(), |
4523 | + 'footer': self.moduleFooter(), |
4524 | + 'mainClassName': self._mainClassName, |
4525 | + } |
4526 | + |
4527 | + self._moduleDef = moduleDef |
4528 | + return moduleDef |
4529 | + |
4530 | + def timestamp(self, theTime=None): |
4531 | + if not theTime: |
4532 | + theTime = time.time() |
4533 | + return time.asctime(time.localtime(theTime)) |
4534 | + |
4535 | + def moduleHeader(self): |
4536 | + header = self._moduleShBang + '\n' |
4537 | + header += self._moduleEncodingStr + '\n' |
4538 | + if self._moduleHeaderLines: |
4539 | + offSet = self.setting('commentOffset') |
4540 | + |
4541 | + header += ( |
4542 | + '#' + ' '*offSet + |
4543 | + ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n') |
4544 | + |
4545 | + return header |
4546 | + |
4547 | + def moduleDocstring(self): |
4548 | + if not self._moduleDocStringLines: |
4549 | + return '' |
4550 | + |
4551 | + return ('"""' + |
4552 | + '\n'.join(self._moduleDocStringLines) + |
4553 | + '\n"""\n') |
4554 | + |
4555 | + def specialVars(self): |
4556 | + chunks = [] |
4557 | + theVars = self._specialVars |
4558 | + keys = sorted(theVars.keys()) |
4559 | + for key in keys: |
4560 | + chunks.append(key + ' = ' + repr(theVars[key]) ) |
4561 | + return '\n'.join(chunks) |
4562 | + |
4563 | + def importStatements(self): |
4564 | + return '\n'.join(self._importStatements) |
4565 | + |
4566 | + def moduleConstants(self): |
4567 | + return '\n'.join(self._moduleConstants) |
4568 | + |
4569 | + def classDefs(self): |
4570 | + classDefs = [klass.classDef() for klass in self._finishedClasses()] |
4571 | + return '\n\n'.join(classDefs) |
4572 | + |
4573 | + def moduleFooter(self): |
4574 | + return """ |
4575 | +# CHEETAH was developed by Tavis Rudd and Mike Orr |
4576 | +# with code, advice and input from many other volunteers. |
4577 | +# For more information visit http://www.CheetahTemplate.org/ |
4578 | + |
4579 | +################################################## |
4580 | +## if run from command line: |
4581 | +if __name__ == '__main__': |
4582 | + from Cheetah.TemplateCmdLineIface import CmdLineIface |
4583 | + CmdLineIface(templateObj=%(className)s()).run() |
4584 | + |
4585 | +""" % {'className':self._mainClassName} |
4586 | + |
4587 | + |
4588 | +################################################## |
4589 | +## Make Compiler an alias for ModuleCompiler |
4590 | + |
4591 | +Compiler = ModuleCompiler |
4592 | |
4593 | === added file 'cheetah/DirectiveAnalyzer.py' |
4594 | --- cheetah/DirectiveAnalyzer.py 1970-01-01 00:00:00 +0000 |
4595 | +++ cheetah/DirectiveAnalyzer.py 2010-10-19 20:12:51 +0000 |
4596 | @@ -0,0 +1,98 @@ |
4597 | +#!/usr/bin/env python |
4598 | + |
4599 | +import os |
4600 | +import pprint |
4601 | + |
4602 | +try: |
4603 | + from functools import reduce |
4604 | +except ImportError: |
4605 | + # Assume we have reduce |
4606 | + pass |
4607 | + |
4608 | +from Cheetah import Parser |
4609 | +from Cheetah import Compiler |
4610 | +from Cheetah import Template |
4611 | + |
4612 | +class Analyzer(Parser.Parser): |
4613 | + def __init__(self, *args, **kwargs): |
4614 | + self.calls = {} |
4615 | + super(Analyzer, self).__init__(*args, **kwargs) |
4616 | + |
4617 | + def eatDirective(self): |
4618 | + directive = self.matchDirective() |
4619 | + try: |
4620 | + self.calls[directive] += 1 |
4621 | + except KeyError: |
4622 | + self.calls[directive] = 1 |
4623 | + super(Analyzer, self).eatDirective() |
4624 | + |
4625 | +class AnalysisCompiler(Compiler.ModuleCompiler): |
4626 | + parserClass = Analyzer |
4627 | + |
4628 | + |
4629 | +def analyze(source): |
4630 | + klass = Template.Template.compile(source, compilerClass=AnalysisCompiler) |
4631 | + return klass._CHEETAH_compilerInstance._parser.calls |
4632 | + |
4633 | +def main_file(f): |
4634 | + fd = open(f, 'r') |
4635 | + try: |
4636 | + print u'>>> Analyzing %s' % f |
4637 | + calls = analyze(fd.read()) |
4638 | + return calls |
4639 | + finally: |
4640 | + fd.close() |
4641 | + |
4642 | + |
4643 | +def _find_templates(directory, suffix): |
4644 | + for root, dirs, files in os.walk(directory): |
4645 | + for f in files: |
4646 | + if not f.endswith(suffix): |
4647 | + continue |
4648 | + yield root + os.path.sep + f |
4649 | + |
4650 | +def _analyze_templates(iterable): |
4651 | + for template in iterable: |
4652 | + yield main_file(template) |
4653 | + |
4654 | +def main_dir(opts): |
4655 | + results = _analyze_templates(_find_templates(opts.dir, opts.suffix)) |
4656 | + totals = {} |
4657 | + for series in results: |
4658 | + if not series: |
4659 | + continue |
4660 | + for k, v in series.iteritems(): |
4661 | + try: |
4662 | + totals[k] += v |
4663 | + except KeyError: |
4664 | + totals[k] = v |
4665 | + return totals |
4666 | + |
4667 | + |
4668 | +def main(): |
4669 | + from optparse import OptionParser |
4670 | + op = OptionParser() |
4671 | + op.add_option('-f', '--file', dest='file', default=None, |
4672 | + help='Specify a single file to analyze') |
4673 | + op.add_option('-d', '--dir', dest='dir', default=None, |
4674 | + help='Specify a directory of templates to analyze') |
4675 | + op.add_option('--suffix', default='tmpl', dest='suffix', |
4676 | + help='Specify a custom template file suffix for the -d option (default: "tmpl")') |
4677 | + opts, args = op.parse_args() |
4678 | + |
4679 | + if not opts.file and not opts.dir: |
4680 | + op.print_help() |
4681 | + return |
4682 | + |
4683 | + results = None |
4684 | + if opts.file: |
4685 | + results = main_file(opts.file) |
4686 | + if opts.dir: |
4687 | + results = main_dir(opts) |
4688 | + |
4689 | + pprint.pprint(results) |
4690 | + |
4691 | + |
4692 | +if __name__ == '__main__': |
4693 | + main() |
4694 | + |
4695 | |
4696 | === added file 'cheetah/Django.py' |
4697 | --- cheetah/Django.py 1970-01-01 00:00:00 +0000 |
4698 | +++ cheetah/Django.py 2010-10-19 20:12:51 +0000 |
4699 | @@ -0,0 +1,16 @@ |
4700 | +import Cheetah.Template |
4701 | + |
4702 | +def render(template_file, **kwargs): |
4703 | + ''' |
4704 | + Cheetah.Django.render() takes the template filename |
4705 | + (the filename should be a file in your Django |
4706 | + TEMPLATE_DIRS) |
4707 | + |
4708 | + Any additional keyword arguments are passed into the |
4709 | + template are propogated into the template's searchList |
4710 | + ''' |
4711 | + import django.http |
4712 | + import django.template.loader |
4713 | + source, loader = django.template.loader.find_template_source(template_file) |
4714 | + t = Cheetah.Template.Template(source, searchList=[kwargs]) |
4715 | + return django.http.HttpResponse(t.__str__()) |
4716 | |
4717 | === added file 'cheetah/DummyTransaction.py' |
4718 | --- cheetah/DummyTransaction.py 1970-01-01 00:00:00 +0000 |
4719 | +++ cheetah/DummyTransaction.py 2010-10-19 20:12:51 +0000 |
4720 | @@ -0,0 +1,108 @@ |
4721 | + |
4722 | +''' |
4723 | +Provides dummy Transaction and Response classes is used by Cheetah in place |
4724 | +of real Webware transactions when the Template obj is not used directly as a |
4725 | +Webware servlet. |
4726 | + |
4727 | +Warning: This may be deprecated in the future, please do not rely on any |
4728 | +specific DummyTransaction or DummyResponse behavior |
4729 | +''' |
4730 | + |
4731 | +import logging |
4732 | +import types |
4733 | + |
4734 | +class DummyResponseFailure(Exception): |
4735 | + pass |
4736 | + |
4737 | +class DummyResponse(object): |
4738 | + ''' |
4739 | + A dummy Response class is used by Cheetah in place of real Webware |
4740 | + Response objects when the Template obj is not used directly as a Webware |
4741 | + servlet |
4742 | + ''' |
4743 | + def __init__(self): |
4744 | + self._outputChunks = [] |
4745 | + |
4746 | + def flush(self): |
4747 | + pass |
4748 | + |
4749 | + def safeConvert(self, chunk): |
4750 | + # Exceptionally gross, but the safest way |
4751 | + # I've found to ensure I get a legit unicode object |
4752 | + if not chunk: |
4753 | + return u'' |
4754 | + if isinstance(chunk, unicode): |
4755 | + return chunk |
4756 | + try: |
4757 | + return chunk.decode('utf-8', 'strict') |
4758 | + except UnicodeDecodeError: |
4759 | + try: |
4760 | + return chunk.decode('latin-1', 'strict') |
4761 | + except UnicodeDecodeError: |
4762 | + return chunk.decode('ascii', 'ignore') |
4763 | + except AttributeError: |
4764 | + return unicode(chunk, errors='ignore') |
4765 | + return chunk |
4766 | + |
4767 | + def write(self, value): |
4768 | + self._outputChunks.append(value) |
4769 | + |
4770 | + def writeln(self, txt): |
4771 | + write(txt) |
4772 | + write('\n') |
4773 | + |
4774 | + def getvalue(self, outputChunks=None): |
4775 | + chunks = outputChunks or self._outputChunks |
4776 | + try: |
4777 | + return u''.join(chunks) |
4778 | + except UnicodeDecodeError, ex: |
4779 | + logging.debug('Trying to work around a UnicodeDecodeError in getvalue()') |
4780 | + logging.debug('...perhaps you could fix "%s" while you\'re debugging') |
4781 | + return ''.join((self.safeConvert(c) for c in chunks)) |
4782 | + |
4783 | + def writelines(self, *lines): |
4784 | + ## not used |
4785 | + [self.writeln(ln) for ln in lines] |
4786 | + |
4787 | + |
4788 | +class DummyTransaction(object): |
4789 | + ''' |
4790 | + A dummy Transaction class is used by Cheetah in place of real Webware |
4791 | + transactions when the Template obj is not used directly as a Webware |
4792 | + servlet. |
4793 | + |
4794 | + It only provides a response object and method. All other methods and |
4795 | + attributes make no sense in this context. |
4796 | + ''' |
4797 | + def __init__(self, *args, **kwargs): |
4798 | + self._response = None |
4799 | + |
4800 | + def response(self, resp=None): |
4801 | + if self._response is None: |
4802 | + self._response = resp or DummyResponse() |
4803 | + return self._response |
4804 | + |
4805 | + |
4806 | +class TransformerResponse(DummyResponse): |
4807 | + def __init__(self, *args, **kwargs): |
4808 | + super(TransformerResponse, self).__init__(*args, **kwargs) |
4809 | + self._filter = None |
4810 | + |
4811 | + def getvalue(self, **kwargs): |
4812 | + output = super(TransformerResponse, self).getvalue(**kwargs) |
4813 | + if self._filter: |
4814 | + _filter = self._filter |
4815 | + if isinstance(_filter, type): |
4816 | + _filter = _filter() |
4817 | + return _filter.filter(output) |
4818 | + return output |
4819 | + |
4820 | + |
4821 | +class TransformerTransaction(object): |
4822 | + def __init__(self, *args, **kwargs): |
4823 | + self._response = None |
4824 | + def response(self): |
4825 | + if self._response: |
4826 | + return self._response |
4827 | + return TransformerResponse() |
4828 | + |
4829 | |
4830 | === added file 'cheetah/ErrorCatchers.py' |
4831 | --- cheetah/ErrorCatchers.py 1970-01-01 00:00:00 +0000 |
4832 | +++ cheetah/ErrorCatchers.py 2010-10-19 20:12:51 +0000 |
4833 | @@ -0,0 +1,62 @@ |
4834 | +# $Id: ErrorCatchers.py,v 1.7 2005/01/03 19:59:07 tavis_rudd Exp $ |
4835 | +"""ErrorCatcher class for Cheetah Templates |
4836 | + |
4837 | +Meta-Data |
4838 | +================================================================================ |
4839 | +Author: Tavis Rudd <tavis@damnsimple.com> |
4840 | +Version: $Revision: 1.7 $ |
4841 | +Start Date: 2001/08/01 |
4842 | +Last Revision Date: $Date: 2005/01/03 19:59:07 $ |
4843 | +""" |
4844 | +__author__ = "Tavis Rudd <tavis@damnsimple.com>" |
4845 | +__revision__ = "$Revision: 1.7 $"[11:-2] |
4846 | + |
4847 | +import time |
4848 | +from Cheetah.NameMapper import NotFound |
4849 | + |
4850 | +class Error(Exception): |
4851 | + pass |
4852 | + |
4853 | +class ErrorCatcher: |
4854 | + _exceptionsToCatch = (NotFound,) |
4855 | + |
4856 | + def __init__(self, templateObj): |
4857 | + pass |
4858 | + |
4859 | + def exceptions(self): |
4860 | + return self._exceptionsToCatch |
4861 | + |
4862 | + def warn(self, exc_val, code, rawCode, lineCol): |
4863 | + return rawCode |
4864 | +## make an alias |
4865 | +Echo = ErrorCatcher |
4866 | + |
4867 | +class BigEcho(ErrorCatcher): |
4868 | + def warn(self, exc_val, code, rawCode, lineCol): |
4869 | + return "="*15 + "<" + rawCode + " could not be found>" + "="*15 |
4870 | + |
4871 | +class KeyError(ErrorCatcher): |
4872 | + def warn(self, exc_val, code, rawCode, lineCol): |
4873 | + raise KeyError("no '%s' in this Template Object's Search List" % rawCode) |
4874 | + |
4875 | +class ListErrors(ErrorCatcher): |
4876 | + """Accumulate a list of errors.""" |
4877 | + _timeFormat = "%c" |
4878 | + |
4879 | + def __init__(self, templateObj): |
4880 | + ErrorCatcher.__init__(self, templateObj) |
4881 | + self._errors = [] |
4882 | + |
4883 | + def warn(self, exc_val, code, rawCode, lineCol): |
4884 | + dict = locals().copy() |
4885 | + del dict['self'] |
4886 | + dict['time'] = time.strftime(self._timeFormat, |
4887 | + time.localtime(time.time())) |
4888 | + self._errors.append(dict) |
4889 | + return rawCode |
4890 | + |
4891 | + def listErrors(self): |
4892 | + """Return the list of errors.""" |
4893 | + return self._errors |
4894 | + |
4895 | + |
4896 | |
4897 | === added file 'cheetah/FileUtils.py' |
4898 | --- cheetah/FileUtils.py 1970-01-01 00:00:00 +0000 |
4899 | +++ cheetah/FileUtils.py 2010-10-19 20:12:51 +0000 |
4900 | @@ -0,0 +1,357 @@ |
4901 | + |
4902 | +from glob import glob |
4903 | +import os |
4904 | +from os import listdir |
4905 | +import os.path |
4906 | +import re |
4907 | +from tempfile import mktemp |
4908 | + |
4909 | +def _escapeRegexChars(txt, |
4910 | + escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')): |
4911 | + return escapeRE.sub(r'\\\1', txt) |
4912 | + |
4913 | +def findFiles(*args, **kw): |
4914 | + """Recursively find all the files matching a glob pattern. |
4915 | + |
4916 | + This function is a wrapper around the FileFinder class. See its docstring |
4917 | + for details about the accepted arguments, etc.""" |
4918 | + |
4919 | + return FileFinder(*args, **kw).files() |
4920 | + |
4921 | +def replaceStrInFiles(files, theStr, repl): |
4922 | + |
4923 | + """Replace all instances of 'theStr' with 'repl' for each file in the 'files' |
4924 | + list. Returns a dictionary with data about the matches found. |
4925 | + |
4926 | + This is like string.replace() on a multi-file basis. |
4927 | + |
4928 | + This function is a wrapper around the FindAndReplace class. See its |
4929 | + docstring for more details.""" |
4930 | + |
4931 | + pattern = _escapeRegexChars(theStr) |
4932 | + return FindAndReplace(files, pattern, repl).results() |
4933 | + |
4934 | +def replaceRegexInFiles(files, pattern, repl): |
4935 | + |
4936 | + """Replace all instances of regex 'pattern' with 'repl' for each file in the |
4937 | + 'files' list. Returns a dictionary with data about the matches found. |
4938 | + |
4939 | + This is like re.sub on a multi-file basis. |
4940 | + |
4941 | + This function is a wrapper around the FindAndReplace class. See its |
4942 | + docstring for more details.""" |
4943 | + |
4944 | + return FindAndReplace(files, pattern, repl).results() |
4945 | + |
4946 | + |
4947 | +################################################## |
4948 | +## CLASSES |
4949 | + |
4950 | +class FileFinder: |
4951 | + |
4952 | + """Traverses a directory tree and finds all files in it that match one of |
4953 | + the specified glob patterns.""" |
4954 | + |
4955 | + def __init__(self, rootPath, |
4956 | + globPatterns=('*',), |
4957 | + ignoreBasenames=('CVS', '.svn'), |
4958 | + ignoreDirs=(), |
4959 | + ): |
4960 | + |
4961 | + self._rootPath = rootPath |
4962 | + self._globPatterns = globPatterns |
4963 | + self._ignoreBasenames = ignoreBasenames |
4964 | + self._ignoreDirs = ignoreDirs |
4965 | + self._files = [] |
4966 | + |
4967 | + self.walkDirTree(rootPath) |
4968 | + |
4969 | + def walkDirTree(self, dir='.', |
4970 | + |
4971 | + listdir=os.listdir, |
4972 | + isdir=os.path.isdir, |
4973 | + join=os.path.join, |
4974 | + ): |
4975 | + |
4976 | + """Recursively walk through a directory tree and find matching files.""" |
4977 | + processDir = self.processDir |
4978 | + filterDir = self.filterDir |
4979 | + |
4980 | + pendingDirs = [dir] |
4981 | + addDir = pendingDirs.append |
4982 | + getDir = pendingDirs.pop |
4983 | + |
4984 | + while pendingDirs: |
4985 | + dir = getDir() |
4986 | + ## process this dir |
4987 | + processDir(dir) |
4988 | + |
4989 | + ## and add sub-dirs |
4990 | + for baseName in listdir(dir): |
4991 | + fullPath = join(dir, baseName) |
4992 | + if isdir(fullPath): |
4993 | + if filterDir(baseName, fullPath): |
4994 | + addDir( fullPath ) |
4995 | + |
4996 | + def filterDir(self, baseName, fullPath): |
4997 | + |
4998 | + """A hook for filtering out certain dirs. """ |
4999 | + |
5000 | + return not (baseName in self._ignoreBasenames or |
The diff has been truncated for viewing.