Mercurial > cgi-bin > hgweb.cgi > tincan
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: |