Generic argument parsing and formatting
authorBernie Innocenti <bernie@codewiz.org>
Fri, 24 Apr 2009 11:31:41 +0000 (20:31 +0900)
committerBernie Innocenti <bernie@codewiz.org>
Fri, 24 Apr 2009 11:31:41 +0000 (20:31 +0900)
geekigeeki.py

index fbf3d93e96b3e1ac322b19055aed56c7d7b3e159..8bd3e0b9be13ec871c6fe3aea0ed84966a2682ff 100755 (executable)
@@ -33,7 +33,6 @@ file_re = re.compile(r"^\b([A-Za-z0-9_\-][A-Za-z0-9_\.\-/]*)\b$")
 img_re = re.compile(r"^.*\.(png|gif|jpg|jpeg|bmp|ico|ogm|ogg|mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt)$", re.IGNORECASE)
 video_re = re.compile(r"^.*\.(ogm|ogg|mkv|mpg|mpeg|mp4|avi|asf|flv|wmv|qt)$", re.IGNORECASE)
 url_re = re.compile(r"^[a-z]{3,8}://[^\s'\"]+\S$")
-link_re = re.compile(r"(?:\[\[|{{)([^\s\|]+)(?:\s*\|\s*([^\]]+)|)(?:\]\]|}})")
 ext_re = re.compile(r"\.([^\./]+)$")
 
 title_done = False
@@ -83,6 +82,27 @@ def relative_url(pathname, privileged=False):
 def permalink(s):
     return re.sub(' ', '-', re.sub('[^a-z0-9_ ]', '', s.lower()).strip())
 
+# 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 = []
+    kwargs = {} 
+    for arg in s.strip('<[{}]>').split('|'):
+        try:
+            key, val = arg.split('=', 1)
+            kwargs[key.strip()] = val.strip()
+        except ValueError:
+            args.append(arg.strip())
+    return (args, kwargs)
+
+def url_args(kvargs):
+    argv = []
+    for k, v in kvargs.items():
+        argv.append(k + '=' + v)
+    if argv:
+        return '?' + '&amp;'.join(argv)
+    return ''
+
 # Formatting stuff --------------------------------------------------
 def emit_header(mime_type="text/html"):
     print("Content-type: " + mime_type + "; charset=utf-8\n")
@@ -175,7 +195,7 @@ def send_httperror(status="403 Not Found", query=""):
     send_title(None, msg_text=("%s: on query '%s'" % (status, query)))
     send_footer()
 
-def link_tag(params, text=None, link_class=None, privileged=False):
+def link_tag(params, text=None, link_class=None, privileged=False, **kvargs):
     if text is None:
         text = params # default
     elif img_re.match(text):
@@ -197,13 +217,13 @@ def link_tag(params, text=None, link_class=None, privileged=False):
 
     return '<a %shref="%s">%s</a>' % (classattr, relative_url(params, privileged=privileged), text)
 
-def link_inline(name, descr=None, args=''):
+def link_inline(name, descr=None, kvargs={}):
     if not descr: descr = name
     url = relative_url(name)
     if video_re.match(name):
         return '<video src="%s">Your browser does not support the HTML5 video tag</video>' % url
     elif img_re.match(name):
-        return '<a href="%s"><img border="0" src="%s" alt="%s" /></a>' % (url, url + args, descr)
+        return '<a href="%s"><img border="0" src="%s" alt="%s" /></a>' % (url, url + url_args(kvargs), descr)
     elif file_re.match(name) and not ext_re.search(name): # FIXME: this guesses a wiki page
         return Page(name).send_naked()
     else:
@@ -358,48 +378,34 @@ class WikiFormatter:
         return self._undent() + '\n<hr size="%d" noshade="noshade" />\n' % (len(word) - 2)
 
     def _macro_repl(self, word):
-        m = re.compile("\<\<([^\s\|\>]+)(?:\s*\|\s*([^\>]+)|)\>\>").match(word)
-        name = m.group(1)
-        argv = [name]
-        if m.group(2):
-            argv.extend(m.group(2).split('|'))
-        argv = list(map(str.strip, argv))
-
-        macro = globals().get('_macro_' + name)
-        if not macro:
-            try:
+        try:
+            args, kwargs = parse_args(word)
+            macro = globals().get('_macro_' + args[0])
+            if not macro:
                 exec(open("macros/" + name + ".py").read(), globals())
-            except IOError as err:
-                if err.errno == errno.ENOENT: pass
-            macro = globals().get('_macro_' + name)
-        if macro:
-            return macro(argv)
-        else:
-            msg = '&lt;&lt;' + '|'.join(argv) + '&gt;&gt;'
+                macro = globals().get('_macro_' + name)
+            return macro(*args, **kwargs)
+        except Exception:
+            msg = cgi.escape(word)
             if not self.in_html:
                 msg = '<strong class="error">' + msg + '</strong>'
             return msg
 
     def _hurl_repl(self, word):
-        m = link_re.match(word)
-        return link_tag(m.group(1), m.group(2))
+        args, kvargs = parse_args(word)
+        return link_tag(*args, **kvargs)
 
     def _inl_repl(self, word):
-        (name, descr) = link_re.match(word).groups()
-
-        if descr:
-            argv = descr.split('|')
-            descr = argv.pop(0)
-            args = ''
-            if argv:
-                args = '?' + '&amp;'.join(argv)
-
+        args, kvargs = parse_args(word)
+        name = args.pop(0)
+        if len(args):
+            descr = args.pop(0)
             # The "extthumb" nonsense works around a limitation of the HTML block model
             return '<div class="extthumb"><div class="thumb">' \
-                + link_inline(name, descr, args) \
+                + link_inline(name, descr, kvargs) \
                 + '<div class="caption">' + descr + '</div></div></div>'
         else:
-            return link_inline(name, name)
+            return link_inline(name, None, kvargs)
 
     def _html_repl(self, word):
         if not self.in_html and word.startswith('<div'): word = '</p>' + word
@@ -611,7 +617,7 @@ class Page:
         except IOError as err:
             if err.errno == errno.ENOENT:
                 if default is None:
-                    default = '//[[?edit=%s|Describe %s]]//' % (self.page_name, self.page_name)
+                    default = '//[[%s|Describe %s|action=edit]]//' % (self.page_name, self.page_name)
                 return default
             if err.errno == errno.EISDIR:
                 return self.format_dir()