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