Use generic head links instead of ad-hoc mechanisms
[geekigeeki.git] / geekigeeki.py
index 854c715dc50d834529bc76f164c34c1cc83323cd..5abd90f3f2c72d2a49ddf0df4a9390e66a79733b 100755 (executable)
@@ -31,8 +31,9 @@ from os import path, environ
 word_re = re.compile(r"^\b((([A-Z][a-z0-9]+){2,}/)*([A-Z][a-z0-9]+){2,})\b$")
 # FIXME: we accept stuff like foo/../bar and we shouldn't
 file_re = re.compile(r"^\b([A-Za-z0-9_\-][A-Za-z0-9_\.\-/]*)\b$")
-img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg)$", re.IGNORECASE)
+img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg|bmp|ico)$", re.IGNORECASE)
 url_re = re.compile(r"^[a-z]{3,8}://[^\s'\"]+\S$")
+link_re = re.compile("(?:\[\[|{{)([^\s\|]+)(?:\s*\|\s*([^\]]+)|)(?:\]\]|}})")
 
 title_done = False
 
@@ -109,7 +110,7 @@ def send_guru(msg_text, msg_type):
         }
     </script>"""
 
-def send_title(name, text="Limbo", msg_text=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
 
@@ -124,12 +125,23 @@ def send_title(name, text="Limbo", msg_text=None, msg_type='error'):
     print ' <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />'
     if not name:
         print ' <meta name="robots" content="noindex,nofollow" />'
-    for css in css_url:
-        print ' <link rel="stylesheet" type="text/css" href="%s" />' % relative_url(css)
+
+    for link in link_urls:
+        rel, href = link
+        print ' <link rel="%s" href="%s" />' % (rel, relative_url(href))
+
+    if name and writable and privileged_url is not None:
+        print ' <link rel="alternate" type="application/x-wiki" title="Edit this page" href="%s" />' \
+            % (privileged_path() + '?edit=' + name)
+
+    if history_url is not None:
+        print ' <link rel="alternate" type="application/rss+xml" title="RSS" href="%s" />' \
+            % relative_url(history_url + '?a=rss')
+
     print '</head>'
 
     # Body
-    if name and privileged_url is not None:
+    if name and writable and privileged_url is not None:
         print '<body ondblclick="location.href=\'' + privileged_path() + '?edit=' + name + '\'">'
     else:
         print '<body>'
@@ -143,17 +155,21 @@ def send_title(name, text="Limbo", msg_text=None, msg_type='error'):
         print '  <b>' + link_tag('?fullsearch=' + name, text, 'navlink') + '</b> '
     else:
         print '  <b>' + text + '</b> '
-    print ' | ' + link_tag('FrontPage', 'Front Page', 'navlink')
+    print ' | ' + link_tag('FrontPage', 'Home', 'navlink')
     print ' | ' + link_tag('FindPage', 'Find Page', 'navlink')
     if 'history_url' in globals():
-        print ' | <a href="' + history_url + '" class="navlink">Recent Changes</a>'
+        print ' | <a href="' + relative_url(history_url) + '" class="navlink">Recent Changes</a>'
         if name:
-            print ' | <a href="' + history_url + '?a=history;f=' + name + '" class="navlink">Page History</a>'
+            print ' | <a href="' + relative_url(history_url + '?a=history;f=' + name) + '" class="navlink">Page History</a>'
 
     if name:
         print ' | ' + link_tag('?raw=' + name, 'Raw Text', 'navlink')
         if privileged_url is not None:
-            print ' | ' + link_tag('?edit=' + name, 'Edit Page', 'navlink', privileged=True)
+            if writable:
+                print ' | ' + link_tag('?edit=' + name, 'Edit', 'navlink', privileged=True)
+            else:
+                print ' | ' + link_tag(name, 'Login', 'navlink', privileged=True)
+
     else:
         print ' | <i>Immutable Page</i>'
 
@@ -342,19 +358,36 @@ class WikiFormatter:
             return '<strong class="error">&lt;&lt;' + '|'.join(argv) + '&gt;&gt;</strong>'
 
     def _hurl_repl(self, word):
-        m = re.compile("\[\[([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\]\]").match(word)
+        m = link_re.match(word)
+        name = m.group(1)
+        if m.group(2) is None:
+            descr = name
+        elif img_re.match(m.group(2)):
+            descr = '<img border="0" src="' + descr + '" />'
+        else:
+            descr = m.group(2)
+
+        return link_tag(name, descr, 'wikilink')
+
+    def _inl_repl(self, word):
+        m = link_re.match(word)
         name = m.group(1)
         descr = m.group(2) or name
+        name = relative_url(name)
+        argv = descr.split('|')
+        descr = argv.pop(0)
 
-        if img_re.match(name):
-            name = relative_url(name)
-            # The "extthumb" nonsense works around a limitation of the HTML block model
-            return '<div class="extthumb"><div class="thumb"><a href="%s"><img border="0" src="%s" alt="%s" /></a><div class="caption">%s</div></div></div>' % (name, name, descr, descr)
+        if argv:
+            args = '?' + '&amp;'.join(argv)
         else:
-            if img_re.match(descr):
-                descr = '<img border="0" src="' + descr + '" />'
+            args = ''
 
-            return link_tag(name, descr, 'wikilink')
+        if descr:
+            # The "extthumb" nonsense works around a limitation of the HTML block model
+            return '<div class="extthumb"><div class="thumb"><a href="%s"><img border="0" src="%s" alt="%s" /></a><div class="caption">%s</div></div></div>' \
+                    % (name, name + args, descr, descr)
+        else:
+            return '<a href="%s"><img border="0" src="%s" /></a>' % (name, name + args)
 
     def _email_repl(self, word):
         return '<a href="mailto:%s">%s</a>' % (word, word)
@@ -466,16 +499,16 @@ class WikiFormatter:
             + r"|(?P<html><(/|)(br|hr|div|form|iframe|input|span))"
             + r"|(?P<ent>[<>&])"
 
-            # Auto links
-            + r"|(?P<img>\b[a-zA-Z0-9_/-]+\.(png|gif|jpg|jpeg|bmp))" # LEGACY
-            + r"|(?P<word>\b(?:[A-Z][a-z]+){2,}\b)" # LEGACY
-            + r"|(?P<url>(http|https|ftp|mailto)\:[^\s'\"]+\S)" # LEGACY
-            + r"|(?P<email>[-\w._+]+\@[\w.-]+)" # LEGACY
+            # Auto links (LEGACY)
+            + r"|(?P<img>\b[a-zA-Z0-9_/-]+\.(png|gif|jpg|jpeg|bmp|ico))"
+            + r"|(?P<word>\b(?:[A-Z][a-z]+){2,}\b)"
+            + r"|(?P<url>(http|https|ftp|mailto)\:[^\s'\"]+\S)"
+            + r"|(?P<email>[-\w._+]+\@[\w.-]+)"
 
             # Lists, divs, spans
             + r"|(?P<li>^\s+[\*#] +)"
             + r"|(?P<pre>\{\{\{|\s*\}\}\})"
-            + r"|(?P<inl>\{\{([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\}\})" #TODO
+            + r"|(?P<inl>\{\{([^\s\|]+)(?:\s*\|\s*([^\]]+)|)\}\})"
 
             # Tables
             + r"|(?P<tr>^\s*\|\|(=|)\s*)"
@@ -560,10 +593,18 @@ class Page:
             raise er
 
     def format_dir(self):
-        out = ''
+        out = '== '
+        path = ''
+        for dir in self.page_name.split('/'):
+            path = (path + '/' + dir) if path else dir
+            out += '[[' + path + '|' + dir + ']]/'
+        out += ' ==\n'
         for file in page_list(self._filename(), file_re):
             if img_re.match(file):
-                out += ' * {{' + self.page_name + '/' + file + '}}\n'
+                if image_maxwidth:
+                    maxwidth_arg = '|maxwidth=' + str(image_maxwidth)
+                out += '{{' + self.page_name + '/' + file + '|' + file + maxwidth_arg + '}}\n'
             else:
                 out += ' * [[' + self.page_name + '/' + file + ']]\n'
         return out
@@ -616,17 +657,16 @@ class Page:
             send_guru("Read access denied by ACLs", "notice")
 
     def format(self):
-        page_name = None
-        if self.can_write():
-            page_name = self.page_name
-
-        #css foo.css bar.css
-        global css_url
-        css_url = self.get_attr("css", "").split() + css_url
-
-        send_title(page_name, self.split_title(), msg_text=self.msg_text, msg_type=self.msg_type)
+        #css foo.css
+        value = self.get_attr("css", None)
+        if value:
+            global link_urls
+            link_urls += { "stylesheet": value }
+
+        send_title(self.page_name, self.split_title(),
+            msg_text=self.msg_text, msg_type=self.msg_type, writable=self.can_write())
         self.send_naked()
-        send_footer(page_name, self._last_modified())
+        send_footer(self.page_name, self._last_modified())
 
     def _last_modified(self):
         try:
@@ -679,6 +719,16 @@ class Page:
         else:
             send_title(None, msg_text='Read access denied by ACLs', msg_type='notice')
 
+    def send_image(self, mimetype, args=[]):
+        if 'maxwidth' in args:
+            import subprocess
+            emit_header(mimetype)
+            sys.stdout.flush()
+            subprocess.check_call(['gm', 'convert', self._filename(),
+                '-scale', args['maxwidth'].value + ' >', '-'])
+        else:
+            self.send_raw(mimetype)
+
     def _write_file(self, data):
         tmp_filename = self._tmp_filename()
         open(tmp_filename, 'wb').write(data)
@@ -741,8 +791,15 @@ try:
             else:
                 from mimetypes import MimeTypes
                 type, encoding = MimeTypes().guess_type(query)
-                type = type or 'text/plain'
-                Page(query).send_raw(mimetype=type)
+                #type = type or 'text/plain'
+                #Page(query).send_raw(mimetype=type)
+                if type:
+                    if type.startswith('image/'):
+                        Page(query).send_image(mimetype=type,args=form)
+                    else:
+                        Page(query).send_raw(mimetype=type)
+                else:
+                    Page(query).format()
         else:
             print "Status: 404 Not Found"
             send_title(None, msg_text='Can\'t work out query: ' + query)