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