1 | # $Id: CheetahWrapper.py,v 1.26 2007/10/02 01:22:04 tavis_rudd Exp $ |
---|
2 | """Cheetah command-line interface. |
---|
3 | |
---|
4 | 2002-09-03 MSO: Total rewrite. |
---|
5 | 2002-09-04 MSO: Bugfix, compile command was using wrong output ext. |
---|
6 | 2002-11-08 MSO: Another rewrite. |
---|
7 | |
---|
8 | Meta-Data |
---|
9 | ================================================================================ |
---|
10 | Author: Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>> |
---|
11 | Version: $Revision: 1.26 $ |
---|
12 | Start Date: 2001/03/30 |
---|
13 | Last Revision Date: $Date: 2007/10/02 01:22:04 $ |
---|
14 | """ |
---|
15 | __author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>" |
---|
16 | __revision__ = "$Revision: 1.26 $"[11:-2] |
---|
17 | |
---|
18 | import getopt, glob, os, pprint, re, shutil, sys |
---|
19 | import cPickle as pickle |
---|
20 | from optparse import OptionParser |
---|
21 | |
---|
22 | from Cheetah.Version import Version |
---|
23 | from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS |
---|
24 | from Cheetah.Utils.Misc import mkdirsWithPyInitFiles |
---|
25 | |
---|
26 | optionDashesRE = re.compile( R"^-{1,2}" ) |
---|
27 | moduleNameRE = re.compile( R"^[a-zA-Z_][a-zA-Z_0-9]*$" ) |
---|
28 | |
---|
29 | def fprintfMessage(stream, format, *args): |
---|
30 | if format[-1:] == '^': |
---|
31 | format = format[:-1] |
---|
32 | else: |
---|
33 | format += '\n' |
---|
34 | if args: |
---|
35 | message = format % args |
---|
36 | else: |
---|
37 | message = format |
---|
38 | stream.write(message) |
---|
39 | |
---|
40 | class Error(Exception): |
---|
41 | pass |
---|
42 | |
---|
43 | |
---|
44 | class Bundle: |
---|
45 | """Wrap the source, destination and backup paths in one neat little class. |
---|
46 | Used by CheetahWrapper.getBundles(). |
---|
47 | """ |
---|
48 | def __init__(self, **kw): |
---|
49 | self.__dict__.update(kw) |
---|
50 | |
---|
51 | def __repr__(self): |
---|
52 | return "<Bundle %r>" % self.__dict__ |
---|
53 | |
---|
54 | |
---|
55 | ################################################## |
---|
56 | ## USAGE FUNCTION & MESSAGES |
---|
57 | |
---|
58 | def usage(usageMessage, errorMessage="", out=sys.stderr): |
---|
59 | """Write help text, an optional error message, and abort the program. |
---|
60 | """ |
---|
61 | out.write(WRAPPER_TOP) |
---|
62 | out.write(usageMessage) |
---|
63 | exitStatus = 0 |
---|
64 | if errorMessage: |
---|
65 | out.write('\n') |
---|
66 | out.write("*** USAGE ERROR ***: %s\n" % errorMessage) |
---|
67 | exitStatus = 1 |
---|
68 | sys.exit(exitStatus) |
---|
69 | |
---|
70 | |
---|
71 | WRAPPER_TOP = """\ |
---|
72 | __ ____________ __ |
---|
73 | \ \/ \/ / |
---|
74 | \/ * * \/ CHEETAH %(Version)s Command-Line Tool |
---|
75 | \ | / |
---|
76 | \ ==----== / by Tavis Rudd <tavis@damnsimple.com> |
---|
77 | \__________/ and Mike Orr <sluggoster@gmail.com> |
---|
78 | |
---|
79 | """ % globals() |
---|
80 | |
---|
81 | |
---|
82 | HELP_PAGE1 = """\ |
---|
83 | USAGE: |
---|
84 | ------ |
---|
85 | cheetah compile [options] [FILES ...] : Compile template definitions |
---|
86 | cheetah fill [options] [FILES ...] : Fill template definitions |
---|
87 | cheetah help : Print this help message |
---|
88 | cheetah options : Print options help message |
---|
89 | cheetah test [options] : Run Cheetah's regression tests |
---|
90 | : (same as for unittest) |
---|
91 | cheetah version : Print Cheetah version number |
---|
92 | |
---|
93 | You may abbreviate the command to the first letter; e.g., 'h' == 'help'. |
---|
94 | If FILES is a single "-", read standard input and write standard output. |
---|
95 | Run "cheetah options" for the list of valid options. |
---|
96 | """ |
---|
97 | |
---|
98 | ################################################## |
---|
99 | ## CheetahWrapper CLASS |
---|
100 | |
---|
101 | class CheetahWrapper(object): |
---|
102 | MAKE_BACKUPS = True |
---|
103 | BACKUP_SUFFIX = ".bak" |
---|
104 | _templateClass = None |
---|
105 | _compilerSettings = None |
---|
106 | |
---|
107 | def __init__(self): |
---|
108 | self.progName = None |
---|
109 | self.command = None |
---|
110 | self.opts = None |
---|
111 | self.pathArgs = None |
---|
112 | self.sourceFiles = [] |
---|
113 | self.searchList = [] |
---|
114 | self.parser = None |
---|
115 | |
---|
116 | ################################################## |
---|
117 | ## MAIN ROUTINE |
---|
118 | |
---|
119 | def main(self, argv=None): |
---|
120 | """The main program controller.""" |
---|
121 | |
---|
122 | if argv is None: |
---|
123 | argv = sys.argv |
---|
124 | |
---|
125 | # Step 1: Determine the command and arguments. |
---|
126 | try: |
---|
127 | self.progName = progName = os.path.basename(argv[0]) |
---|
128 | self.command = command = optionDashesRE.sub("", argv[1]) |
---|
129 | if command == 'test': |
---|
130 | self.testOpts = argv[2:] |
---|
131 | else: |
---|
132 | self.parseOpts(argv[2:]) |
---|
133 | except IndexError: |
---|
134 | usage(HELP_PAGE1, "not enough command-line arguments") |
---|
135 | |
---|
136 | # Step 2: Call the command |
---|
137 | meths = (self.compile, self.fill, self.help, self.options, |
---|
138 | self.test, self.version) |
---|
139 | for meth in meths: |
---|
140 | methName = meth.__name__ |
---|
141 | # Or meth.im_func.func_name |
---|
142 | # Or meth.func_name (Python >= 2.1 only, sometimes works on 2.0) |
---|
143 | methInitial = methName[0] |
---|
144 | if command in (methName, methInitial): |
---|
145 | sys.argv[0] += (" " + methName) |
---|
146 | # @@MO: I don't necessarily agree sys.argv[0] should be |
---|
147 | # modified. |
---|
148 | meth() |
---|
149 | return |
---|
150 | # If none of the commands matched. |
---|
151 | usage(HELP_PAGE1, "unknown command '%s'" % command) |
---|
152 | |
---|
153 | def parseOpts(self, args): |
---|
154 | C, D, W = self.chatter, self.debug, self.warn |
---|
155 | self.isCompile = isCompile = self.command[0] == 'c' |
---|
156 | defaultOext = isCompile and ".py" or ".html" |
---|
157 | self.parser = OptionParser() |
---|
158 | pao = self.parser.add_option |
---|
159 | pao("--idir", action="store", dest="idir", default='', help='Input directory (defaults to current directory)') |
---|
160 | pao("--odir", action="store", dest="odir", default="", help='Output directory (defaults to current directory)') |
---|
161 | pao("--iext", action="store", dest="iext", default=".tmpl", help='File input extension (defaults: compile: .tmpl, fill: .tmpl)') |
---|
162 | pao("--oext", action="store", dest="oext", default=defaultOext, help='File output extension (defaults: compile: .py, fill: .html)') |
---|
163 | pao("-R", action="store_true", dest="recurse", default=False, help='Recurse through subdirectories looking for input files') |
---|
164 | pao("--stdout", "-p", action="store_true", dest="stdout", default=False, help='Verbosely print informational messages to stdout') |
---|
165 | pao("--debug", action="store_true", dest="debug", default=False, help='Print diagnostic/debug information to stderr') |
---|
166 | pao("--env", action="store_true", dest="env", default=False, help='Pass the environment into the search list') |
---|
167 | pao("--pickle", action="store", dest="pickle", default="", help='Unpickle FILE and pass it through in the search list') |
---|
168 | pao("--flat", action="store_true", dest="flat", default=False, help='Do not build destination subdirectories') |
---|
169 | pao("--nobackup", action="store_true", dest="nobackup", default=False, help='Do not make backup files when generating new ones') |
---|
170 | pao("--settings", action="store", dest="compilerSettingsString", default=None, help='String of compiler settings to pass through, e.g. --settings="useNameMapper=False,useFilters=False"') |
---|
171 | pao('--print-settings', action='store_true', dest='print_settings', help='Print out the list of available compiler settings') |
---|
172 | pao("--templateAPIClass", action="store", dest="templateClassName", default=None, help='Name of a subclass of Cheetah.Template.Template to use for compilation, e.g. MyTemplateClass') |
---|
173 | pao("--parallel", action="store", type="int", dest="parallel", default=1, help='Compile/fill templates in parallel, e.g. --parallel=4') |
---|
174 | 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"') |
---|
175 | |
---|
176 | opts, files = self.parser.parse_args(args) |
---|
177 | self.opts = opts |
---|
178 | if sys.platform == "win32": |
---|
179 | new_files = [] |
---|
180 | for spec in files: |
---|
181 | file_list = glob.glob(spec) |
---|
182 | if file_list: |
---|
183 | new_files.extend(file_list) |
---|
184 | else: |
---|
185 | new_files.append(spec) |
---|
186 | files = new_files |
---|
187 | self.pathArgs = files |
---|
188 | |
---|
189 | D("""\ |
---|
190 | cheetah compile %s |
---|
191 | Options are |
---|
192 | %s |
---|
193 | Files are %s""", args, pprint.pformat(vars(opts)), files) |
---|
194 | |
---|
195 | |
---|
196 | if opts.print_settings: |
---|
197 | print |
---|
198 | print '>> Available Cheetah compiler settings:' |
---|
199 | from Cheetah.Compiler import _DEFAULT_COMPILER_SETTINGS |
---|
200 | listing = _DEFAULT_COMPILER_SETTINGS |
---|
201 | listing.sort(key=lambda l: l[0][0].lower()) |
---|
202 | |
---|
203 | for l in listing: |
---|
204 | print '\t%s (default: "%s")\t%s' % l |
---|
205 | sys.exit(0) |
---|
206 | |
---|
207 | #cleanup trailing path separators |
---|
208 | seps = [sep for sep in [os.sep, os.altsep] if sep] |
---|
209 | for attr in ['idir', 'odir']: |
---|
210 | for sep in seps: |
---|
211 | path = getattr(opts, attr, None) |
---|
212 | if path and path.endswith(sep): |
---|
213 | path = path[:-len(sep)] |
---|
214 | setattr(opts, attr, path) |
---|
215 | break |
---|
216 | |
---|
217 | self._fixExts() |
---|
218 | if opts.env: |
---|
219 | self.searchList.insert(0, os.environ) |
---|
220 | if opts.pickle: |
---|
221 | f = open(opts.pickle, 'rb') |
---|
222 | unpickled = pickle.load(f) |
---|
223 | f.close() |
---|
224 | self.searchList.insert(0, unpickled) |
---|
225 | opts.verbose = not opts.stdout |
---|
226 | |
---|
227 | ################################################## |
---|
228 | ## COMMAND METHODS |
---|
229 | |
---|
230 | def compile(self): |
---|
231 | self._compileOrFill() |
---|
232 | |
---|
233 | def fill(self): |
---|
234 | from Cheetah.ImportHooks import install |
---|
235 | install() |
---|
236 | self._compileOrFill() |
---|
237 | |
---|
238 | def help(self): |
---|
239 | usage(HELP_PAGE1, "", sys.stdout) |
---|
240 | |
---|
241 | def options(self): |
---|
242 | return self.parser.print_help() |
---|
243 | |
---|
244 | def test(self): |
---|
245 | # @@MO: Ugly kludge. |
---|
246 | TEST_WRITE_FILENAME = 'cheetah_test_file_creation_ability.tmp' |
---|
247 | try: |
---|
248 | f = open(TEST_WRITE_FILENAME, 'w') |
---|
249 | except: |
---|
250 | sys.exit("""\ |
---|
251 | Cannot run the tests because you don't have write permission in the current |
---|
252 | directory. The tests need to create temporary files. Change to a directory |
---|
253 | you do have write permission to and re-run the tests.""") |
---|
254 | else: |
---|
255 | f.close() |
---|
256 | os.remove(TEST_WRITE_FILENAME) |
---|
257 | # @@MO: End ugly kludge. |
---|
258 | from Cheetah.Tests import Test |
---|
259 | import unittest |
---|
260 | verbosity = 1 |
---|
261 | if '-q' in self.testOpts: |
---|
262 | verbosity = 0 |
---|
263 | if '-v' in self.testOpts: |
---|
264 | verbosity = 2 |
---|
265 | runner = unittest.TextTestRunner(verbosity=verbosity) |
---|
266 | runner.run(unittest.TestSuite(Test.suites)) |
---|
267 | |
---|
268 | def version(self): |
---|
269 | print Version |
---|
270 | |
---|
271 | # If you add a command, also add it to the 'meths' variable in main(). |
---|
272 | |
---|
273 | ################################################## |
---|
274 | ## LOGGING METHODS |
---|
275 | |
---|
276 | def chatter(self, format, *args): |
---|
277 | """Print a verbose message to stdout. But don't if .opts.stdout is |
---|
278 | true or .opts.verbose is false. |
---|
279 | """ |
---|
280 | if self.opts.stdout or not self.opts.verbose: |
---|
281 | return |
---|
282 | fprintfMessage(sys.stdout, format, *args) |
---|
283 | |
---|
284 | |
---|
285 | def debug(self, format, *args): |
---|
286 | """Print a debugging message to stderr, but don't if .debug is |
---|
287 | false. |
---|
288 | """ |
---|
289 | if self.opts.debug: |
---|
290 | fprintfMessage(sys.stderr, format, *args) |
---|
291 | |
---|
292 | def warn(self, format, *args): |
---|
293 | """Always print a warning message to stderr. |
---|
294 | """ |
---|
295 | fprintfMessage(sys.stderr, format, *args) |
---|
296 | |
---|
297 | def error(self, format, *args): |
---|
298 | """Always print a warning message to stderr and exit with an error code. |
---|
299 | """ |
---|
300 | fprintfMessage(sys.stderr, format, *args) |
---|
301 | sys.exit(1) |
---|
302 | |
---|
303 | ################################################## |
---|
304 | ## HELPER METHODS |
---|
305 | |
---|
306 | |
---|
307 | def _fixExts(self): |
---|
308 | assert self.opts.oext, "oext is empty!" |
---|
309 | iext, oext = self.opts.iext, self.opts.oext |
---|
310 | if iext and not iext.startswith("."): |
---|
311 | self.opts.iext = "." + iext |
---|
312 | if oext and not oext.startswith("."): |
---|
313 | self.opts.oext = "." + oext |
---|
314 | |
---|
315 | |
---|
316 | |
---|
317 | def _compileOrFill(self): |
---|
318 | C, D, W = self.chatter, self.debug, self.warn |
---|
319 | opts, files = self.opts, self.pathArgs |
---|
320 | if files == ["-"]: |
---|
321 | self._compileOrFillStdin() |
---|
322 | return |
---|
323 | elif not files and opts.recurse: |
---|
324 | which = opts.idir and "idir" or "current" |
---|
325 | C("Drilling down recursively from %s directory.", which) |
---|
326 | sourceFiles = [] |
---|
327 | dir = os.path.join(self.opts.idir, os.curdir) |
---|
328 | os.path.walk(dir, self._expandSourceFilesWalk, sourceFiles) |
---|
329 | elif not files: |
---|
330 | usage(HELP_PAGE1, "Neither files nor -R specified!") |
---|
331 | else: |
---|
332 | sourceFiles = self._expandSourceFiles(files, opts.recurse, True) |
---|
333 | sourceFiles = [os.path.normpath(x) for x in sourceFiles] |
---|
334 | D("All source files found: %s", sourceFiles) |
---|
335 | bundles = self._getBundles(sourceFiles) |
---|
336 | D("All bundles: %s", pprint.pformat(bundles)) |
---|
337 | if self.opts.flat: |
---|
338 | self._checkForCollisions(bundles) |
---|
339 | |
---|
340 | # In parallel mode a new process is forked for each template |
---|
341 | # compilation, out of a pool of size self.opts.parallel. This is not |
---|
342 | # really optimal in all cases (e.g. probably wasteful for small |
---|
343 | # templates), but seems to work well in real life for me. |
---|
344 | # |
---|
345 | # It also won't work for Windows users, but I'm not going to lose any |
---|
346 | # sleep over that. |
---|
347 | if self.opts.parallel > 1: |
---|
348 | bad_child_exit = 0 |
---|
349 | pid_pool = set() |
---|
350 | |
---|
351 | def child_wait(): |
---|
352 | pid, status = os.wait() |
---|
353 | pid_pool.remove(pid) |
---|
354 | return os.WEXITSTATUS(status) |
---|
355 | |
---|
356 | while bundles: |
---|
357 | b = bundles.pop() |
---|
358 | pid = os.fork() |
---|
359 | if pid: |
---|
360 | pid_pool.add(pid) |
---|
361 | else: |
---|
362 | self._compileOrFillBundle(b) |
---|
363 | sys.exit(0) |
---|
364 | |
---|
365 | if len(pid_pool) == self.opts.parallel: |
---|
366 | bad_child_exit = child_wait() |
---|
367 | if bad_child_exit: |
---|
368 | break |
---|
369 | |
---|
370 | while pid_pool: |
---|
371 | child_exit = child_wait() |
---|
372 | if not bad_child_exit: |
---|
373 | bad_child_exit = child_exit |
---|
374 | |
---|
375 | if bad_child_exit: |
---|
376 | sys.exit("Child process failed, exited with code %d" % bad_child_exit) |
---|
377 | |
---|
378 | else: |
---|
379 | for b in bundles: |
---|
380 | self._compileOrFillBundle(b) |
---|
381 | |
---|
382 | def _checkForCollisions(self, bundles): |
---|
383 | """Check for multiple source paths writing to the same destination |
---|
384 | path. |
---|
385 | """ |
---|
386 | C, D, W = self.chatter, self.debug, self.warn |
---|
387 | isError = False |
---|
388 | dstSources = {} |
---|
389 | for b in bundles: |
---|
390 | if dstSources.has_key(b.dst): |
---|
391 | dstSources[b.dst].append(b.src) |
---|
392 | else: |
---|
393 | dstSources[b.dst] = [b.src] |
---|
394 | keys = dstSources.keys() |
---|
395 | keys.sort() |
---|
396 | for dst in keys: |
---|
397 | sources = dstSources[dst] |
---|
398 | if len(sources) > 1: |
---|
399 | isError = True |
---|
400 | sources.sort() |
---|
401 | fmt = "Collision: multiple source files %s map to one destination file %s" |
---|
402 | W(fmt, sources, dst) |
---|
403 | if isError: |
---|
404 | what = self.isCompile and "Compilation" or "Filling" |
---|
405 | sys.exit("%s aborted due to collisions" % what) |
---|
406 | |
---|
407 | |
---|
408 | def _expandSourceFilesWalk(self, arg, dir, files): |
---|
409 | """Recursion extension for .expandSourceFiles(). |
---|
410 | This method is a callback for os.path.walk(). |
---|
411 | 'arg' is a list to which successful paths will be appended. |
---|
412 | """ |
---|
413 | iext = self.opts.iext |
---|
414 | for f in files: |
---|
415 | path = os.path.join(dir, f) |
---|
416 | if path.endswith(iext) and os.path.isfile(path): |
---|
417 | arg.append(path) |
---|
418 | elif os.path.islink(path) and os.path.isdir(path): |
---|
419 | os.path.walk(path, self._expandSourceFilesWalk, arg) |
---|
420 | # If is directory, do nothing; 'walk' will eventually get it. |
---|
421 | |
---|
422 | |
---|
423 | def _expandSourceFiles(self, files, recurse, addIextIfMissing): |
---|
424 | """Calculate source paths from 'files' by applying the |
---|
425 | command-line options. |
---|
426 | """ |
---|
427 | C, D, W = self.chatter, self.debug, self.warn |
---|
428 | idir = self.opts.idir |
---|
429 | iext = self.opts.iext |
---|
430 | files = [] |
---|
431 | for f in self.pathArgs: |
---|
432 | oldFilesLen = len(files) |
---|
433 | D("Expanding %s", f) |
---|
434 | path = os.path.join(idir, f) |
---|
435 | pathWithExt = path + iext # May or may not be valid. |
---|
436 | if os.path.isdir(path): |
---|
437 | if recurse: |
---|
438 | os.path.walk(path, self._expandSourceFilesWalk, files) |
---|
439 | else: |
---|
440 | raise Error("source file '%s' is a directory" % path) |
---|
441 | elif os.path.isfile(path): |
---|
442 | files.append(path) |
---|
443 | elif (addIextIfMissing and not path.endswith(iext) and |
---|
444 | os.path.isfile(pathWithExt)): |
---|
445 | files.append(pathWithExt) |
---|
446 | # Do not recurse directories discovered by iext appending. |
---|
447 | elif os.path.exists(path): |
---|
448 | W("Skipping source file '%s', not a plain file.", path) |
---|
449 | else: |
---|
450 | W("Skipping source file '%s', not found.", path) |
---|
451 | if len(files) > oldFilesLen: |
---|
452 | D(" ... found %s", files[oldFilesLen:]) |
---|
453 | return files |
---|
454 | |
---|
455 | |
---|
456 | def _getBundles(self, sourceFiles): |
---|
457 | flat = self.opts.flat |
---|
458 | idir = self.opts.idir |
---|
459 | iext = self.opts.iext |
---|
460 | nobackup = self.opts.nobackup |
---|
461 | odir = self.opts.odir |
---|
462 | oext = self.opts.oext |
---|
463 | idirSlash = idir + os.sep |
---|
464 | bundles = [] |
---|
465 | for src in sourceFiles: |
---|
466 | # 'base' is the subdirectory plus basename. |
---|
467 | base = src |
---|
468 | if idir and src.startswith(idirSlash): |
---|
469 | base = src[len(idirSlash):] |
---|
470 | if iext and base.endswith(iext): |
---|
471 | base = base[:-len(iext)] |
---|
472 | basename = os.path.basename(base) |
---|
473 | if flat: |
---|
474 | dst = os.path.join(odir, basename + oext) |
---|
475 | else: |
---|
476 | dbn = basename |
---|
477 | if odir and base.startswith(os.sep): |
---|
478 | odd = odir |
---|
479 | while odd != '': |
---|
480 | idx = base.find(odd) |
---|
481 | if idx == 0: |
---|
482 | dbn = base[len(odd):] |
---|
483 | if dbn[0] == '/': |
---|
484 | dbn = dbn[1:] |
---|
485 | break |
---|
486 | odd = os.path.dirname(odd) |
---|
487 | if odd == '/': |
---|
488 | break |
---|
489 | dst = os.path.join(odir, dbn + oext) |
---|
490 | else: |
---|
491 | dst = os.path.join(odir, base + oext) |
---|
492 | bak = dst + self.BACKUP_SUFFIX |
---|
493 | b = Bundle(src=src, dst=dst, bak=bak, base=base, basename=basename) |
---|
494 | bundles.append(b) |
---|
495 | return bundles |
---|
496 | |
---|
497 | |
---|
498 | def _getTemplateClass(self): |
---|
499 | C, D, W = self.chatter, self.debug, self.warn |
---|
500 | modname = None |
---|
501 | if self._templateClass: |
---|
502 | return self._templateClass |
---|
503 | |
---|
504 | modname = self.opts.templateClassName |
---|
505 | |
---|
506 | if not modname: |
---|
507 | return Template |
---|
508 | p = modname.rfind('.') |
---|
509 | if ':' not in modname: |
---|
510 | self.error('The value of option --templateAPIClass is invalid\n' |
---|
511 | 'It must be in the form "module:class", ' |
---|
512 | 'e.g. "Cheetah.Template:Template"') |
---|
513 | |
---|
514 | modname, classname = modname.split(':') |
---|
515 | |
---|
516 | C('using --templateAPIClass=%s:%s'%(modname, classname)) |
---|
517 | |
---|
518 | if p >= 0: |
---|
519 | mod = getattr(__import__(modname[:p], {}, {}, [modname[p+1:]]), modname[p+1:]) |
---|
520 | else: |
---|
521 | mod = __import__(modname, {}, {}, []) |
---|
522 | |
---|
523 | klass = getattr(mod, classname, None) |
---|
524 | if klass: |
---|
525 | self._templateClass = klass |
---|
526 | return klass |
---|
527 | else: |
---|
528 | self.error('**Template class specified in option --templateAPIClass not found\n' |
---|
529 | '**Falling back on Cheetah.Template:Template') |
---|
530 | |
---|
531 | |
---|
532 | def _getCompilerSettings(self): |
---|
533 | if self._compilerSettings: |
---|
534 | return self._compilerSettings |
---|
535 | |
---|
536 | def getkws(**kws): |
---|
537 | return kws |
---|
538 | if self.opts.compilerSettingsString: |
---|
539 | try: |
---|
540 | exec 'settings = getkws(%s)'%self.opts.compilerSettingsString |
---|
541 | except: |
---|
542 | self.error("There's an error in your --settings option." |
---|
543 | "It must be valid Python syntax.\n" |
---|
544 | +" --settings='%s'\n"%self.opts.compilerSettingsString |
---|
545 | +" %s: %s"%sys.exc_info()[:2] |
---|
546 | ) |
---|
547 | |
---|
548 | validKeys = DEFAULT_COMPILER_SETTINGS.keys() |
---|
549 | if [k for k in settings.keys() if k not in validKeys]: |
---|
550 | self.error( |
---|
551 | 'The --setting "%s" is not a valid compiler setting name.'%k) |
---|
552 | |
---|
553 | self._compilerSettings = settings |
---|
554 | return settings |
---|
555 | else: |
---|
556 | return {} |
---|
557 | |
---|
558 | def _compileOrFillStdin(self): |
---|
559 | TemplateClass = self._getTemplateClass() |
---|
560 | compilerSettings = self._getCompilerSettings() |
---|
561 | if self.isCompile: |
---|
562 | pysrc = TemplateClass.compile(file=sys.stdin, |
---|
563 | compilerSettings=compilerSettings, |
---|
564 | returnAClass=False) |
---|
565 | output = pysrc |
---|
566 | else: |
---|
567 | output = str(TemplateClass(file=sys.stdin, compilerSettings=compilerSettings)) |
---|
568 | sys.stdout.write(output) |
---|
569 | |
---|
570 | def _compileOrFillBundle(self, b): |
---|
571 | C, D, W = self.chatter, self.debug, self.warn |
---|
572 | TemplateClass = self._getTemplateClass() |
---|
573 | compilerSettings = self._getCompilerSettings() |
---|
574 | src = b.src |
---|
575 | dst = b.dst |
---|
576 | base = b.base |
---|
577 | basename = b.basename |
---|
578 | dstDir = os.path.dirname(dst) |
---|
579 | what = self.isCompile and "Compiling" or "Filling" |
---|
580 | C("%s %s -> %s^", what, src, dst) # No trailing newline. |
---|
581 | if os.path.exists(dst) and not self.opts.nobackup: |
---|
582 | bak = b.bak |
---|
583 | C(" (backup %s)", bak) # On same line as previous message. |
---|
584 | else: |
---|
585 | bak = None |
---|
586 | C("") |
---|
587 | if self.isCompile: |
---|
588 | if not moduleNameRE.match(basename): |
---|
589 | tup = basename, src |
---|
590 | raise Error("""\ |
---|
591 | %s: base name %s contains invalid characters. It must |
---|
592 | be named according to the same rules as Python modules.""" % tup) |
---|
593 | pysrc = TemplateClass.compile(file=src, returnAClass=False, |
---|
594 | moduleName=basename, |
---|
595 | className=basename, |
---|
596 | commandlineopts=self.opts, |
---|
597 | compilerSettings=compilerSettings) |
---|
598 | output = pysrc |
---|
599 | else: |
---|
600 | #output = str(TemplateClass(file=src, searchList=self.searchList)) |
---|
601 | tclass = TemplateClass.compile(file=src, compilerSettings=compilerSettings) |
---|
602 | output = str(tclass(searchList=self.searchList)) |
---|
603 | |
---|
604 | if bak: |
---|
605 | shutil.copyfile(dst, bak) |
---|
606 | if dstDir and not os.path.exists(dstDir): |
---|
607 | if self.isCompile: |
---|
608 | mkdirsWithPyInitFiles(dstDir) |
---|
609 | else: |
---|
610 | os.makedirs(dstDir) |
---|
611 | if self.opts.stdout: |
---|
612 | sys.stdout.write(output) |
---|
613 | else: |
---|
614 | f = open(dst, 'w') |
---|
615 | f.write(output) |
---|
616 | f.close() |
---|
617 | |
---|
618 | |
---|
619 | # Called when invoked as `cheetah` |
---|
620 | def _cheetah(): |
---|
621 | CheetahWrapper().main() |
---|
622 | |
---|
623 | # Called when invoked as `cheetah-compile` |
---|
624 | def _cheetah_compile(): |
---|
625 | sys.argv.insert(1, "compile") |
---|
626 | CheetahWrapper().main() |
---|
627 | |
---|
628 | |
---|
629 | ################################################## |
---|
630 | ## if run from the command line |
---|
631 | if __name__ == '__main__': CheetahWrapper().main() |
---|
632 | |
---|
633 | # vim: shiftwidth=4 tabstop=4 expandtab |
---|