X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=geekigeeki.py;h=7fa7982e683e983207cdfa1dc4df89dda67f0772;hb=dc3e571131ca2f8f98ea22c8b0376a087155e334;hp=99234c539c0d23b2cd69c587773de617bdc05a70;hpb=64737bb01dc7cf5cb5759f7a077e8edbede2dbca;p=geekigeeki.git
diff --git a/geekigeeki.py b/geekigeeki.py
index 99234c5..7fa7982 100755
--- a/geekigeeki.py
+++ b/geekigeeki.py
@@ -1,153 +1,249 @@
-#! /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 1999, 2000 Martin Pool
+# Copyright 2002 Gerardo Poggiali
+# Copyright 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
-# along with this program. If not, see .
+# (at your option) any later version. 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];
+__version__ = '4.0-' + '$Id$'[4:11]
-import cgi, sys, string, os, re, errno, time, stat
-from os import path, environ
-from socket import gethostbyaddr
-from time import localtime, strftime
+from time import clock
+start_time = clock()
+title_done = False
-# 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$")
+import cgi, sys, os, re, errno, stat, glob
-title_done = False
+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"\.([^\./]+)$")
-def emit_header(type="text/html"):
- print "Content-type: " + type + "; charset=utf-8"
- print
+# CGI stuff ---------------------------------------------------------
+def config_get(key, default=None):
+ return globals().get(key, default)
+def script_name():
+ return os.environ.get('SCRIPT_NAME', '')
-# Formatting stuff --------------------------------------------------
+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 privileged_path():
+ return config_get('privileged_url') or script_name()
+
+def remote_user():
+ user = os.environ.get('REMOTE_USER', '')
+ if user is None or user == '' or user == 'anonymous':
+ user = 'AnonymousCoward'
+ return user
-def get_scriptname():
- return environ.get('SCRIPT_NAME', '')
+def remote_host():
+ 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
-def send_guru(msg, msg_type):
- if msg is None or len(msg) == 0: return
- print '
'
+def is_external_url(pathname):
+ return (url_re.match(pathname) or pathname.startswith('/'))
+
+def relative_url(pathname, privileged=False):
+ if not is_external_url(pathname):
+ if privileged:
+ url = privileged_path()
+ 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 ''
+
+# Formatting stuff --------------------------------------------------
+def emit_header(mime_type="text/html"):
+ print("Content-type: " + mime_type + "; charset=utf-8\n")
+
+def send_guru(msg_text, msg_type):
+ if not msg_text: return
+ print('
')
if msg_type == 'error':
- print ' Software Failure. Press left mouse button to continue.\n'
- print msg
+ print(' Software Failure. Press left mouse button to continue.\n')
+ print(cgi.escape(msg_text))
if msg_type == 'error':
- print ' Guru Meditation #DEADBEEF.ABADC0DE'
- print '
'
- # FIXME: This simple JS code is harder to pass than ACID 3.0
- print """
- """
+ print '\n Guru Meditation #DEADBEEF.ABADC0DE'
+ print('
' \
+ % relative_url('sys/GuruMeditation.js'))
-def send_title(name, text="Limbo", msg=None, msg_type = 'error'):
+def send_title(name, text="Limbo", msg_text=None, msg_type='error', writable=False):
global title_done
if title_done: return
# Head
emit_header()
- print """
-
-"""
- print "%s: %s" % (site_name, text)
- print ' '
+ print('')
+ print('')
+
+ print("%s: %s" % (config_get('site_name', "Unconfigured Wiki"), text))
+ print(' ')
if not name:
- print ' '
- if css_url:
- print ' ' % css_url
- print ''
+ print(' ')
+
+ for http_equiv, content in config_get('meta_urls', {}):
+ print(' ' % (http_equiv, relative_url(content)))
+
+ for link in config_get('link_urls', {}):
+ rel, href = link
+ print(' ' % (rel, relative_url(href)))
+
+ editable = name and writable and config_get('privileged_url') is not None
+ if editable:
+ print(' ' \
+ % (privileged_path() + '?a=edit&q=' + name))
+
+ history = config_get('history_url')
+ if history is not None:
+ print(' ' \
+ % relative_url(history + '?a=rss'))
+
+ print('')
# Body
- if name and allow_edit:
- print ''
+ if editable:
+ print('')
else:
- print ''
+ print('')
- send_guru(msg, msg_type)
+ title_done = True
+ send_guru(msg_text, msg_type)
# Navbar
- 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(mod_string=None):
+ if config_get('debug_cgi', False):
+ cgi.print_arguments()
+ cgi.print_form(form)
+ cgi.print_environ()
+ link_inline("sys/footer", kvargs = { 'LAST_MODIFIED': mod_string })
+ 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 = []
+ 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;
-
- 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
+ link = permalink(self.line)
+ result = '\n
\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 '