comparison tincan.py @ 37:ce67eac10fc7 draft header-includes

Allow global character encoding specification.
author David Barts <n5jrn@me.com>
date Tue, 28 May 2019 17:08:36 -0700
parents 4ed261056057
children df27cf08c093
comparison
equal deleted inserted replaced
36:4ed261056057 37:ce67eac10fc7
192 192
193 class ChameleonTemplate(bottle.BaseTemplate): 193 class ChameleonTemplate(bottle.BaseTemplate):
194 def prepare(self, **options): 194 def prepare(self, **options):
195 from chameleon import PageTemplate, PageTemplateFile 195 from chameleon import PageTemplate, PageTemplateFile
196 if self.source: 196 if self.source:
197 self.tpl = PageTemplate(self.source, encoding=self.encoding, 197 self.tpl = PageTemplate(self.source, **options)
198 **options)
199 else: 198 else:
200 self.tpl = PageTemplateFile(self.filename, encoding=self.encoding, 199 self.tpl = PageTemplateFile(self.filename, encoding=self.encoding,
201 search_path=self.lookup, **options) 200 search_path=self.lookup, **options)
201 # XXX - work around broken Chameleon decoding
202 self.tpl.default_encoding = self.encoding
202 203
203 def render(self, *args, **kwargs): 204 def render(self, *args, **kwargs):
204 for dictarg in args: 205 for dictarg in args:
205 kwargs.update(dictarg) 206 kwargs.update(dictarg)
206 _defaults = self.defaults.copy() 207 _defaults = self.defaults.copy()
352 raise ValueError("file does not end in {0}".format(_IEXTEN)) 353 raise ValueError("file does not end in {0}".format(_IEXTEN))
353 354
354 # Using a cache is likely to help efficiency a lot, since many pages 355 # Using a cache is likely to help efficiency a lot, since many pages
355 # will typically #load the same standard stuff. 356 # will typically #load the same standard stuff.
356 _tcache = {} 357 _tcache = {}
357 def _get_template(name, direct): 358 def _get_template(name, direct, coding):
358 aname = os.path.abspath(os.path.join(direct, name)) 359 aname = os.path.abspath(os.path.join(direct, name))
359 if aname not in _tcache: 360 if aname not in _tcache:
360 tmpl = ChameleonTemplate(name=name, lookup=[direct]) 361 tmpl = ChameleonTemplate(name=name, lookup=[direct], encoding=coding)
361 tmpl.prepare() 362 tmpl.prepare()
362 assert aname == tmpl.filename 363 assert aname == tmpl.filename
363 _tcache[aname] = tmpl 364 _tcache[aname] = tmpl
364 return _tcache[aname] 365 return _tcache[aname]
365 366
423 self._origin = self._urlpath 424 self._origin = self._urlpath
424 self._subdir = subdir 425 self._subdir = subdir
425 self._seen = set() 426 self._seen = set()
426 self._app = launcher.app 427 self._app = launcher.app
427 self._save_loads = launcher.debug 428 self._save_loads = launcher.debug
429 self._encoding = launcher.encoding
428 430
429 def launch(self): 431 def launch(self):
430 """ 432 """
431 Launch a single page. 433 Launch a single page.
432 """ 434 """
478 tfile = TemplateFile(tpath) 480 tfile = TemplateFile(tpath)
479 except OSError as e: 481 except OSError as e:
480 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e 482 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e
481 except IndexError as e: 483 except IndexError as e:
482 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e 484 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e
483 self._body = ChameleonTemplate(source=tfile.body) 485 self._body = ChameleonTemplate(source=tfile.body, encoding=self._encoding)
484 else: 486 else:
485 self._body = ChameleonTemplate(source=self._template.body) 487 self._body = ChameleonTemplate(source=self._template.body, encoding=self._encoding)
486 self._body.prepare() 488 self._body.prepare()
487 # Process loads 489 # Process loads
488 self._loads = {} 490 self._loads = {}
489 for load in self._header.load: 491 for load in self._header.load:
490 try: 492 try:
494 if load.in_lib: 496 if load.in_lib:
495 fdir = os.path.join(self._fsroot, _WINF, "tlib") 497 fdir = os.path.join(self._fsroot, _WINF, "tlib")
496 else: 498 else:
497 fdir = os.path.join(self._fsroot, *self._subdir) 499 fdir = os.path.join(self._fsroot, *self._subdir)
498 try: 500 try:
499 tmpl = _get_template(load.fname, fdir) 501 tmpl = _get_template(load.fname, fdir, self._encoding)
500 except Exception as e: 502 except Exception as e:
501 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e 503 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e
502 self._loads[load.vname] = tmpl.tpl 504 self._loads[load.vname] = tmpl.tpl
503 # If this is an #errors page, register it as such. 505 # If this is an #errors page, register it as such.
504 if oheader.errors is not None: 506 if oheader.errors is not None:
523 errors = [ int(i) for i in rerrors.split() ] 525 errors = [ int(i) for i in rerrors.split() ]
524 except ValueError as e: 526 except ValueError as e:
525 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e 527 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e
526 if not errors: 528 if not errors:
527 errors = range(_ERRMIN, _ERRMAX+1) 529 errors = range(_ERRMIN, _ERRMAX+1)
528 route = _TinCanErrorRoute(ChameleonTemplate(source=self._template.body), 530 route = _TinCanErrorRoute(
531 ChameleonTemplate(source=self._template.body, encoding=self._encoding),
529 self._loads, self._class) 532 self._loads, self._class)
530 for error in errors: 533 for error in errors:
531 if error < _ERRMIN or error > _ERRMAX: 534 if error < _ERRMIN or error > _ERRMAX:
532 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) 535 raise TinCanError("{0}: bad #errors code".format(self._urlpath))
533 self._app.error_handler[error] = route # XXX 536 self._app.error_handler[error] = route # XXX
674 677
675 # L a u n c h e r 678 # L a u n c h e r
676 679
677 _WINF = "WEB-INF" 680 _WINF = "WEB-INF"
678 _BANNED = set([_WINF]) 681 _BANNED = set([_WINF])
682 ENCODING = "utf-8"
679 683
680 class _Launcher(object): 684 class _Launcher(object):
681 """ 685 """
682 Helper class for launching webapps. 686 Helper class for launching webapps.
683 """ 687 """
689 self.urlroot = urlroot 693 self.urlroot = urlroot
690 self.logger = logger 694 self.logger = logger
691 self.app = None 695 self.app = None
692 self.errors = 0 696 self.errors = 0
693 self.debug = False 697 self.debug = False
698 self.encoding = ENCODING
694 699
695 def launch(self): 700 def launch(self):
696 """ 701 """
697 Does the actual work of launching something. XXX - modifies sys.path 702 Does the actual work of launching something. XXX - modifies sys.path
698 and never un-modifies it. 703 and never un-modifies it.
749 754
750 def _logger(message): 755 def _logger(message):
751 sys.stderr.write(message) 756 sys.stderr.write(message)
752 sys.stderr.write('\n') 757 sys.stderr.write('\n')
753 758
754 def launch(fsroot=None, urlroot='/', logger=_logger, debug=False): 759 def launch(fsroot=None, urlroot='/', logger=_logger, debug=False, encoding=ENCODING):
755 """ 760 """
756 Launch and return a TinCan webapp. Does not run the app; it is the 761 Launch and return a TinCan webapp. Does not run the app; it is the
757 caller's responsibility to call app.run() 762 caller's responsibility to call app.run()
758 """ 763 """
759 if fsroot is None: 764 if fsroot is None:
760 fsroot = os.getcwd() 765 fsroot = os.getcwd()
761 launcher = _Launcher(fsroot, urlroot, logger) 766 launcher = _Launcher(fsroot, urlroot, logger)
762 launcher.debug = debug 767 launcher.debug = debug
768 launcher.encoding = encoding
763 launcher.launch() 769 launcher.launch()
764 return launcher.app, launcher.errors 770 return launcher.app, launcher.errors
765 771
766 # XXX - We cannot implement a command-line launcher here; see the 772 # XXX - We cannot implement a command-line launcher here; see the
767 # launcher script for why. 773 # launcher script for why.