diff options
Diffstat (limited to 'mwlib/magics.py')
-rwxr-xr-x | mwlib/magics.py | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/mwlib/magics.py b/mwlib/magics.py new file mode 100755 index 0000000..985c321 --- /dev/null +++ b/mwlib/magics.py @@ -0,0 +1,472 @@ +#! /usr/bin/env python + +# Copyright (c) 2007-2008 PediaPress GmbH +# See README.txt for additional licensing information. + +"""expand magic variables/colon functions +http://meta.wikimedia.org/wiki/Help:Colon_function +http://meta.wikimedia.org/wiki/Help:Magic_words +http://meta.wikimedia.org/wiki/ParserFunctions +""" + +import datetime +import urllib +from mwlib.log import Log +from mwlib import expr + +log = Log("expander") + +def singlearg(fun): + def wrap(self, args): + rl=args + if not rl: + a=u'' + else: + a=rl[0] + + return fun(self, a) + + return wrap + +def noarg(fun): + def wrap(self, *args): + return fun(self) + return wrap + +def as_numeric(x): + try: + return int(x) + except ValueError: + pass + return float(x) + + +def maybe_numeric_compare(a,b): + if a==b: + return True + try: + a=as_numeric(a) + b=as_numeric(b) + except ValueError: + return False + + return a==b + + +class OtherMagic(object): + def DEFAULTSORT(self, args): + """see http://en.wikipedia.org/wiki/Template:DEFAULTSORT""" + return u"" + + +class TimeMagic(object): + now = datetime.datetime.now() + + @noarg + def CURRENTDAY(self): + """Displays the current day in numeric form.""" + return "%s" % self.now.day + + @noarg + def CURRENTDAY2(self): + """[MW1.5+] Ditto with leading zero 01 .. 31).""" + return "%02d" % self.now.day + + @noarg + def CURRENTDAYNAME(self): + """Displays the current day in named form.""" + return self.now.strftime("%A") + + @noarg + def CURRENTDOW(self): + """current day as number (0=Sunday, 1=Monday...).""" + return str((self.now.weekday()+1) % 7) + + @noarg + def CURRENTMONTH(self): + """The number 01 .. 12 of the current month.""" + return "%02d" % self.now.month + + @noarg + def CURRENTMONTHABBREV(self): + """[MW1.5+] current month abbreviated Jan .. Dec.""" + return self.now.strftime("%b") + + @noarg + def CURRENTMONTHNAME(self): + """current month in named form January .. December. """ + return self.now.strftime("%B") + + @noarg + def CURRENTTIME(self): + """The current time of day (00:00 .. 23:59).""" + return self.now.strftime("%H:%M") + + @noarg + def CURRENTWEEK(self): + """Number of the current week (1-53) according to ISO 8601 with no leading zero.""" + return str(self.now.isocalendar()[1]) + + @noarg + def CURRENTYEAR(self): + """Returns the current year.""" + return str(self.now.year) + + @noarg + def CURRENTTIMESTAMP(self): + """[MW1.7+] Returns the current time stamp. e.g.: 20060528125203""" + return self.now.strftime("%Y%m%d%H%M%S") + + def MONTHNAME(self, args): + rl = args + if not rl: + return u"Missing required parameter 1=month!" + try: + m=int(rl[0].strip()) % 12 + except ValueError: + return u"month should be an integer" + if m==0: + m=12 + + return datetime.datetime(2000, m, 1).strftime("%B") + +class PageMagic(object): + def __init__(self, pagename='', server="http://en.wikipedia.org", revisionid=0): + self.pagename = pagename + self.server = server + self.revisionid = revisionid + + def PAGENAME(self, args): + """Returns the name of the current page, including all levels (Title/Subtitle/Sub-subtitle)""" + return self.pagename + + def PAGENAMEE(self, args): + """same as PAGENAME but More URL-friendly percent encoded + special characters (To use an articlename in an external link). + """ + return urllib.quote(self.pagename.encode('utf8')) + + + def SUBPAGENAME(self, args): + """[MW1.6+] Returns the name of the current page, excluding parent + pages ('Title/Subtitle' becomes 'Subtitle'). + """ + return self.pagename.split('/')[-1] + + def SUBPAGENAMEE(self, args): + return urllib.quote(self.SUBPAGENAMEE()) + + def BASEPAGENAME(self, args): + """[MW1.7+] The basename of a subpage ('Title/Subtitle' becomes 'Title') + """ + return self.pagename.rsplit('/', 1)[0] + + def BASEPAGENAMEE(self, args): + """[MW1.7+] The basename of a subpage ('Title/Subtitle' becomes 'Title') + """ + return urllib.quote(self.BASEPAGENAME(args)) + + def NAMESPACE(self, args): + """Returns the name of the namespace the current page resides in.""" + return u"" # we currently only have articles living in the main/empty namespace + + def NAMESPACEE(self, args): + """Returns the name of the namespace the current page resides in. (quoted)""" + return urllib.quote(self.NAMESPACE(args)) + + def REVISIONID(self, args): + """[MW1.5+] The unique identifying number of a page, see Help:Diff.""" + return str(self.revisionid) + + @noarg + def SITENAME(self): + """Value of $wgSitename.""" + return "" + + def NS(self, args): + """Returns the name of a given namespace number.""" + return "++NS not implemented++" + + def LOCALURL(self, args): + """Returns the local URL of a given page. The page might not exist.""" + try: + url = "/wiki"+ "".join(args) + except: + url = '' # FIXME + return "/wiki"+url + + def LOCALURLE(self, args): + """Returns the local URL of a given page. The page might not exist.""" + return urllib.quote(self.LOCALURL(args)) + + def URLENCODE(self, args): + """[MW1.7+] To use a variable (parameter in a template) with spaces in an external link.""" + try: + url = urllib.quote_plus("".join(args[0])) + except: + url = "".join(args[0]) + return url + + @noarg + def SERVER(self): + """Value of $wgServer""" + return self.server + + def FULLURL(self, args): + return u'' + u = "".join(args) + self.SERVERNAME({}) + + @noarg + def SERVERNAME(self): + return self.SERVER({})[len("http://"):] + + +class NumberMagic(object): + def DISPLAYTITLE(self, args): + """[MW 1.7+] (unclear)""" + return "" + + def NUMBEROFARTICLES(self, args): + """A variable which returns the total number of articles on the Wiki.""" + return "0" + + def NUMBEROFPAGES(self, args): + """[MW1.7+] Returns the total number of pages. """ + return "0" + + def NUMBEROFFILES(self, args): + """[MW1.5+] Returns the number of uploaded files (rows in the image table).""" + return "0" + + def NUMBEROFUSERS(self, args): + """[MW1.7+] Returns the number of registered users (rows in the user table).""" + return "0" + + def CURRENTVERSION(self, args): + """[MW1.7+] Returns the current version of MediaWiki being run. [5]""" + return "1.7alpha" + + + +class StringMagic(object): + @singlearg + def LC(self, a): + return a.lower() + + @singlearg + def UC(self, a): + return a.upper() + + @singlearg + def LCFIRST(self, a): + return a[:1].lower()+a[1:] + + @singlearg + def UCFIRST(self, a): + return a[:1].upper()+a[1:] + + @singlearg + def FORMATNUM(self, a): + return a + +class ParserFunctions(object): + wikidb = None + def _error(self,s): + return '<strong class="error">%s</strong>' % (s,) + + def TAG(self, args): + name = args[0].strip() + r= u"<%s>%s</%s>" % (name, args[1], name) + return r + + + def IF(self, rl): + if rl[0]: + return rl[1] + else: + return rl[2] + + def IFEXIST(self, args): + name = args[0] + if not self.wikidb: + return args.get(args[2], "") + + # wrong place. FIXME. + if ':' in name: + ns, name = name.split(':', 1) + if ns.lower() in ['vorlage', 'template']: + r=self.wikidb.getTemplate(name) + else: + r=None + else: + if name == '': + r = None + else: + r=self.wikidb.getRawArticle(name) + + if r: + return args[1] + else: + return args[2] + + + + def IFEQ(self, rl): + if maybe_numeric_compare(rl[0], rl[1]): + return rl[2] + else: + return rl[3] + + def EXPR(self, rl): + if rl: + try: + r=str(expr.expr(rl[0])) + except Exception, err: + return self._error(err) + + if "e" in r: + f,i = r.split("e") + i=int(i) + if i<0: + sign = '' + else: + sign = '+' + fixed=str(float(f))+"E"+sign+str(int(i)) + return fixed + return r + return u"0" + + + def IFEXPR(self, rl): + try: + r = expr.expr(rl[0]) + except Exception, err: + return self._error(err) + + if r: + return rl[1] + else: + return rl[2] + + def SWITCH(self, args): + """see http://meta.wikimedia.org/wiki/ParserFunctions#.23switch:""" + cmpval = args[0].strip() + found=False # used for fall through + for c in args[1:]: + if '=' in c: + val, result = c.split('=', 1) + val=val.strip() + result=result.strip() + if found or maybe_numeric_compare(val, cmpval): + return result + else: + if maybe_numeric_compare(cmpval,c.strip()): + found=True + + d=args["#default"] + if d: + return d + + + last = args[-1] + + if '=' not in last: + return last + return u'' + + def TITLEPARTS(self, args): + title = args[0] + try: + numseg = int(args[1]) + except ValueError: + numseg = 0 + + try: + start = int(args[2]) + except ValueError: + start = 1 + + if start>0: + start -= 1 + + parts = title.split("/")[start:] + if numseg: + parts = parts[:numseg] + return "/".join(parts) + + def IFERROR(self, args): + errmark = '<strong class="error">' + val = args[0] + bad=args[1] + good=args[2] or val + + if errmark in val: + return bad + else: + return good + + +for x in dir(ParserFunctions): + if x.startswith("_"): + continue + setattr(ParserFunctions, "#"+x, getattr(ParserFunctions, x)) + delattr(ParserFunctions, x) + +class DummyResolver(object): + pass + +class MagicResolver(TimeMagic, PageMagic, NumberMagic, StringMagic, ParserFunctions, OtherMagic, DummyResolver): + def __call__(self, name, args): + try: + name = str(name) + except UnicodeEncodeError: + return None + + + m = getattr(self, name.upper(), None) + if m is None: + return None + + if isinstance(m, basestring): + return m + + res = m(args) or '' # FIXME: catch TypeErros + assert isinstance(res, basestring), "MAGIC %r returned %r" % (name, res) + return res + + def has_magic(self, name): + try: + name = str(name) + except UnicodeEncodeError: + return False + + + m = getattr(self, name.upper(), None) + return m is not None + + + +magic_words = ['basepagename', 'basepagenamee', 'contentlanguage', 'currentday', 'currentday2', 'currentdayname', 'currentdow', 'currenthour', 'currentmonth', 'currentmonthabbrev', 'currentmonthname', 'currentmonthnamegen', 'currenttime', 'currenttimestamp', 'currentversion', 'currentweek', 'currentyear', 'defaultsort', 'directionmark', 'displaytitle', 'fullpagename', 'fullpagenamee', 'language', 'localday', 'localday2', 'localdayname', 'localdow', 'localhour', 'localmonth', 'localmonthabbrev', 'localmonthname', 'localmonthnamegen', 'localtime', 'localtimestamp', 'localweek', 'localyear', 'namespace', 'namespacee', 'newsectionlink', 'numberofadmins', 'numberofarticles', 'numberofedits', 'numberoffiles', 'numberofpages', 'numberofusers', 'pagename', 'pagenamee', 'pagesinnamespace', 'revisionday', 'revisionday2', 'revisionid', 'revisionmonth', 'revisiontimestamp', 'revisionyear', 'scriptpath', 'server', 'servername', 'sitename', 'subjectpagename', 'subjectpagenamee', 'subjectspace', 'subjectspacee', 'subpagename', 'subpagenamee', 'talkpagename', 'talkpagenamee', 'talkspace', 'talkspacee', 'urlencode'] + +def _populate_dummy(): + m=MagicResolver() + + def get_dummy(name): + def resolve(*args): + log.warn("using dummy resolver for %s" % (name,)) + return u"" + return resolve + + missing = set() + for x in magic_words: + if not m.has_magic(x): + missing.add(x) + setattr(DummyResolver, x.upper(), get_dummy(x)) + + if missing: + missing = list(missing) + missing.sort() + #log.info("installed dummy resolvers for %s" % (", ".join(missing),)) + +_populate_dummy() |