# HG changeset patch # User David Barts # Date 1558896228 25200 # Node ID e93e5e746cc501fda8d0b4114882a2bea4ec3d7f # Parent 34d3cfcd37ef013ee04efa9b7f37770685015e58 Preliminary debugging, still not fully tested. diff -r 34d3cfcd37ef -r e93e5e746cc5 launch --- a/launch Wed May 22 07:47:16 2019 -0700 +++ b/launch Sun May 26 11:43:48 2019 -0700 @@ -18,13 +18,16 @@ parser = ArgumentParser(prog=sys.argv[0], usage="%(prog)s [options] [directory [path]]") opt = parser.add_argument opt("-b", "--bind", default="localhost", help="address to bind to") +opt("-d", "--debug", action="store_true", help="enable debug mode") +opt("-f", "--force", action="store_true", help="do not abort on errors") opt("-p", "--port", default=8080, help="port to listen on") opt("directory", default=".", help="directory to serve", nargs='?') opt("path", default="/", help="URL path to serve", nargs='?') args = parser.parse_args(sys.argv[1:]) -app, errors = launch(fsroot=args.directory, urlroot=args.path) +app, errors = launch(fsroot=args.directory, urlroot=args.path, debug=args.debug) if errors: - sys.stderr.write("{0}: {1} error{2} detected, aborting\n".format( - MYNAME, errors, "" if errors == 1 else "s")) - sys.exit(1) + action = "continuing" if args.force else "aborting" + sys.stderr.write("{0}: {1} error{2} detected, {3}\n".format( + MYNAME, errors, "" if errors == 1 else "s", action)) + if not args.force: sys.exit(1) app.run(host=args.bind, port=args.port) diff -r 34d3cfcd37ef -r e93e5e746cc5 tincan.py --- a/tincan.py Wed May 22 07:47:16 2019 -0700 +++ b/tincan.py Sun May 26 11:43:48 2019 -0700 @@ -28,9 +28,6 @@ """ pass -class BaseSyntaxError(TinCanException): - - class TemplateHeaderError(TinCanException): """ Raised upon encountering a syntax error in the template headers. @@ -54,7 +51,7 @@ self.source = source def __str__(self): - return "{0}: {1}".format(self.source, self.message) + return "{0}: #include error: {1}".format(self.source, self.message) class ForwardException(TinCanException): """ @@ -121,7 +118,6 @@ if line.startswith(self._END) and (len(line) == self._LEND or line[self._LEND] in self._WS): self._state = self._body self._hbuf.append(line) - self._bbuf.append("\n") def _body(self, line): self._bbuf.append(line) @@ -140,9 +136,11 @@ setattr(self, i, None) for i in self._FNAMES: setattr(self, i, False) + for i in self._ANAMES: + setattr(self, i, []) # Parse the string count = 0 - nameset = set(self._NAMES + self._FNAMES) + nameset = set(self._NAMES + self._FNAMES + self._ANAMES) seen = set() lines = string.split("\n") if lines and lines[-1] == "": @@ -340,12 +338,11 @@ self.included = set() if included is None else included self.encoding = encoding self._buf = [] - self._tlib = os.path.join(base, _WINF, "tlib") def render(self, includes, body): for i in includes: if i.startswith('<') and i.endswith('>'): - self._buf.append(self._render1(self._tlib, i[1:-1])) + self._buf.append(self._render1([_WINF, "tlib"], i[1:-1])) else: self._buf.append(self._render1(self.subdir, i)) self._buf.append(body) @@ -353,8 +350,8 @@ def _render1(self, subdir, path): # Reject bad file names - if not path.endswith(_IEXTEN) or path.endswith(_PEXTEN): - raise IncludeError(self.name, "#include files must end with {0} or {1}".format(_IEXTEN, _PEXTEN)) + 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 @@ -368,11 +365,11 @@ try: tf = TemplateFile(npath) except OSError as e: - raise IncludeError(self.name, str(e)) from e + raise IncludeError(str(e), self.name) from e try: - th = TemplateHeader(tf.headers) + th = TemplateHeader(tf.header) except TemplateHeaderError as e: - raise IncludeError(npath, str(e)) from 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': @@ -380,12 +377,12 @@ v = getattr(th, i) if callable(v): continue - if v is not None and v is not False: + 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.includes, tf.body) + return nested.render(th.include, tf.body) # R o u t e s # @@ -417,7 +414,7 @@ try: obj = self._class(bottle.request, e) obj.handle() - return self._template.render(obj.export()).lstrip('\n') + return self._template.render(obj.export()) except bottle.HTTPResponse as e: return e except Exception as e: @@ -445,6 +442,7 @@ self._seen = set() self._tclass = launcher.tclass self._app = launcher.app + self._save_includes = launcher.debug def launch(self): """ @@ -488,34 +486,28 @@ self._python_specified = True # Obtain a class object by importing and introspecting a module. self._getclass() - # Build body object (#template) + # Build body object (#template) and process #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) tfile = TemplateFile(tpath) - thead = TemplateHeader(tpath.header) + thead = TemplateHeader(tfile.header) except (OSError, TemplateHeaderError) 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 - includer = _Includer(self._fspath, tsubdir, turlpath) - try: - self._body = self._tclass(source=includer.render(thead.include, tfile.body)) - except IncludeError as e: - raise TinCanError("{0}: #include error: {1!s}".format(turlpath, e)) from e + r = self._getbody(tsubdir, tname, thead.include, tfile.body) + self._dumpbody(r, tpath, turlpath) else: - includer = _Includer(self._fspath, self._subdir, self._urlpath) - try: - self._body = self._tclass( - source=includer.render(self._header.include, self._template.body)) - except IncludeError as e: - raise TinCanError("{0}: #include error: {1!s}".format(self._urlpath, e)) from e - self._body.prepare() + r = self._getbody(self._subdir, self._name+_TEXTEN, + self._header.include, self._template.body) + self._dumpbody(r, self._fspath, self._urlpath) # If this is an #errors page, register it as such. if oheader.errors is not None: self._mkerror(oheader.errors) @@ -531,6 +523,27 @@ 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(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) @@ -654,7 +667,7 @@ try: obj = self._class(bottle.request, bottle.response) obj.handle() - return self._body.render(obj.export()).lstrip('\n') + return self._body.render(obj.export()) except ForwardException as fwd: target = fwd.target except bottle.HTTPResponse as e: @@ -775,7 +788,7 @@ sys.stderr.write(message) sys.stderr.write('\n') -def launch(fsroot=None, urlroot='/', tclass=ChameleonTemplate, logger=_logger): +def launch(fsroot=None, urlroot='/', tclass=ChameleonTemplate, logger=_logger, debug=False): """ Launch and return a TinCan webapp. Does not run the app; it is the caller's responsibility to call app.run() @@ -783,7 +796,7 @@ if fsroot is None: fsroot = os.getcwd() launcher = _Launcher(fsroot, urlroot, tclass, logger) - # launcher.debug = True + launcher.debug = debug launcher.launch() return launcher.app, launcher.errors