X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=geekigeeki.py;h=b6e802c8277f9e67be9e118c0a3eaef9cdc25fc7;hb=0c40ff65375f2cb3fd33da40a83f777eda407adb;hp=190964478d979134e3c699776a5656923f0ed183;hpb=01057766514a73e0df07470e71c94b0508b2e5bf;p=geekigeeki.git
diff --git a/geekigeeki.py b/geekigeeki.py
index 1909644..b6e802c 100755
--- a/geekigeeki.py
+++ b/geekigeeki.py
@@ -1,165 +1,260 @@
-#! /usr/bin/env python
-"""Quick-quick implementation of WikiWikiWeb in Python
-"""
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
# Copyright (C) 1999, 2000 Martin Pool
-# This version includes additional changes by Gerardo Poggiali (2002)
-# This version includes additional changes by Bernardo Innocenti (2007)
+# Copyright (C) 2002 Gerardo Poggiali
+# Copyright (C) 2007, 2008, 2009 Bernie Innocenti
#
# 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
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# You should have received a copy of the GNU Affero 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
-
-# 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$")
+__version__ = '4.0-' + '$Id$'[4:11]
+from time import clock, localtime, gmtime, strftime
+start_time = clock()
title_done = False
+import cgi, sys, os, re, errno, stat, glob
+
+image_ext = 'png|gif|jpg|jpeg|bmp|ico'
+video_ext = "ogg|ogv|oga" # Not supported by Firefox 3.5: mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt
+image_re = re.compile(r".*\.(" + image_ext + "|" + video_ext + ")", re.IGNORECASE)
+video_re = re.compile(r".*\.(" + video_ext + ")", re.IGNORECASE)
+# FIXME: we accept stuff like foo/../bar and we shouldn't
+file_re = re.compile(r"([A-Za-z0-9_\-][A-Za-z0-9_\.\-/]*)")
+url_re = re.compile(r"[a-z]{3,8}://[^\s'\"]+\S")
+ext_re = re.compile(r"\.([^\./]+)$")
# CGI stuff ---------------------------------------------------------
+def config_get(key, default=None):
+ return globals().get(key, default)
+
+def script_name():
+ return os.environ.get('SCRIPT_NAME', '')
+
+#TODO: move post-edit hook into wiki, then kill this
+def script_path():
+ return os.path.split(os.environ.get('SCRIPT_FILENAME', ''))[0]
-def get_scriptname():
- return environ.get('SCRIPT_NAME', '')
+def query_string():
+ path_info = os.environ.get('PATH_INFO', '')
+ if len(path_info) and path_info[0] == '/':
+ return path_info[1:] or 'FrontPage'
+ else:
+ return os.environ.get('QUERY_STRING', '') or 'FrontPage'
+
+def is_privileged():
+ purl = config_get('privileged_url')
+ return (purl is not None) and os.environ.get('SCRIPT_URI', '').startswith(purl)
def remote_user():
- return environ.get('REMOTE_USER', 'AnonymousCoward')
+ user = os.environ.get('REMOTE_USER', '')
+ if user is None or user == '' or user == 'anonymous':
+ user = 'AnonymousCoward'
+ return user
def remote_host():
- return environ.get('REMOTE_ADDR', '')
+ return os.environ.get('REMOTE_ADDR', '')
def get_hostname(addr):
try:
from socket import gethostbyaddr
return gethostbyaddr(addr)[0] + ' (' + addr + ')'
- except:
- return addr;
+ except Exception:
+ return addr
-# Formatting stuff --------------------------------------------------
+def is_external_url(pathname):
+ return (url_re.match(pathname) or pathname.startswith('/'))
-def emit_header(type="text/html"):
- print "Content-type: " + type + "; charset=utf-8"
- print
+def relative_url(pathname, privileged=False):
+ if not is_external_url(pathname):
+ if privileged:
+ url = config_get('privileged_url') or script_name()
+ else:
+ url = script_name()
+ pathname = url + '/' + pathname
+ return cgi.escape(pathname, quote=True)
+
+def permalink(s):
+ return re.sub(' ', '-', re.sub('[^a-z0-9_ ]', '', s.lower()).strip())
+
+def humanlink(s):
+ return re.sub(r'(?:.*[/:]|)([^:/\.]+)(?:\.[^/:]+|)$', r'\1', s.replace('_', ' '))
+
+# Split arg lists like "blah|blah blah| width=100 | align = center",
+# return a list containing anonymous arguments and a map containing the named arguments
+def parse_args(s):
+ args = []
+ kvargs = {}
+ for arg in s.strip('<[{}]>').split('|'):
+ m = re.match('\s*(\w+)\s*=\s*(.+)\s*', arg)
+ if m is not None:
+ kvargs[m.group(1)] = m.group(2)
+ else:
+ args.append(arg.strip())
+ return (args, kvargs)
+
+def url_args(kvargs):
+ argv = []
+ for k, v in kvargs.items():
+ argv.append(k + '=' + v)
+ if argv:
+ return '?' + '&'.join(argv)
+ return ''
-def send_guru(msg, msg_type):
- if msg is None or len(msg) == 0: return
- print '
' % (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 + '
' % (letter, letter)
- current_letter = letter
+ pg.format()
+ else: # preview or edit
+ text = None
+ if 'preview' in form:
+ text = form['savetext'].value
+ pg.send_editor(text)
+
+def handle_get(pagename, form):
+ if file_re.match(pagename):
+ # FIMXE: this is all bullshit, MimeTypes bases its guess on the extension!
+ from mimetypes import MimeTypes
+ mimetype, encoding = MimeTypes().guess_type(pagename)
+ if mimetype:
+ Page(pagename).send_raw(mimetype=mimetype, args=form)
+ else:
+ Page(pagename).format()
else:
- s = s + ' '
- s = s + Page(name).link_to()
- return s
+ send_httperror("403 Forbidden", pagename)
-
-# ----------------------------------------------------------
-class PageFormatter:
+# Used by sys/macros/WordIndex and sys/macros/TitleIndex
+def make_index_key():
+ links = ['%s' % (ch, ch) for ch in 'abcdefghijklmnopqrstuvwxyz']
+ return '
' + ' | '.join(links) + '
'
+
+def page_list(dirname=None, search_re=None):
+ if search_re is None:
+ # FIXME: WikiWord is too restrictive now!
+ search_re = re.compile(r"^\b((([A-Z][a-z0-9]+){2,}/)*([A-Z][a-z0-9]+){2,})\b$")
+ return sorted(filter(search_re.match, os.listdir(dirname or '.')))
+
+def send_footer(mtime=None):
+ if config_get('debug_cgi', False):
+ cgi.print_arguments()
+ cgi.print_form(form)
+ cgi.print_environ()
+ link_inline("sys/footer", kvargs = {
+ 'LAST_MODIFIED': strftime(config_get('datetime_fmt', '%a %d %b %Y %I:%M %p'), localtime(mtime))
+ })
+ print("")
+
+def _macro_ELAPSED_TIME(*args, **kvargs):
+ return "%03f" % (clock() - start_time)
+
+def _macro_VERSION(*args, **kvargs):
+ return __version__
+
+class WikiFormatter:
"""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):
+ def __init__(self, raw, kvargs=None):
self.raw = raw
- self.is_em = self.is_b = 0
+ self.kvargs = kvargs or {}
self.h_level = 0
- self.list_indents = []
- self.in_pre = 0
- self.in_var = 0
+ self.in_pre = self.in_html = self.in_table = self.in_li = False
self.in_header = True
+ self.list_indents = [] # a list of pairs (indent_level, list_type) to track nested lists
+ self.tr_cnt = 0
+ self.styles = {
+ #wiki html enabled?
+ "//": ["em", False],
+ "**": ["b", False],
+ "##": ["tt", False],
+ "__": ["u", False],
+ "--": ["del", False],
+ "^^": ["sup", False],
+ ",,": ["sub", False],
+ "''": ["em", False], # LEGACY
+ "'''": ["b", False], # LEGACY
+ "``": ["tt", False], # LEGACY
+ }
- 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 _b_repl(self, word):
+ style = self.styles[word]
+ style[1] = not style[1]
+ return ['', '<'][style[1]] + style[0] + '>'
+
+ def _glyph_repl(self, word):
+ return '—'
def _tit_repl(self, word):
if self.h_level:
- result = "" % self.h_level
+ result = '
\n' % self.h_level
self.h_level = 0
else:
self.h_level = len(word) - 1
- result = "" % self.h_level
- return result;
+ link = permalink(self.line)
+ result = '\n
¶ ' % (self.h_level, link, link)
+ 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 _br_repl(self, word):
+ return ' '
- def _img_repl(self, word):
- return '' % (get_scriptname(), word)
+ def _rule_repl(self, word):
+ return '\n\n' % (len(word) - 2)
- def _url_repl(self, word):
- if img_re.match(word):
- return '' % word
- else:
- return '%s' % (word, word)
+ def _macro_repl(self, word):
+ try:
+ args, kvargs = parse_args(word)
+ if args[0] in self.kvargs:
+ return self.kvargs[args[0]]
+ macro = globals().get('_macro_' + args[0])
+ if not macro:
+ exec(open("sys/macros/" + args[0] + ".py").read(), globals())
+ macro = globals().get('_macro_' + args[0])
+ return macro(*args, **kvargs)
+ except Exception, e:
+ msg = cgi.escape(word) + ": " + cgi.escape(str(e))
+ if not self.in_html:
+ msg = '' + msg + ''
+ return msg
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 '' % (anchor, descr)
- elif url_re.match(anchor):
- return '%s' % (anchor, descr)
- elif anchor.startswith('/'):
- return '%s' % (anchor, descr)
+ args, kvargs = parse_args(word)
+ return link_tag(*args, **kvargs)
+
+ def _inl_repl(self, word):
+ args, kvargs = parse_args(word)
+ name = args.pop(0)
+ if len(args):
+ descr = args.pop(0)
+ # This double div nonsense works around a limitation of the HTML block model
+ return '
\s*\|\|(=|)\s*)
+
+ # TODO: highlight search words (look at referrer)
+ )""", re.VERBOSE)
+ pre_re = re.compile("""(?:
+ (?P
\s*\}\}\})
+ | (?P[<>&])"
+ )""", re.VERBOSE)
+ blank_re = re.compile(r"^\s*$")
+ indent_re = re.compile(r"^(\s*)(\*|\#|)")
+ tr_re = re.compile(r"^\s*\|\|")
+ eol_re = re.compile(r"\r?\n")
+ # For each line, we scan through looking for magic strings, outputting verbatim any intervening text
+ #3.0: for self.line in eol_re.split(str(self.raw.expandtabs(), 'utf-8')):
+ for self.line in eol_re.split(str(self.raw.expandtabs())):
+ # Skip pragmas
if self.in_header:
- if line.startswith('#'):
- continue
+ if self.line.startswith('#'):
+ continue
self.in_header = False
+
if self.in_pre:
- print re.sub(pre_re, self.replace, line)
+ print(re.sub(pre_re, self.replace, self.line))
else:
- # XXX: Should we check these conditions in this order?
- if blank_re.match(line):
- print '