10 import http |
10 import http |
11 |
11 |
12 # for TemplatePage |
12 # for TemplatePage |
13 import template |
13 import template |
14 |
14 |
|
15 from page_tree import page_tree |
|
16 |
15 # path to directory containing the page heirarcy |
17 # path to directory containing the page heirarcy |
16 PAGE_DIR = "pages" |
18 PAGE_DIR = "pages" |
17 |
19 |
18 # path to directory containing the list of visible pages |
|
19 PAGE_LIST_FILE = os.path.join(PAGE_DIR, "list") |
|
20 |
|
21 class PageError (http.ResponseError) : |
20 class PageError (http.ResponseError) : |
22 """ |
21 """ |
23 Error looking up/handling a page |
22 Error looking up/handling a page |
24 """ |
23 """ |
25 |
24 |
26 pass |
25 pass |
27 |
|
28 class PageInfo (object) : |
|
29 """ |
|
30 Contains metainformation about a page |
|
31 """ |
|
32 |
|
33 def __init__ (self, parent, name, title, children=None) : |
|
34 """ |
|
35 Initialize, children defaults to empty list |
|
36 """ |
|
37 |
|
38 # store |
|
39 self.parent = parent |
|
40 self.name = name |
|
41 self.title = title |
|
42 self.children = children if children else [] |
|
43 |
|
44 # no url get |
|
45 self._url = None |
|
46 |
|
47 def set_parent (self, parent) : |
|
48 """ |
|
49 Set a parent where non was set before |
|
50 """ |
|
51 |
|
52 assert self.parent is None |
|
53 |
|
54 self.parent = parent |
|
55 |
|
56 def add_child (self, child) : |
|
57 """ |
|
58 Add a PageInfo child |
|
59 """ |
|
60 |
|
61 self.children.append(child) |
|
62 |
|
63 def get_child (self, name) : |
|
64 """ |
|
65 Look up a child by name, returning None if not found |
|
66 """ |
|
67 |
|
68 return dict((c.name, c) for c in self.children).get(name) |
|
69 |
|
70 def get_ancestry (self) : |
|
71 """ |
|
72 Returns a list of this page's parents and the page itself, but not root |
|
73 """ |
|
74 |
|
75 # collect in reverse order |
|
76 ancestry = [] |
|
77 |
|
78 # starting from self |
|
79 item = self |
|
80 |
|
81 # add all items, but not root |
|
82 while item and item.parent : |
|
83 ancestry.append(item) |
|
84 |
|
85 item = item.parent |
|
86 |
|
87 # reverse |
|
88 ancestry.reverse() |
|
89 |
|
90 # done |
|
91 return ancestry |
|
92 |
|
93 @property |
|
94 def url (self) : |
|
95 """ |
|
96 Build this page's URL |
|
97 """ |
|
98 |
|
99 # cached? |
|
100 if self._url : |
|
101 return self._url |
|
102 |
|
103 segments = [item.name for item in self.get_ancestry()] |
|
104 |
|
105 # add empty segment if dir |
|
106 if self.children : |
|
107 segments.append('') |
|
108 |
|
109 # join |
|
110 url = '/'.join(segments) |
|
111 |
|
112 # cache |
|
113 self._url = url |
|
114 |
|
115 # done |
|
116 return url |
|
117 |
|
118 class PageTree (object) : |
|
119 """ |
|
120 The list of pages |
|
121 """ |
|
122 |
|
123 def __init__ (self) : |
|
124 """ |
|
125 Empty PageList, must call load_page_list to initialize, once |
|
126 """ |
|
127 |
|
128 def _load (self, path=PAGE_LIST_FILE) : |
|
129 """ |
|
130 Processes the lines in the given file |
|
131 """ |
|
132 |
|
133 # collect the page list |
|
134 pages = [] |
|
135 |
|
136 # stack of (indent, PageInfo) items |
|
137 stack = [] |
|
138 |
|
139 # the previous item processed, None for first one |
|
140 prev = None |
|
141 |
|
142 for line in open(path, 'rb') : |
|
143 indent = 0 |
|
144 |
|
145 # count indent |
|
146 for char in line : |
|
147 # tabs break things |
|
148 assert char != '\t' |
|
149 |
|
150 # increment up to first non-space char |
|
151 if char == ' ' : |
|
152 indent += 1 |
|
153 |
|
154 elif char == ':' : |
|
155 indent = 0 |
|
156 break |
|
157 |
|
158 else : |
|
159 break |
|
160 |
|
161 # strip whitespace |
|
162 line = line.strip() |
|
163 |
|
164 # ignore empty lines |
|
165 if not line : |
|
166 continue |
|
167 |
|
168 # parse line |
|
169 url, title = line.split(':') |
|
170 |
|
171 # remove whitespace |
|
172 url = url.strip() |
|
173 title = title.strip() |
|
174 |
|
175 # create PageInfo item without parent |
|
176 item = PageInfo(None, url, title) |
|
177 |
|
178 # are we the first item? |
|
179 if not prev : |
|
180 assert url == '', "Page list must begin with root item" |
|
181 |
|
182 # root node does not have a parent |
|
183 parent = None |
|
184 |
|
185 # set root |
|
186 self.root = item |
|
187 |
|
188 # tee hee hee |
|
189 self.root.add_child(self.root) |
|
190 |
|
191 # initialize stack |
|
192 stack.append((0, self.root)) |
|
193 |
|
194 else : |
|
195 # peek stack |
|
196 stack_indent, stack_parent = stack[-1] |
|
197 |
|
198 # new indent level? |
|
199 if indent > stack_indent : |
|
200 # set parent to previous item, and push new indent level + parent to stack |
|
201 parent = prev |
|
202 |
|
203 # push new indent level + its parent |
|
204 stack.append((indent, parent)) |
|
205 |
|
206 # same indent level as previous |
|
207 elif indent == stack_indent : |
|
208 # parent is the one of the current stack level, stack doesn't change |
|
209 parent = stack_parent |
|
210 |
|
211 # unravel stack |
|
212 elif indent < stack_indent : |
|
213 while True : |
|
214 # remove current stack level |
|
215 stack.pop(-1) |
|
216 |
|
217 # peek next level |
|
218 stack_indent, stack_parent = stack[-1] |
|
219 |
|
220 # found the level to return to? |
|
221 if stack_indent == indent : |
|
222 # restore prev |
|
223 parent = stack_parent |
|
224 |
|
225 break |
|
226 |
|
227 elif stack_indent < indent : |
|
228 assert False, "Bad un-indent" |
|
229 |
|
230 # add to parent? |
|
231 if parent : |
|
232 item.set_parent(parent) |
|
233 parent.add_child(item) |
|
234 |
|
235 # update prev |
|
236 prev = item |
|
237 |
|
238 # get root |
|
239 assert hasattr(self, 'root'), "No root item found!" |
|
240 |
|
241 def get_page (self, url) : |
|
242 """ |
|
243 Lookup the given page URL, and return the matching PageInfo object, or None, if not found |
|
244 """ |
|
245 |
|
246 # start from root |
|
247 node = self.root |
|
248 |
|
249 # traverse the object tree |
|
250 for segment in url.split('/') : |
|
251 if segment : |
|
252 node = node.get_child(segment) |
|
253 |
|
254 if not node : |
|
255 return None |
|
256 |
|
257 # return |
|
258 return node |
|
259 |
|
260 def get_siblings (self, url) : |
|
261 """ |
|
262 Get the list of siblings for the given url, including the given page itself |
|
263 """ |
|
264 |
|
265 # look up the page itself |
|
266 page = self.get_page(url) |
|
267 |
|
268 # specialcase root/unknown node |
|
269 if page and page.parent : |
|
270 return page.parent.children |
|
271 |
|
272 else : |
|
273 return self.root.children |
|
274 |
|
275 def dump (self) : |
|
276 """ |
|
277 Returns a string representation of the tree |
|
278 """ |
|
279 |
|
280 def _print_node (indent, node) : |
|
281 return '\n'.join('%s%s' % (' '*indent, line) for line in [ |
|
282 "%-15s : %s" % (node.name, node.title) |
|
283 ] + [ |
|
284 _print_node(indent + 4, child) for child in node.children if child != node |
|
285 ]) |
|
286 |
|
287 return _print_node(0, self.root) |
|
288 |
|
289 # global singleton PageList instance |
|
290 page_tree = PageTree() |
|
291 |
|
292 def load_page_tree () : |
|
293 """ |
|
294 Load the global singleton PageInfo instance |
|
295 """ |
|
296 |
|
297 page_tree._load() |
|
298 |
26 |
299 # XXX: should inherit from PageInfo |
27 # XXX: should inherit from PageInfo |
300 class Page (object) : |
28 class Page (object) : |
301 """ |
29 """ |
302 This object represents the information about our attempt to render some specific page |
30 This object represents the information about our attempt to render some specific page |