comparison tincan.py @ 35:41da0b3d2156 draft header-includes

Rename #include to #load (more descriptive).
author David Barts <n5jrn@me.com>
date Tue, 28 May 2019 15:49:59 -0700
parents 0fb455b46e5f
children 4ed261056057
comparison
equal deleted inserted replaced
34:0fb455b46e5f 35:41da0b3d2156
38 self.line = line 38 self.line = line
39 39
40 def __str__(self): 40 def __str__(self):
41 return "line {0}: {1}".format(self.line, self.message) 41 return "line {0}: {1}".format(self.line, self.message)
42 42
43 class IncludeError(TinCanException): 43 class LoadError(TinCanException):
44 """ 44 """
45 Raised when we run into problems #include'ing something, usually 45 Raised when we run into problems #load'ing something, usually
46 because it doesn't exist. 46 because it doesn't exist.
47 """ 47 """
48 def __init__(self, message, source): 48 def __init__(self, message, source):
49 super().__init__(message, source) 49 super().__init__(message, source)
50 self.message = message 50 self.message = message
51 self.source = source 51 self.source = source
52 52
53 def __str__(self): 53 def __str__(self):
54 return "{0}: #include error: {1}".format(self.source, self.message) 54 return "{0}: #load error: {1}".format(self.source, self.message)
55 55
56 class ForwardException(TinCanException): 56 class ForwardException(TinCanException):
57 """ 57 """
58 Raised to effect the flow control needed to do a forward (server-side 58 Raised to effect the flow control needed to do a forward (server-side
59 redirect). It is ugly to do this, but other Python frameworks do and 59 redirect). It is ugly to do this, but other Python frameworks do and
126 """ 126 """
127 Parses and represents a set of header lines. 127 Parses and represents a set of header lines.
128 """ 128 """
129 _NAMES = [ "errors", "forward", "methods", "python", "template" ] 129 _NAMES = [ "errors", "forward", "methods", "python", "template" ]
130 _FNAMES = [ "hidden" ] 130 _FNAMES = [ "hidden" ]
131 _ANAMES = [ "include" ] 131 _ANAMES = [ "load" ]
132 132
133 def __init__(self, string): 133 def __init__(self, string):
134 # Initialize our state 134 # Initialize our state
135 for i in self._NAMES: 135 for i in self._NAMES:
136 setattr(self, i, None) 136 setattr(self, i, None)
185 else: 185 else:
186 setattr(self, name, param) 186 setattr(self, name, param)
187 187
188 # C h a m e l e o n 188 # C h a m e l e o n
189 # 189 #
190 # Support for Chameleon templates (the kind TinCan uses by default). 190 # Support for Chameleon templates (the kind TinCan uses).
191 191
192 class ChameleonTemplate(bottle.BaseTemplate): 192 class ChameleonTemplate(bottle.BaseTemplate):
193 def prepare(self, **options): 193 def prepare(self, **options):
194 from chameleon import PageTemplate, PageTemplateFile 194 from chameleon import PageTemplate, PageTemplateFile
195 if self.source: 195 if self.source:
325 self.error = err 325 self.error = err
326 326
327 # I n c l u s i o n 327 # I n c l u s i o n
328 # 328 #
329 # Most processing is in the TinCanRoute class; this just interprets and 329 # Most processing is in the TinCanRoute class; this just interprets and
330 # represents arguments to the #include header directive. 330 # represents arguments to the #load header directive.
331 331
332 class _IncludedFile(object): 332 class _LoadedFile(object):
333 def __init__(self, raw): 333 def __init__(self, raw):
334 if raw.startswith('<') and raw.endswith('>'): 334 if raw.startswith('<') and raw.endswith('>'):
335 raw = raw[1:-1] 335 raw = raw[1:-1]
336 self.in_lib = True 336 self.in_lib = True
337 else: 337 else:
349 raise ValueError("empty file name") 349 raise ValueError("empty file name")
350 if not self.fname.endswith(_IEXTEN): 350 if not self.fname.endswith(_IEXTEN):
351 raise ValueError("file does not end in {0}".format(_IEXTEN)) 351 raise ValueError("file does not end in {0}".format(_IEXTEN))
352 352
353 # Using a cache is likely to help efficiency a lot, since many pages 353 # Using a cache is likely to help efficiency a lot, since many pages
354 # will typically #include the same standard stuff. 354 # will typically #load the same standard stuff.
355 _tcache = {} 355 _tcache = {}
356 def _get_template(name, direct): 356 def _get_template(name, direct):
357 aname = os.path.abspath(os.path.join(direct, name)) 357 aname = os.path.abspath(os.path.join(direct, name))
358 if aname not in _tcache: 358 if aname not in _tcache:
359 tmpl = ChameleonTemplate(name=name, lookup=[direct]) 359 tmpl = ChameleonTemplate(name=name, lookup=[direct])
381 A route to an error page. These don't get routes created for them, 381 A route to an error page. These don't get routes created for them,
382 and are only reached if an error routes them there. Unless you create 382 and are only reached if an error routes them there. Unless you create
383 custom code-behind, only two variables are available to your template: 383 custom code-behind, only two variables are available to your template:
384 request (bottle.Request) and error (bottle.HTTPError). 384 request (bottle.Request) and error (bottle.HTTPError).
385 """ 385 """
386 def __init__(self, template, includes, klass): 386 def __init__(self, template, loads, klass):
387 self._template = template 387 self._template = template
388 self._template.prepare() 388 self._template.prepare()
389 self._includes = includes 389 self._loads = loads
390 self._class = klass 390 self._class = klass
391 391
392 def __call__(self, e): 392 def __call__(self, e):
393 bottle.request.environ[_FTYPE] = True 393 bottle.request.environ[_FTYPE] = True
394 try: 394 try:
395 obj = self._class(bottle.request, e) 395 obj = self._class(bottle.request, e)
396 obj.handle() 396 obj.handle()
397 tvars = self._includes.copy() 397 tvars = self._loads.copy()
398 tvars.update(obj.export()) 398 tvars.update(obj.export())
399 return self._template.render(tvars) 399 return self._template.render(tvars)
400 except bottle.HTTPResponse as e: 400 except bottle.HTTPResponse as e:
401 return e 401 return e
402 except Exception as e: 402 except Exception as e:
421 self._urlpath = self._urljoin(launcher.urlroot, *subdir, name + _TEXTEN) 421 self._urlpath = self._urljoin(launcher.urlroot, *subdir, name + _TEXTEN)
422 self._origin = self._urlpath 422 self._origin = self._urlpath
423 self._subdir = subdir 423 self._subdir = subdir
424 self._seen = set() 424 self._seen = set()
425 self._app = launcher.app 425 self._app = launcher.app
426 self._save_includes = launcher.debug 426 self._save_loads = launcher.debug
427 427
428 def launch(self): 428 def launch(self):
429 """ 429 """
430 Launch a single page. 430 Launch a single page.
431 """ 431 """
465 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) 465 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN))
466 self._python = self._header.python 466 self._python = self._header.python
467 self._python_specified = True 467 self._python_specified = True
468 # Obtain a class object by importing and introspecting a module. 468 # Obtain a class object by importing and introspecting a module.
469 self._getclass() 469 self._getclass()
470 # Build body object (#template) and obtain #includes. 470 # Build body object (#template) and obtain #loads.
471 if self._header.template is not None: 471 if self._header.template is not None:
472 if not self._header.template.endswith(_TEXTEN): 472 if not self._header.template.endswith(_TEXTEN):
473 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) 473 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN))
474 try: 474 try:
475 rtpath = self._splitpath(self._header.template) 475 rtpath = self._splitpath(self._header.template)
481 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e 481 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e
482 self._body = ChameleonTemplate(source=tfile.body) 482 self._body = ChameleonTemplate(source=tfile.body)
483 else: 483 else:
484 self._body = ChameleonTemplate(source=self._template.body) 484 self._body = ChameleonTemplate(source=self._template.body)
485 self._body.prepare() 485 self._body.prepare()
486 # Process includes 486 # Process loads
487 self._includes = {} 487 self._loads = {}
488 for include in self._header.include: 488 for load in self._header.load:
489 try: 489 try:
490 include = _IncludedFile(include) 490 load = _LoadedFile(load)
491 except ValueError as e: 491 except ValueError as e:
492 raise TinCanError("{0}: bad #include: {1!s}".format(self._urlpath, e)) from e 492 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e
493 if include.in_lib: 493 if load.in_lib:
494 fdir = os.path.join(self._fsroot, _WINF, "tlib") 494 fdir = os.path.join(self._fsroot, _WINF, "tlib")
495 else: 495 else:
496 fdir = os.path.join(self._fsroot, *self._subdir) 496 fdir = os.path.join(self._fsroot, *self._subdir)
497 try: 497 try:
498 tmpl = _get_template(include.fname, fdir) 498 tmpl = _get_template(load.fname, fdir)
499 except Exception as e: 499 except Exception as e:
500 raise TinCanError("{0}: bad #include: {1!s}".format(self._urlpath, e)) from e 500 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e
501 self._includes[include.vname] = tmpl.tpl 501 self._loads[load.vname] = tmpl.tpl
502 # If this is an #errors page, register it as such. 502 # If this is an #errors page, register it as such.
503 if oheader.errors is not None: 503 if oheader.errors is not None:
504 self._mkerror(oheader.errors) 504 self._mkerror(oheader.errors)
505 return # this implies #hidden 505 return # this implies #hidden
506 # Get #methods for this route 506 # Get #methods for this route
523 except ValueError as e: 523 except ValueError as e:
524 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e 524 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e
525 if not errors: 525 if not errors:
526 errors = range(_ERRMIN, _ERRMAX+1) 526 errors = range(_ERRMIN, _ERRMAX+1)
527 route = _TinCanErrorRoute(ChameleonTemplate(source=self._template.body), 527 route = _TinCanErrorRoute(ChameleonTemplate(source=self._template.body),
528 self._includes, self._class) 528 self._loads, self._class)
529 for error in errors: 529 for error in errors:
530 if error < _ERRMIN or error > _ERRMAX: 530 if error < _ERRMIN or error > _ERRMAX:
531 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) 531 raise TinCanError("{0}: bad #errors code".format(self._urlpath))
532 self._app.error_handler[error] = route # XXX 532 self._app.error_handler[error] = route # XXX
533 533
636 """ 636 """
637 target = None 637 target = None
638 try: 638 try:
639 obj = self._class(bottle.request, bottle.response) 639 obj = self._class(bottle.request, bottle.response)
640 obj.handle() 640 obj.handle()
641 tvars = self._includes.copy() 641 tvars = self._loads.copy()
642 tvars.update(obj.export()) 642 tvars.update(obj.export())
643 return self._body.render(tvars) 643 return self._body.render(tvars)
644 except ForwardException as fwd: 644 except ForwardException as fwd:
645 target = fwd.target 645 target = fwd.target
646 except bottle.HTTPResponse as e: 646 except bottle.HTTPResponse as e: