changeset 1:94b36e721500 draft

Another check in to back stuff up.
author David Barts <n5jrn@me.com>
date Sun, 12 May 2019 19:19:40 -0700
parents e726fafcffac
children ca6f8ca38cf2
files tincan.py
diffstat 1 files changed, 67 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- 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 = {}