Mercurial > cgi-bin > hgweb.cgi > tincan
annotate tincan.py @ 28:4c6d3704e51f draft chameleon-load
Fails; the thing apparently wants strings and not objects.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sun, 26 May 2019 18:14:14 -0700 |
parents | f6a1492fe56e |
children |
rev | line source |
---|---|
0 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | |
3 # As with Bottle, it's all in one big, ugly file. For now. | |
4 | |
5 # I m p o r t s | |
6 | |
7 import os, sys | |
8 import ast | |
9 import binascii | |
10 from base64 import b16encode, b16decode | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
11 import functools |
2 | 12 import importlib |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
13 from inspect import isclass |
0 | 14 import io |
2 | 15 import py_compile |
16 from stat import S_ISDIR, S_ISREG | |
5 | 17 from string import whitespace |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
18 import traceback |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
19 import urllib |
0 | 20 |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
21 from chameleon import PageTemplate, PageTemplateFile |
0 | 22 import bottle |
23 | |
2 | 24 # E x c e p t i o n s |
0 | 25 |
26 class TinCanException(Exception): | |
27 """ | |
28 The parent class of all exceptions we raise. | |
29 """ | |
30 pass | |
31 | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
32 class TemplateHeaderError(TinCanException): |
0 | 33 """ |
34 Raised upon encountering a syntax error in the template headers. | |
35 """ | |
36 def __init__(self, message, line): | |
37 super().__init__(message, line) | |
38 self.message = message | |
39 self.line = line | |
40 | |
41 def __str__(self): | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
42 return "line {0}: {1}".format(self.line, self.message) |
0 | 43 |
44 class ForwardException(TinCanException): | |
45 """ | |
46 Raised to effect the flow control needed to do a forward (server-side | |
47 redirect). It is ugly to do this, but other Python frameworks do and | |
48 there seems to be no good alternative. | |
49 """ | |
50 def __init__(self, target): | |
51 self.target = target | |
52 | |
53 class TinCanError(TinCanException): | |
54 """ | |
55 General-purpose exception thrown by TinCan when things go wrong, often | |
56 when attempting to launch webapps. | |
57 """ | |
58 pass | |
59 | |
2 | 60 # T e m p l a t e s |
61 # | |
0 | 62 # Template (.pspx) files. These are standard templates for a supported |
63 # template engine, but with an optional set of header lines that begin | |
64 # with '#'. | |
65 | |
66 class TemplateFile(object): | |
67 """ | |
68 Parse a template file into a header part and the body part. The header | |
69 is always a leading set of lines, each starting with '#', that is of the | |
70 same format regardless of the template body. The template body varies | |
71 depending on the selected templating engine. The body part has | |
72 each header line replaced by a blank line. This preserves the overall | |
73 line numbering when processing the body. The added newlines are normally | |
74 stripped out before the rendered page is sent back to the client. | |
75 """ | |
5 | 76 _END = "#end" |
77 _LEND = len(_END) | |
78 _WS = set(whitespace) | |
79 | |
0 | 80 def __init__(self, raw, encoding='utf-8'): |
81 if isinstance(raw, io.TextIOBase): | |
82 self._do_init(raw) | |
83 elif isinstance(raw, str): | |
84 with open(raw, "r", encoding=encoding) as fp: | |
85 self._do_init(fp) | |
86 else: | |
87 raise TypeError("Expecting a string or Text I/O object.") | |
88 | |
89 def _do_init(self, fp): | |
90 self._hbuf = [] | |
91 self._bbuf = [] | |
92 self._state = self._header | |
93 while True: | |
94 line = fp.readline() | |
95 if line == '': | |
96 break | |
97 self._state(line) | |
98 self.header = ''.join(self._hbuf) | |
99 self.body = ''.join(self._bbuf) | |
100 | |
101 def _header(self, line): | |
102 if not line.startswith('#'): | |
103 self._state = self._body | |
104 self._state(line) | |
105 return | |
5 | 106 if line.startswith(self._END) and (len(line) == self._LEND or line[self._LEND] in self._WS): |
107 self._state = self._body | |
0 | 108 self._hbuf.append(line) |
109 self._bbuf.append("\n") | |
110 | |
111 def _body(self, line): | |
112 self._bbuf.append(line) | |
113 | |
114 class TemplateHeader(object): | |
115 """ | |
116 Parses and represents a set of header lines. | |
117 """ | |
2 | 118 _NAMES = [ "errors", "forward", "methods", "python", "template" ] |
0 | 119 _FNAMES = [ "hidden" ] |
120 | |
121 def __init__(self, string): | |
122 # Initialize our state | |
123 for i in self._NAMES: | |
124 setattr(self, i, None) | |
125 for i in self._FNAMES: | |
126 setattr(self, i, False) | |
127 # Parse the string | |
128 count = 0 | |
129 nameset = set(self._NAMES + self._FNAMES) | |
130 seen = set() | |
131 lines = string.split("\n") | |
132 if lines and lines[-1] == "": | |
133 del lines[-1] | |
134 for line in lines: | |
135 # Get line | |
136 count += 1 | |
137 if not line.startswith("#"): | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
138 raise TemplateHeaderError("Does not start with '#'.", count) |
0 | 139 try: |
140 rna, rpa = line.split(maxsplit=1) | |
141 except ValueError: | |
5 | 142 rna = line.rstrip() |
143 rpa = None | |
0 | 144 # Get name, ignoring remarks. |
145 name = rna[1:] | |
146 if name == "rem": | |
147 continue | |
5 | 148 if name == "end": |
149 break | |
0 | 150 if name not in nameset: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
151 raise TemplateHeaderError("Invalid directive: {0!r}".format(rna), count) |
0 | 152 if name in seen: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
153 raise TemplateHeaderError("Duplicate {0!r} directive.".format(rna), count) |
0 | 154 seen.add(name) |
155 # Flags | |
5 | 156 if name in self._FNAMES: |
0 | 157 setattr(self, name, True) |
158 continue | |
159 # Get parameter | |
5 | 160 if rpa is None: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
161 raise TemplateHeaderError("Missing parameter.", count) |
0 | 162 param = rpa.strip() |
163 for i in [ "'", '"']: | |
164 if param.startswith(i) and param.endswith(i): | |
165 param = ast.literal_eval(param) | |
166 break | |
167 # Update this object | |
168 setattr(self, name, param) | |
169 | |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
170 # C h a m e l e o n ( B o t t l e ) |
2 | 171 # |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
172 # Support for Chameleon templates (the kind TinCan uses by default). This |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
173 # allows the usage of Chameleon with Bottle itself. It is here as a |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
174 # convenience for those using Bottle routes as well as TinCan webapps; |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
175 # this is NOT how Tincan uses Chameleon |
0 | 176 |
177 class ChameleonTemplate(bottle.BaseTemplate): | |
178 def prepare(self, **options): | |
179 if self.source: | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
180 self.tpl = PageTemplate(self.source, encoding=self.encoding, |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
181 **options) |
0 | 182 else: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
183 self.tpl = PageTemplateFile(self.filename, encoding=self.encoding, |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
184 search_path=self.lookup, **options) |
0 | 185 |
186 def render(self, *args, **kwargs): | |
187 for dictarg in args: | |
188 kwargs.update(dictarg) | |
189 _defaults = self.defaults.copy() | |
190 _defaults.update(kwargs) | |
191 return self.tpl.render(**_defaults) | |
192 | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
193 chameleon_template = functools.partial(bottle.template, template_adapter=ChameleonTemplate) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
194 chameleon_view = functools.partial(bottle.view, template_adapter=ChameleonTemplate) |
0 | 195 |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
196 # C h a m e l e o n ( T i n c a n ) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
197 # |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
198 # How we use Chameleon ourselves. Everything is loaded as a string, and we |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
199 # provide convenience routines to load other templates. |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
200 |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
201 class TinCanChameleon(PageTemplate): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
202 """ |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
203 Basically, a standard PageTemplate with load and lload functionality. |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
204 """ |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
205 def __init__(self, body, base=None, subdir=None, **config): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
206 super(TinCanChameleon, self).__init__(body, **config) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
207 if base is not None: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
208 encoding = config.get("encoding", "utf-8") |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
209 if subdir is not None: |
28
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
210 self.expression_types['lload'] = \ |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
211 _LoaderFactory(base, subdir, encoding) |
28
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
212 self.expression_types['load'] = \ |
22
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
213 _LoaderFactory(os.path.join(base,_WINF,"tlib"), [], encoding) |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
214 |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
215 class _LoaderFactory(object): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
216 """ |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
217 One of two helper classes for the above. |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
218 """ |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
219 def __init__(self, base, subdir, encoding): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
220 self.base = base |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
221 self.subdir = subdir |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
222 self.encoding = encoding |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
223 |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
224 def __call__(self, string): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
225 return _Loader(self, string) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
226 |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
227 class _Loader(object): |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
228 """ |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
229 Two of two helper classes for the above. |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
230 """ |
28
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
231 _EXTEN = ".pt" |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
232 def __init__(self, based_on, string): |
28
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
233 if not string.endswith(self._EXTEN): |
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
234 raise ValueError("loaded templates must end in {0}".format(self._EXTEN)) |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
235 self.path = string |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
236 self.params = based_on |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
237 |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
238 def __call__(self, target, engine): |
22
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
239 try: |
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
240 normalized = _normpath(self.params.subdir, self.path) |
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
241 except IndexError: |
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
242 raise ValueError("invalid path: {0!s}".format(self.path)) |
f6a1492fe56e
Template loading (includes) pass all tests.
David Barts <n5jrn@me.com>
parents:
21
diff
changeset
|
243 npath = os.path.join(self.params.base, *normalized) |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
244 with open(npath, "r", encoding=self.params.encoding) as fp: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
245 contents = fp.read() |
28
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
246 value = ast.Call(func=TinCanChameleon, args=[contents], |
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
247 kwargs={"base": self.params.base, "subdir": self.params.subdir, |
4c6d3704e51f
Fails; the thing apparently wants strings and not objects.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
248 "encoding": self.params.encoding}) |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
249 return [ast.Assign(targets=[target], value=value)] |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
250 |
2 | 251 # U t i l i t i e s |
0 | 252 |
253 def _normpath(base, unsplit): | |
254 """ | |
255 Split, normalize and ensure a possibly relative path is absolute. First | |
256 argument is a list of directory names, defining a base. Second | |
257 argument is a string, which may either be relative to that base, or | |
258 absolute. Only '/' is supported as a separator. | |
259 """ | |
260 scratch = unsplit.strip('/').split('/') | |
261 if not unsplit.startswith('/'): | |
262 scratch = base + scratch | |
263 ret = [] | |
264 for i in scratch: | |
265 if i == '.': | |
266 continue | |
267 if i == '..': | |
268 ret.pop() # may raise IndexError | |
269 continue | |
270 ret.append(i) | |
271 return ret | |
272 | |
273 def _mangle(string): | |
274 """ | |
275 Turn a possibly troublesome identifier into a mangled one. | |
276 """ | |
277 first = True | |
278 ret = [] | |
279 for ch in string: | |
280 if ch == '_' or not (ch if first else "x" + ch).isidentifier(): | |
281 ret.append('_') | |
282 ret.append(b16encode(ch.encode("utf-8")).decode("us-ascii")) | |
283 else: | |
284 ret.append(ch) | |
285 first = False | |
286 return ''.join(ret) | |
287 | |
288 # The TinCan class. Simply a Bottle webapp that contains a forward method, so | |
289 # the code-behind can call request.app.forward(). | |
290 | |
291 class TinCan(bottle.Bottle): | |
292 def forward(self, target): | |
293 """ | |
294 Forward this request to the specified target route. | |
295 """ | |
296 source = bottle.request.environ['PATH_INFO'] | |
297 base = source.strip('/').split('/')[:-1] | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
298 if bottle.request.environ.get(_FTYPE, False): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
299 raise TinCanError("{0}: forward from error page".format(source)) |
0 | 300 try: |
301 exc = ForwardException('/' + '/'.join(_normpath(base, target))) | |
302 except IndexError as e: | |
303 raise TinCanError("{0}: invalid forward to {1!r}".format(source, target)) from e | |
304 raise exc | |
305 | |
2 | 306 # C o d e B e h i n d |
307 # | |
0 | 308 # Represents the code-behind of one of our pages. This gets subclassed, of |
309 # course. | |
310 | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
311 class BasePage(object): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
312 """ |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
313 The parent class of both error and normal pages' code-behind. |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
314 """ |
0 | 315 def handle(self): |
316 """ | |
317 This is the entry point for the code-behind logic. It is intended | |
318 to be overridden. | |
319 """ | |
320 pass | |
321 | |
322 def export(self): | |
323 """ | |
324 Export template variables. The default behavior is to export all | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
325 non-hidden non-callables that don't start with an underscore. |
0 | 326 This method can be overridden if a different behavior is |
327 desired. It should always return a dict or dict-like object. | |
328 """ | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
329 ret = { 'page': self } |
0 | 330 for name in dir(self): |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
331 if name in self._HIDDEN or name.startswith('_'): |
0 | 332 continue |
333 value = getattr(self, name) | |
334 if callable(value): | |
335 continue | |
336 ret[name] = value | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
337 return ret |
0 | 338 |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
339 class Page(BasePage): |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
340 """ |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
341 The code-behind for a normal page. |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
342 """ |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
343 # Non-private things we refuse to export anyhow. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
344 _HIDDEN = set([ "request", "response" ]) |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
345 |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
346 def __init__(self, req, resp): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
347 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
348 Constructor. This is a lightweight operation. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
349 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
350 self.request = req # app context is request.app in Bottle |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
351 self.response = resp |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
352 |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
353 class ErrorPage(BasePage): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
354 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
355 The code-behind for an error page. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
356 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
357 _HIDDEN = set() |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
358 |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
359 def __init__(self, req, err): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
360 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
361 Constructor. This is a lightweight operation. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
362 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
363 self.request = req |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
364 self.error = err |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
365 |
2 | 366 # R o u t e s |
367 # | |
0 | 368 # Represents a route in TinCan. Our launcher creates these on-the-fly based |
369 # on the files it finds. | |
370 | |
2 | 371 _ERRMIN = 400 |
372 _ERRMAX = 599 | |
373 _PEXTEN = ".py" | |
374 _TEXTEN = ".pspx" | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
375 _FLOOP = "tincan.forwards" |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
376 _FORIG = "tincan.origin" |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
377 _FTYPE = "tincan.iserror" |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
378 |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
379 class _TinCanErrorRoute(object): |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
380 """ |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
381 A route to an error page. These don't get routes created for them, |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
382 and are only reached if an error routes them there. Unless you create |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
383 custom code-behind, only two variables are available to your template: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
384 request (bottle.Request) and error (bottle.HTTPError). |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
385 """ |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
386 def __init__(self, template, klass): |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
387 self._template = template |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
388 self._class = klass |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
389 |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
390 def __call__(self, e): |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
391 bottle.request.environ[_FTYPE] = True |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
392 try: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
393 obj = self._class(bottle.request, e) |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
394 obj.handle() |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
395 return self._template.render(**obj.export()).lstrip('\n') |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
396 except bottle.HTTPResponse as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
397 return e |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
398 except Exception as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
399 traceback.print_exc() |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
400 # Bottle doesn't allow error handlers to themselves cause |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
401 # errors, most likely as a measure to prevent looping. So |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
402 # this will cause a "Critical error while processing request" |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
403 # page to be displayed, and any installed error pages to be |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
404 # ignored. |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
405 raise bottle.HTTPError(status=500, exception=e) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
406 |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
407 class _TinCanRoute(object): |
0 | 408 """ |
409 A route created by the TinCan launcher. | |
410 """ | |
411 def __init__(self, launcher, name, subdir): | |
412 self._fsroot = launcher.fsroot | |
413 self._urlroot = launcher.urlroot | |
414 self._name = name | |
2 | 415 self._python = name + _PEXTEN |
416 self._fspath = os.path.join(launcher.fsroot, *subdir, name + _TEXTEN) | |
417 self._urlpath = self._urljoin(launcher.urlroot, *subdir, name + _TEXTEN) | |
0 | 418 self._origin = self._urlpath |
419 self._subdir = subdir | |
420 self._seen = set() | |
421 self._app = launcher.app | |
422 | |
2 | 423 def launch(self): |
0 | 424 """ |
425 Launch a single page. | |
426 """ | |
427 # Build master and header objects, process #forward directives | |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
428 oheader = None |
0 | 429 while True: |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
430 try: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
431 self._template = TemplateFile(self._fspath) |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
432 except IOError as e: |
12
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
433 if oheader is not None: |
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
434 note = "{0}: invalid #forward: ".format(self._origin) |
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
435 else: |
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
436 note = "" |
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
437 raise TinCanError("{0}{1!s}".format(note, e)) from e |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
438 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
439 self._header = TemplateHeader(self._template.header) |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
440 except TemplateHeaderError as e: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
441 raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
442 if oheader is None: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
443 oheader = self._header # save original header |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
444 elif (oheader.errors is None) != (self._header.errors is None): |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
445 raise TinCanError("{0}: invalid #forward".format(self._origin)) |
0 | 446 if self._header.forward is None: |
447 break | |
12
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
448 # print("forwarding from:", self._urlpath) # debug |
0 | 449 self._redirect() |
12
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
450 # print("forwarded to:", self._urlpath) # debug |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
451 # If this is a #hidden page, we ignore it for now, since hidden pages |
0 | 452 # don't get routes made for them. |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
453 if oheader.hidden and not oheader.errors: |
0 | 454 return |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
455 # Get the code-behind #python |
15
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
456 if self._header.python is None: |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
457 self._python_specified = False |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
458 else: |
2 | 459 if not self._header.python.endswith(_PEXTEN): |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
460 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) |
0 | 461 self._python = self._header.python |
15
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
462 self._python_specified = True |
0 | 463 # Obtain a class object by importing and introspecting a module. |
3 | 464 self._getclass() |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
465 # Build body object (#template) |
3 | 466 if self._header.template is not None: |
467 if not self._header.template.endswith(_TEXTEN): | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
468 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) |
16 | 469 try: |
470 tpath = os.path.normpath(os.path.join(self._fsroot, *self._splitpath(self._header.template))) | |
471 tfile = TemplateFile(tpath) | |
472 except OSError as e: | |
473 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e | |
474 except IndexError as e: | |
475 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e | |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
476 try: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
477 self._body = TinCanChameleon(tfile.body, base=self._fsroot, subdir=self._subdir) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
478 except Exception as e: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
479 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e |
3 | 480 else: |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
481 try: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
482 self._body = TinCanChameleon(self._template.body, self._fsroot, subdir=self._subdir) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
483 except Exception as e: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
484 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
485 # If this is an #errors page, register it as such. |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
486 if oheader.errors is not None: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
487 self._mkerror(oheader.errors) |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
488 return # this implies #hidden |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
489 # Get #methods for this route |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
490 if self._header.methods is None: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
491 methods = [ 'GET' ] |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
492 else: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
493 methods = [ i.upper() for i in self._header.methods.split() ] |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
494 if not methods: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
495 raise TinCanError("{0}: no #methods specified".format(self._urlpath)) |
3 | 496 # Register this thing with Bottle |
6 | 497 print("adding route:", self._origin, '('+','.join(methods)+')') # debug |
3 | 498 self._app.route(self._origin, methods, self) |
499 | |
500 def _splitpath(self, unsplit): | |
501 return _normpath(self._subdir, unsplit) | |
502 | |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
503 def _mkerror(self, rerrors): |
3 | 504 try: |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
505 errors = [ int(i) for i in rerrors.split() ] |
3 | 506 except ValueError as e: |
507 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e | |
508 if not errors: | |
509 errors = range(_ERRMIN, _ERRMAX+1) | |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
510 try: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
511 template = TinCanChameleon(self._template.body, base=self._fsroot, subdir=self._subdir) |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
512 except Exception as e: |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
513 raise TinCanError("{0}: template error: {1!s}".format(self._urlpath, e)) from e |
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
514 route = _TinCanErrorRoute(template, self._class) |
3 | 515 for error in errors: |
516 if error < _ERRMIN or error > _ERRMAX: | |
517 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) | |
5 | 518 self._app.error_handler[error] = route # XXX |
3 | 519 |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
520 def _gettime(self, path): |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
521 try: |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
522 return os.stat(path).st_mtime |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
523 except FileNotFoundError: |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
524 return 0 |
8 | 525 except OSError as e: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
526 raise TinCanError(str(e)) from e |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
527 |
3 | 528 def _getclass(self): |
15
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
529 try: |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
530 pypath = os.path.normpath(os.path.join(self._fsroot, *self._splitpath(self._python))) |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
531 except IndexError as e: |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
532 raise TinCanError("{0}: invalid #python".format(self._urlpath)) from e |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
533 klass = ErrorPage if self._header.errors else Page |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
534 # Give 'em a default code-behind if they don't furnish one |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
535 pytime = self._gettime(pypath) |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
536 if not pytime: |
15
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
537 if self._python_specified: |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
538 raise TinCanError("{0}: #python file not found".format(self._urlpath)) |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
539 self._class = klass |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
540 return |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
541 # Else load the code-behind from a .py file |
0 | 542 pycpath = pypath + 'c' |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
543 pyctime = self._gettime(pycpath) |
0 | 544 try: |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
545 if pyctime < pytime: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
546 py_compile.compile(pypath, cfile=pycpath, doraise=True) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
547 except py_compile.PyCompileError as e: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
548 raise TinCanError(str(e)) from e |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
549 except Exception as e: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
550 raise TinCanError("{0}: {1!s}".format(pypath, e)) from e |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
551 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
552 spec = importlib.util.spec_from_file_location(_mangle(self._name), pycpath) |
0 | 553 mod = importlib.util.module_from_spec(spec) |
554 spec.loader.exec_module(mod) | |
555 except Exception as e: | |
16 | 556 raise TinCanError("{0}: error importing: {1!s}".format(pycpath, e)) from e |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
557 # Locate a suitable class. We look for the "deepest" class object |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
558 # we can find in the inheritance tree. |
0 | 559 self._class = None |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
560 score = -1 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
561 ambig = False |
0 | 562 for i in dir(mod): |
563 v = getattr(mod, i) | |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
564 if not isclass(v): |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
565 continue |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
566 d = self._cldepth(klass, v) |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
567 if d > score: |
15
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
568 self._class = v |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
569 score = d |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
570 ambig = False |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
571 elif d == score: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
572 ambig = True |
3 | 573 if self._class is None: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
574 raise TinCanError("{0}: contains no {1} classes".format(pypath, klass.__name__)) |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
575 if ambig: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
576 raise TinCanError("{0}: contains ambiguous {1} classes".format(pypath, klass.__name__)) |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
577 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
578 # This might fail for complex inheritance schemes from the classes of |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
579 # interest (so don't use them!). |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
580 def _cldepth(self, base, klass, count=0): |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
581 if klass is object: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
582 # not found |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
583 return -1 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
584 elif klass is base: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
585 # just found |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
586 return count |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
587 else: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
588 # must recurse |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
589 for c in klass.__bases__: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
590 result = self._cldepth(base, c, count=count+1) |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
591 if result > 0: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
592 return result |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
593 return -1 |
0 | 594 |
595 def _redirect(self): | |
596 try: | |
597 rlist = self._splitpath(self._header.forward) | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
598 forw = '/' + '/'.join(rlist) |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
599 if forw in self._seen: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
600 raise TinCanError("{0}: #forward loop".format(self._origin)) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
601 self._seen.add(forw) |
0 | 602 rname = rlist.pop() |
603 except IndexError as e: | |
604 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e | |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
605 name, ext = os.path.splitext(rname) |
2 | 606 if ext != _TEXTEN: |
0 | 607 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) |
608 self._subdir = rlist | |
2 | 609 self._python = name + _PEXTEN |
0 | 610 self._fspath = os.path.join(self._fsroot, *self._subdir, rname) |
12
496d43d551d2
More redirecting fixes and improved error reportage.
David Barts <n5jrn@me.com>
parents:
11
diff
changeset
|
611 self._urlpath = '/' + self._urljoin(*self._subdir, rname) |
0 | 612 |
613 def _urljoin(self, *args): | |
614 args = list(args) | |
615 if args[0] == '/': | |
616 args[0] = '' | |
617 return '/'.join(args) | |
618 | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
619 def __call__(self): |
0 | 620 """ |
621 This gets called by the framework AFTER the page is launched. | |
622 """ | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
623 target = None |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
624 try: |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
625 obj = self._class(bottle.request, bottle.response) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
626 obj.handle() |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
627 return self._body.render(**obj.export()).lstrip('\n') |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
628 except ForwardException as fwd: |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
629 target = fwd.target |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
630 except bottle.HTTPResponse as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
631 return e |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
632 except Exception as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
633 traceback.print_exc() |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
634 raise bottle.HTTPError(status=500, exception=e) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
635 if target is None: |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
636 message = "{0}: unexpected null target".format(self._urlpath) |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
637 sys.stderr.write(message + '\n') |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
638 raise bottle.HTTPError(status=500, exception=TinCanError(message)) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
639 # We get here if we are doing a server-side programmatic |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
640 # forward. |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
641 environ = bottle.request.environ |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
642 if _FORIG not in environ: |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
643 environ[_FORIG] = self._urlpath |
2 | 644 if _FLOOP not in environ: |
645 environ[_FLOOP] = set([self._urlpath]) | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
646 elif target in environ[_FLOOP]: |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
647 message = "{0}: forward loop detected".format(environ[_FORIG]) |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
648 sys.stderr.write(message + '\n') |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
649 raise bottle.HTTPError(status=500, exception=TinCanError(message)) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
650 environ[_FLOOP].add(target) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
651 environ['bottle.raw_path'] = target |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
652 environ['PATH_INFO'] = urllib.parse.quote(target) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
653 route, args = self._app.router.match(environ) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
654 environ['route.handle'] = environ['bottle.route'] = route |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
655 environ['route.url_args'] = args |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
656 return route.call(**args) |
0 | 657 |
658 def _mkdict(self, obj): | |
659 ret = {} | |
660 for name in dir(obj): | |
661 if name.startswith('_'): | |
662 continue | |
663 value = getattr(obj, name) | |
664 if not callable(value): | |
665 ret[name] = value | |
666 return ret | |
2 | 667 |
668 # L a u n c h e r | |
669 | |
670 _WINF = "WEB-INF" | |
671 _BANNED = set([_WINF]) | |
672 | |
673 class _Launcher(object): | |
674 """ | |
675 Helper class for launching webapps. | |
676 """ | |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
677 def __init__(self, fsroot, urlroot, logger): |
2 | 678 """ |
679 Lightweight constructor. The real action happens in .launch() below. | |
680 """ | |
681 self.fsroot = fsroot | |
682 self.urlroot = urlroot | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
683 self.logger = logger |
2 | 684 self.app = None |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
685 self.errors = 0 |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
686 self.debug = False |
2 | 687 |
688 def launch(self): | |
689 """ | |
690 Does the actual work of launching something. XXX - modifies sys.path | |
691 and never un-modifies it. | |
692 """ | |
693 # Sanity checks | |
694 if not self.urlroot.startswith("/"): | |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
695 self.errors = 1 |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
696 self.logger("urlroot not absolute: {0!r}".format(self.urlroot)) |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
697 return self |
2 | 698 if not os.path.isdir(self.fsroot): |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
699 self.errors = 1 |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
700 self.logger("no such directory: {0!r}".format(self.fsroot)) |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
701 return self |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
702 # Make any needed directories. Refuse to launch things that don't |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
703 # contain WEB-INF, to prevent accidental launches of undesired |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
704 # directory trees containing sensitive files. |
2 | 705 winf = os.path.join(self.fsroot, _WINF) |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
706 if not os.path.isdir(winf): |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
707 self.errors = 1 |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
708 self.logger("no WEB-INF directory in {0!r}".format(self.fsroot)) |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
709 return self |
2 | 710 lib = os.path.join(winf, "lib") |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
711 for i in [ lib ]: |
2 | 712 if not os.path.isdir(i): |
713 os.mkdir(i) | |
714 # Add our private lib directory to sys.path | |
715 sys.path.insert(1, os.path.abspath(lib)) | |
716 # Do what we gotta do | |
717 self.app = TinCan() | |
718 self._launch([]) | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
719 return self |
2 | 720 |
721 def _launch(self, subdir): | |
722 for entry in os.listdir(os.path.join(self.fsroot, *subdir)): | |
723 if not subdir and entry in _BANNED: | |
724 continue | |
725 etype = os.stat(os.path.join(self.fsroot, *subdir, entry)).st_mode | |
726 if S_ISREG(etype): | |
727 ename, eext = os.path.splitext(entry) | |
728 if eext != _TEXTEN: | |
729 continue # only look at interesting files | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
730 route = _TinCanRoute(self, ename, subdir) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
731 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
732 route.launch() |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
733 except TinCanError as e: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
734 self.logger(str(e)) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
735 if self.debug: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
736 while e.__cause__ != None: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
737 e = e.__cause__ |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
738 self.logger("\t{0}: {1!s}".format(e.__class__.__name__, e)) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
739 self.errors += 1 |
2 | 740 elif S_ISDIR(etype): |
741 self._launch(subdir + [entry]) | |
742 | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
743 def _logger(message): |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
744 sys.stderr.write(message) |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
745 sys.stderr.write('\n') |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
746 |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
747 def launch(fsroot=None, urlroot='/', logger=_logger): |
2 | 748 """ |
749 Launch and return a TinCan webapp. Does not run the app; it is the | |
750 caller's responsibility to call app.run() | |
751 """ | |
752 if fsroot is None: | |
753 fsroot = os.getcwd() | |
21
ca2029ce95c7
Mostly done with new template logic, working through testing.
David Barts <n5jrn@me.com>
parents:
19
diff
changeset
|
754 launcher = _Launcher(fsroot, urlroot, logger) |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
755 # launcher.debug = True |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
756 launcher.launch() |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
757 return launcher.app, launcher.errors |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
758 |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
759 # XXX - We cannot implement a command-line launcher here; see the |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
760 # launcher script for why. |