comparison tincan.py @ 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 75e375b1976a
children 496d43d551d2
comparison
equal deleted inserted replaced
10:84998cd4e123 11:8037bad7d5a8
248 # Represents the code-behind of one of our pages. This gets subclassed, of 248 # Represents the code-behind of one of our pages. This gets subclassed, of
249 # course. 249 # course.
250 250
251 class BasePage(object): 251 class BasePage(object):
252 """ 252 """
253 The parent class of both error and normal pages. 253 The parent class of both error and normal pages' code-behind.
254 """ 254 """
255 def handle(self): 255 def handle(self):
256 """ 256 """
257 This is the entry point for the code-behind logic. It is intended 257 This is the entry point for the code-behind logic. It is intended
258 to be overridden. 258 to be overridden.
275 continue 275 continue
276 ret[name] = value 276 ret[name] = value
277 return ret 277 return ret
278 278
279 class Page(BasePage): 279 class Page(BasePage):
280 """
281 The code-behind for a normal page.
282 """
280 # Non-private things we refuse to export anyhow. 283 # Non-private things we refuse to export anyhow.
281 _HIDDEN = set([ "request", "response" ]) 284 _HIDDEN = set([ "request", "response" ])
282 285
283 def __init__(self, req, resp): 286 def __init__(self, req, resp):
284 """ 287 """
324 self._template = template 327 self._template = template
325 self._template.prepare() 328 self._template.prepare()
326 self._class = klass 329 self._class = klass
327 330
328 def __call__(self, e): 331 def __call__(self, e):
332 bottle.request.environ[_FTYPE] = True
329 obj = self._class(bottle.request, e) 333 obj = self._class(bottle.request, e)
330 obj.handle() 334 obj.handle()
331 return self._template.render(obj.export()).lstrip('\n') 335 return self._template.render(obj.export()).lstrip('\n')
332 336
333 class _TinCanRoute(object): 337 class _TinCanRoute(object):
350 def launch(self): 354 def launch(self):
351 """ 355 """
352 Launch a single page. 356 Launch a single page.
353 """ 357 """
354 # Build master and header objects, process #forward directives 358 # Build master and header objects, process #forward directives
355 hidden = oerrors = None 359 oheader = None
356 while True: 360 while True:
357 if oerrors is not None and oerrors != self._header.errors: 361 try:
358 raise TinCanError("{0}: invalid redirect") 362 self._template = TemplateFile(self._fspath)
359 self._template = TemplateFile(self._fspath) 363 except IOError as e:
364 raise TinCanError(str(e)) from e
360 try: 365 try:
361 self._header = TemplateHeader(self._template.header) 366 self._header = TemplateHeader(self._template.header)
362 except TemplateHeaderError as e: 367 except TemplateHeaderError as e:
363 raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e 368 raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e
364 oerrors = self._header.errors 369 if oheader is None:
365 if hidden is None: 370 oheader = self._header # save original header
366 hidden = self._header.hidden 371 elif (oheader.errors is None) != (self._header.errors is None):
367 elif self._header.errors is not None: 372 raise TinCanError("{0}: invalid #forward".format(self._origin))
368 raise TinCanError("{0}: #forward to #errors not allowed".format(self._origin))
369 if self._header.forward is None: 373 if self._header.forward is None:
370 break 374 break
375 print("forwarding from:", self._urlpath) # debug
371 self._redirect() 376 self._redirect()
377 print("forwarded to:", self._urlpath) # debug
372 # If this is a #hidden page, we ignore it for now, since hidden pages 378 # If this is a #hidden page, we ignore it for now, since hidden pages
373 # don't get routes made for them. 379 # don't get routes made for them.
374 if hidden and not self._headers.errors: 380 if oheader.hidden and not oheader.errors:
375 return 381 return
376 # Get the code-behind #python 382 # Get the code-behind #python
377 if self._header.python is not None: 383 if self._header.python is not None:
378 if not self._header.python.endswith(_PEXTEN): 384 if not self._header.python.endswith(_PEXTEN):
379 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) 385 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN))
389 self._body = self._tclass(source=tfile.body) 395 self._body = self._tclass(source=tfile.body)
390 else: 396 else:
391 self._body = self._tclass(source=self._template.body) 397 self._body = self._tclass(source=self._template.body)
392 self._body.prepare() 398 self._body.prepare()
393 # If this is an #errors page, register it as such. 399 # If this is an #errors page, register it as such.
394 if self._header.errors is not None: 400 if oheader.errors is not None:
395 self._mkerror() 401 self._mkerror(oheader.errors)
396 return # this implies #hidden 402 return # this implies #hidden
397 # Get #methods for this route 403 # Get #methods for this route
398 if self._header.methods is None: 404 if self._header.methods is None:
399 methods = [ 'GET' ] 405 methods = [ 'GET' ]
400 else: 406 else:
406 self._app.route(self._origin, methods, self) 412 self._app.route(self._origin, methods, self)
407 413
408 def _splitpath(self, unsplit): 414 def _splitpath(self, unsplit):
409 return _normpath(self._subdir, unsplit) 415 return _normpath(self._subdir, unsplit)
410 416
411 def _mkerror(self): 417 def _mkerror(self, rerrors):
412 try: 418 try:
413 errors = [ int(i) for i in self._header.errors.split() ] 419 errors = [ int(i) for i in rerrors.split() ]
414 except ValueError as e: 420 except ValueError as e:
415 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e 421 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e
416 if not errors: 422 if not errors:
417 errors = range(_ERRMIN, _ERRMAX+1) 423 errors = range(_ERRMIN, _ERRMAX+1)
418 route = _TinCanErrorRoute(self._tclass(source=self._template.body), self._class) 424 route = _TinCanErrorRoute(self._tclass(source=self._template.body), self._class)
465 471
466 def _redirect(self): 472 def _redirect(self):
467 try: 473 try:
468 rlist = self._splitpath(self._header.forward) 474 rlist = self._splitpath(self._header.forward)
469 forw = '/' + '/'.join(rlist) 475 forw = '/' + '/'.join(rlist)
470 if forw in self.seen: 476 if forw in self._seen:
471 raise TinCanError("{0}: #forward loop".format(self._origin)) 477 raise TinCanError("{0}: #forward loop".format(self._origin))
472 self._seen.add(forw) 478 self._seen.add(forw)
473 rname = rlist.pop() 479 rname = rlist.pop()
474 except IndexError as e: 480 except IndexError as e:
475 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e 481 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e
476 name, ext = os.path.splitext(rname)[1] 482 name, ext = os.path.splitext(rname)
477 if ext != _TEXTEN: 483 if ext != _TEXTEN:
478 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) 484 raise TinCanError("{0}: invalid #forward".format(self._urlpath))
479 self._subdir = rlist 485 self._subdir = rlist
480 self._python = name + _PEXTEN 486 self._python = name + _PEXTEN
481 self._fspath = os.path.join(self._fsroot, *self._subdir, rname) 487 self._fspath = os.path.join(self._fsroot, *self._subdir, rname)