# HG changeset patch # User David Barts # Date 1558991482 25200 # Node ID 2e3ac3d7b0a485f886beadee4a31d2c0ad88384e # Parent cc6ba7834294ea73bf9342785d8466d8b991b805 A possible workaround for the drainbamage (needs testing)? diff -r cc6ba7834294 -r 2e3ac3d7b0a4 tincan.py --- a/tincan.py Sun May 26 13:14:26 2019 -0700 +++ b/tincan.py Mon May 27 14:11:22 2019 -0700 @@ -326,63 +326,27 @@ # I n c l u s i o n # -# This is where the #include directives get processed - -_IEXTEN = ".pt" - -class _Includer(object): - def __init__(self, base, subdir, name, included=None, encoding="utf-8"): - self.base = base - self.subdir = subdir - self.name = name - self.included = set() if included is None else included - self.encoding = encoding - self._buf = [] - - def render(self, includes, body): - for i in includes: - if i.startswith('<') and i.endswith('>'): - self._buf.append(self._render1([_WINF, "tlib"], i[1:-1])) - else: - self._buf.append(self._render1(self.subdir, i)) - self._buf.append(body) - return ''.join(self._buf) +# Most processing is in the TinCanRoute class; this just interprets and +# represents arguments to the #include header directive. - def _render1(self, subdir, path): - # Reject bad file names - if not path.endswith(_IEXTEN) or path.endswith(_TEXTEN): - raise IncludeError("file names must end with {0} or {1}".format(_IEXTEN, _TEXTEN), self.name) - # Normalize - rawpath = _normpath(subdir, path) - # Only include once - relpath = '/' + '/'.join(rawpath) - if relpath in self.included: - return - self.included.add(relpath) - # Do actual inclusion of a file - npath = os.path.join(self.base, *rawpath) - nsubdir = rawpath[:-1] - try: - tf = TemplateFile(npath) - except OSError as e: - raise IncludeError(str(e), self.name) from e - try: - th = TemplateHeader(tf.header) - except TemplateHeaderError as e: - raise IncludeError(str(e), npath) from e - # Reject unsupported crap (included files can only #include) - for i in dir(th): - if i.startswith('_') or i == 'include': - continue - v = getattr(th, i) - if callable(v): - continue - if v is not None and v != False: - raise IncludeError(npath, "unsupported #{0}".format(i)) - # Inclusion is recursive... - nested = _Includer(self.base, nsubdir, relpath, - included=self.included, encoding=self.encoding) - return nested.render(th.include, tf.body) +class _IncludedFile(object): + def __init__(self, raw): + if raw.startswith('<') and raw.endswith('>'): + raw = raw[1:-1] + self.in_lib = True + else: + self.in_lib = False + comma = raw.find(',') + if comma < 0: + raise ValueError("missing comma") + self.vname = raw[:comma] + if self.vname == "": + raise ValueError("empty variable name") + self.fname = raw[comma+1:] + if self.fname == "": + raise ValueError("empty file name") + if not self.fname.endswith(_IEXTEN): + raise ValueError("file does not end in {0}".format(_IEXTEN)) # R o u t e s # @@ -391,6 +355,7 @@ _ERRMIN = 400 _ERRMAX = 599 +_IEXTEN = ".pt" _PEXTEN = ".py" _TEXTEN = ".pspx" _FLOOP = "tincan.forwards" @@ -404,9 +369,10 @@ custom code-behind, only two variables are available to your template: request (bottle.Request) and error (bottle.HTTPError). """ - def __init__(self, template, klass): + def __init__(self, template, includes, klass): self._template = template self._template.prepare() + self._includes = includes self._class = klass def __call__(self, e): @@ -414,7 +380,9 @@ try: obj = self._class(bottle.request, e) obj.handle() - return self._template.render(obj.export()) + tvars = self._includes.copy() + tvars.update(obj.export()) + return self._template.render(tvars) except bottle.HTTPResponse as e: return e except Exception as e: @@ -486,28 +454,47 @@ self._python_specified = True # Obtain a class object by importing and introspecting a module. self._getclass() - # Build body object (#template) and process #includes. + # Build body object (#template) and obtain #includes. if self._header.template is not None: if not self._header.template.endswith(_TEXTEN): raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) try: - rawpath = self._splitpath(self._header.template) - tsubdir = rawpath[:-1] - tname = rawpath[-1] - tpath = os.path.normpath(os.path.join(self._fsroot, *rawpath)) - turlpath = '/' + self._urljoin(rawpath) + rtpath = self._splitpath(self._header.template) + tpath = os.path.normpath(os.path.join(self._fsroot, *rtpath)) tfile = TemplateFile(tpath) - thead = TemplateHeader(tfile.header) - except (OSError, TemplateHeaderError) as e: + except OSError as e: raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e except IndexError as e: raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e - r = self._getbody(tsubdir, tname, thead.include, tfile.body) - self._dumpbody(r, tpath, turlpath) + self._body = self._tclass(source=tfile.body) + try: + includes = TemplateHeader(tfile.header).include + except TemplateHeaderError as e: + raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e + ibase = rtpath[:-1] else: - r = self._getbody(self._subdir, self._name+_TEXTEN, - self._header.include, self._template.body) - self._dumpbody(r, self._fspath, self._urlpath) + self._body = self._tclass(source=self._template.body) + includes = self._header.include + ibase = self._subdir + self._body.prepare() + # Process includes + self._includes = {} + for include in includes: + try: + include = _IncludedFile(include) + except ValueError as e: + raise TinCanError("{0}: bad #include: {1!s}", self._urlpath, e) from e + if include.in_lib: + fdir = os.path.join(self._fsroot, _WINF, "tlib") + else: + fdir = os.path.join(self._fsroot, *ibase) + try: + tmpl = self._tclass(name=include.fname, lookup=[fdir]) + tmpl.prepare() + # tmpl.render() # is this needed? + except Exception as e: + raise TinCanError("{0}: bad #include: {1!s}".format(self._urlpath, e)) from e + self._includes[include.vname] = tmpl.tpl # If this is an #errors page, register it as such. if oheader.errors is not None: self._mkerror(oheader.errors) @@ -523,27 +510,6 @@ print("adding route:", self._origin, '('+','.join(methods)+')') # debug self._app.route(self._origin, methods, self) - def _getbody(self, subdir, name, include, body): - includer = _Includer(self._fsroot, subdir, name) - try: - rendered = includer.render(include, body) - except IncludeError as e: - raise TinCanError(str(e)) from e - try: - self._body = self._tclass(source=rendered) - self._body.prepare() - except Exception as e: - raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e - return rendered - - def _dumpbody(self, rendered, based_on, logname): - if self._save_includes: - try: - with open(based_on + "i", "w") as fp: - fp.write(rendered) - except OSError as e: - raise TinCanError("{0}: {1!s}".format(logname, e)) from e - def _splitpath(self, unsplit): return _normpath(self._subdir, unsplit) @@ -554,7 +520,8 @@ raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e if not errors: errors = range(_ERRMIN, _ERRMAX+1) - route = _TinCanErrorRoute(self._tclass(source=self._template.body), self._class) + route = _TinCanErrorRoute(self._tclass(source=self._template.body), + self._includes, self._class) for error in errors: if error < _ERRMIN or error > _ERRMAX: raise TinCanError("{0}: bad #errors code".format(self._urlpath)) @@ -667,7 +634,9 @@ try: obj = self._class(bottle.request, bottle.response) obj.handle() - return self._body.render(obj.export()) + tvars = self._includes.copy() + tvars.update(obj.export()) + return self._body.render(tvars) except ForwardException as fwd: target = fwd.target except bottle.HTTPResponse as e: @@ -698,16 +667,6 @@ environ['route.url_args'] = args return route.call(**args) - def _mkdict(self, obj): - ret = {} - for name in dir(obj): - if name.startswith('_'): - continue - value = getattr(obj, name) - if not callable(value): - ret[name] = value - return ret - # L a u n c h e r _WINF = "WEB-INF"