Mercurial > cgi-bin > hgweb.cgi > tincan
comparison tincan.py @ 23:e8b6ee7e5b6b draft
Well, *that* attempt at includes didn't work. Revert.
author | David Barts <n5jrn@me.com> |
---|---|
date | Tue, 21 May 2019 18:01:44 -0700 |
parents | f6a1492fe56e |
children | 34d3cfcd37ef |
comparison
equal
deleted
inserted
replaced
22:f6a1492fe56e | 23:e8b6ee7e5b6b |
---|---|
16 from stat import S_ISDIR, S_ISREG | 16 from stat import S_ISDIR, S_ISREG |
17 from string import whitespace | 17 from string import whitespace |
18 import traceback | 18 import traceback |
19 import urllib | 19 import urllib |
20 | 20 |
21 from chameleon import PageTemplate, PageTemplateFile | |
22 import bottle | 21 import bottle |
23 | 22 |
24 # E x c e p t i o n s | 23 # E x c e p t i o n s |
25 | 24 |
26 class TinCanException(Exception): | 25 class TinCanException(Exception): |
165 param = ast.literal_eval(param) | 164 param = ast.literal_eval(param) |
166 break | 165 break |
167 # Update this object | 166 # Update this object |
168 setattr(self, name, param) | 167 setattr(self, name, param) |
169 | 168 |
170 # C h a m e l e o n ( B o t t l e ) | 169 # C h a m e l e o n |
171 # | 170 # |
172 # Support for Chameleon templates (the kind TinCan uses by default). This | 171 # Support for Chameleon templates (the kind TinCan uses by default). |
173 # allows the usage of Chameleon with Bottle itself. It is here as a | |
174 # convenience for those using Bottle routes as well as TinCan webapps; | |
175 # this is NOT how Tincan uses Chameleon | |
176 | 172 |
177 class ChameleonTemplate(bottle.BaseTemplate): | 173 class ChameleonTemplate(bottle.BaseTemplate): |
178 def prepare(self, **options): | 174 def prepare(self, **options): |
175 from chameleon import PageTemplate, PageTemplateFile | |
179 if self.source: | 176 if self.source: |
180 self.tpl = PageTemplate(self.source, encoding=self.encoding, | 177 self.tpl = PageTemplate(self.source, encoding=self.encoding, |
181 **options) | 178 **options) |
182 else: | 179 else: |
183 self.tpl = PageTemplateFile(self.filename, encoding=self.encoding, | 180 self.tpl = PageTemplateFile(self.filename, encoding=self.encoding, |
190 _defaults.update(kwargs) | 187 _defaults.update(kwargs) |
191 return self.tpl.render(**_defaults) | 188 return self.tpl.render(**_defaults) |
192 | 189 |
193 chameleon_template = functools.partial(bottle.template, template_adapter=ChameleonTemplate) | 190 chameleon_template = functools.partial(bottle.template, template_adapter=ChameleonTemplate) |
194 chameleon_view = functools.partial(bottle.view, template_adapter=ChameleonTemplate) | 191 chameleon_view = functools.partial(bottle.view, template_adapter=ChameleonTemplate) |
195 | |
196 # C h a m e l e o n ( T i n c a n ) | |
197 # | |
198 # How we use Chameleon ourselves. Everything is loaded as a string, and we | |
199 # provide convenience routines to load other templates. | |
200 | |
201 class TinCanChameleon(PageTemplate): | |
202 """ | |
203 Basically, a standard PageTemplate with load and lload functionality. | |
204 """ | |
205 def __init__(self, body, base=None, subdir=None, **config): | |
206 super(TinCanChameleon, self).__init__(body, **config) | |
207 if base is not None: | |
208 encoding = config.get("encoding", "utf-8") | |
209 if subdir is not None: | |
210 self.expression_types['load'] = \ | |
211 _LoaderFactory(base, subdir, encoding) | |
212 self.expression_types['lload'] = \ | |
213 _LoaderFactory(os.path.join(base,_WINF,"tlib"), [], encoding) | |
214 | |
215 class _LoaderFactory(object): | |
216 """ | |
217 One of two helper classes for the above. | |
218 """ | |
219 def __init__(self, base, subdir, encoding): | |
220 self.base = base | |
221 self.subdir = subdir | |
222 self.encoding = encoding | |
223 | |
224 def __call__(self, string): | |
225 return _Loader(self, string) | |
226 | |
227 class _Loader(object): | |
228 """ | |
229 Two of two helper classes for the above. | |
230 """ | |
231 def __init__(self, based_on, string): | |
232 if not (string.endswith(".pspx") or string.endswith(".pt")): | |
233 raise ValueError("loaded templates must end in .pspx or .pt") | |
234 self.path = string | |
235 self.params = based_on | |
236 | |
237 def __call__(self, target, engine): | |
238 try: | |
239 normalized = _normpath(self.params.subdir, self.path) | |
240 except IndexError: | |
241 raise ValueError("invalid path: {0!s}".format(self.path)) | |
242 npath = os.path.join(self.params.base, *normalized) | |
243 with open(npath, "r", encoding=self.params.encoding) as fp: | |
244 contents = fp.read() | |
245 value = ast.Str(contents) | |
246 return [ast.Assign(targets=[target], value=value)] | |
247 | 192 |
248 # U t i l i t i e s | 193 # U t i l i t i e s |
249 | 194 |
250 def _normpath(base, unsplit): | 195 def _normpath(base, unsplit): |
251 """ | 196 """ |
380 custom code-behind, only two variables are available to your template: | 325 custom code-behind, only two variables are available to your template: |
381 request (bottle.Request) and error (bottle.HTTPError). | 326 request (bottle.Request) and error (bottle.HTTPError). |
382 """ | 327 """ |
383 def __init__(self, template, klass): | 328 def __init__(self, template, klass): |
384 self._template = template | 329 self._template = template |
330 self._template.prepare() | |
385 self._class = klass | 331 self._class = klass |
386 | 332 |
387 def __call__(self, e): | 333 def __call__(self, e): |
388 bottle.request.environ[_FTYPE] = True | 334 bottle.request.environ[_FTYPE] = True |
389 try: | 335 try: |
390 obj = self._class(bottle.request, e) | 336 obj = self._class(bottle.request, e) |
391 obj.handle() | 337 obj.handle() |
392 return self._template.render(**obj.export()).lstrip('\n') | 338 return self._template.render(obj.export()).lstrip('\n') |
393 except bottle.HTTPResponse as e: | 339 except bottle.HTTPResponse as e: |
394 return e | 340 return e |
395 except Exception as e: | 341 except Exception as e: |
396 traceback.print_exc() | 342 traceback.print_exc() |
397 # Bottle doesn't allow error handlers to themselves cause | 343 # Bottle doesn't allow error handlers to themselves cause |
413 self._fspath = os.path.join(launcher.fsroot, *subdir, name + _TEXTEN) | 359 self._fspath = os.path.join(launcher.fsroot, *subdir, name + _TEXTEN) |
414 self._urlpath = self._urljoin(launcher.urlroot, *subdir, name + _TEXTEN) | 360 self._urlpath = self._urljoin(launcher.urlroot, *subdir, name + _TEXTEN) |
415 self._origin = self._urlpath | 361 self._origin = self._urlpath |
416 self._subdir = subdir | 362 self._subdir = subdir |
417 self._seen = set() | 363 self._seen = set() |
364 self._tclass = launcher.tclass | |
418 self._app = launcher.app | 365 self._app = launcher.app |
419 | 366 |
420 def launch(self): | 367 def launch(self): |
421 """ | 368 """ |
422 Launch a single page. | 369 Launch a single page. |
468 tfile = TemplateFile(tpath) | 415 tfile = TemplateFile(tpath) |
469 except OSError as e: | 416 except OSError as e: |
470 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e | 417 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e |
471 except IndexError as e: | 418 except IndexError as e: |
472 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e | 419 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e |
473 try: | 420 self._body = self._tclass(source=tfile.body) |
474 self._body = TinCanChameleon(tfile.body, base=self._fsroot, subdir=self._subdir) | |
475 except Exception as e: | |
476 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e | |
477 else: | 421 else: |
478 try: | 422 self._body = self._tclass(source=self._template.body) |
479 self._body = TinCanChameleon(self._template.body, self._fsroot, subdir=self._subdir) | 423 self._body.prepare() |
480 except Exception as e: | |
481 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e | |
482 # If this is an #errors page, register it as such. | 424 # If this is an #errors page, register it as such. |
483 if oheader.errors is not None: | 425 if oheader.errors is not None: |
484 self._mkerror(oheader.errors) | 426 self._mkerror(oheader.errors) |
485 return # this implies #hidden | 427 return # this implies #hidden |
486 # Get #methods for this route | 428 # Get #methods for this route |
502 errors = [ int(i) for i in rerrors.split() ] | 444 errors = [ int(i) for i in rerrors.split() ] |
503 except ValueError as e: | 445 except ValueError as e: |
504 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e | 446 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e |
505 if not errors: | 447 if not errors: |
506 errors = range(_ERRMIN, _ERRMAX+1) | 448 errors = range(_ERRMIN, _ERRMAX+1) |
507 try: | 449 route = _TinCanErrorRoute(self._tclass(source=self._template.body), self._class) |
508 template = TinCanChameleon(self._template.body, base=self._fsroot, subdir=self._subdir) | |
509 except Exception as e: | |
510 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e | |
511 route = _TinCanErrorRoute(template, self._class) | |
512 for error in errors: | 450 for error in errors: |
513 if error < _ERRMIN or error > _ERRMAX: | 451 if error < _ERRMIN or error > _ERRMAX: |
514 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) | 452 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) |
515 self._app.error_handler[error] = route # XXX | 453 self._app.error_handler[error] = route # XXX |
516 | 454 |
619 """ | 557 """ |
620 target = None | 558 target = None |
621 try: | 559 try: |
622 obj = self._class(bottle.request, bottle.response) | 560 obj = self._class(bottle.request, bottle.response) |
623 obj.handle() | 561 obj.handle() |
624 return self._body.render(**obj.export()).lstrip('\n') | 562 return self._body.render(obj.export()).lstrip('\n') |
625 except ForwardException as fwd: | 563 except ForwardException as fwd: |
626 target = fwd.target | 564 target = fwd.target |
627 except bottle.HTTPResponse as e: | 565 except bottle.HTTPResponse as e: |
628 return e | 566 return e |
629 except Exception as e: | 567 except Exception as e: |
669 | 607 |
670 class _Launcher(object): | 608 class _Launcher(object): |
671 """ | 609 """ |
672 Helper class for launching webapps. | 610 Helper class for launching webapps. |
673 """ | 611 """ |
674 def __init__(self, fsroot, urlroot, logger): | 612 def __init__(self, fsroot, urlroot, tclass, logger): |
675 """ | 613 """ |
676 Lightweight constructor. The real action happens in .launch() below. | 614 Lightweight constructor. The real action happens in .launch() below. |
677 """ | 615 """ |
678 self.fsroot = fsroot | 616 self.fsroot = fsroot |
679 self.urlroot = urlroot | 617 self.urlroot = urlroot |
618 self.tclass = tclass | |
680 self.logger = logger | 619 self.logger = logger |
681 self.app = None | 620 self.app = None |
682 self.errors = 0 | 621 self.errors = 0 |
683 self.debug = False | 622 self.debug = False |
684 | 623 |
739 | 678 |
740 def _logger(message): | 679 def _logger(message): |
741 sys.stderr.write(message) | 680 sys.stderr.write(message) |
742 sys.stderr.write('\n') | 681 sys.stderr.write('\n') |
743 | 682 |
744 def launch(fsroot=None, urlroot='/', logger=_logger): | 683 def launch(fsroot=None, urlroot='/', tclass=ChameleonTemplate, logger=_logger): |
745 """ | 684 """ |
746 Launch and return a TinCan webapp. Does not run the app; it is the | 685 Launch and return a TinCan webapp. Does not run the app; it is the |
747 caller's responsibility to call app.run() | 686 caller's responsibility to call app.run() |
748 """ | 687 """ |
749 if fsroot is None: | 688 if fsroot is None: |
750 fsroot = os.getcwd() | 689 fsroot = os.getcwd() |
751 launcher = _Launcher(fsroot, urlroot, logger) | 690 launcher = _Launcher(fsroot, urlroot, tclass, logger) |
752 # launcher.debug = True | 691 # launcher.debug = True |
753 launcher.launch() | 692 launcher.launch() |
754 return launcher.app, launcher.errors | 693 return launcher.app, launcher.errors |
755 | 694 |
756 # XXX - We cannot implement a command-line launcher here; see the | 695 # XXX - We cannot implement a command-line launcher here; see the |