From: Bernardo Innocenti 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?p=geekigeeki.git;a=commitdiff_plain;h=76a9e7542e0ed64a3054882db2cf4ae18e4f558e 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 + */ + +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 +# 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 . + +__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 '
'
+    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 '
' + # FIXME: This simple JS code is harder to pass than ACID 3.0 + print """ + """ + +def send_title(name, text="Limbo", msg=None, msg_type = 'error'): + global title_done + if title_done: return + + # Head + emit_header() + print """ + +""" + print "%s: %s" % (site_name, text) + print ' ' + if not name: + print ' ' + if css_url: + print ' ' % css_url + print '' + + # Body + if name and allow_edit: + print '' + else: + print '' + + send_guru(msg, msg_type) + + # Navbar + print '' + + 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 '%s' % (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 "
    " + for (count, page_name) in hits: + print '
  • ' + Page(page_name).link_to() + print ' . . . . ' + `count` + print ['match', 'matches'][count <> 1] + print '

  • ' + print "
" + + 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 "
    " + for filename in hits: + print '
  • ' + Page(filename).link_to() + "

  • " + print "
" + + print_search_stats(len(hits), len(all_pages)) + + +def print_search_stats(hits, searched): + print "

%d hits out of %d pages searched.

" % (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 = '

' + links = map(lambda ch: '%s' % (ch, ch), + string.lowercase) + s = s + string.join(links, ' | ') + s = s + '

' + return s + + +def page_list(): + return filter(word_re.match, os.listdir(text_dir)) + + +def send_footer(name, mod_string=None): + print '' + + +# ---------------------------------------------------------- +# 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 """
""" % (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 + ';

%s

' % (letter, letter) + last_letter = letter + + s = s + '%s
    ' % word + links = map[word] + links.sort() + last_page = None + for name in links: + if name == last_page: continue + s = s + '
  • ' + Page(name).link_to() + s = s + '
' + 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 + '

%s

' % (letter, letter) + current_letter = letter + else: + s = s + '
' + 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 ['', ''][self.is_b] + else: + self.is_em = not self.is_em + return ['', ''][self.is_em] + + def _tit_repl(self, word): + if self.h_level: + result = "" % self.h_level + self.h_level = 0 + else: + self.h_level = len(word) - 1 + result = "" % self.h_level + return result; + + def _rule_repl(self, word): + s = self._undent() + if len(word) <= 3: + s = s + "\n
\n" + else: + s = s + "\n
\n" % (len(word) - 2 ) + return s + + def _word_repl(self, word): + return Page(word).link_to() + + def _img_repl(self, word): + return '' % (get_scriptname(), word) + + def _url_repl(self, word): + if img_re.match(word): + return '' % word + else: + return '%s' % (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 '%s' % (anchor, descr) + elif url_re.match(anchor): + return '%s' % (anchor, descr) + elif anchor.startswith('/'): + return '%s' % (anchor, descr) + else: + return link_tag(anchor, descr) + + def _email_repl(self, word): + return '%s' % (word, word) + + + def _ent_repl(self, s): + return {'&': '&', + '<': '<', + '>': '>'}[s] + + + def _li_repl(self, match): + return '
  • ' + + + def _pre_repl(self, word): + if word == '{{{' and not self.in_pre: + self.in_pre = 1 + return '
    '
    +        elif self.in_pre:
    +            self.in_pre = 0
    +            return '
    ' + else: + return '' + + def _var_repl(self, word): + if word == '{{' and not self.in_var: + self.in_var = 1 + return '' + elif self.in_var: + self.in_var = 0 + return '' + 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 = '

    ' + while self._indent_level() > new_level: + del(self.list_indents[-1]) + s += '\n' + while self._indent_level() < new_level: + self.list_indents.append(new_level) + s += '
      \n' + s += '

      ' + return s + + def _undent(self): + res = '

      ' + res += '
    ' * len(self.list_indents) + res += '

    ' + 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 "

    " + + # For each line, we scan through looking for magic + # strings, outputting verbatim any intervening text + scan_re = re.compile( + r"(?:" + + r"(?P'{2,3})" + + r"|(?P\={2,6})" + + r"|(?P[<>&])" + + r"|(?P\b[a-zA-Z0-9_-]+\.(png|gif|jpg|jpeg|bmp))" + + r"|(?P\b(?:[A-Z][a-z]+){2,}\b)" + + r"|(?P^-{3,})" + + r"|(?P\[\[\S+\s+.+\]\])" + + r"|(?P(http|ftp|nntp|news|mailto)\:[^\s'\"]+\S)" + + r"|(?P[-\w._+]+\@[\w.-]+)" + + r"|(?P

  • ^\s+\*)" + + r"|(?P
    (\{\{\{|\s*\}\}\}))"
    +            + r"|(?P(\{\{|\}\}))"
    +            + r"|(?P\[\[(TitleSearch|FullSearch|WordIndex|TitleIndex)\]\])"
    +            + r")")
    +        pre_re = re.compile(
    +            r"(?:"
    +            + r"(?P
    \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 '

    ' + 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 '

    ' + print self._undent() + print "

    " + +# ---------------------------------------------------------- +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) + "
    \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 ('

    Editing ' + self.page_name + + ' for ' + self.remote_user + + ' from ' + get_hostname(self.remote_host) + + '

    ') + print '
    ' % (get_scriptname(), self.page_name) + print '' % (self.page_name) + print """""" % (preview or self.get_raw_body()) + print """ +
    + + + + +
    +
    """ + print "

    " + Page('EditingTips').link_to() + "

    " + if preview: + print "
    " + PageFormatter(preview).print_html() + print "
    " + + 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 - */ - -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 -# 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 . - -__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 '
    '
    -    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 '
    ' - # FIXME: This simple JS code is harder to pass than ACID 3.0 - print """ - """ - -def send_title(name, text="Limbo", msg=None, msg_type = 'error'): - global title_done - if title_done: return - - # Head - emit_header() - print """ - -""" - print "%s: %s" % (site_name, text) - print ' ' - if not name: - print ' ' - if css_url: - print ' ' % css_url - print '' - - # Body - if name and allow_edit: - print '' - else: - print '' - - send_guru(msg, msg_type) - - # Navbar - print '
    ' - - 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 '%s' % (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 "
      " - for (count, page_name) in hits: - print '
    • ' + Page(page_name).link_to() - print ' . . . . ' + `count` - print ['match', 'matches'][count <> 1] - print '

    • ' - print "
    " - - 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 "
      " - for filename in hits: - print '
    • ' + Page(filename).link_to() + "

    • " - print "
    " - - print_search_stats(len(hits), len(all_pages)) - - -def print_search_stats(hits, searched): - print "

    %d hits out of %d pages searched.

    " % (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 = '

    ' - links = map(lambda ch: '%s' % (ch, ch), - string.lowercase) - s = s + string.join(links, ' | ') - s = s + '

    ' - return s - - -def page_list(): - return filter(word_re.match, os.listdir(text_dir)) - - -def send_footer(name, mod_string=None): - print '' - - -# ---------------------------------------------------------- -# 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 """
    """ % (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 + ';

    %s

    ' % (letter, letter) - last_letter = letter - - s = s + '%s
      ' % word - links = map[word] - links.sort() - last_page = None - for name in links: - if name == last_page: continue - s = s + '
    • ' + Page(name).link_to() - s = s + '
    ' - 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 + '

    %s

    ' % (letter, letter) - current_letter = letter - else: - s = s + '
    ' - 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 ['', ''][self.is_b] - else: - self.is_em = not self.is_em - return ['
    ', ''][self.is_em] - - def _tit_repl(self, word): - if self.h_level: - result = "" % self.h_level - self.h_level = 0 - else: - self.h_level = len(word) - 1 - result = "" % self.h_level - return result; - - def _rule_repl(self, word): - s = self._undent() - if len(word) <= 3: - s = s + "\n
    \n" - else: - s = s + "\n
    \n" % (len(word) - 2 ) - return s - - def _word_repl(self, word): - return Page(word).link_to() - - def _img_repl(self, word): - return '' % (get_scriptname(), word) - - def _url_repl(self, word): - if img_re.match(word): - return '' % word - else: - return '%s' % (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 '%s' % (anchor, descr) - elif url_re.match(anchor): - return '%s' % (anchor, descr) - elif anchor.startswith('/'): - return '%s' % (anchor, descr) - else: - return link_tag(anchor, descr) - - def _email_repl(self, word): - return '%s' % (word, word) - - - def _ent_repl(self, s): - return {'&': '&', - '<': '<', - '>': '>'}[s] - - - def _li_repl(self, match): - return '
  • ' - - - def _pre_repl(self, word): - if word == '{{{' and not self.in_pre: - self.in_pre = 1 - return '
    '
    -        elif self.in_pre:
    -            self.in_pre = 0
    -            return '
    ' - else: - return '' - - def _var_repl(self, word): - if word == '{{' and not self.in_var: - self.in_var = 1 - return '' - elif self.in_var: - self.in_var = 0 - return '' - 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 = '

    ' - while self._indent_level() > new_level: - del(self.list_indents[-1]) - s += '\n' - while self._indent_level() < new_level: - self.list_indents.append(new_level) - s += '
      \n' - s += '

      ' - return s - - def _undent(self): - res = '

      ' - res += '
    ' * len(self.list_indents) - res += '

    ' - 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 "

    " - - # For each line, we scan through looking for magic - # strings, outputting verbatim any intervening text - scan_re = re.compile( - r"(?:" - + r"(?P'{2,3})" - + r"|(?P\={2,6})" - + r"|(?P[<>&])" - + r"|(?P\b[a-zA-Z0-9_-]+\.(png|gif|jpg|jpeg|bmp))" - + r"|(?P\b(?:[A-Z][a-z]+){2,}\b)" - + r"|(?P^-{3,})" - + r"|(?P\[\[\S+\s+.+\]\])" - + r"|(?P(http|ftp|nntp|news|mailto)\:[^\s'\"]+\S)" - + r"|(?P[-\w._+]+\@[\w.-]+)" - + r"|(?P

  • ^\s+\*)" - + r"|(?P
    (\{\{\{|\s*\}\}\}))"
    -            + r"|(?P(\{\{|\}\}))"
    -            + r"|(?P\[\[(TitleSearch|FullSearch|WordIndex|TitleIndex)\]\])"
    -            + r")")
    -        pre_re = re.compile(
    -            r"(?:"
    -            + r"(?P
    \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 '

    ' - 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 '

    ' - print self._undent() - print "

    " - -# ---------------------------------------------------------- -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) + "
    \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 ('

    Editing ' + self.page_name - + ' for ' + self.remote_user - + ' from ' + get_hostname(self.remote_host) - + '

    ') - print '
    ' % (get_scriptname(), self.page_name) - print '' % (self.page_name) - print """""" % (preview or self.get_raw_body()) - print """ -
    - - - - -
    -
    """ - print "

    " + Page('EditingTips').link_to() + "

    " - if preview: - print "
    " - PageFormatter(preview).print_html() - print "
    " - - 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()