Attachment 'diamond.py'
Download 1 #! /usr/bin/env python2.2
2
3 """Quick-quick implementation of WikiWikiWeb in Python
4 """
5
6 # Copyright (C) 1999, 2000 Martin Pool <mbp@humbug.org.au>
7 # Copyright (C) 2003 Kimberley Burchett http://www.kimbly.com/
8
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13
14 # This program is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # General Public License for more details.
18
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 # USA
23
24 __version__ = '0.2';
25
26
27 import cgi, sys, string, os, re, errno, time, stat, urllib
28 from cgi import log
29 from os import path, environ
30 from socket import gethostbyaddr
31 from time import localtime, strftime
32 from cStringIO import StringIO
33
34 True = 1
35 False = 0
36
37 def emit_header():
38 print "Content-type: text/html"
39 print
40
41
42 # Regular expression defining a WikiWord (but this definition
43 # is also assumed in other places.
44 word_re_str = r"\b([A-Z][a-z]+){2,}\b"
45 word_anchored_re = re.compile('^' + word_re_str + '$')
46 command_re_str = "(search|edit|fullsearch|titlesearch)\=(.*)"
47
48 # Editlog -----------------------------------------------------------
49
50 # Functions to keep track of when people have changed pages, so we can
51 # do the recent changes page and so on.
52 # The editlog is stored with one record per line, as tab-separated
53 # words: page_name, host, time
54
55 # TODO: Check values written in are reasonable
56
57 def editlog_add(page_name, host):
58 editlog = open(editlog_name, 'a+')
59 try:
60 # fcntl.flock(editlog.fileno(), fcntl.LOCK_EX)
61 editlog.seek(0, 2) # to end
62 editlog.write(string.join((page_name,host,`time.time()`), "\t") + "\n")
63 finally:
64 # fcntl.flock(editlog.fileno(), fcntl.LOCK_UN)
65 editlog.close()
66
67
68 def editlog_raw_lines():
69 editlog = open(editlog_name, 'rt')
70 try:
71 # fcntl.flock(editlog.fileno(), fcntl.LOCK_SH)
72 return editlog.readlines()
73 finally:
74 # fcntl.flock(editlog.fileno(), fcntl.LOCK_UN)
75 editlog.close()
76
77
78
79
80 # Formatting stuff --------------------------------------------------
81
82
83 def get_scriptname():
84 return environ.get('SCRIPT_NAME', '')
85
86
87 def send_title(text, link=None, msg=None):
88 print "<head><title>%s</title>" % text
89 print """<style type="text/css">
90 .body {
91 font: 10pt/15pt arial;
92 background-color: #FFFFFF;
93 color: #000000;
94 }
95 a {
96 font-weight: bold;
97 color: #7050c0;
98 text-decoration: none;
99 }
100 a:hover, a:active {
101 text-decoration: underline;
102 color: #5080a0;
103 }
104 a.nonexistent {
105 vertical-align: top;
106 }
107 .title, .title a, .title a:hover, .title a:active {
108 font: normal 24pt georgia;
109 letter-spacing: 1px;
110 color: #000000;
111 }
112 .metabox {
113 background-color: #d8d8f4;
114 }
115 .metakey {
116 background-color: #e8e8f4;
117 font: normal 10pt/11pt arial;
118 }
119 .metaval {
120 background-color: #ffffff;
121 font: normal 10pt/11pt arial;
122 }
123 </style>"""
124 print "</head>"
125 print '<body class="body" bgcolor="#ffffff" text="#000000" link="#4040ff vlink="#4040ff>'
126 print '<table><tr><td valign=absmiddle>'
127 print '<a href="%s">%s</a>' % (get_scriptname()+"/BrowseFacets/", logo_string)
128 print '</td>'
129 print '<td width=10></td>'
130 print '<td><h1 class="title">'
131 if link:
132 print '<a href="%s">%s</a>' % (link, text)
133 else:
134 print text
135 print '</h1></td></table><p>'
136 if msg: print msg, "<hr>"
137
138
139
140 def link_tag(params, text=None, ss_class=None):
141 if text is None:
142 text = params # default
143 if ss_class:
144 classattr = 'class="%s" ' % ss_class
145 else:
146 classattr = ''
147 return '<a %s href="%s/%s">%s</a>' % (classattr, get_scriptname(),
148 params, text)
149
150
151 def send_unordered_list(list):
152 print "<UL>"
153 for item in list:
154 print '<LI>' + item
155 print "</UL>"
156
157
158
159 # Search ---------------------------------------------------
160
161 def do_fullsearch(needle):
162 send_title('Full text search for "%s"' % (needle))
163
164 needle_re = re.compile(needle, re.IGNORECASE)
165 hits = []
166 all_pages = page_list()
167 for page_name in all_pages:
168 body = Page(page_name).get_body()
169 count = len(needle_re.findall(body))
170 if count:
171 hits.append((count, page_name))
172
173 # The default comparison for tuples compares elements in order,
174 # so this sorts by number of hits
175 hits.sort()
176 hits.reverse()
177
178 print "<span class='body nav'>"
179 print "<UL>"
180 for (count, page_name) in hits:
181 print '<LI>' + Page(page_name).link_to()
182 print ' . . . . ' + `count`
183 print ['match', 'matches'][count != 1]
184 print "</UL>"
185 print "</span>"
186
187 print_search_stats(len(hits), len(all_pages))
188
189
190 def do_browse(view):
191 if (view.breadcrumb()):
192 send_title("Browsing "+view.breadcrumb())
193 else:
194 send_title("Browsing All Pages")
195
196 all_pages = page_list()
197 hits = view.pages()
198
199 refinements = view.refinements(hits)
200 groups = group_refinements(refinements)
201
202 print "<table class='body nav'><tr><td valign=top>"
203
204 group_names = groups.keys()
205 group_names.sort()
206 first = True
207 for group_name in group_names:
208 if not first: print "<br>"
209 first = False
210 print "<b>%s</b><br>" % group_name
211 for kv in groups[group_name]:
212 subview = View().copy(view).narrow(kv)
213 print " %s <small>(%s)</small><br>" % \
214 (subview.link_to(kv.val), kv.count)
215 none_view = View().copy(view).narrow(KeyVal(group_name, ""))
216 none_view_pages = none_view.pages()
217 if none_view_pages:
218 print " %s <small>(%s)</small><br>" % \
219 (none_view.link_to("<none>"), len(none_view_pages))
220
221 print "</td><td width=30>"
222 print "</td><td valign='top'>"
223
224 print "<p><b>%d pages" % len(hits)
225 if len(all_pages) == len(hits):
226 print "</b>"
227 else:
228 print " (out of %s)</b>\n" % View().link_to(len(all_pages))
229
230 send_unordered_list([p.link_to() for p in hits])
231
232 print "</td></tr></table>"
233
234
235 def do_titlesearch(needle):
236 # TODO: check needle is legal -- but probably we can just accept any RE
237
238 send_title("Title search for \"" + needle + '"')
239
240 needle_re = re.compile(needle, re.IGNORECASE)
241 all_pages = page_list()
242 hits = filter(needle_re.search, all_pages)
243
244 print "<span class='body nav'>"
245 send_unordered_list([Page(file).link_to() for file in hits])
246 print "</span>"
247
248 print_search_stats(len(hits), len(all_pages))
249
250
251 def print_search_stats(hits, searched):
252 print "<p>%d hits " % hits
253 print " out of %d pages searched." % searched
254
255
256 def do_edit(pagename):
257 Page(pagename).send_editor()
258
259
260 def do_savepage(pagename):
261 global form
262 pg = Page(pagename)
263 if form.has_key('savetext'):
264 pg.save_text(form['savetext'].value)
265 else:
266 pg.save_text("")
267 if form.has_key('savemeta'):
268 pg.save_metadata(form['savemeta'].value)
269 else:
270 pg.save_metadata("");
271 msg = """<i>Thank you for your changes. Your attention to
272 detail is appreciated.</i>"""
273
274 pg.send_page(msg=msg)
275
276
277 def make_index_key():
278 s = '<p><center>'
279 links = map(lambda ch: '<a href="#%s">%s</a>' % (ch, ch),
280 string.lowercase)
281 s = s + string.join(links, ' | ')
282 s = s + '</center><p>'
283 return s
284
285
286 def page_list():
287 files = filter(word_anchored_re.match, os.listdir(text_dir))
288 files.sort()
289 return files
290
291
292 def print_footer(name, editable=True, mod_string=None):
293 base = get_scriptname()
294 print '<hr noshade size=1>'
295 if editable:
296 print link_tag('?edit='+name, 'EditText')
297 #if mod_string:
298 #print "(last modified %s)" % mod_string
299 print "|", link_tag('BrowseFacets', 'BrowseFacets')
300 print "|", link_tag('RecentChanges', 'RecentChanges')
301 print "|", link_tag('FindPage?value='+name, 'FindPage')
302
303
304 def group_refinements(refinements):
305 result = {}
306 for kv in refinements:
307 if result.has_key(kv.key):
308 result[kv.key].append(kv)
309 else:
310 result[kv.key] = [kv]
311 for key in result.keys():
312 result[key].sort()
313 return result
314
315
316 # raises ValueError on failure
317 def str_to_pair(str, separator):
318 i = string.index(str, separator)
319 return (str[0:i], str[i+len(separator):])
320
321
322
323 # ----------------------------------------------------------
324 # Macros
325 def _macro_TitleSearch():
326 return _macro_search("titlesearch")
327
328 def _macro_FullSearch():
329 return _macro_search("fullsearch")
330
331 def _macro_search(type):
332 if form.has_key('value'):
333 default = form["value"].value
334 else:
335 default = ''
336 return """<form method=get>
337 <input name=%s size=30 value="%s">
338 <input type=submit value="Go">
339 </form>""" % (type, default)
340
341 def _macro_GoTo():
342 return """<form method=get><input name=goto size=30>
343 <input type=submit value="Go">
344 </form>"""
345 # isindex is deprecated, but it gives the right result here
346
347 def _macro_WordIndex():
348 s = make_index_key()
349 pages = list(page_list())
350 map = {}
351 word_re = re.compile('[A-Z][a-z]+')
352 for name in pages:
353 for word in word_re.findall(name):
354 try:
355 map[word].append(name)
356 except KeyError:
357 map[word] = [name]
358
359 all_words = map.keys()
360 all_words.sort()
361 last_letter = None
362 for word in all_words:
363 letter = string.lower(word[0])
364 if letter != last_letter:
365 s = s + '<a name="%s"></a><h3>%s</h3>\n' % (letter, letter)
366 last_letter = letter
367
368 s = s + '<b>%s</b><br>\n' % word
369 links = map[word]
370 links.sort()
371 last_page = None
372 for name in links:
373 if name == last_page: continue
374 s = s + ' '*5 + Page(name).link_to() + "<br>\n"
375 return s
376
377
378 def _macro_TitleIndex():
379 s = make_index_key()
380 pages = list(page_list())
381 pages.sort()
382 current_letter = None
383 for name in pages:
384 letter = string.lower(name[0])
385 if letter != current_letter:
386 s = s + '<a name="%s"></a><h3>%s</h3>\n' % (letter, letter)
387 current_letter = letter
388 else:
389 s = s + '<br>\n'
390 s = s + Page(name).link_to()
391 return s
392
393
394 def _macro_RecentChanges():
395 lines = editlog_raw_lines()
396 lines.reverse()
397
398 ratchet_day = None
399 done_words = {}
400 buf = StringIO()
401 for line in lines:
402 page_name, addr, ed_time = line.split('\t')
403 # year, month, day, DoW
404 time_tuple = localtime(float(ed_time) - 4*60*60)
405 day = tuple(time_tuple[0:3])
406
407 if done_words.has_key(page_name):
408 continue
409
410 if day != ratchet_day:
411 buf.write('</table>\n')
412 buf.write('\n<h3>%s</h3>\n\n' % strftime(date_fmt, time_tuple))
413 buf.write('<table class="body nav" width="100%">\n')
414 ratchet_day = day
415
416 done_words[page_name] = True
417 buf.write('<tr><td width="35%">')
418 buf.write(Page(page_name).link_to())
419 if changed_time_fmt:
420 buf.write('</td><td>')
421 buf.write(time.strftime(changed_time_fmt, time_tuple))
422 buf.write('</td></tr>\n')
423 buf.write('</table>\n')
424
425 return buf.getvalue()
426
427
428
429 # ----------------------------------------------------------
430 class KeyVal:
431 """A key-value pair. This class is used to represent the metadata
432 for individual pages, as well as refinements when browsing."""
433
434 def __init__(self, key, val):
435 self.key = key
436 self.val = val
437 self.count = 0
438
439 def __cmp__(self, other):
440 key_lower = self.key.lower()
441 other_key_lower = other.key.lower()
442 if key_lower < other_key_lower: return -1
443 if key_lower > other_key_lower: return 1
444
445 if self.count and other.count:
446 if self.count < other.count: return 1
447 if self.count > other.count: return -1
448
449 val_lower = self.val.lower()
450 other_val_lower = other.val.lower()
451 if val_lower < other_val_lower: return -1
452 if val_lower > other_val_lower: return 1
453
454 return 0
455
456
457 def describe_val(self):
458 return self.val or "No "+self.key
459
460 def url_piece(self):
461 return "/%s=%s" % (urllib.quote(self.key), urllib.quote(self.val))
462
463
464 # ----------------------------------------------------------
465 class View:
466 """A view is the subset of pages that match particular metadata
467 key-value pairs. In fact, it's just the key/value pairs, and the
468 pages are handled separately"""
469
470 def __init__(self):
471 # note that keyvals is a list, not a dictionary. This is because
472 # we want to support multi-assignment (e.g. "Subject=foo" and
473 # "Subject=bar"). Also, we want to preserve order for breadcrumbs.
474 self.keyvals = []
475
476
477 def copy(self, other_view):
478 self.keyvals = [keyval for keyval in other_view.keyvals]
479 return self
480
481 def from_url(self, url):
482 self.keyvals = []
483 for chunk in url.split("/"):
484 try:
485 (key, val) = str_to_pair(chunk, "=")
486 self.narrow(KeyVal(key, val))
487 except ValueError, er:
488 pass
489 return self
490
491
492 # narrow the view to only pages that have the given key/value pair.
493 def narrow(self, keyval):
494 self.keyvals.append(keyval)
495 return self
496
497 def contains(self, keyval):
498 return keyval in self.keyvals
499
500
501 def pages(self):
502 all_pages = [Page(page_name) for page_name in page_list()]
503 return [p for p in all_pages if self.includes(p.metadata())]
504
505
506 def breadcrumb(self):
507 crumbs = [kv.describe_val() for kv in self.keyvals]
508 return string.join(crumbs, ", ")
509
510
511 def includes(self, other_view):
512 for kv in self.keyvals:
513 if kv.val == "":
514 # if we have no value, then make sure the other view has no
515 # value for this key
516 if len([okv for okv in other_view.keyvals if okv.key==kv.key]):
517 return False
518 else:
519 # if we do have a value, then make sure it's the same as the
520 # other view's value for this key
521 if kv not in other_view.keyvals:
522 return False
523 return True
524
525 def is_empty(self):
526 return len(self.keyvals) == 0
527
528
529 # returns [(String group, String val)]
530 def refinements(self, pages):
531 potential_refinements = []
532 for page in pages:
533 for kv in page.metadata().keyvals:
534 # ignore refinements that are already contained by this view
535 if not self.contains(kv):
536 already_seen = False
537 for ref in potential_refinements:
538 if ref == kv:
539 already_seen = True
540 ref.count = ref.count + 1
541 if not already_seen:
542 kv.count = 1
543 potential_refinements.append(kv)
544
545 # Only include refinements that aren't shared by all pages in the view.
546 # Otherwise we sometimes get boring, redundant refinements that "don't
547 # add any information"
548 result = []
549 for refinement in potential_refinements:
550 restricts_view = False
551 for page in pages:
552 if not page.metadata().contains(refinement):
553 restricts_view = True
554 if restricts_view:
555 result.append(refinement)
556
557 return result
558
559
560 def link_to(self, title):
561 url = ""
562 for kv in self.keyvals:
563 url += kv.url_piece()
564 return "<A HREF='%s/BrowseFacets%s'>%s</A>" % \
565 (get_scriptname(), url, title)
566
567
568 def to_string(self):
569 result = ""
570 for kv in self.keyvals:
571 if len(result): result += ", "
572 result += kv.key + ":" + kv.val
573 return result
574
575
576 # ----------------------------------------------------------
577 class PageFormatter:
578 """Object that turns Wiki markup into HTML.
579
580 All formatting commands can be parsed one line at a time, though
581 some state is carried over between lines.
582 """
583 def __init__(self, raw_body):
584 self.raw_body = raw_body
585 self.is_em = self.is_b = 0
586 self.list_indents = []
587 self.in_pre = 0
588
589
590 def _emph_repl(self, word):
591 if len(word) == 3:
592 self.is_b = not self.is_b
593 return ['</b>', '<b>'][self.is_b]
594 else:
595 self.is_em = not self.is_em
596 return ['</em>', '<em>'][self.is_em]
597
598 def _rule_repl(self, word):
599 s = self._undent()
600 width = (len(word) - 3) * 25;
601 if width < 0: width = 25
602 if width > 100: width = 100
603 s = s + "\n<hr noshade size=1 width='%s%%'>\n" % (width)
604 return s
605
606 def _word_repl(self, word):
607 return Page(word).link_to()
608
609
610 def _url_repl(self, word):
611 return '<a href="%s">%s</a>' % (word, word)
612
613
614 def _email_repl(self, word):
615 return '<a href="mailto:%s">%s</a>' % (word, word)
616
617
618 def _ent_repl(self, s):
619 return {'&': '&',
620 '<': '<',
621 '>': '>'}[s]
622
623
624 def _li_repl(self, match):
625 return '<li>'
626
627
628 def _pre_repl(self, word):
629 if word == '{{{' and not self.in_pre:
630 self.in_pre = True
631 return '<pre>'
632 elif self.in_pre:
633 self.in_pre = False
634 return '</pre>'
635 else:
636 return ''
637
638 def _macro_repl(self, word):
639 macro_name = word[2:-2]
640 # TODO: Somehow get the default value into the search field
641 return apply(globals()['_macro_' + macro_name], ())
642
643
644 def _indent_level(self):
645 return len(self.list_indents) and self.list_indents[-1]
646
647 def _indent_to(self, new_level):
648 s = ''
649 while self._indent_level() > new_level:
650 del(self.list_indents[-1])
651 s = s + '</ul>\n'
652 while self._indent_level() < new_level:
653 self.list_indents.append(new_level)
654 s = s + '<ul>\n'
655 return s
656
657 def _undent(self):
658 res = '</ul>' * len(self.list_indents)
659 self.list_indents = []
660 return res
661
662
663 def replace(self, match):
664 for type, hit in match.groupdict().items():
665 if hit:
666 return apply(getattr(self, '_' + type + '_repl'), (hit,))
667 else:
668 raise "Can't handle match " + `match`
669
670
671 def print_html(self):
672 # For each line, we scan through looking for magic
673 # strings, outputting verbatim any intervening text
674 scan_re = re.compile(
675 r"(?:(?P<emph>'{2,3})"
676 + r"|(?P<ent>[<>&])"
677 + r"|(?P<word>\b(?:[A-Z][a-z]+){2,}\b)"
678 + r"|(?P<rule>-{4,})"
679 + r"|(?P<url>(http|ftp|nntp|news|mailto)\:[^\s'\"]+\S)"
680 + r"|(?P<email>[-\w._+]+\@[\w.-]+)"
681 + r"|(?P<li>^\s+\*)"
682 + r"|(?P<pre>(\{\{\{|\}\}\}))"
683 + r"|(?P<macro>\[\[(TitleSearch|FullSearch|WordIndex"
684 + r"|TitleIndex|RecentChanges|GoTo)\]\])"
685 + r")")
686 blank_re = re.compile("^\s*$")
687 bullet_re = re.compile("^\s+\*")
688 indent_re = re.compile("^\s*")
689 eol_re = re.compile(r'\r?\n')
690 raw_body = string.expandtabs(self.raw_body)
691 for line in eol_re.split(raw_body):
692 if not self.in_pre:
693 # XXX: Should we check these conditions in this order?
694 if blank_re.match(line):
695 print '<p>'
696 continue
697 indent = indent_re.match(line)
698 print self._indent_to(len(indent.group(0)))
699 print re.sub(scan_re, self.replace, line)
700 if self.in_pre: print '</pre>'
701 print self._undent()
702
703
704 # ----------------------------------------------------------
705 class Page:
706 def __init__(self, page_name):
707 self.page_name = page_name
708
709 def split_title(self):
710 # look for the end of words and the start of a new word,
711 # and insert a space there
712 return re.sub('([a-z])([A-Z])', r'\1 \2', self.page_name)
713
714
715 def _body_filename(self):
716 return path.join(text_dir, self.page_name)
717
718 def _metadata_filename(self):
719 return path.join(text_dir, self.page_name + ".meta")
720
721
722 def _tmp_filename(self):
723 return path.join(text_dir, ('#' + self.page_name + '.' + `os.getpid()` + '#'))
724
725
726 def exists(self):
727 try:
728 os.stat(self._body_filename())
729 return True
730 except OSError, er:
731 if er.errno == errno.ENOENT:
732 return False
733 else:
734 raise er
735
736
737 def link_to(self):
738 word = self.page_name
739 if self.exists():
740 return link_tag(word)
741 else:
742 return word + link_tag(word, '*', 'nonexistent')
743
744
745 def raw_metadata(self):
746 try:
747 metatext = open(self._metadata_filename(), 'rt').read()
748 return metatext or 'Enter meta data here.'
749 except IOError, er:
750 if er.errno == errno.ENOENT:
751 # just doesn't exist, use default
752 return 'Enter meta data here.'
753 else:
754 raise er
755
756 def metadata(self):
757 metatext = self.raw_metadata()
758 metatext = string.replace(metatext, "\r\n", "\n")
759 view = View()
760 for line in metatext.splitlines():
761 try:
762 (key, val) = str_to_pair(line, ":")
763 view.narrow(KeyVal(key.strip(), val.strip()))
764 except ValueError, er:
765 # ignore invalid metatext lines
766 pass
767 # add automatic metadata
768 return view
769
770
771 def get_body(self):
772 try:
773 return open(self._body_filename(), 'rt').read()
774 except IOError, er:
775 if er.errno == errno.ENOENT:
776 # just doesn't exist, use default
777 return 'Describe %s here.' % self.page_name
778 else:
779 raise er
780
781
782 def send_page(self, msg=None):
783 link = get_scriptname() + '?fullsearch=' + self.page_name
784 send_title(self.split_title(), link, msg)
785 PageFormatter(self.get_body()).print_html()
786
787 # kbkb -- move this to a method somewhere
788 if not self.metadata().is_empty():
789 print "<p><table class='body metabox'>\n"
790
791 for kv in self.metadata().keyvals:
792 print "<tr>"
793 print "<td class='metakey'>%s: </td> " % (kv.key)
794 val_link = View().narrow(kv).link_to(kv.val)
795 print "<td class='metaval'> %s</td>" % (val_link)
796 print "</tr>\n"
797
798 if not self.metadata().is_empty():
799 print "</table>\n"
800
801 print_footer(self.page_name, True, self._last_modified())
802
803
804 def _last_modified(self):
805 if not self.exists():
806 return None
807 modtime = localtime(os.stat(self._body_filename())[stat.ST_MTIME])
808 return strftime(datetime_fmt, modtime)
809
810
811 def send_editor(self):
812 send_title('Edit ' + self.split_title())
813 print '<form method="post" action="%s">' % (get_scriptname())
814 print '<input type=hidden name="savepage" value="%s">' % (self.page_name)
815 body = string.replace(self.get_body(), '\r\n', '\n')
816 meta = string.replace(self.raw_metadata(), '\r\n', '\n')
817 print """<textarea wrap="virtual" name="savetext" rows="17"
818 cols="80">%s</textarea><br>""" % body
819 print """<textarea wrap="virtual" name="savemeta" rows="4"
820 cols="80">%s</textarea>""" % meta
821 print """<br><input type=submit value="Save">
822 <input type=reset value="Reset">
823 """
824 print "</form>"
825 print "<p>" + Page('EditingTips').link_to()
826
827
828 def _write_file(self, text, filename):
829 tmp_filename = self._tmp_filename()
830 open(tmp_filename, 'wt').write(text)
831 if os.name == 'nt':
832 # Bad Bill! POSIX rename ought to replace. :-(
833 try:
834 os.remove(filename)
835 except OSError, er:
836 if er.errno != errno.ENOENT: raise er
837 os.rename(tmp_filename, filename)
838
839
840 def save_text(self, newtext):
841 self._write_file(newtext, self._body_filename())
842 remote_name = environ.get('REMOTE_ADDR', '')
843 editlog_add(self.page_name, remote_name)
844
845 def save_metadata(self, newmetatext):
846 self._write_file(newmetatext, self._metadata_filename())
847 remote_name = environ.get('REMOTE_ADDR', '')
848 editlog_add(self.page_name, remote_name)
849
850
851 emit_header()
852
853 # Configurable parts ------------------------------------------
854 data_dir = '/users/web/kimbly/web/piki-data/'
855 text_dir = path.join(data_dir, 'text')
856 editlog_name = path.join(data_dir, 'editlog')
857 cgi.logfile = path.join(data_dir, 'cgi_log')
858 logo_string = """<img style="vertical-align: middle"
859 src="/piki-data/diamond.jpg"
860 border=0
861 alt="diamond logo">"""
862 changed_time_fmt = '%I:%M %P'
863 date_fmt = '%A, %d %B %Y'
864 datetime_fmt = '%a %d %b %Y %I:%M %p'
865 css_url = '/piki-data/piki.css' # stylesheet link, or ''
866
867 try:
868 form = cgi.FieldStorage()
869
870 handlers = { 'fullsearch': do_fullsearch,
871 'titlesearch': do_titlesearch,
872 'edit': do_edit,
873 'savepage': do_savepage }
874
875 for cmd in handlers.keys():
876 if form.has_key(cmd):
877 apply(handlers[cmd], (form[cmd].value,))
878 break
879 else:
880 path_info = environ.get('PATH_INFO', '')
881
882 if form.has_key('goto'):
883 query = form['goto'].value
884 elif len(path_info) and path_info[0] == '/':
885 query = path_info[1:] or 'FrontPage'
886 else:
887 query = environ.get('QUERY_STRING', '') or 'FrontPage'
888
889 word_match = re.match(word_re_str, query)
890 if path_info.startswith("/BrowseFacets/") or path_info=="/BrowseFacets":
891 do_browse(View().from_url(path_info[13:]))
892 elif word_match:
893 word = word_match.group(0)
894 Page(word).send_page()
895 else:
896 print "<p>Can't work out query \"<pre>" + query + "</pre>\""
897
898 except:
899 cgi.print_exception()
900
901 sys.stdout.flush()
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.