# HG changeset patch # User David Barts # Date 1557713980 25200 # Node ID 94b36e721500647b53bb7ea8cbedf1330c08db47 # Parent e726fafcffaca109693de3f0af2a6c5d9e8917d8 Another check in to back stuff up. diff -r e726fafcffac -r 94b36e721500 tincan.py --- a/tincan.py Sun May 12 15:26:28 2019 -0700 +++ b/tincan.py Sun May 12 19:19:40 2019 -0700 @@ -264,6 +264,29 @@ # Represents a route in TinCan. Our launcher creates these on-the-fly based # on the files it finds. +EXTENSION = ".pspx" +CONTENT = "text/html" +_WINF = "WEB-INF" +_BANNED = set([_WINF]) +_CODING = "utf-8" +_CLASS = "Page" +_FLOOP = "tincan.forwards" +_FORIG = "tincan.origin" + +class TinCanErrorRoute(object): + """ + A route to an error page. These never have code-behind, don't get + routes created for them, and are only reached if an error routes them + there. Error templates only have two variables available: e (the + HTTPError object associated with the error) and request. + """ + def __init__(self, template): + self._template = template + self._template.prepare() + + def __call__(self, e): + return self._template.render(e=e, request=bottle.request).lstrip('\n') + class TinCanRoute(object): """ A route created by the TinCan launcher. @@ -302,6 +325,18 @@ # don't get routes made for them. if hidden: return + # If this is an error page, register it as such. + if self._header.error is not None: + try: + errors = [ int(i) for i in self._header.error.split() ] + except ValueError as e: + raise TinCanError("{0}: bad #error line".format(self._urlpath)) from e + if not errors: + errors = range(400, 600) + route = TinCanErrorRoute(self._tclass(source=self._template.body)) + for error in errors: + self._app.error(code=error, callback=route) + return # this implies #hidden # Get methods for this route if self._header.methods is None: methods = [ 'GET' ] @@ -339,7 +374,12 @@ raise TinCanError("{0}: contains multiple Page classes", pypath) self._class = v # Build body object (Chameleon template) - self._body = self._tclass(source=self._template.body) + if self._header.template is not None: + tpath = os.path.join(self._fsroot, *self._splitpath(self._header.template)) + tfile = TemplateFile(tpath) + self._body = self._tclass(source=tfile.body) + else: + self._body = self._tclass(source=self._template.body) self._body.prepare() # Register this thing with Bottle print("adding route:", self._origin) # debug @@ -371,16 +411,35 @@ args[0] = '' return '/'.join(args) - def __call__(self, request): + def __call__(self): """ This gets called by the framework AFTER the page is launched. """ - ### needs to honor self._header.error if set - mod = importlib.import_module(self._mangled) - cls = getattr(mod, _CLASS) - obj = cls(request) - return Response(self._body.render(**self._mkdict(obj)).lstrip("\n"), - content_type=self._content) + target = None + try: + obj = self._class(bottle.request, bottle.response) + obj.handle() + return self._body.render(obj.export()) + except ForwardException as fwd: + target = fwd.target + if target is None: + raise TinCanError("Unexpected null target!") + # We get here if we are doing a server-side programmatic + # forward. + environ = bottle.request.environ + if _FLOOP not in environ: + environ[_FLOOP] = set() + if _FORIG not in environ: + environ[_FORIG] = self._urlpath + elif target in environ[_FLOOP]: + TinCanError("{0}: forward loop detected".format(environ[_FORIG])) + environ[_FLOOP].add(target) + environ['bottle.raw_path'] = target + environ['PATH_INFO'] = urllib.parse.quote(target) + route, args = self._app.router.match(environ) + environ['route.handle'] = environ['bottle.route'] = route + environ['route.url_args'] = args + return route.call(**args) def _mkdict(self, obj): ret = {}