Mercurial > cgi-bin > hgweb.cgi > tincan
annotate tincan.py @ 72:e8b3b336e63e draft default tip
Update version.
author | David Barts <n5jrn@me.com> |
---|---|
date | Mon, 15 Jul 2019 13:17:48 -0700 |
parents | a78c74c73d98 |
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 | |
57
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
11 from chameleon import PageTemplate, PageTemplateFile, TemplateError |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
12 import email.utils |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
13 import functools |
2 | 14 import importlib |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
15 from inspect import isclass |
0 | 16 import io |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
17 import logging |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
18 import mimetypes |
2 | 19 import py_compile |
20 from stat import S_ISDIR, S_ISREG | |
5 | 21 from string import whitespace |
43 | 22 from threading import Lock |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
23 import time |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
24 import urllib |
0 | 25 |
26 import bottle | |
27 | |
2 | 28 # E x c e p t i o n s |
0 | 29 |
30 class TinCanException(Exception): | |
31 """ | |
32 The parent class of all exceptions we raise. | |
33 """ | |
34 pass | |
35 | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
36 class TemplateHeaderError(TinCanException): |
0 | 37 """ |
38 Raised upon encountering a syntax error in the template headers. | |
39 """ | |
40 def __init__(self, message, line): | |
41 super().__init__(message, line) | |
42 self.message = message | |
43 self.line = line | |
44 | |
45 def __str__(self): | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
46 return "line {0}: {1}".format(self.line, self.message) |
0 | 47 |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
48 class LoadError(TinCanException): |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
49 """ |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
50 Raised when we run into problems #load'ing something, usually |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
51 because it doesn't exist. |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
52 """ |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
53 def __init__(self, message, source): |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
54 super().__init__(message, source) |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
55 self.message = message |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
56 self.source = source |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
57 |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
58 def __str__(self): |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
59 return "{0}: #load error: {1}".format(self.source, self.message) |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
60 |
0 | 61 class ForwardException(TinCanException): |
62 """ | |
63 Raised to effect the flow control needed to do a forward (server-side | |
64 redirect). It is ugly to do this, but other Python frameworks do and | |
65 there seems to be no good alternative. | |
66 """ | |
67 def __init__(self, target): | |
68 self.target = target | |
69 | |
70 class TinCanError(TinCanException): | |
71 """ | |
72 General-purpose exception thrown by TinCan when things go wrong, often | |
73 when attempting to launch webapps. | |
74 """ | |
75 pass | |
76 | |
2 | 77 # T e m p l a t e s |
78 # | |
0 | 79 # Template (.pspx) files. These are standard templates for a supported |
80 # template engine, but with an optional set of header lines that begin | |
81 # with '#'. | |
82 | |
83 class TemplateFile(object): | |
84 """ | |
85 Parse a template file into a header part and the body part. The header | |
86 is always a leading set of lines, each starting with '#', that is of the | |
87 same format regardless of the template body. The template body varies | |
88 depending on the selected templating engine. The body part has | |
89 each header line replaced by a blank line. This preserves the overall | |
90 line numbering when processing the body. The added newlines are normally | |
91 stripped out before the rendered page is sent back to the client. | |
92 """ | |
5 | 93 _END = "#end" |
94 _LEND = len(_END) | |
95 _WS = set(whitespace) | |
96 | |
0 | 97 def __init__(self, raw, encoding='utf-8'): |
98 if isinstance(raw, io.TextIOBase): | |
99 self._do_init(raw) | |
100 elif isinstance(raw, str): | |
101 with open(raw, "r", encoding=encoding) as fp: | |
102 self._do_init(fp) | |
103 else: | |
104 raise TypeError("Expecting a string or Text I/O object.") | |
105 | |
106 def _do_init(self, fp): | |
107 self._hbuf = [] | |
108 self._bbuf = [] | |
109 self._state = self._header | |
110 while True: | |
111 line = fp.readline() | |
112 if line == '': | |
113 break | |
114 self._state(line) | |
115 self.header = ''.join(self._hbuf) | |
116 self.body = ''.join(self._bbuf) | |
117 | |
118 def _header(self, line): | |
119 if not line.startswith('#'): | |
120 self._state = self._body | |
121 self._state(line) | |
122 return | |
5 | 123 if line.startswith(self._END) and (len(line) == self._LEND or line[self._LEND] in self._WS): |
124 self._state = self._body | |
0 | 125 self._hbuf.append(line) |
36 | 126 self._bbuf.append("\n") |
0 | 127 |
128 def _body(self, line): | |
129 self._bbuf.append(line) | |
130 | |
131 class TemplateHeader(object): | |
132 """ | |
133 Parses and represents a set of header lines. | |
134 """ | |
2 | 135 _NAMES = [ "errors", "forward", "methods", "python", "template" ] |
0 | 136 _FNAMES = [ "hidden" ] |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
137 _ANAMES = [ "load" ] |
67
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
138 _ONAMES = [ "errors" ] |
0 | 139 |
140 def __init__(self, string): | |
141 # Initialize our state | |
142 for i in self._NAMES: | |
143 setattr(self, i, None) | |
144 for i in self._FNAMES: | |
145 setattr(self, i, False) | |
25
e93e5e746cc5
Preliminary debugging, still not fully tested.
David Barts <n5jrn@me.com>
parents:
24
diff
changeset
|
146 for i in self._ANAMES: |
e93e5e746cc5
Preliminary debugging, still not fully tested.
David Barts <n5jrn@me.com>
parents:
24
diff
changeset
|
147 setattr(self, i, []) |
0 | 148 # Parse the string |
149 count = 0 | |
25
e93e5e746cc5
Preliminary debugging, still not fully tested.
David Barts <n5jrn@me.com>
parents:
24
diff
changeset
|
150 nameset = set(self._NAMES + self._FNAMES + self._ANAMES) |
0 | 151 seen = set() |
152 lines = string.split("\n") | |
153 if lines and lines[-1] == "": | |
154 del lines[-1] | |
155 for line in lines: | |
156 # Get line | |
157 count += 1 | |
158 if not line.startswith("#"): | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
159 raise TemplateHeaderError("Does not start with '#'.", count) |
0 | 160 try: |
161 rna, rpa = line.split(maxsplit=1) | |
162 except ValueError: | |
5 | 163 rna = line.rstrip() |
164 rpa = None | |
0 | 165 # Get name, ignoring remarks. |
166 name = rna[1:] | |
167 if name == "rem": | |
168 continue | |
5 | 169 if name == "end": |
170 break | |
0 | 171 if name not in nameset: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
172 raise TemplateHeaderError("Invalid directive: {0!r}".format(rna), count) |
0 | 173 if name in seen: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
174 raise TemplateHeaderError("Duplicate {0!r} directive.".format(rna), count) |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
175 if name not in self._ANAMES: |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
176 seen.add(name) |
0 | 177 # Flags |
5 | 178 if name in self._FNAMES: |
0 | 179 setattr(self, name, True) |
180 continue | |
181 # Get parameter | |
5 | 182 if rpa is None: |
67
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
183 if name in self._ONAMES: |
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
184 rpa = "" |
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
185 else: |
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
186 raise TemplateHeaderError("Missing parameter.", count) |
0 | 187 param = rpa.strip() |
188 for i in [ "'", '"']: | |
189 if param.startswith(i) and param.endswith(i): | |
190 param = ast.literal_eval(param) | |
191 break | |
192 # Update this object | |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
193 if name in self._ANAMES: |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
194 getattr(self, name).append(param) |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
195 else: |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
196 setattr(self, name, param) |
0 | 197 |
23
e8b6ee7e5b6b
Well, *that* attempt at includes didn't work. Revert.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
198 # C h a m e l e o n |
2 | 199 # |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
200 # Support for Chameleon templates (the kind TinCan uses). |
0 | 201 |
202 class ChameleonTemplate(bottle.BaseTemplate): | |
203 def prepare(self, **options): | |
204 if self.source: | |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
205 self.tpl = PageTemplate(self.source, **options) |
0 | 206 else: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
207 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
|
208 search_path=self.lookup, **options) |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
209 # XXX - work around broken Chameleon decoding |
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
210 self.tpl.default_encoding = self.encoding |
0 | 211 |
212 def render(self, *args, **kwargs): | |
213 for dictarg in args: | |
214 kwargs.update(dictarg) | |
215 _defaults = self.defaults.copy() | |
216 _defaults.update(kwargs) | |
217 return self.tpl.render(**_defaults) | |
218 | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
219 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
|
220 chameleon_view = functools.partial(bottle.view, template_adapter=ChameleonTemplate) |
0 | 221 |
2 | 222 # U t i l i t i e s |
0 | 223 |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
224 def _hterror(**kwargs): |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
225 """ |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
226 Make a suitable bottle.HttpError object, with a message that is |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
227 always meaningful. |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
228 """ |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
229 if "status" not in kwargs: |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
230 raise ValueError("status argument is mandatory") |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
231 if "body" not in kwargs: |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
232 kwargs["body"] = "No further details available." |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
233 return bottle.HTTPError(**kwargs) |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
234 |
0 | 235 def _normpath(base, unsplit): |
236 """ | |
237 Split, normalize and ensure a possibly relative path is absolute. First | |
238 argument is a list of directory names, defining a base. Second | |
239 argument is a string, which may either be relative to that base, or | |
240 absolute. Only '/' is supported as a separator. | |
241 """ | |
242 scratch = unsplit.strip('/').split('/') | |
243 if not unsplit.startswith('/'): | |
244 scratch = base + scratch | |
245 ret = [] | |
246 for i in scratch: | |
247 if i == '.': | |
248 continue | |
249 if i == '..': | |
250 ret.pop() # may raise IndexError | |
251 continue | |
252 ret.append(i) | |
253 return ret | |
254 | |
255 def _mangle(string): | |
256 """ | |
257 Turn a possibly troublesome identifier into a mangled one. | |
258 """ | |
259 first = True | |
260 ret = [] | |
261 for ch in string: | |
262 if ch == '_' or not (ch if first else "x" + ch).isidentifier(): | |
263 ret.append('_') | |
264 ret.append(b16encode(ch.encode("utf-8")).decode("us-ascii")) | |
265 else: | |
266 ret.append(ch) | |
267 first = False | |
268 return ''.join(ret) | |
269 | |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
270 _FOLDS_CASE = sys.platform in ['darwin', 'win32'] |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
271 |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
272 def _casef(string, case="lower"): |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
273 """ |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
274 If we're on an OS with case-insensitive file names, fold case. |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
275 Else leave things alone. |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
276 """ |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
277 return getattr(string, case)() if _FOLDS_CASE else string |
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
278 |
0 | 279 # The TinCan class. Simply a Bottle webapp that contains a forward method, so |
280 # the code-behind can call request.app.forward(). | |
281 | |
282 class TinCan(bottle.Bottle): | |
283 def forward(self, target): | |
284 """ | |
285 Forward this request to the specified target route. | |
286 """ | |
287 source = bottle.request.environ['PATH_INFO'] | |
288 base = source.strip('/').split('/')[:-1] | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
289 if bottle.request.environ.get(_FTYPE, False): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
290 raise TinCanError("{0}: forward from error page".format(source)) |
0 | 291 try: |
292 exc = ForwardException('/' + '/'.join(_normpath(base, target))) | |
293 except IndexError as e: | |
294 raise TinCanError("{0}: invalid forward to {1!r}".format(source, target)) from e | |
295 raise exc | |
296 | |
2 | 297 # C o d e B e h i n d |
298 # | |
0 | 299 # Represents the code-behind of one of our pages. This gets subclassed, of |
300 # course. | |
301 | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
302 class BasePage(object): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
303 """ |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
304 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
|
305 """ |
0 | 306 def handle(self): |
307 """ | |
308 This is the entry point for the code-behind logic. It is intended | |
309 to be overridden. | |
310 """ | |
311 pass | |
312 | |
313 def export(self): | |
314 """ | |
315 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
|
316 non-hidden non-callables that don't start with an underscore. |
0 | 317 This method can be overridden if a different behavior is |
318 desired. It should always return a dict or dict-like object. | |
319 """ | |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
320 ret = { 'page': self } |
0 | 321 for name in dir(self): |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
322 if name in self._HIDDEN or name.startswith('_'): |
0 | 323 continue |
324 value = getattr(self, name) | |
325 if callable(value): | |
326 continue | |
327 ret[name] = value | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
328 return ret |
0 | 329 |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
330 class Page(BasePage): |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
331 """ |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
332 The code-behind for a normal page. |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
333 """ |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
334 # 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
|
335 _HIDDEN = set([ "request", "response" ]) |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
336 |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
337 def __init__(self, req, resp): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
338 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
339 Constructor. This is a lightweight operation. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
340 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
341 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
|
342 self.response = resp |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
343 |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
344 class ErrorPage(BasePage): |
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 The code-behind for an error page. |
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 _HIDDEN = set() |
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 def __init__(self, req, err): |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
351 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
352 Constructor. This is a lightweight operation. |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
353 """ |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
354 self.request = req |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
355 self.error = err |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
356 |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
357 # I n c l u s i o n |
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
358 # |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
359 # Most processing is in the TinCanRoute class; this just interprets and |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
360 # represents arguments to the #load header directive. |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
361 |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
362 class _LoadedFile(object): |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
363 def __init__(self, raw): |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
364 if raw.startswith('<') and raw.endswith('>'): |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
365 raw = raw[1:-1] |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
366 self.in_lib = True |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
367 else: |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
368 self.in_lib = False |
31
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
369 equals = raw.find('=') |
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
370 if equals < 0: |
33
cc975bf7a3fa
Fix bug in auto-generated include variables.
David Barts <n5jrn@me.com>
parents:
32
diff
changeset
|
371 self.vname = os.path.splitext(os.path.basename(raw))[0] |
31
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
372 self.fname = raw |
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
373 else: |
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
374 self.vname = raw[:equals] |
443a0001d841
Improve the #include syntax a bit.
David Barts <n5jrn@me.com>
parents:
30
diff
changeset
|
375 self.fname = raw[equals+1:] |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
376 if self.vname == "": |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
377 raise ValueError("empty variable name") |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
378 if self.fname == "": |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
379 raise ValueError("empty file name") |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
380 if not _casef(self.fname).endswith(_IEXTEN): |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
381 raise ValueError("file does not end in {0}".format(_IEXTEN)) |
24
34d3cfcd37ef
First batch of work on getting a #include header. Unfinished.
David Barts <n5jrn@me.com>
parents:
23
diff
changeset
|
382 |
34 | 383 # Using a cache is likely to help efficiency a lot, since many pages |
43 | 384 # will typically #load the same standard stuff. Except if we're |
385 # multithreading, then we want each page's templates to be private. | |
34 | 386 _tcache = {} |
43 | 387 def _get_template_cache(name, direct, coding): |
34 | 388 aname = os.path.abspath(os.path.join(direct, name)) |
389 if aname not in _tcache: | |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
390 tmpl = ChameleonTemplate(name=name, lookup=[direct], encoding=coding) |
34 | 391 assert aname == tmpl.filename |
392 _tcache[aname] = tmpl | |
393 return _tcache[aname] | |
394 | |
43 | 395 def _get_template_nocache(name, direct, coding): |
396 return ChameleonTemplate(name=name, lookup=[direct], encoding=coding) | |
397 | |
2 | 398 # R o u t e s |
399 # | |
0 | 400 # Represents a route in TinCan. Our launcher creates these on-the-fly based |
401 # on the files it finds. | |
402 | |
2 | 403 _ERRMIN = 400 |
404 _ERRMAX = 599 | |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
405 _IEXTEN = ".pt" |
2 | 406 _PEXTEN = ".py" |
407 _TEXTEN = ".pspx" | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
408 _FLOOP = "tincan.forwards" |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
409 _FORIG = "tincan.origin" |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
410 _FTYPE = "tincan.iserror" |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
411 _INDEX = "/index" + _TEXTEN |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
412 _LINDEX = len(_INDEX) |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
413 _INDICES = [ _INDEX, "/index.html", "/index.htm" ] |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
414 |
43 | 415 class _TinCanBaseRoute(object): |
416 """ | |
417 The base class for all NON ERROR routes. Error routes are just a little | |
418 bit different. | |
419 """ | |
420 def __init__(self, launcher, name, subdir): | |
421 global _get_template_cache, _get_template_nocache | |
422 if launcher.multithread: | |
423 self.lock = Lock() | |
424 self.get_template = _get_template_nocache | |
425 else: | |
426 self.lock = _DummyLock() | |
427 self.get_template = _get_template_cache | |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
428 self.logger = launcher.logger |
43 | 429 |
430 def urljoin(self, *args): | |
431 """ | |
432 Normalize a parsed-out URL fragment. | |
433 """ | |
434 args = list(args) | |
435 if args[0] == '/': | |
436 args[0] = '' | |
437 return '/'.join(args) | |
438 | |
439 def launch(self): | |
440 raise NotImplementedError("This must be overridden.") | |
441 | |
442 def __call__(self): | |
443 raise NotImplementedError("This must be overridden.") | |
444 | |
445 class _TinCanStaticRoute(_TinCanBaseRoute): | |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
446 """ |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
447 A route to a static file. These are useful for test servers. For |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
448 production servers, one is better off using a real web server and |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
449 a WSGI plugin, and having that handle static files. Much of this |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
450 logic is cribbed from the Bottle source code (we don't call it |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
451 directly because it is undocumented and thus subject to change). |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
452 """ |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
453 def __init__(self, launcher, name, subdir): |
43 | 454 super().__init__(launcher, name, subdir) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
455 self._app = launcher.app |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
456 self._fspath = os.path.join(launcher.fsroot, *subdir, name) |
43 | 457 self._urlpath = self.urljoin(launcher.urlroot, *subdir, name) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
458 self._type = mimetypes.guess_type(name)[0] |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
459 if self._type is None: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
460 self._type = "application/octet-stream" |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
461 if self._type.startswith("text/"): |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
462 self._encoding = launcher.encoding |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
463 self._type += "; charset=" + launcher.encoding |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
464 else: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
465 self._encoding = None |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
466 |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
467 def launch(self): |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
468 self.logger.info("adding static route: %s", self._urlpath) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
469 self._app.route(self._urlpath, 'GET', self) |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
470 for i in _INDICES: |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
471 if _casef(self._urlpath).endswith(i): |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
472 li = len(i) |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
473 for j in [ self._urlpath[:1-li], self._urlpath[:-li] ]: |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
474 if j: |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
475 self.logger.info("adding static route: %s", j) |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
476 self._app.route(j, 'GET', self) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
477 |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
478 def _parse_date(self, ims): |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
479 """ |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
480 Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
481 """ |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
482 try: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
483 ts = email.utils.parsedate_tz(ims) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
484 return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
485 except (TypeError, ValueError, IndexError, OverflowError): |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
486 return None |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
487 |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
488 def __call__(self): |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
489 # Get file contents and time stamp. If we can't, return an |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
490 # appropriate HTTP error response. |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
491 try: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
492 with open(self._fspath, "rb") as fp: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
493 mtime = os.fstat(fp.fileno()).st_mtime |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
494 bytes = fp.read() |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
495 except FileNotFoundError as e: |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
496 return _hterror(status=404, exception=e) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
497 except PermissionError as e: |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
498 return _hterror(status=403, exception=e) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
499 except OSError as e: |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
500 self.logger.exception("unexpected exception reading %r", self._fspath) |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
501 return _hterror(status=500, exception=e) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
502 # Establish preliminary standard headers. |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
503 headers = { |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
504 "Content-Type": self._type, |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
505 "Last-Modified": time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(mtime)), |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
506 } |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
507 if self._encoding: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
508 headers["Content-Encoding"] = self._encoding |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
509 # Support the If-Modified-Since request header. |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
510 ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE') |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
511 if ims: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
512 ims = self._parse_date(ims.split(";")[0].strip()) |
41 | 513 if ims is not None and ims >= int(mtime): |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
514 headers["Content-Length"] = "0" |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
515 return bottle.HTTPResponse(body=b"", status=304, headers=headers) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
516 # Standard response. |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
517 headers["Content-Length"] = str(len(bytes)) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
518 return bottle.HTTPResponse(body=bytes, status=200, headers=headers) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
519 |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
520 class _TinCanErrorRoute(object): |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
521 """ |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
522 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
|
523 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
|
524 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
|
525 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
|
526 """ |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
527 def __init__(self, template, loads, klass, lock, logger): |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
528 self._template = template |
23
e8b6ee7e5b6b
Well, *that* attempt at includes didn't work. Revert.
David Barts <n5jrn@me.com>
parents:
22
diff
changeset
|
529 self._template.prepare() |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
530 self._loads = loads |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
531 self._class = klass |
43 | 532 self.lock = lock |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
533 self.logger = logger |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
534 |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
535 def __call__(self, e): |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
536 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
|
537 try: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
538 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
|
539 obj.handle() |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
540 tvars = self._loads.copy() |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
541 tvars.update(obj.export()) |
43 | 542 with self.lock: |
543 return self._template.render(tvars).lstrip('\n') | |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
544 except bottle.HTTPResponse as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
545 return e |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
546 except Exception as e: |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
547 self.logger.exception("unexpected exception in error page") |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
548 # 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
|
549 # 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
|
550 # 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
|
551 # 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
|
552 # ignored. |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
553 raise _hterror(status=500, exception=e) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
554 |
43 | 555 class _TinCanRoute(_TinCanBaseRoute): |
0 | 556 """ |
557 A route created by the TinCan launcher. | |
558 """ | |
559 def __init__(self, launcher, name, subdir): | |
43 | 560 super().__init__(launcher, name, subdir) |
0 | 561 self._fsroot = launcher.fsroot |
562 self._urlroot = launcher.urlroot | |
563 self._name = name | |
2 | 564 self._python = name + _PEXTEN |
565 self._fspath = os.path.join(launcher.fsroot, *subdir, name + _TEXTEN) | |
43 | 566 self._urlpath = self.urljoin(launcher.urlroot, *subdir, name + _TEXTEN) |
0 | 567 self._origin = self._urlpath |
568 self._subdir = subdir | |
569 self._seen = set() | |
570 self._app = launcher.app | |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
571 self._encoding = launcher.encoding |
0 | 572 |
2 | 573 def launch(self): |
0 | 574 """ |
575 Launch a single page. | |
576 """ | |
577 # 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
|
578 oheader = None |
0 | 579 while True: |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
580 try: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
581 self._template = TemplateFile(self._fspath) |
69 | 582 except (OSError, UnicodeError) as e: |
583 raise TinCanError("{0}: {1!s}".format(self._fspath, e)) from e | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
584 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
585 self._header = TemplateHeader(self._template.header) |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
586 except TemplateHeaderError as e: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
587 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
|
588 if oheader is None: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
589 oheader = self._header # save original header |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
590 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
|
591 raise TinCanError("{0}: invalid #forward".format(self._origin)) |
0 | 592 if self._header.forward is None: |
593 break | |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
594 # self.logger.debug("forwarding from: %s", self._urlpath) # debug |
0 | 595 self._redirect() |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
596 # self.logger.debug("forwarded to: %s", self._urlpath) # debug |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
597 # If this is a #hidden page, we ignore it for now, since hidden pages |
0 | 598 # don't get routes made for them. |
67
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
599 if oheader.hidden and oheader.errors is None: |
0 | 600 return |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
601 # 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
|
602 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
|
603 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
|
604 else: |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
605 if not _casef(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
|
606 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) |
0 | 607 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
|
608 self._python_specified = True |
0 | 609 # Obtain a class object by importing and introspecting a module. |
3 | 610 self._getclass() |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
611 # Build body object (#template) and obtain #loads. |
3 | 612 if self._header.template is not None: |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
613 if not _casef(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
|
614 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) |
69 | 615 tpath = None |
16 | 616 try: |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
617 rtpath = self._splitpath(self._header.template) |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
618 tpath = os.path.normpath(os.path.join(self._fsroot, *rtpath)) |
16 | 619 tfile = TemplateFile(tpath) |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
620 except OSError as e: |
16 | 621 raise TinCanError("{0}: invalid #template: {1!s}".format(self._urlpath, e)) from e |
69 | 622 except UnicodeError as e: |
623 raise TinCanError("{0}: {1!s}".format(tpath, e)) from e | |
16 | 624 except IndexError as e: |
625 raise TinCanError("{0}: invalid #template".format(self._urlpath)) from e | |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
626 self._body = self._mktemplate(tfile.body, self.urljoin(*rtpath)) |
3 | 627 else: |
57
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
628 self._body = self._mktemplate(self._template.body, self._urlpath) |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
629 self._body.prepare() |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
630 # Process loads |
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
631 self._loads = {} |
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
632 for load in self._header.load: |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
633 try: |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
634 load = _LoadedFile(load) |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
635 except ValueError as e: |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
636 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e |
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
637 if load.in_lib: |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
638 fdir = os.path.join(self._fsroot, _WINF, "tlib") |
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
639 else: |
32 | 640 fdir = os.path.join(self._fsroot, *self._subdir) |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
641 try: |
43 | 642 tmpl = self.get_template(load.fname, fdir, self._encoding) |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
643 except Exception as e: |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
644 raise TinCanError("{0}: bad #load: {1!s}".format(self._urlpath, e)) from e |
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
645 self._loads[load.vname] = tmpl.tpl |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
646 # 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
|
647 if oheader.errors is not None: |
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
648 self._mkerror(oheader.errors) |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
649 return # this implies #hidden |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
650 # Get #methods for this route |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
651 if self._header.methods is None: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
652 methods = [ 'GET' ] |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
653 else: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
654 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
|
655 if not methods: |
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
656 raise TinCanError("{0}: no #methods specified".format(self._urlpath)) |
3 | 657 # Register this thing with Bottle |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
658 mtxt = ','.join(methods) |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
659 self.logger.info("adding route: %s (%s)", self._origin, mtxt) |
3 | 660 self._app.route(self._origin, methods, self) |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
661 if _casef(self._origin).endswith(_INDEX): |
58
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
662 for i in [ self._origin[:1-_LINDEX], self._origin[:-_LINDEX] ]: |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
663 if i: |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
664 self.logger.info("adding route: %s (%s)", i, mtxt) |
e08e24707da1
Recognize index.pspx (and index.html, and index.htm if static).
David Barts <n5jrn@me.com>
parents:
57
diff
changeset
|
665 self._app.route(i, methods, self) |
3 | 666 |
57
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
667 def _mktemplate(self, source, name): |
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
668 try: |
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
669 return ChameleonTemplate(source=source, encoding=self._encoding) |
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
670 except TemplateError as e: |
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
671 raise TinCanError("{0}: {1!s}".format(name, e)) from e |
935f8013f540
Fix lack of error detection and recovery in template parsing.
David Barts <n5jrn@me.com>
parents:
56
diff
changeset
|
672 |
3 | 673 def _splitpath(self, unsplit): |
674 return _normpath(self._subdir, unsplit) | |
675 | |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
676 def _mkerror(self, rerrors): |
3 | 677 try: |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
678 errors = [ int(i) for i in rerrors.split() ] |
3 | 679 except ValueError as e: |
680 raise TinCanError("{0}: bad #errors line".format(self._urlpath)) from e | |
681 if not errors: | |
682 errors = range(_ERRMIN, _ERRMAX+1) | |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
683 route = _TinCanErrorRoute( |
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
684 ChameleonTemplate(source=self._template.body, encoding=self._encoding), |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
685 self._loads, self._class, self.lock, self.logger) |
3 | 686 for error in errors: |
687 if error < _ERRMIN or error > _ERRMAX: | |
688 raise TinCanError("{0}: bad #errors code".format(self._urlpath)) | |
5 | 689 self._app.error_handler[error] = route # XXX |
3 | 690 |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
691 def _gettime(self, path): |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
692 try: |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
693 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
|
694 except FileNotFoundError: |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
695 return 0 |
8 | 696 except OSError as e: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
697 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
|
698 |
3 | 699 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
|
700 try: |
560c8fb55e4a
Fix bugs in #python directive, make code-behind class loading simpler.
David Barts <n5jrn@me.com>
parents:
14
diff
changeset
|
701 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
|
702 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
|
703 raise TinCanError("{0}: invalid #python".format(self._urlpath)) from e |
67
b5e72129ef72
Support #errors w/ no params, as documented.
David Barts <n5jrn@me.com>
parents:
66
diff
changeset
|
704 klass = ErrorPage if self._header.errors is not None else Page |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
705 # 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
|
706 pytime = self._gettime(pypath) |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
707 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
|
708 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
|
709 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
|
710 self._class = klass |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
711 return |
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
712 # Else load the code-behind from a .py file |
0 | 713 pycpath = pypath + 'c' |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
714 pyctime = self._gettime(pycpath) |
0 | 715 try: |
7
57ec65f527e9
Eliminate a stat() call, allow no code-behind on pages.
David Barts <n5jrn@me.com>
parents:
6
diff
changeset
|
716 if pyctime < pytime: |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
717 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
|
718 except py_compile.PyCompileError as e: |
68 | 719 msg = str(e) |
720 if pypath not in msg: | |
721 msg = pypath + ": " + msg | |
722 raise TinCanError(msg) from e | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
723 except Exception as e: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
724 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
|
725 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
726 spec = importlib.util.spec_from_file_location(_mangle(self._name), pycpath) |
0 | 727 mod = importlib.util.module_from_spec(spec) |
728 spec.loader.exec_module(mod) | |
729 except Exception as e: | |
16 | 730 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
|
731 # 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
|
732 # we can find in the inheritance tree. |
0 | 733 self._class = None |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
734 score = -1 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
735 ambig = False |
0 | 736 for i in dir(mod): |
737 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
|
738 if not isclass(v): |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
739 continue |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
740 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
|
741 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
|
742 self._class = v |
19
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
743 score = d |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
744 ambig = False |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
745 elif d == score: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
746 ambig = True |
3 | 747 if self._class is None: |
9
75e375b1976a
Error pages now can have code-behind.
David Barts <n5jrn@me.com>
parents:
8
diff
changeset
|
748 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
|
749 if ambig: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
750 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
|
751 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
752 # 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
|
753 # 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
|
754 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
|
755 if klass is object: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
756 # not found |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
757 return -1 |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
758 elif klass is base: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
759 # just found |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
760 return count |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
761 else: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
762 # must recurse |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
763 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
|
764 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
|
765 if result > 0: |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
766 return result |
5d9a1b82251a
Return the "deepest" subclass; this allows subclassing tincan.Page and
David Barts <n5jrn@me.com>
parents:
18
diff
changeset
|
767 return -1 |
0 | 768 |
769 def _redirect(self): | |
770 try: | |
771 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
|
772 forw = '/' + '/'.join(rlist) |
11
8037bad7d5a8
Update documentation, fix some #forward bugs.
David Barts <n5jrn@me.com>
parents:
9
diff
changeset
|
773 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
|
774 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
|
775 self._seen.add(forw) |
0 | 776 rname = rlist.pop() |
777 except IndexError as e: | |
778 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
|
779 name, ext = os.path.splitext(rname) |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
780 if _casef(ext) != _TEXTEN: |
0 | 781 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) |
782 self._subdir = rlist | |
2 | 783 self._python = name + _PEXTEN |
0 | 784 self._fspath = os.path.join(self._fsroot, *self._subdir, rname) |
43 | 785 self._urlpath = '/' + self.urljoin(*self._subdir, rname) |
0 | 786 |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
787 def __call__(self): |
0 | 788 """ |
789 This gets called by the framework AFTER the page is launched. | |
790 """ | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
791 target = None |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
792 try: |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
793 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
|
794 obj.handle() |
35
41da0b3d2156
Rename #include to #load (more descriptive).
David Barts <n5jrn@me.com>
parents:
34
diff
changeset
|
795 tvars = self._loads.copy() |
29
2e3ac3d7b0a4
A possible workaround for the drainbamage (needs testing)?
David Barts <n5jrn@me.com>
parents:
26
diff
changeset
|
796 tvars.update(obj.export()) |
43 | 797 with self.lock: |
798 return self._body.render(tvars).lstrip('\n') | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
799 except ForwardException as fwd: |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
800 target = fwd.target |
17
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
801 except bottle.HTTPResponse as e: |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
802 return e |
8186de188daf
Improve the run-time error handling in code-behinds.
David Barts <n5jrn@me.com>
parents:
16
diff
changeset
|
803 except Exception as e: |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
804 self.logger.exception("%s: unexpected exception", self._urlpath) |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
805 raise _hterror(status=500, exception=e) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
806 if target is None: |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
807 message = "{0}: unexpected null target".format(self._urlpath) |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
808 self.logger.error(message) |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
809 raise _hterror(status=500, exception=TinCanError(message)) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
810 # 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
|
811 # forward. |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
812 environ = bottle.request.environ |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
813 if _FORIG not in environ: |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
814 environ[_FORIG] = self._urlpath |
2 | 815 if _FLOOP not in environ: |
816 environ[_FLOOP] = set([self._urlpath]) | |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
817 elif target in environ[_FLOOP]: |
70
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
818 message = "{0}: forward loop detected".format(environ[_FORIG]) |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
819 self.logger.error(message) |
a78c74c73d98
Get rid of bogus "None" messages in the standard error pages.
David Barts <n5jrn@me.com>
parents:
69
diff
changeset
|
820 raise _hterror(status=500, exception=TinCanError(message)) |
1
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
821 environ[_FLOOP].add(target) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
822 environ['bottle.raw_path'] = target |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
823 environ['PATH_INFO'] = urllib.parse.quote(target) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
824 route, args = self._app.router.match(environ) |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
825 environ['route.handle'] = environ['bottle.route'] = route |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
826 environ['route.url_args'] = args |
94b36e721500
Another check in to back stuff up.
David Barts <n5jrn@me.com>
parents:
0
diff
changeset
|
827 return route.call(**args) |
0 | 828 |
43 | 829 # M u t e x |
830 # | |
831 # A dummy lock class, which is what we use if we don't need locking. | |
832 | |
833 class _DummyLock(object): | |
834 def acquire(self, blocking=True, timeout=-1): | |
835 pass | |
836 | |
837 def release(self): | |
838 pass | |
839 | |
840 def __enter__(self): | |
841 self.acquire() | |
842 return self | |
843 | |
844 def __exit__(self, exc_type, exc_value, traceback): | |
845 self.release() | |
846 return False | |
847 | |
2 | 848 # L a u n c h e r |
849 | |
850 _WINF = "WEB-INF" | |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
851 _EBANNED = set([_IEXTEN, _TEXTEN, _PEXTEN, _PEXTEN+"c"]) |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
852 ENCODING = "utf-8" |
53 | 853 _BITBUCKET = logging.getLogger(__name__) |
854 _BITBUCKET.addHandler(logging.NullHandler) | |
2 | 855 |
856 class _Launcher(object): | |
857 """ | |
858 Helper class for launching webapps. | |
859 """ | |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
860 def __init__(self, fsroot, urlroot, multithread): |
2 | 861 """ |
862 Lightweight constructor. The real action happens in .launch() below. | |
863 """ | |
864 self.fsroot = fsroot | |
865 self.urlroot = urlroot | |
866 self.app = None | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
867 self.errors = 0 |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
868 self.debug = False |
37
ce67eac10fc7
Allow global character encoding specification.
David Barts <n5jrn@me.com>
parents:
36
diff
changeset
|
869 self.encoding = ENCODING |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
870 self.static = False |
43 | 871 self.multithread = multithread |
53 | 872 self.logger = _BITBUCKET |
2 | 873 |
874 def launch(self): | |
875 """ | |
876 Does the actual work of launching something. XXX - modifies sys.path | |
877 and never un-modifies it. | |
878 """ | |
879 # Sanity checks | |
880 if not self.urlroot.startswith("/"): | |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
881 self.errors = 1 |
66 | 882 self.logger.error("urlroot not absolute: %r", self.urlroot) |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
883 return self |
2 | 884 if not os.path.isdir(self.fsroot): |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
885 self.errors = 1 |
66 | 886 self.logger.error("no such directory: %r", self.fsroot) |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
887 return self |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
888 # 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
|
889 # 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
|
890 # directory trees containing sensitive files. |
2 | 891 winf = os.path.join(self.fsroot, _WINF) |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
892 if not os.path.isdir(winf): |
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
893 self.errors = 1 |
66 | 894 self.logger.error("no WEB-INF directory in %r", self.fsroot) |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
895 return self |
2 | 896 lib = os.path.join(winf, "lib") |
18
e88ab99914cf
More improvements to the error reportage.
David Barts <n5jrn@me.com>
parents:
17
diff
changeset
|
897 for i in [ lib ]: |
2 | 898 if not os.path.isdir(i): |
899 os.mkdir(i) | |
900 # Add our private lib directory to sys.path | |
901 sys.path.insert(1, os.path.abspath(lib)) | |
902 # Do what we gotta do | |
903 self.app = TinCan() | |
56 | 904 config = self.app.config |
905 config['tincan.fsroot'] = os.path.abspath(self.fsroot) | |
906 config['tincan.urlroot'] = self.urlroot | |
907 config['tincan.logger'] = self.logger | |
908 config['tincan.encoding'] = self.encoding | |
2 | 909 self._launch([]) |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
910 return self |
2 | 911 |
912 def _launch(self, subdir): | |
913 for entry in os.listdir(os.path.join(self.fsroot, *subdir)): | |
42
8948020c54fd
Remove some debug deadwood, ignore hidden files.
David Barts <n5jrn@me.com>
parents:
41
diff
changeset
|
914 if entry.startswith("."): |
8948020c54fd
Remove some debug deadwood, ignore hidden files.
David Barts <n5jrn@me.com>
parents:
41
diff
changeset
|
915 continue # hidden file |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
916 if not subdir and _casef(entry, "upper") == _WINF: |
2 | 917 continue |
918 etype = os.stat(os.path.join(self.fsroot, *subdir, entry)).st_mode | |
919 if S_ISREG(etype): | |
920 ename, eext = os.path.splitext(entry) | |
59
60907204a265
Support case-insensitive filesystems properly.
David Barts <n5jrn@me.com>
parents:
58
diff
changeset
|
921 eext = _casef(eext) |
40
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
922 if eext == _TEXTEN: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
923 route = _TinCanRoute(self, ename, subdir) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
924 else: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
925 if eext in _EBANNED: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
926 continue |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
927 if self.static: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
928 route = _TinCanStaticRoute(self, entry, subdir) |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
929 else: |
df27cf08c093
Add support for serving static files.
David Barts <n5jrn@me.com>
parents:
37
diff
changeset
|
930 continue |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
931 try: |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
932 route.launch() |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
933 except TinCanError as e: |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
934 if self.logger.getEffectiveLevel() <= logging.DEBUG: |
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
935 self.logger.exception(str(e)) |
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
936 else: |
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
937 self.logger.error(str(e)) |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
938 self.errors += 1 |
2 | 939 elif S_ISDIR(etype): |
940 self._launch(subdir + [entry]) | |
941 | |
53 | 942 def launch(fsroot=None, urlroot='/', multithread=True, **kwargs): |
2 | 943 """ |
944 Launch and return a TinCan webapp. Does not run the app; it is the | |
945 caller's responsibility to call app.run() | |
946 """ | |
947 if fsroot is None: | |
948 fsroot = os.getcwd() | |
45
969f515b505b
Make it easy to leave stdout and stderr alone (untested).
David Barts <n5jrn@me.com>
parents:
44
diff
changeset
|
949 launcher = _Launcher(fsroot, urlroot, multithread) |
54 | 950 allowed = set(["logger", "encoding", "static"]) |
951 for k, v in kwargs.items(): | |
952 if k not in allowed: | |
953 raise TypeError("launch() got an unexpected keyword argument {0!r}".format(k)) | |
954 setattr(launcher, k, v) | |
4
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
955 launcher.launch() |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
956 return launcher.app, launcher.errors |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
957 |
0d47859f792a
Finally got "hello, world" working. Still likely many bugs.
David Barts <n5jrn@me.com>
parents:
3
diff
changeset
|
958 # 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
|
959 # launcher script for why. |