From: Bernardo Innocenti <bernie@trinity.develer.com> Date: Sat, 8 Sep 2007 19:34:56 +0000 (+0200) Subject: Rename pikiplus to geekigeeki. X-Git-Tag: v2.0~11 X-Git-Url: https://codewiz.org/gitweb?a=commitdiff_plain;h=76a9e7542e0ed64a3054882db2cf4ae18e4f558e;p=geekigeeki.git Rename pikiplus to geekigeeki. --- diff --git a/geekigeeki.css b/geekigeeki.css new file mode 100755 index 0000000..666659f --- /dev/null +++ b/geekigeeki.css @@ -0,0 +1,256 @@ +/* + * $Id: intrafm.css,v 1.10 2002/12/04 17:57:21 gerry Exp $ + * + * Author: Gerardo Poggiali <g.poggiali@develer.com> + */ + +body { + font-family: sans-serif; + background-color: #F0F0F8; + color: #000000; + margin-top: 0px; + margin-left: 0px; + margin-right: 0px; +} + +a { + color: #1010FF; +} +a:visited { + color: #501080; +} + +a.nonexistent { + color: #D04040; +} + +a.navlink { + font-size: smaller; +} + +a.external { + font-style: italic; +} + +h1 { + font-size: x-large; + font-weight: bold; + color: #3377FF; + border: #5599EE; + background-color: #C2E2FF; + + margin-bottom: 5px; + margin-top: 5px; + padding-right: 10px; + padding-left: 10px; + + border-top-width: 1px; + border-bottom-width: 1px; + border-right-width: 0px; + border-left-width: 0px; + border-style: solid; +} + +h2 { + font-size: large; + font-weight: bold; + color: #22AA33; + border: #22AA33; + background-color: #E8F8F0; + + margin-left: 5px; + margin-right: 5px; + margin-bottom: 5px; + margin-top: 5px; + padding-right: 10px; + padding-left: 10px; + + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 0px; + border-right-width: 0px; + border-style: solid; +} + +h3 { + font-size: large; + font-weight: bold; + color: #DD2255; + border: #BB5599; + + margin-left: 10px; + margin-right: 10px; + padding-right: 5px; + padding-left: 5px; + + border-top-width: 0px; + border-bottom-width: 1px; + border-right-width: 0px; + border-left-width: 0px; + border-style: solid; +} + +h4 { + font-size: medium; + font-weight: bold; + color: #2222AA; + + margin-left: 10px; + margin-right: 10px; + padding-right: 5px; + padding-left: 5px; + + border: #333399; + border-top-width: 0px; + border-bottom-width: 1px; + border-right-width: 0px; + border-left-width: 0px; + border-style: solid; +} + +h5 { + font-size: medium; + font-weight: bold; + color: #222288; + + margin-left: 10px; + margin-right: 10px; + padding-right: 5px; + padding-left: 5px; +} + +div { + margin-left: 10px; + margin-right: 10px; + text-align: justify; +} +div.wiki { + margin-left: 0; + margin-right: 0; +} + +div.preview { + border: 2px dashed #FF0000; +} + +div.navigator { + border-color: #cc9933; + background-color: #ffcc66; + border-style: solid; + border-top-width: 0px; + border-bottom-width: 2px; + border-left-width: 0px; + border-right-width: 0px; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 5px; + padding-right: 5px; + margin-top: 0px; + margin-bottom: 10px; + margin-left: 0px; + margin-right: 0px; +} + +div.footer { + font-size: smaller; + color: #888888; + border-color: black; + + margin-top: 10px; + margin-left: 0px; + margin-right: 0px; + + border-style: solid; + border-top-width: 1px; + border-right-width: 0px; + border-bottom-width: 0px; + border-left-width: 0px; + + text-align: right; +} + +p.dialog { + border-color: black; + background-color: white; + padding-left: 5px; + margin-left: 10px; + margin-right: 10px; + border-style: solid; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px +} + +p { + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; + text-align: justify; +} + +caption { + background-color: #4682B4; + color: #FFFFFF; + font-weight: bold; + border: #000000; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 0px; + border-left-width: 1px; + border-style: solid; +} + +pre { + color: #222222; + border-color: #E9E609; + background: #FFFDD0; + + margin-left: 10px; + margin-right: 10px; + padding-left: 5px; + padding-right: 5px; + padding-top: 5px; + padding-bottom: 5px; + border-style: solid; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; +} + +pre.error { + color: #FF0000; + background: #000000; + border-top-color: #FF0000; + border-color: #FF0000; + border-style: solid; + border-width: 6px; + margin: 0px; +} + +pre.success { + color: #00FF00; + background: #000000; + border-color: #00FF00; + border-style: solid; + border-width: 6px; + margin: 0px; +} + +pre.notice { + color: #FFCC00; + background: #000000; + border-color: #FFCC00; + border-style: solid; + border-width: 6px; + margin: 0px; +} + +#editor { + font-family: monospace; + width: 100%; + color: black; + background-color: white; + border: 1px solid #8cacbb; +} diff --git a/geekigeeki.py b/geekigeeki.py new file mode 100755 index 0000000..e7001ab --- /dev/null +++ b/geekigeeki.py @@ -0,0 +1,708 @@ +#! /usr/bin/env python +"""Quick-quick implementation of WikiWikiWeb in Python +""" +# +# Copyright (C) 1999, 2000 Martin Pool <mbp@humbug.org.au> +# This version includes additional changes by Gerardo Poggiali (2002) +# This version includes additional changes by Bernardo Innocenti (2007) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +__version__ = '$Revision: 1.63+gerry+bernie $'[11:-2]; + +import cgi, sys, string, os, re, errno, time, stat +from os import path, environ +from socket import gethostbyaddr +from time import localtime, strftime + +# Regular expression defining a WikiWord +# (but this definition is also assumed in other places) +file_re = re.compile(r"^\b([A-Za-z0-9_\.\-]+)\b$") +word_re = re.compile(r"^\b([A-Z][a-z]+){2,}\b$") +img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg)$", re.IGNORECASE) +url_re = re.compile(r"^[a-z]{3,8}://[^\s'\"]+\S$") + +title_done = False + +def emit_header(type="text/html"): + print "Content-type: " + type + "; charset=utf-8" + print + + +# Formatting stuff -------------------------------------------------- + +def get_scriptname(): + return environ.get('SCRIPT_NAME', '') + +def get_hostname(addr): + try: + return gethostbyaddr(addr)[0] + ' (' + addr + ')' + except: + return addr; + +def send_guru(msg, msg_type): + if msg is None or len(msg) == 0: return + print '<pre id="guru" onclick="this.style.display = \'none\'" class="' + msg_type + '">' + if msg_type == 'error': + print ' Software Failure. Press left mouse button to continue.\n' + print msg + if msg_type == 'error': + print ' Guru Meditation #DEADBEEF.ABADC0DE' + print '</pre>' + # FIXME: This simple JS code is harder to pass than ACID 3.0 + print """ + <script language="JavaScript" type="text/javascript"> + var guru = document.getElementById('guru'); + // Firefox 2.0 doesn't take border-color, but returns border-top-color fine + var color = document.defaultView.getComputedStyle(guru,null).getPropertyValue('border-top-color'); + + function guruOn() { + guru.style.setProperty('border-color', color, ''); + setTimeout('guruOff()', 1000); + } + function guruOff() { + guru.style.setProperty('border-color', '#000000', ''); + setTimeout('guruOn()', 1000); + } + // Safari 2.0 returns this rgba crap + // Konqueror 3.5.6 doesn't seem to support computed properties + if (color && color != 'rgba(0, 0, 0, 0)') { + //window.alert("enabled! color='" + color + "'"); + guruOn(); + } + </script>""" + +def send_title(name, text="Limbo", msg=None, msg_type = 'error'): + global title_done + if title_done: return + + # Head + emit_header() + print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +""" + print "<head><title>%s: %s</title>" % (site_name, text) + print ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' + if not name: + print ' <meta name="robots" content="noindex,nofollow">' + if css_url: + print ' <link rel="stylesheet" type="text/css" href="%s" />' % css_url + print '</head>' + + # Body + if name and allow_edit: + print '<body ondblclick="location.href=\'?edit=' + name + '\'">' + else: + print '<body>' + + send_guru(msg, msg_type) + + # Navbar + print '<div class="navigator">' + print '<b>' + site_name + ': ' + if name: + print link_tag('?fullsearch=' + name, text) + '</b> ' + else: + print text + '</b> ' + print ' | ' + link_tag('FrontPage', 'Front Page', 'navlink') + print ' | ' + link_tag('FindPage', 'Find Page', 'navlink') + print ' | <a href="/wikigit/wiki.git" class="navlink">Recent Changes</a>' + if name: + print ' | <a href="/wikigit/wiki.git?a=history;f=' + name + '" class="navlink">Page History</a>' + print ' | ' + link_tag('?raw=' + name, 'Raw Text', 'navlink') + if allow_edit: + print ' | ' + link_tag('?edit=' + name, 'Edit Page', 'navlink') + else: + print ' | <i>Immutable Page</i>' + print '</div>' + + title_done = True + + +def link_tag(params, text=None, ss_class=None): + if text is None: + text = params # default + classattr = '' + # Prevent crawlers from following links to generated pages + # and links added by potential spammers + if ss_class == 'external' or ss_class == 'navlink': + classattr += 'rel="nofollow" ' + if ss_class: + classattr += 'class="%s" ' % ss_class + return '<a %shref="%s/%s">%s</a>' % (classattr, get_scriptname(), + params, text) + +# Search --------------------------------------------------- + +def do_fullsearch(needle): + send_title(None, 'Full text search for "%s"' % (needle)) + + needle_re = re.compile(needle, re.IGNORECASE) + hits = [] + all_pages = page_list() + for page_name in all_pages: + body = Page(page_name).get_raw_body() + count = len(needle_re.findall(body)) + if count: + hits.append((count, page_name)) + + # The default comparison for tuples compares elements in order, + # so this sorts by number of hits + hits.sort() + hits.reverse() + + print "<ul>" + for (count, page_name) in hits: + print '<li><p>' + Page(page_name).link_to() + print ' . . . . ' + `count` + print ['match', 'matches'][count <> 1] + print '</p></li>' + print "</ul>" + + print_search_stats(len(hits), len(all_pages)) + + +def do_titlesearch(needle): + # TODO: check needle is legal -- but probably we can just accept any RE + send_title(None, "Title search for \"" + needle + '"') + + needle_re = re.compile(needle, re.IGNORECASE) + all_pages = page_list() + hits = filter(needle_re.search, all_pages) + + print "<ul>" + for filename in hits: + print '<li><p>' + Page(filename).link_to() + "</p></li>" + print "</ul>" + + print_search_stats(len(hits), len(all_pages)) + + +def print_search_stats(hits, searched): + print "<p>%d hits out of %d pages searched.</p>" % (hits, searched) + +def do_edit(pagename): + Page(pagename).send_editor() + +def do_raw(pagename): + Page(pagename).send_raw() + +def do_savepage(pagename): + if not allow_edit: + raise 'editing disallowed for ' + pagename + + global form + pg = Page(pagename) + if 'preview' in form: + pg.send_editor(form['savetext'].value) + elif 'save' in form: + pg.save_text(form['savetext'].value) + pg.send_page() + elif 'cancel' in form: + pg.msg = 'Editing cancelled' + pg.msg_type = 'notice' + pg.send_page() + else: + raise 'What did you press?' + +def make_index_key(): + s = '<p><center>' + links = map(lambda ch: '<a href="#%s">%s</a>' % (ch, ch), + string.lowercase) + s = s + string.join(links, ' | ') + s = s + '</center></p>' + return s + + +def page_list(): + return filter(word_re.match, os.listdir(text_dir)) + + +def send_footer(name, mod_string=None): + print '<div class="footer">' + if mod_string: + print "last modified %s" % mod_string + print '</div></body></html>' + + +# ---------------------------------------------------------- +# Macros +def _macro_TitleSearch(): + return _macro_search("titlesearch") + +def _macro_FullSearch(): + return _macro_search("fullsearch") + +def _macro_search(type): + if form.has_key('value'): + default = form["value"].value + else: + default = '' + return """<form method="get"><input name="%s" size="30" value="%s"><input type="submit" value="Go"></form>""" % (type, default) + +def _macro_WordIndex(): + s = make_index_key() + pages = list(page_list()) + map = {} + word_re = re.compile('[A-Z][a-z]+') + for name in pages: + for word in word_re.findall(name): + try: + map[word].append(name) + except KeyError: + map[word] = [name] + + all_words = map.keys() + all_words.sort() + last_letter = None + # set title + for word in all_words: + letter = string.lower(word[0]) + if letter <> last_letter: + s = s + '; <a name="%s"><h3>%s</h3></a>' % (letter, letter) + last_letter = letter + + s = s + '<b>%s</b><ul>' % word + links = map[word] + links.sort() + last_page = None + for name in links: + if name == last_page: continue + s = s + '<li>' + Page(name).link_to() + s = s + '</ul>' + return s + + +def _macro_TitleIndex(): + s = make_index_key() + pages = list(page_list()) + pages.sort() + current_letter = None + for name in pages: + letter = string.lower(name[0]) + if letter <> current_letter: + s = s + '<a name="%s"><h3>%s</h3></a>' % (letter, letter) + current_letter = letter + else: + s = s + '<br />' + s = s + Page(name).link_to() + return s + + +# ---------------------------------------------------------- +class PageFormatter: + """Object that turns Wiki markup into HTML. + + All formatting commands can be parsed one line at a time, though + some state is carried over between lines. + """ + def __init__(self, raw): + self.raw = raw + self.is_em = self.is_b = 0 + self.h_level = 0 + self.list_indents = [] + self.in_pre = 0 + self.in_var = 0 + self.in_header = True + + def _emph_repl(self, word): + if len(word) == 3: + self.is_b = not self.is_b + return ['</b>', '<b>'][self.is_b] + else: + self.is_em = not self.is_em + return ['</em>', '<em>'][self.is_em] + + def _tit_repl(self, word): + if self.h_level: + result = "</h%d>" % self.h_level + self.h_level = 0 + else: + self.h_level = len(word) - 1 + result = "<h%d>" % self.h_level + return result; + + def _rule_repl(self, word): + s = self._undent() + if len(word) <= 3: + s = s + "\n<hr size='1' noshade=\"noshade\" />\n" + else: + s = s + "\n<hr size='%d' noshade=\"noshade\" />\n" % (len(word) - 2 ) + return s + + def _word_repl(self, word): + return Page(word).link_to() + + def _img_repl(self, word): + return '<img border="0" src="%s/%s" />' % (get_scriptname(), word) + + def _url_repl(self, word): + if img_re.match(word): + return '<img border="0" src="%s" />' % word + else: + return '<a href="%s" rel="nofollow" class="external">%s</a>' % (word, word) + + def _hurl_repl(self, word): + m = re.compile("\[\[(\S+)\ (.+)\]\]").match(word) + anchor = m.group(1) + descr = m.group(2) + if img_re.match(anchor): + return '<img border="0" src="%s" alt="%s" />' % (anchor, descr) + elif url_re.match(anchor): + return '<a href="%s" rel="nofollow" class="external">%s</a>' % (anchor, descr) + elif anchor.startswith('/'): + return '<a href="%s">%s</a>' % (anchor, descr) + else: + return link_tag(anchor, descr) + + def _email_repl(self, word): + return '<a href="mailto:%s">%s</a>' % (word, word) + + + def _ent_repl(self, s): + return {'&': '&', + '<': '<', + '>': '>'}[s] + + + def _li_repl(self, match): + return '<li>' + + + def _pre_repl(self, word): + if word == '{{{' and not self.in_pre: + self.in_pre = 1 + return '<pre>' + elif self.in_pre: + self.in_pre = 0 + return '</pre>' + else: + return '' + + def _var_repl(self, word): + if word == '{{' and not self.in_var: + self.in_var = 1 + return '<code>' + elif self.in_var: + self.in_var = 0 + return '</code>' + else: + return '' + def _macro_repl(self, word): + macro_name = word[2:-2] + # TODO: Somehow get the default value into the search field + return apply(globals()['_macro_' + macro_name], ()) + + def _indent_level(self): + return len(self.list_indents) and self.list_indents[-1] + + def _indent_to(self, new_level): + if self._indent_level() == new_level: + return '' + s = '</p>' + while self._indent_level() > new_level: + del(self.list_indents[-1]) + s += '</ul>\n' + while self._indent_level() < new_level: + self.list_indents.append(new_level) + s += '<ul>\n' + s += '<p>' + return s + + def _undent(self): + res = '</p>' + res += '</ul>' * len(self.list_indents) + res += '<p>' + self.list_indents = [] + return res + + def replace(self, match): + for type, hit in match.groupdict().items(): + if hit: + return apply(getattr(self, '_' + type + '_repl'), (hit,)) + else: + raise "Can't handle match " + `match` + + def print_html(self): + print "<div class='wiki'><p>" + + # For each line, we scan through looking for magic + # strings, outputting verbatim any intervening text + scan_re = re.compile( + r"(?:" + + r"(?P<emph>'{2,3})" + + r"|(?P<tit>\={2,6})" + + r"|(?P<ent>[<>&])" + + r"|(?P<img>\b[a-zA-Z0-9_-]+\.(png|gif|jpg|jpeg|bmp))" + + r"|(?P<word>\b(?:[A-Z][a-z]+){2,}\b)" + + r"|(?P<rule>^-{3,})" + + r"|(?P<hurl>\[\[\S+\s+.+\]\])" + + r"|(?P<url>(http|ftp|nntp|news|mailto)\:[^\s'\"]+\S)" + + r"|(?P<email>[-\w._+]+\@[\w.-]+)" + + r"|(?P<li>^\s+\*)" + + r"|(?P<pre>(\{\{\{|\s*\}\}\}))" + + r"|(?P<var>(\{\{|\}\}))" + + r"|(?P<macro>\[\[(TitleSearch|FullSearch|WordIndex|TitleIndex)\]\])" + + r")") + pre_re = re.compile( + r"(?:" + + r"(?P<pre>\s*\}\}\})" + + r")") + blank_re = re.compile("^\s*$") + indent_re = re.compile("^\s*") + eol_re = re.compile(r'\r?\n') + raw = string.expandtabs(self.raw) + for line in eol_re.split(raw): + # Skip ACLs + if self.in_header: + if line.startswith('#'): + continue + self.in_header = False + if self.in_pre: + print re.sub(pre_re, self.replace, line) + else: + # XXX: Should we check these conditions in this order? + if blank_re.match(line): + print '</p><p>' + continue + indent = indent_re.match(line) + print self._indent_to(len(indent.group(0))) + print re.sub(scan_re, self.replace, line) + if self.in_pre: print '</pre>' + print self._undent() + print "</p></div>" + +# ---------------------------------------------------------- +class Page: + def __init__(self, page_name): + self.page_name = page_name + self.remote_user = 'AnonymousCoward' + self.remote_host = environ.get('REMOTE_ADDR', '') + self.msg = '' + self.msg_type = 'error' + self.attrs = {} + + def split_title(self): + # look for the end of words and the start of a new word, + # and insert a space there + return re.sub('([a-z])([A-Z])', r'\1 \2', self.page_name) + + + def _text_filename(self): + return path.join(text_dir, self.page_name) + + + def _tmp_filename(self): + return path.join(text_dir, ('#' + self.page_name + '.' + `os.getpid()` + '#')) + + + def exists(self): + try: + os.stat(self._text_filename()) + return 1 + except OSError, er: + if er.errno == errno.ENOENT: + return 0 + else: + raise er + + def link_to(self): + word = self.page_name + if self.exists(): + return link_tag(word, word, 'wikilink') + else: + if nonexist_qm: + return link_tag(word, '?', 'nonexistent') + word + else: + return link_tag(word, word, 'nonexistent') + + + def get_raw_body(self): + try: + return open(self._text_filename(), 'rt').read() + except IOError, er: + if er.errno == errno.ENOENT: + # just doesn't exist, use default + return 'Describe %s here.' % self.page_name + else: + raise er + + def get_attrs(self): + if self.attrs: + return self.attrs + try: + file = open(self._text_filename(), 'rt') + attr_re = re.compile(r"^#(\S*)(.*)$") + for line in file: + m = attr_re.match(line) + if not m: + break + self.attrs[m.group(1)] = m.group(2).strip() + #print "bernie: attrs[" + m.group(1) + "] = " + m.group(2) + "<br>\n" + except IOError, er: + if er.errno != errno.ENOENT: + raise er + return self.attrs + + def can_edit(self): + attrs = self.get_attrs() + try: + # SomeUser:read,write All:read + acl = attrs["acl"] + for rule in acl.split(): + (user,perms) = acl.split(':') + if user == self.remote_user or user == "All": + if 'write' in perms.split(','): + return True + return False + except: + pass + return True + + def send_page(self): + page_name = None + if self.can_edit(): + page_name = self.page_name + send_title(page_name, self.split_title(), msg=self.msg, msg_type=self.msg_type) + PageFormatter(self.get_raw_body()).print_html() + send_footer(page_name, self._last_modified()) + + def _last_modified(self): + if not self.exists(): + return None + modtime = localtime(os.stat(self._text_filename())[stat.ST_MTIME]) + return strftime(datetime_fmt, modtime) + + def send_editor(self, preview=None): + send_title(None, 'Edit ' + self.split_title(), msg=self.msg, msg_type=self.msg_type) + + print ('<p><b>Editing ' + self.page_name + + ' for ' + self.remote_user + + ' from ' + get_hostname(self.remote_host) + + '</b></p>') + print '<div class="editor"><form method="post" action="%s/%s">' % (get_scriptname(), self.page_name) + print '<input type="hidden" name="savepage" value="%s">' % (self.page_name) + print """<textarea wrap="virtual" id="editor" name="savetext" rows="17" cols="80">%s</textarea>""" % (preview or self.get_raw_body()) + print """ + <br /> + <input type="submit" name="save" value="Save" /> + <input type="submit" name="preview" value="Preview" /> + <input type="reset" value="Reset" /> + <input type="submit" name="cancel" value="Cancel" /> + <br /> + </form></div>""" + print "<p>" + Page('EditingTips').link_to() + "</p>" + if preview: + print "<div class='preview'>" + PageFormatter(preview).print_html() + print "</div>" + + def send_raw(self): + emit_header("text/plain") + print self.get_raw_body() + + def _write_file(self, text): + tmp_filename = self._tmp_filename() + open(tmp_filename, 'wt').write(text.replace('\r\n', '\n')) + text = self._text_filename() + if os.name == 'nt': + # Bad Bill! POSIX rename ought to replace. :-( + try: + os.remove(text) + except OSError, er: + if er.errno <> errno.ENOENT: raise er + os.rename(tmp_filename, text) + + def save_text(self, newtext): + self._write_file(newtext) + rc = 0 + if post_edit_hook: + cmd = ( post_edit_hook + + ' ' + text_dir + '/' + self.page_name + + ' ' + self.remote_user + + ' ' + self.remote_host + ) + out = os.popen(cmd) + msg = out.read() + rc = out.close() + if rc: + self.msg += "Post-editing hook returned %d.\n" % rc + self.msg += 'Command was: ' + cmd + '\n' + if msg: + self.msg += 'Output follows:\n' + msg + else: + self.msg = 'Thankyou for your contribution. Your attention to detail is appreciated.' + self.msg_type = 'success' + +def send_verbatim(filename, mime_type='application/octet-stream'): + pathname = path.join(text_dir, filename) + data = open(pathname, 'rb').read() + emit_header(mime_type) + sys.stdout.write(data) + +# --------------------------------------------------------------- +try: + # Configuration values + data_dir = '/home/bernie/public_html/wiki' + text_dir = path.join(data_dir, 'text') + allow_edit = True # Is it possible to edit pages? + site_name = 'codewiz.org' + changed_time_fmt = '[%I:%M %p] ' + date_fmt = '%a %d %b %Y' + datetime_fmt = '%a %d %b %Y %I:%M %p' + show_hosts = True # show hostnames? + css_url = '../wikidata/pikiplus.css' # stylesheet link, or '' + nonexist_qm = False # show '?' for nonexistent? + post_edit_hook = './post_edit_hook.sh' + + form = cgi.FieldStorage() + + handlers = { 'fullsearch': do_fullsearch, + 'titlesearch': do_titlesearch, + 'edit': do_edit, + 'raw': do_raw, + 'savepage': do_savepage } + + for cmd in handlers.keys(): + if form.has_key(cmd): + apply(handlers[cmd], (form[cmd].value,)) + break + else: + path_info = environ.get('PATH_INFO', '') + if len(path_info) and path_info[0] == '/': + query = path_info[1:] or 'FrontPage' + else: + query = environ.get('QUERY_STRING', '') or 'FrontPage' + + if file_re.match(query): + if word_re.match(query): + Page(query).send_page() + elif img_re.match(query): + send_verbatim(query, 'image/jpeg') + else: + send_verbatim(query) + else: + # TODO: return 404? + send_title(None, msg='Can\'t work out query: ' + query) +except: + import traceback + msg=traceback.format_exc() + if title_done: + send_guru(msg, "error") + else: + send_title(None, msg=msg) + send_footer(None) + +sys.stdout.flush() diff --git a/pikiplus.css b/pikiplus.css deleted file mode 100755 index 666659f..0000000 --- a/pikiplus.css +++ /dev/null @@ -1,256 +0,0 @@ -/* - * $Id: intrafm.css,v 1.10 2002/12/04 17:57:21 gerry Exp $ - * - * Author: Gerardo Poggiali <g.poggiali@develer.com> - */ - -body { - font-family: sans-serif; - background-color: #F0F0F8; - color: #000000; - margin-top: 0px; - margin-left: 0px; - margin-right: 0px; -} - -a { - color: #1010FF; -} -a:visited { - color: #501080; -} - -a.nonexistent { - color: #D04040; -} - -a.navlink { - font-size: smaller; -} - -a.external { - font-style: italic; -} - -h1 { - font-size: x-large; - font-weight: bold; - color: #3377FF; - border: #5599EE; - background-color: #C2E2FF; - - margin-bottom: 5px; - margin-top: 5px; - padding-right: 10px; - padding-left: 10px; - - border-top-width: 1px; - border-bottom-width: 1px; - border-right-width: 0px; - border-left-width: 0px; - border-style: solid; -} - -h2 { - font-size: large; - font-weight: bold; - color: #22AA33; - border: #22AA33; - background-color: #E8F8F0; - - margin-left: 5px; - margin-right: 5px; - margin-bottom: 5px; - margin-top: 5px; - padding-right: 10px; - padding-left: 10px; - - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 0px; - border-right-width: 0px; - border-style: solid; -} - -h3 { - font-size: large; - font-weight: bold; - color: #DD2255; - border: #BB5599; - - margin-left: 10px; - margin-right: 10px; - padding-right: 5px; - padding-left: 5px; - - border-top-width: 0px; - border-bottom-width: 1px; - border-right-width: 0px; - border-left-width: 0px; - border-style: solid; -} - -h4 { - font-size: medium; - font-weight: bold; - color: #2222AA; - - margin-left: 10px; - margin-right: 10px; - padding-right: 5px; - padding-left: 5px; - - border: #333399; - border-top-width: 0px; - border-bottom-width: 1px; - border-right-width: 0px; - border-left-width: 0px; - border-style: solid; -} - -h5 { - font-size: medium; - font-weight: bold; - color: #222288; - - margin-left: 10px; - margin-right: 10px; - padding-right: 5px; - padding-left: 5px; -} - -div { - margin-left: 10px; - margin-right: 10px; - text-align: justify; -} -div.wiki { - margin-left: 0; - margin-right: 0; -} - -div.preview { - border: 2px dashed #FF0000; -} - -div.navigator { - border-color: #cc9933; - background-color: #ffcc66; - border-style: solid; - border-top-width: 0px; - border-bottom-width: 2px; - border-left-width: 0px; - border-right-width: 0px; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 5px; - padding-right: 5px; - margin-top: 0px; - margin-bottom: 10px; - margin-left: 0px; - margin-right: 0px; -} - -div.footer { - font-size: smaller; - color: #888888; - border-color: black; - - margin-top: 10px; - margin-left: 0px; - margin-right: 0px; - - border-style: solid; - border-top-width: 1px; - border-right-width: 0px; - border-bottom-width: 0px; - border-left-width: 0px; - - text-align: right; -} - -p.dialog { - border-color: black; - background-color: white; - padding-left: 5px; - margin-left: 10px; - margin-right: 10px; - border-style: solid; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px -} - -p { - margin-left: 10px; - margin-right: 10px; - margin-bottom: 10px; - text-align: justify; -} - -caption { - background-color: #4682B4; - color: #FFFFFF; - font-weight: bold; - border: #000000; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 0px; - border-left-width: 1px; - border-style: solid; -} - -pre { - color: #222222; - border-color: #E9E609; - background: #FFFDD0; - - margin-left: 10px; - margin-right: 10px; - padding-left: 5px; - padding-right: 5px; - padding-top: 5px; - padding-bottom: 5px; - border-style: solid; - border-top-width: 1px; - border-right-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; -} - -pre.error { - color: #FF0000; - background: #000000; - border-top-color: #FF0000; - border-color: #FF0000; - border-style: solid; - border-width: 6px; - margin: 0px; -} - -pre.success { - color: #00FF00; - background: #000000; - border-color: #00FF00; - border-style: solid; - border-width: 6px; - margin: 0px; -} - -pre.notice { - color: #FFCC00; - background: #000000; - border-color: #FFCC00; - border-style: solid; - border-width: 6px; - margin: 0px; -} - -#editor { - font-family: monospace; - width: 100%; - color: black; - background-color: white; - border: 1px solid #8cacbb; -} diff --git a/pikiplus.py b/pikiplus.py deleted file mode 100755 index e7001ab..0000000 --- a/pikiplus.py +++ /dev/null @@ -1,708 +0,0 @@ -#! /usr/bin/env python -"""Quick-quick implementation of WikiWikiWeb in Python -""" -# -# Copyright (C) 1999, 2000 Martin Pool <mbp@humbug.org.au> -# This version includes additional changes by Gerardo Poggiali (2002) -# This version includes additional changes by Bernardo Innocenti (2007) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -__version__ = '$Revision: 1.63+gerry+bernie $'[11:-2]; - -import cgi, sys, string, os, re, errno, time, stat -from os import path, environ -from socket import gethostbyaddr -from time import localtime, strftime - -# Regular expression defining a WikiWord -# (but this definition is also assumed in other places) -file_re = re.compile(r"^\b([A-Za-z0-9_\.\-]+)\b$") -word_re = re.compile(r"^\b([A-Z][a-z]+){2,}\b$") -img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg)$", re.IGNORECASE) -url_re = re.compile(r"^[a-z]{3,8}://[^\s'\"]+\S$") - -title_done = False - -def emit_header(type="text/html"): - print "Content-type: " + type + "; charset=utf-8" - print - - -# Formatting stuff -------------------------------------------------- - -def get_scriptname(): - return environ.get('SCRIPT_NAME', '') - -def get_hostname(addr): - try: - return gethostbyaddr(addr)[0] + ' (' + addr + ')' - except: - return addr; - -def send_guru(msg, msg_type): - if msg is None or len(msg) == 0: return - print '<pre id="guru" onclick="this.style.display = \'none\'" class="' + msg_type + '">' - if msg_type == 'error': - print ' Software Failure. Press left mouse button to continue.\n' - print msg - if msg_type == 'error': - print ' Guru Meditation #DEADBEEF.ABADC0DE' - print '</pre>' - # FIXME: This simple JS code is harder to pass than ACID 3.0 - print """ - <script language="JavaScript" type="text/javascript"> - var guru = document.getElementById('guru'); - // Firefox 2.0 doesn't take border-color, but returns border-top-color fine - var color = document.defaultView.getComputedStyle(guru,null).getPropertyValue('border-top-color'); - - function guruOn() { - guru.style.setProperty('border-color', color, ''); - setTimeout('guruOff()', 1000); - } - function guruOff() { - guru.style.setProperty('border-color', '#000000', ''); - setTimeout('guruOn()', 1000); - } - // Safari 2.0 returns this rgba crap - // Konqueror 3.5.6 doesn't seem to support computed properties - if (color && color != 'rgba(0, 0, 0, 0)') { - //window.alert("enabled! color='" + color + "'"); - guruOn(); - } - </script>""" - -def send_title(name, text="Limbo", msg=None, msg_type = 'error'): - global title_done - if title_done: return - - # Head - emit_header() - print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> -""" - print "<head><title>%s: %s</title>" % (site_name, text) - print ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' - if not name: - print ' <meta name="robots" content="noindex,nofollow">' - if css_url: - print ' <link rel="stylesheet" type="text/css" href="%s" />' % css_url - print '</head>' - - # Body - if name and allow_edit: - print '<body ondblclick="location.href=\'?edit=' + name + '\'">' - else: - print '<body>' - - send_guru(msg, msg_type) - - # Navbar - print '<div class="navigator">' - print '<b>' + site_name + ': ' - if name: - print link_tag('?fullsearch=' + name, text) + '</b> ' - else: - print text + '</b> ' - print ' | ' + link_tag('FrontPage', 'Front Page', 'navlink') - print ' | ' + link_tag('FindPage', 'Find Page', 'navlink') - print ' | <a href="/wikigit/wiki.git" class="navlink">Recent Changes</a>' - if name: - print ' | <a href="/wikigit/wiki.git?a=history;f=' + name + '" class="navlink">Page History</a>' - print ' | ' + link_tag('?raw=' + name, 'Raw Text', 'navlink') - if allow_edit: - print ' | ' + link_tag('?edit=' + name, 'Edit Page', 'navlink') - else: - print ' | <i>Immutable Page</i>' - print '</div>' - - title_done = True - - -def link_tag(params, text=None, ss_class=None): - if text is None: - text = params # default - classattr = '' - # Prevent crawlers from following links to generated pages - # and links added by potential spammers - if ss_class == 'external' or ss_class == 'navlink': - classattr += 'rel="nofollow" ' - if ss_class: - classattr += 'class="%s" ' % ss_class - return '<a %shref="%s/%s">%s</a>' % (classattr, get_scriptname(), - params, text) - -# Search --------------------------------------------------- - -def do_fullsearch(needle): - send_title(None, 'Full text search for "%s"' % (needle)) - - needle_re = re.compile(needle, re.IGNORECASE) - hits = [] - all_pages = page_list() - for page_name in all_pages: - body = Page(page_name).get_raw_body() - count = len(needle_re.findall(body)) - if count: - hits.append((count, page_name)) - - # The default comparison for tuples compares elements in order, - # so this sorts by number of hits - hits.sort() - hits.reverse() - - print "<ul>" - for (count, page_name) in hits: - print '<li><p>' + Page(page_name).link_to() - print ' . . . . ' + `count` - print ['match', 'matches'][count <> 1] - print '</p></li>' - print "</ul>" - - print_search_stats(len(hits), len(all_pages)) - - -def do_titlesearch(needle): - # TODO: check needle is legal -- but probably we can just accept any RE - send_title(None, "Title search for \"" + needle + '"') - - needle_re = re.compile(needle, re.IGNORECASE) - all_pages = page_list() - hits = filter(needle_re.search, all_pages) - - print "<ul>" - for filename in hits: - print '<li><p>' + Page(filename).link_to() + "</p></li>" - print "</ul>" - - print_search_stats(len(hits), len(all_pages)) - - -def print_search_stats(hits, searched): - print "<p>%d hits out of %d pages searched.</p>" % (hits, searched) - -def do_edit(pagename): - Page(pagename).send_editor() - -def do_raw(pagename): - Page(pagename).send_raw() - -def do_savepage(pagename): - if not allow_edit: - raise 'editing disallowed for ' + pagename - - global form - pg = Page(pagename) - if 'preview' in form: - pg.send_editor(form['savetext'].value) - elif 'save' in form: - pg.save_text(form['savetext'].value) - pg.send_page() - elif 'cancel' in form: - pg.msg = 'Editing cancelled' - pg.msg_type = 'notice' - pg.send_page() - else: - raise 'What did you press?' - -def make_index_key(): - s = '<p><center>' - links = map(lambda ch: '<a href="#%s">%s</a>' % (ch, ch), - string.lowercase) - s = s + string.join(links, ' | ') - s = s + '</center></p>' - return s - - -def page_list(): - return filter(word_re.match, os.listdir(text_dir)) - - -def send_footer(name, mod_string=None): - print '<div class="footer">' - if mod_string: - print "last modified %s" % mod_string - print '</div></body></html>' - - -# ---------------------------------------------------------- -# Macros -def _macro_TitleSearch(): - return _macro_search("titlesearch") - -def _macro_FullSearch(): - return _macro_search("fullsearch") - -def _macro_search(type): - if form.has_key('value'): - default = form["value"].value - else: - default = '' - return """<form method="get"><input name="%s" size="30" value="%s"><input type="submit" value="Go"></form>""" % (type, default) - -def _macro_WordIndex(): - s = make_index_key() - pages = list(page_list()) - map = {} - word_re = re.compile('[A-Z][a-z]+') - for name in pages: - for word in word_re.findall(name): - try: - map[word].append(name) - except KeyError: - map[word] = [name] - - all_words = map.keys() - all_words.sort() - last_letter = None - # set title - for word in all_words: - letter = string.lower(word[0]) - if letter <> last_letter: - s = s + '; <a name="%s"><h3>%s</h3></a>' % (letter, letter) - last_letter = letter - - s = s + '<b>%s</b><ul>' % word - links = map[word] - links.sort() - last_page = None - for name in links: - if name == last_page: continue - s = s + '<li>' + Page(name).link_to() - s = s + '</ul>' - return s - - -def _macro_TitleIndex(): - s = make_index_key() - pages = list(page_list()) - pages.sort() - current_letter = None - for name in pages: - letter = string.lower(name[0]) - if letter <> current_letter: - s = s + '<a name="%s"><h3>%s</h3></a>' % (letter, letter) - current_letter = letter - else: - s = s + '<br />' - s = s + Page(name).link_to() - return s - - -# ---------------------------------------------------------- -class PageFormatter: - """Object that turns Wiki markup into HTML. - - All formatting commands can be parsed one line at a time, though - some state is carried over between lines. - """ - def __init__(self, raw): - self.raw = raw - self.is_em = self.is_b = 0 - self.h_level = 0 - self.list_indents = [] - self.in_pre = 0 - self.in_var = 0 - self.in_header = True - - def _emph_repl(self, word): - if len(word) == 3: - self.is_b = not self.is_b - return ['</b>', '<b>'][self.is_b] - else: - self.is_em = not self.is_em - return ['</em>', '<em>'][self.is_em] - - def _tit_repl(self, word): - if self.h_level: - result = "</h%d>" % self.h_level - self.h_level = 0 - else: - self.h_level = len(word) - 1 - result = "<h%d>" % self.h_level - return result; - - def _rule_repl(self, word): - s = self._undent() - if len(word) <= 3: - s = s + "\n<hr size='1' noshade=\"noshade\" />\n" - else: - s = s + "\n<hr size='%d' noshade=\"noshade\" />\n" % (len(word) - 2 ) - return s - - def _word_repl(self, word): - return Page(word).link_to() - - def _img_repl(self, word): - return '<img border="0" src="%s/%s" />' % (get_scriptname(), word) - - def _url_repl(self, word): - if img_re.match(word): - return '<img border="0" src="%s" />' % word - else: - return '<a href="%s" rel="nofollow" class="external">%s</a>' % (word, word) - - def _hurl_repl(self, word): - m = re.compile("\[\[(\S+)\ (.+)\]\]").match(word) - anchor = m.group(1) - descr = m.group(2) - if img_re.match(anchor): - return '<img border="0" src="%s" alt="%s" />' % (anchor, descr) - elif url_re.match(anchor): - return '<a href="%s" rel="nofollow" class="external">%s</a>' % (anchor, descr) - elif anchor.startswith('/'): - return '<a href="%s">%s</a>' % (anchor, descr) - else: - return link_tag(anchor, descr) - - def _email_repl(self, word): - return '<a href="mailto:%s">%s</a>' % (word, word) - - - def _ent_repl(self, s): - return {'&': '&', - '<': '<', - '>': '>'}[s] - - - def _li_repl(self, match): - return '<li>' - - - def _pre_repl(self, word): - if word == '{{{' and not self.in_pre: - self.in_pre = 1 - return '<pre>' - elif self.in_pre: - self.in_pre = 0 - return '</pre>' - else: - return '' - - def _var_repl(self, word): - if word == '{{' and not self.in_var: - self.in_var = 1 - return '<code>' - elif self.in_var: - self.in_var = 0 - return '</code>' - else: - return '' - def _macro_repl(self, word): - macro_name = word[2:-2] - # TODO: Somehow get the default value into the search field - return apply(globals()['_macro_' + macro_name], ()) - - def _indent_level(self): - return len(self.list_indents) and self.list_indents[-1] - - def _indent_to(self, new_level): - if self._indent_level() == new_level: - return '' - s = '</p>' - while self._indent_level() > new_level: - del(self.list_indents[-1]) - s += '</ul>\n' - while self._indent_level() < new_level: - self.list_indents.append(new_level) - s += '<ul>\n' - s += '<p>' - return s - - def _undent(self): - res = '</p>' - res += '</ul>' * len(self.list_indents) - res += '<p>' - self.list_indents = [] - return res - - def replace(self, match): - for type, hit in match.groupdict().items(): - if hit: - return apply(getattr(self, '_' + type + '_repl'), (hit,)) - else: - raise "Can't handle match " + `match` - - def print_html(self): - print "<div class='wiki'><p>" - - # For each line, we scan through looking for magic - # strings, outputting verbatim any intervening text - scan_re = re.compile( - r"(?:" - + r"(?P<emph>'{2,3})" - + r"|(?P<tit>\={2,6})" - + r"|(?P<ent>[<>&])" - + r"|(?P<img>\b[a-zA-Z0-9_-]+\.(png|gif|jpg|jpeg|bmp))" - + r"|(?P<word>\b(?:[A-Z][a-z]+){2,}\b)" - + r"|(?P<rule>^-{3,})" - + r"|(?P<hurl>\[\[\S+\s+.+\]\])" - + r"|(?P<url>(http|ftp|nntp|news|mailto)\:[^\s'\"]+\S)" - + r"|(?P<email>[-\w._+]+\@[\w.-]+)" - + r"|(?P<li>^\s+\*)" - + r"|(?P<pre>(\{\{\{|\s*\}\}\}))" - + r"|(?P<var>(\{\{|\}\}))" - + r"|(?P<macro>\[\[(TitleSearch|FullSearch|WordIndex|TitleIndex)\]\])" - + r")") - pre_re = re.compile( - r"(?:" - + r"(?P<pre>\s*\}\}\})" - + r")") - blank_re = re.compile("^\s*$") - indent_re = re.compile("^\s*") - eol_re = re.compile(r'\r?\n') - raw = string.expandtabs(self.raw) - for line in eol_re.split(raw): - # Skip ACLs - if self.in_header: - if line.startswith('#'): - continue - self.in_header = False - if self.in_pre: - print re.sub(pre_re, self.replace, line) - else: - # XXX: Should we check these conditions in this order? - if blank_re.match(line): - print '</p><p>' - continue - indent = indent_re.match(line) - print self._indent_to(len(indent.group(0))) - print re.sub(scan_re, self.replace, line) - if self.in_pre: print '</pre>' - print self._undent() - print "</p></div>" - -# ---------------------------------------------------------- -class Page: - def __init__(self, page_name): - self.page_name = page_name - self.remote_user = 'AnonymousCoward' - self.remote_host = environ.get('REMOTE_ADDR', '') - self.msg = '' - self.msg_type = 'error' - self.attrs = {} - - def split_title(self): - # look for the end of words and the start of a new word, - # and insert a space there - return re.sub('([a-z])([A-Z])', r'\1 \2', self.page_name) - - - def _text_filename(self): - return path.join(text_dir, self.page_name) - - - def _tmp_filename(self): - return path.join(text_dir, ('#' + self.page_name + '.' + `os.getpid()` + '#')) - - - def exists(self): - try: - os.stat(self._text_filename()) - return 1 - except OSError, er: - if er.errno == errno.ENOENT: - return 0 - else: - raise er - - def link_to(self): - word = self.page_name - if self.exists(): - return link_tag(word, word, 'wikilink') - else: - if nonexist_qm: - return link_tag(word, '?', 'nonexistent') + word - else: - return link_tag(word, word, 'nonexistent') - - - def get_raw_body(self): - try: - return open(self._text_filename(), 'rt').read() - except IOError, er: - if er.errno == errno.ENOENT: - # just doesn't exist, use default - return 'Describe %s here.' % self.page_name - else: - raise er - - def get_attrs(self): - if self.attrs: - return self.attrs - try: - file = open(self._text_filename(), 'rt') - attr_re = re.compile(r"^#(\S*)(.*)$") - for line in file: - m = attr_re.match(line) - if not m: - break - self.attrs[m.group(1)] = m.group(2).strip() - #print "bernie: attrs[" + m.group(1) + "] = " + m.group(2) + "<br>\n" - except IOError, er: - if er.errno != errno.ENOENT: - raise er - return self.attrs - - def can_edit(self): - attrs = self.get_attrs() - try: - # SomeUser:read,write All:read - acl = attrs["acl"] - for rule in acl.split(): - (user,perms) = acl.split(':') - if user == self.remote_user or user == "All": - if 'write' in perms.split(','): - return True - return False - except: - pass - return True - - def send_page(self): - page_name = None - if self.can_edit(): - page_name = self.page_name - send_title(page_name, self.split_title(), msg=self.msg, msg_type=self.msg_type) - PageFormatter(self.get_raw_body()).print_html() - send_footer(page_name, self._last_modified()) - - def _last_modified(self): - if not self.exists(): - return None - modtime = localtime(os.stat(self._text_filename())[stat.ST_MTIME]) - return strftime(datetime_fmt, modtime) - - def send_editor(self, preview=None): - send_title(None, 'Edit ' + self.split_title(), msg=self.msg, msg_type=self.msg_type) - - print ('<p><b>Editing ' + self.page_name - + ' for ' + self.remote_user - + ' from ' + get_hostname(self.remote_host) - + '</b></p>') - print '<div class="editor"><form method="post" action="%s/%s">' % (get_scriptname(), self.page_name) - print '<input type="hidden" name="savepage" value="%s">' % (self.page_name) - print """<textarea wrap="virtual" id="editor" name="savetext" rows="17" cols="80">%s</textarea>""" % (preview or self.get_raw_body()) - print """ - <br /> - <input type="submit" name="save" value="Save" /> - <input type="submit" name="preview" value="Preview" /> - <input type="reset" value="Reset" /> - <input type="submit" name="cancel" value="Cancel" /> - <br /> - </form></div>""" - print "<p>" + Page('EditingTips').link_to() + "</p>" - if preview: - print "<div class='preview'>" - PageFormatter(preview).print_html() - print "</div>" - - def send_raw(self): - emit_header("text/plain") - print self.get_raw_body() - - def _write_file(self, text): - tmp_filename = self._tmp_filename() - open(tmp_filename, 'wt').write(text.replace('\r\n', '\n')) - text = self._text_filename() - if os.name == 'nt': - # Bad Bill! POSIX rename ought to replace. :-( - try: - os.remove(text) - except OSError, er: - if er.errno <> errno.ENOENT: raise er - os.rename(tmp_filename, text) - - def save_text(self, newtext): - self._write_file(newtext) - rc = 0 - if post_edit_hook: - cmd = ( post_edit_hook - + ' ' + text_dir + '/' + self.page_name - + ' ' + self.remote_user - + ' ' + self.remote_host - ) - out = os.popen(cmd) - msg = out.read() - rc = out.close() - if rc: - self.msg += "Post-editing hook returned %d.\n" % rc - self.msg += 'Command was: ' + cmd + '\n' - if msg: - self.msg += 'Output follows:\n' + msg - else: - self.msg = 'Thankyou for your contribution. Your attention to detail is appreciated.' - self.msg_type = 'success' - -def send_verbatim(filename, mime_type='application/octet-stream'): - pathname = path.join(text_dir, filename) - data = open(pathname, 'rb').read() - emit_header(mime_type) - sys.stdout.write(data) - -# --------------------------------------------------------------- -try: - # Configuration values - data_dir = '/home/bernie/public_html/wiki' - text_dir = path.join(data_dir, 'text') - allow_edit = True # Is it possible to edit pages? - site_name = 'codewiz.org' - changed_time_fmt = '[%I:%M %p] ' - date_fmt = '%a %d %b %Y' - datetime_fmt = '%a %d %b %Y %I:%M %p' - show_hosts = True # show hostnames? - css_url = '../wikidata/pikiplus.css' # stylesheet link, or '' - nonexist_qm = False # show '?' for nonexistent? - post_edit_hook = './post_edit_hook.sh' - - form = cgi.FieldStorage() - - handlers = { 'fullsearch': do_fullsearch, - 'titlesearch': do_titlesearch, - 'edit': do_edit, - 'raw': do_raw, - 'savepage': do_savepage } - - for cmd in handlers.keys(): - if form.has_key(cmd): - apply(handlers[cmd], (form[cmd].value,)) - break - else: - path_info = environ.get('PATH_INFO', '') - if len(path_info) and path_info[0] == '/': - query = path_info[1:] or 'FrontPage' - else: - query = environ.get('QUERY_STRING', '') or 'FrontPage' - - if file_re.match(query): - if word_re.match(query): - Page(query).send_page() - elif img_re.match(query): - send_verbatim(query, 'image/jpeg') - else: - send_verbatim(query) - else: - # TODO: return 404? - send_title(None, msg='Can\'t work out query: ' + query) -except: - import traceback - msg=traceback.format_exc() - if title_done: - send_guru(msg, "error") - else: - send_title(None, msg=msg) - send_footer(None) - -sys.stdout.flush()