changeset 11:8037bad7d5a8 draft

Update documentation, fix some #forward bugs.
author David Barts <n5jrn@me.com>
date Wed, 15 May 2019 00:00:45 -0700
parents 84998cd4e123
children 496d43d551d2
files code_behind.html pspx.html tincan.py
diffstat 3 files changed, 48 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/code_behind.html	Mon May 13 21:24:48 2019 -0700
+++ b/code_behind.html	Wed May 15 00:00:45 2019 -0700
@@ -15,7 +15,7 @@
       file names match while only the extensions differ; e.g. <var>foo.py</var>
       will contain the code-behind logic associated with <var>foo.pspx</var>.
       Use of the <code>#python</code> and/or <code>#template</code> header
-      directives will of course change this default association.</p>
+      directives can of course change this default association.</p>
     <p>Pretty much anything can be in the code-behind files, with one
       restriction. There must be one and only one instance of a subclass of
       either <var>Page</var> (normal pages) or <var>ErrorPage</var> (error
@@ -42,6 +42,21 @@
       method; the default behavior as described in the <em>Default Template
         Variables</em> section of the template documentation is normally
       sufficient.</p>
+    <h3>Page Objects</h3>
+    <p>These contain the code-behind for normal pages. When the <var>handle</var>
+      method begins executing, they contain two instance variables: <var>request</var>
+      (a <var>bottle.Request</var> object) and <var>response</var> (a <var>bottle.Response</var>
+      object). </p>
+    <h3>ErrorPage Objects</h3>
+    <p>These contain the code-behind for error pages.&nbsp; When the <var>handle</var>
+      method begins executing, they contain two instance variables: <var>request</var>
+      (a <var>bottle.Request</var> object) and <var>error</var> (a <var>bottle.HTTPError</var>
+      object). </p>
+    <h3>The Application Context</h3>
+    <p>There is no separate standard instance variable in either <var>Page</var>
+      or <var>ErrorPage</var> that provides such, because it is available via
+      <var> request.app</var>. See the Bottle documentation for more
+      information.</p>
     <h2>Code-Behind is Optional</h2>
     <p>If you define a template with no code-behind file, TinCan will use either
       <var>Page</var> or <var>ErrorPage</var> as appropriate, which will
--- a/pspx.html	Mon May 13 21:24:48 2019 -0700
+++ b/pspx.html	Wed May 15 00:00:45 2019 -0700
@@ -18,8 +18,10 @@
       header lines may appear only once in a given file.</p>
     <dl>
       <dt><code>#end</code></dt>
-      <dd>Marks the last line of the headers. Needed only for templating
-        languages where lines often start with <var>#</var>, such as Cheetah.</dd>
+      <dd>Marks the last line of the headers. Since the end of the header lines
+        is implicitly marked by the first line that does not start with <var>#</var>,
+        this is needed only for templating languages where lines often start
+        with <var>#</var>, such as Cheetah.</dd>
       <dt><code>#errors</code></dt>
       <dd>This is an error page which handles the specified HTTP error codes.
         See the subsection on error pages below. </dd>
@@ -74,10 +76,11 @@
       class as appropriate. </p>
     <h2>The Body</h2>
     <p>The body begins with the first line that <em>does not</em> start with <code>#</code>
-      and has the exact same syntax that the templates are in for this webapp.
-      By default, Chameleon templates are used. Cheetah, Jinja2, Mako, and
-      Bottle SimpleTemplate templates are also supported, provided the webapp
-      was launched to use them. (Only one template style per webapp is
+      (or the first line after the <code>#end</code> directive, whichever comes
+      first) and has the exact same syntax that the templates are in for this
+      webapp. By default, Chameleon templates are used. Cheetah, Jinja2, Mako,
+      and Bottle SimpleTemplate templates are also supported, provided the
+      webapp was launched to use them. (Only one template style per webapp is
       supported.)</p>
     <p>In order to make line numbers match file line numbers for reported
       errors, the template engine will be passed a blank line for each header
--- a/tincan.py	Mon May 13 21:24:48 2019 -0700
+++ b/tincan.py	Wed May 15 00:00:45 2019 -0700
@@ -250,7 +250,7 @@
 
 class BasePage(object):
     """
-    The parent class of both error and normal pages.
+    The parent class of both error and normal pages' code-behind.
     """
     def handle(self):
         """
@@ -277,6 +277,9 @@
         return ret
 
 class Page(BasePage):
+    """
+    The code-behind for a normal page.
+    """
     # Non-private things we refuse to export anyhow.
     _HIDDEN = set([ "request", "response" ])
 
@@ -326,6 +329,7 @@
         self._class = klass
 
     def __call__(self, e):
+        bottle.request.environ[_FTYPE] = True
         obj = self._class(bottle.request, e)
         obj.handle()
         return self._template.render(obj.export()).lstrip('\n')
@@ -352,26 +356,28 @@
         Launch a single page.
         """
         # Build master and header objects, process #forward directives
-        hidden = oerrors = None
+        oheader = None
         while True:
-            if oerrors is not None and oerrors != self._header.errors:
-                raise TinCanError("{0}: invalid redirect")
-            self._template = TemplateFile(self._fspath)
+            try:
+                self._template = TemplateFile(self._fspath)
+            except IOError as e:
+                raise TinCanError(str(e)) from e
             try:
                 self._header = TemplateHeader(self._template.header)
             except TemplateHeaderError as e:
                 raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e
-            oerrors = self._header.errors
-            if hidden is None:
-                hidden = self._header.hidden
-            elif self._header.errors is not None:
-                raise TinCanError("{0}: #forward to #errors not allowed".format(self._origin))
+            if oheader is None:
+                oheader = self._header  # save original header
+            elif (oheader.errors is None) != (self._header.errors is None):
+                raise TinCanError("{0}: invalid #forward".format(self._origin))
             if self._header.forward is None:
                 break
+            print("forwarding from:", self._urlpath)  # debug
             self._redirect()
+            print("forwarded to:", self._urlpath)  # debug
         # If this is a #hidden page, we ignore it for now, since hidden pages
         # don't get routes made for them.
-        if hidden and not self._headers.errors:
+        if oheader.hidden and not oheader.errors:
             return
         # Get the code-behind #python
         if self._header.python is not None:
@@ -391,8 +397,8 @@
             self._body = self._tclass(source=self._template.body)
         self._body.prepare()
         # If this is an #errors page, register it as such.
-        if self._header.errors is not None:
-            self._mkerror()
+        if oheader.errors is not None:
+            self._mkerror(oheader.errors)
             return  # this implies #hidden
         # Get #methods for this route
         if self._header.methods is None:
@@ -408,9 +414,9 @@
     def _splitpath(self, unsplit):
         return _normpath(self._subdir, unsplit)
 
-    def _mkerror(self):
+    def _mkerror(self, rerrors):
         try:
-            errors = [ int(i) for i in self._header.errors.split() ]
+            errors = [ int(i) for i in rerrors.split() ]
         except ValueError as e:
             raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e
         if not errors:
@@ -467,13 +473,13 @@
         try:
             rlist = self._splitpath(self._header.forward)
             forw = '/' + '/'.join(rlist)
-            if forw in self.seen:
+            if forw in self._seen:
                 raise TinCanError("{0}: #forward loop".format(self._origin))
             self._seen.add(forw)
             rname = rlist.pop()
         except IndexError as e:
             raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e
-        name, ext = os.path.splitext(rname)[1]
+        name, ext = os.path.splitext(rname)
         if ext != _TEXTEN:
             raise TinCanError("{0}: invalid #forward".format(self._urlpath))
         self._subdir = rlist