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