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