1 |
|
2 """ |
|
3 Handling page requests |
|
4 """ |
|
5 |
|
6 # for filesystem ops |
|
7 import os, os.path |
|
8 import time |
|
9 |
|
10 # for ResponseError |
|
11 import http |
|
12 |
|
13 # for TemplatePage |
|
14 import template |
|
15 |
|
16 from page_tree import page_tree |
|
17 import config |
|
18 |
|
19 # path to directory containing the page heirarcy |
|
20 PAGE_DIR = "pages" |
|
21 |
|
22 class PageError (http.ResponseError) : |
|
23 """ |
|
24 Error looking up/handling a page |
|
25 """ |
|
26 |
|
27 pass |
|
28 |
|
29 # XXX: should inherit from PageInfo |
|
30 class Page (object) : |
|
31 """ |
|
32 This object represents the information about our attempt to render some specific page |
|
33 """ |
|
34 |
|
35 def __init__ (self, url, path, basename, url_tail, charset='utf8') : |
|
36 """ |
|
37 Initialize the page at the given location |
|
38 |
|
39 @param url the URL leading to this page |
|
40 @param path the filesystem path to this page's file |
|
41 @param basename the filesystem name of this page's file, without the file extension |
|
42 @param url_trail trailing URL for this page |
|
43 @param charset file charset |
|
44 """ |
|
45 |
|
46 # store |
|
47 self.url = url |
|
48 self.path = path |
|
49 self.basename = basename |
|
50 self.url_tail = url_tail |
|
51 self.charset = charset |
|
52 |
|
53 # unbound |
|
54 self.request = None |
|
55 |
|
56 # sub-init |
|
57 self._init() |
|
58 |
|
59 def _init (self) : |
|
60 """ |
|
61 Do initial data loading, etc |
|
62 """ |
|
63 |
|
64 pass |
|
65 |
|
66 def bind_request (self, request) : |
|
67 """ |
|
68 Bind this page-render to the given request |
|
69 """ |
|
70 |
|
71 self.request = request |
|
72 |
|
73 @property |
|
74 def title (self) : |
|
75 """ |
|
76 Return the page's title |
|
77 |
|
78 Defaults to the retreiving the page title from page_list, or basename in Titlecase. |
|
79 """ |
|
80 |
|
81 # lookup in page_list |
|
82 page_info = page_tree.get_page(self.url) |
|
83 |
|
84 # fallback to titlecase |
|
85 if page_info : |
|
86 title = page_info.title |
|
87 |
|
88 else : |
|
89 title = self.basename.title() |
|
90 |
|
91 return title |
|
92 |
|
93 @property |
|
94 def content (self) : |
|
95 """ |
|
96 Return the page content as a string |
|
97 """ |
|
98 |
|
99 abstract |
|
100 |
|
101 @property |
|
102 def modified (self) : |
|
103 """ |
|
104 Returns the page modification timestamp |
|
105 """ |
|
106 |
|
107 # stat |
|
108 timestamp = os.stat(self.path).st_mtime |
|
109 |
|
110 return time.strftime(config.DATETIME_FMT, time.gmtime(timestamp)) |
|
111 |
|
112 class HTMLPage (Page) : |
|
113 """ |
|
114 A simple .html page that's just passed through directly |
|
115 """ |
|
116 |
|
117 @property |
|
118 def content (self) : |
|
119 """ |
|
120 Opens the .html file, reads and returns contents |
|
121 """ |
|
122 |
|
123 return open(self.path, 'rb').read().decode(self.charset) |
|
124 |
|
125 class TemplatePage (Page) : |
|
126 """ |
|
127 A template that's rendered using our template library |
|
128 """ |
|
129 |
|
130 @property |
|
131 def content (self) : |
|
132 """ |
|
133 Loads the .tmpl file, and renders it |
|
134 """ |
|
135 |
|
136 return template.render_file(self.path, |
|
137 request = self.request, |
|
138 page_tree = page_tree |
|
139 ) |
|
140 |
|
141 # list of page handlers, by type |
|
142 TYPE_HANDLERS = [ |
|
143 ('html', HTMLPage ), |
|
144 (template.TEMPLATE_EXT, TemplatePage ), |
|
145 ] |
|
146 |
|
147 def _lookup_handler (url, path, filename, basename, extension, tail) : |
|
148 """ |
|
149 We found the file that we looked for, now get its handler |
|
150 """ |
|
151 |
|
152 # find appropriate handler |
|
153 for handler_ext, handler in TYPE_HANDLERS : |
|
154 # match against file extension? |
|
155 if handler_ext == extension : |
|
156 # found handler, return instance |
|
157 return handler(url, path, basename, tail) |
|
158 |
|
159 # no handler found |
|
160 raise PageError("No handler found for page %r of type %r" % (url, extension)) |
|
161 |
|
162 def lookup (name) : |
|
163 """ |
|
164 Look up and return a Page object for the given page, or raise an error |
|
165 """ |
|
166 |
|
167 # inital path |
|
168 path = PAGE_DIR |
|
169 url_segments = [] |
|
170 |
|
171 # name segments |
|
172 segments = name.split('/') |
|
173 |
|
174 # iterate through the parts of the page segments |
|
175 while True : |
|
176 segment = None |
|
177 |
|
178 # pop segment |
|
179 if segments : |
|
180 segment = segments.pop(0) |
|
181 |
|
182 url_segments.append(segment) |
|
183 |
|
184 # translate empty -> index |
|
185 if not segment : |
|
186 segment = 'index' |
|
187 |
|
188 # look for it in the dir |
|
189 for filename in os.listdir(path) : |
|
190 # build full file path |
|
191 file_path = os.path.join(path, filename) |
|
192 |
|
193 # stat, recurse into subdirectory? |
|
194 if os.path.isdir(file_path) and filename == segment : |
|
195 # use new dir |
|
196 path = file_path |
|
197 |
|
198 # break for-loop to look at next segment |
|
199 break |
|
200 |
|
201 # split into basename + extension |
|
202 basename, extension = os.path.splitext(filename) |
|
203 |
|
204 # ...remove that dot |
|
205 extension = extension.lstrip('.') |
|
206 |
|
207 # match against requested page name? |
|
208 if basename == segment : |
|
209 # found the file we wanted |
|
210 return _lookup_handler('/'.join(url_segments), file_path, filename, basename, extension, '/'.join(segments)) |
|
211 |
|
212 else : |
|
213 # inspect next file in dir |
|
214 continue |
|
215 |
|
216 else : |
|
217 # did not find any dir or file, break out of while loop |
|
218 break |
|
219 |
|
220 # did not find the filename we were looking for in os.listdir |
|
221 raise PageError("Page not found: %s" % name, status='404 Not Found') |
|
222 |
|