changeset 0:e76551809b85

initial commit
author Radomir Dopieralski <sheep-devel@sheep.art.pl>
date Thu, 07 Oct 2010 23:00:29 +0200
parents
children 9be21971a6a2
files .hgignore docs/%C5%81apanie%20Za%20Du%C5%BCo docs/%C5%B9r%C3%B3d%C5%82a docs/Dekoratory docs/Domkni%C4%99cia docs/Domy%C5%9Blne%20Parametry docs/Dowolna%20Liczba%20Argument%C3%B3w docs/Etykiety%2FPojemniki docs/Filtrowanie docs/Funkcje%20Pierwszej%20Klasy docs/Generatory docs/Home docs/Identyczno%C5%9B%C4%87 docs/Iteratory docs/Kolejka docs/Krojenie docs/Modyfikowalne%20Obiekty docs/Przebaczenie%2FPozwolenie docs/Przegadane docs/Przypisanie%20Do%20Plasterk%C3%B3w docs/Python%3A%20Fragmenty docs/Referencje docs/Rozpakowywanie docs/S%C5%82owniki docs/Sortowanie docs/Stos docs/Stra%C5%BCnicy docs/Sumowanie docs/Szukanie%20yap-%C3%B3w docs/Tylko%20Niekt%C3%B3re%20B%C5%82%C4%99dy docs/Tytu%C5%82 docs/Wiele%20Rodzaj%C3%B3w%20Wyj%C4%85tk%C3%B3w docs/Wielokrotne%20Przypisanie docs/Wywo%C5%82anie%20Funkcji docs/Za%20Du%C5%BCo%20w%20try docs/Zen docs/Zen%20c.d. docs/ast.literal_eval docs/defaultdict docs/enumerate%20i%20zip docs/finally docs/for%20else docs/gencomp docs/getattr%2Fsetattr docs/is%20None docs/jquery.js docs/listcomp docs/logo.png docs/property docs/setdefault docs/slide-background.png docs/slides.css docs/slides.js hatta/__init__.py hatta/__main__.py hatta/config.py hatta/data.py hatta/error.py hatta/hg_integration.py hatta/page.py hatta/parser.py hatta/search.py hatta/storage.py hatta/wiki.py start.py
diffstat 65 files changed, 4553 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,3 @@
+\.pyc$
+^\.
+\.swp$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/%C5%81apanie%20Za%20Du%C5%BCo	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,17 @@
+Źle:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> try:
+...     print fooo['bar']
+>>> except:
+...     print "nie ma bar"
+}}}
+
+Dobrze:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> try:
+...     print fooo['bar']
+>>> except KeyError:
+...     print "nie ma bar"
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/%C5%B9r%C3%B3d%C5%82a	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,6 @@
+* http://docs.python.org/tutorial
+* http://docs.python.org/howto/doanddont.html
+* **Robust Python Programs** //Stefan Schwarzer//
+* **Idiomatic Python** //Raymond Hettinger//
+* **Python Cookbook** //Alex Martelli, Anna Ravenscoft, David Ascher//
+* Dziki kod
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Dekoratory	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,13 @@
+{{{#!python
+>>> marked = {}
+>>> def mark(func):
+...     marked[func.__name__] = func
+...     return func
+... 
+>>> @mark
+... def foo(bar):
+...     return bar
+... 
+>>> marked
+{'foo': <function foo at 0xb753c72c>}
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Domkni%C4%99cia	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,11 @@
+{{{#!python
+>>> def make_adder(additive):
+...     def adder(what):
+...         return what + additive
+...
+...     return adder
+...
+>>> adder = make_adder(3)
+>>> adder(2)
+5
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Domy%C5%9Blne%20Parametry	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,17 @@
+Dobrze:
+{{{#!python
+>>> def function(foo=1, bar=None):
+...     if bar is None:
+...         bar = []
+}}}
+
+Źle:
+{{{#!python
+>>> def append_one(where=[]):
+...     where.append(1)
+...     return where
+>>> append_one()
+[1]
+>>> append_one()
+[1, 1]
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Dowolna%20Liczba%20Argument%C3%B3w	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,6 @@
+{{{#!python
+>>> def function(first, second, *args, **kwargs):
+...     return first, second, args, kwargs
+>>> function(1, 2, 3, 4, 5, foo=6, bar=7)
+(1, 2, [3, 4, 5], {'foo': 6, 'bar':7})
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Etykiety%2FPojemniki	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,10 @@
+Zmienne nie są pojemnikami, tylko etykietami
+{{{#!python
+>>> x = [1]
+>>> y = x
+>>> x is y
+True
+>>> y = [1]
+>>> x is y
+False
+}}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Filtrowanie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+{{{#!python
+>>> def parenthize(what):          
+...     for item in what:          
+...         yield '(%s)' % item    
+...
+>>> ', '.join(parenthize('abc'))
+'(a), (b), (c)'
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Funkcje%20Pierwszej%20Klasy	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,13 @@
+Funkcje mozna przekazywac jako zmienne i parametry:
+{{{#!python
+>>> def foo(name):
+...     return 'hello %s' % name
+>>> bar = foo
+>>> bar('world')
+'hello world'
+>>> functions = {
+...     'foo': foo,
+>>> }
+>>> functions['foo']('world')
+'hello world'
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Generatory	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,14 @@
+{{{#!python
+>>> class MyIterable(object):
+...     def __iter__(self):
+...         yield 1
+...         yield 2
+...         yield 3
+... 
+>>> for i in MyIterable():   
+...     print i              
+... 
+1
+2
+3
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Home	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,58 @@
+* [[Python: Fragmenty]]
+* [[Źródła]]
+* [[Zen]]
+
+* [[Identyczność]]
+* [[is None]]
+* [[Etykiety/Pojemniki]]
+* [[Modyfikowalne Obiekty]]
+* [[Referencje]]
+
+
+* [[Przegadane]]
+* [[Strażnicy]]
+* [[Wielokrotne Przypisanie]]
+* [[Rozpakowywanie]]
+* [[for else]]
+
+* [[Krojenie]]
+* [[Przypisanie Do Plasterków]]
+* [[Szukanie yap-ów]]
+* [[listcomp]]
+* [[Sortowanie]]
+* [[Sumowanie]]
+
+* [[Iteratory]]
+* [[Generatory]]
+* [[Filtrowanie]]
+* [[gencomp]]
+* [[enumerate i zip]]
+
+
+* [[Funkcje Pierwszej Klasy]]
+* [[Wywołanie Funkcji]]
+* [[Domyślne Parametry]]
+* [[Dowolna Liczba Argumentów]]
+
+* [[Łapanie Za Dużo]]
+* [[Za Dużo w try]]
+* [[Przebaczenie/Pozwolenie]]
+* [[finally]]
+* [[Wiele Rodzajów Wyjątków]]
+* [[Tylko Niektóre Błędy]]
+
+* [[Słowniki]]
+* [[setdefault]]
+* [[defaultdict]]
+* [[Stos]]
+* [[Kolejka]]
+
+* [[Domknięcia]]
+* [[getattr/setattr]]
+* [[ast.literal_eval]]
+
+* [[Dekoratory]]
+* [[property]]
+* [[super]]
+* [[Magiczne Metody]]
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Identyczno%C5%9B%C4%87	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,11 @@
+Identyczność to nie to samo co równość:
+{{{#!python
+>>> 1 == 1.0
+True
+>>> 1 is 1.0
+False
+>>> [1] == [1]
+True
+>>> [1] is [1]
+False
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Iteratory	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,22 @@
+{{{#!python
+
+>>> class MyIter(object):     
+...     def __init__(self):   
+...          self.count = 0   
+...     def next(self):       
+...          self.count += 1  
+...          if self.count > 3:
+...              raise StopIteration()
+...          return self.count
+... 
+>>> class MyIterable(object):
+...     def __iter__(self):
+...         return MyIter()
+... 
+>>> for i in MyIterable():
+...     print i
+... 
+1
+2
+3
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Kolejka	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,20 @@
+Źle:
+{{{#!python
+>>> queue = [root]
+>>> while queue:
+>>>     node = queue.pop(0)
+>>>     for child in node.children:
+>>>         queue.append(child)
+>>>     print node
+}}}
+
+Dobrze:
+{{{#!python
+>>> import collections
+>>> queue = collections.deque([root])
+>>> while queue:
+>>>     node = queue.popleft()
+>>>     for child in node.children:
+>>>         queue.append(child)
+>>>     print node
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Krojenie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,17 @@
+{{{#!pycon
+>>> hello = "Hello world!"
+>>> hello[0]
+'H'
+>>> hello[0:5]
+'Hello'
+>>> hello[-1]
+'!'
+>>> hello[1:-1]
+'ello world'
+>>> hello[:5]+hello[5:]
+'Hello world!'
+>>> hello[::2]
+'Hlowrd'
+>>> hello[::-1]
+'!dlrow olleH'
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Modyfikowalne%20Obiekty	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,15 @@
+{{{#!python
+>>> list1 = [1]
+>>> list2 = list1 # Nie tworzy kopii!
+>>> list1.append(2)
+>>> list1
+[1, 2]
+>>> list2
+[1, 2]
+>>> list2 = [3, 4]
+>>> list1.append(5)
+>>> list1
+[1, 2, 5]
+>>> list2
+[3, 4]
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Przebaczenie%2FPozwolenie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,19 @@
+Źle:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> if 'bar' in foo:
+...     bar = foo['bar']
+>>> else:
+...     bar = 0
+>>> bar = foo['bar'] if foo.has_key('foo') else 0
+}}}
+
+Dobrze:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> try:
+...     bar = foo['bar']
+>>> except KeyError:
+...     bar = 0
+>>> bar = foo.get('bar', 0)
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Przegadane	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,18 @@
+Źle:
+{{{#!python
+>>> if x == True:
+>>> if x != []:
+>>> if x == []:
+>>> if x == {}:
+>>> if len(x) == 0:
+>>> if x == '':
+>>> return True if x else False
+}}}
+
+Dobrze:
+{{{#!python
+>>> if x:
+>>> if not x:
+>>> return x
+>>> return bool(x)
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Przypisanie%20Do%20Plasterk%C3%B3w	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,13 @@
+{{{#!python
+>>> a = [1, 2, 3, 4]
+>>> a[1:3] = [5, 6, 7, 8]
+>>> a                    
+[1, 5, 6, 7, 8, 4]
+}}}
+
+{{{#!python
+>>> s = list('abc')
+>>> s[1:2] = 'ABC'
+>>> ''.join(s)
+'aABCc'
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Python%3A%20Fragmenty	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,6 @@
+//Pouczające fragmenty kodu w języku Python//
+
+Radomir Dopieralski (**STX Next**)\\
+//pycon@sheep.art.pl//
+
+**PyCon.pl 2010**
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Referencje	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,21 @@
+Parametry są przekazywane przez referencję:
+
+Źle:
+{{{#!python
+>>> def empty_list(what):
+...     what = []
+>>> list1 = [1, 2, 3]
+>>> empty_list(list1)
+>>> list1
+[1, 2, 3]
+}}}
+
+Dobrze:
+{{{#!python
+>>> def empty_list(what):
+...     what[:] = []
+>>> list1 = [1, 2, 3]
+>>> empty_list(list1)
+>>> list1
+[]
+}}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Rozpakowywanie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,19 @@
+{{{#!python
+>>> a, b, c = 1, 2, 3
+>>> a, b, c
+(1, 2, 3)
+>>> FOO, BAR, BAZ = range(3)
+>>> BAR     
+1
+>>> head, tail = 'a, b, c'.split(',', 1)
+>>> head, tail
+('a', ' b, c')
+>>> a, b = 1, 2
+>>> b, a = a, b
+>>> a, b
+(2, 1)
+>>> a, b = 1, 2, 3
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+ValueError: too many values to unpack
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/S%C5%82owniki	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,14 @@
+{{{#!python
+>>> d1 = {
+...     'foo': 1,
+...     'bar': 2,
+... }
+>>> d2 = dict(baz=3, bop=4)
+>>> d3 = dict((k, v) for k, v in d.iteritems() if v % 2)
+>>> d3
+{'foo': 1}
+>>> d3.update(d2)
+>>> d3
+{'foo': 1, 'bar': 2}
+>>> d3.update(baz=3)
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Sortowanie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,20 @@
+{{{#!python
+>>> a = [(0, 1), (3, 0), (1, 2)]
+>>> a.sort()
+>>> a
+[(0, 1), (1, 2), (3, 0)]
+}}}
+
+{{{#!python
+>>> import operator
+>>> a.sort(key=operator.itemgetter(1))
+>>> a
+[(3, 0), (0, 1), (1, 2)]
+}}}
+
+{{{#!python
+>>> sorted(a)
+[(0, 1), (1, 2), (3, 0)]
+>>> a
+[(3, 0), (0, 1), (1, 2)]
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Stos	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+{{{#!python
+>>> stack = [root]
+>>> while stack:
+>>>     node = stack.pop()
+>>>     for child in node.children:
+>>>         stack.append(child)
+>>>     print node
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Stra%C5%BCnicy	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,20 @@
+Źle:
+{{{#!python
+>>> ...
+>>> if 0 < x and x < 5:
+...     ...
+}}}
+
+Dobrze:
+{{{#!python
+>>> ...
+>>> if 0 < x < 5:
+...     ...
+}}}
+
+Przy okazji:
+{{{#!python
+>>> None < float('-inf') < 0 < float('+inf') < ()
+True
+}}}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Sumowanie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,19 @@
+{{{#!python
+>>> sum([1, 2, 3])
+6
+>>> max([1, 2, 3])
+3
+}}}
+
+Źle:
+{{{#!python
+>>> sum([0.0000000001]*10000000)           
+0.0010000000001300626
+}}}
+
+Dobrze:
+{{{#!python
+>>> import math
+>>> math.fsum([0.0000000001]*10000000)
+0.001
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Szukanie%20yap-%C3%B3w	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,10 @@
+{{{#!python
+>>> input = u'yap foo yup bar yep yelp'
+>>> for prev, this, next in zip(input, input[1:], input[2:]):
+...     if prev.lower() == 'y' and next.lower() == 'p':
+...         print prev+this+next
+... 
+yap
+yup
+yep
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Tylko%20Niekt%C3%B3re%20B%C5%82%C4%99dy	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,11 @@
+{{{#!pycon
+>>> try:
+...     f = open('filename')
+>>> except IOError, e:
+...     if e.errno == 2:
+...         print "nie ma pliku"
+...     else:
+...         raise
+>>> except OSError:
+...     print "blad systemowy"
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Tytu%C5%82	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,3 @@
+= Python -- Fragmenty =
+== Radomir Dopieralski ==
+=== pyconpl@sheep.art.pl ===
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Wiele%20Rodzaj%C3%B3w%20Wyj%C4%85tk%C3%B3w	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,14 @@
+Źle:
+{{{#!python
+>>> try:
+...     f = open('filename')
+>>> except OSError, IOError:
+...     # OSError jest łapany i przypisywany do zmiennej IOError
+}}}
+Dobrze:
+{{{#!python
+>>> try:
+...     f = open('filename')
+>>> except (OSError, IOError):
+...     print 'something went wrong'
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Wielokrotne%20Przypisanie	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,13 @@
+{{{#!python
+>>> a = b = c = 0
+>>> a, b, c
+(0, 0, 0)
+}}}
+
+Uwaga!
+{{{#!python
+>>> a = b = c = []
+>>> a.append(1)
+>>> a, b, c
+([1], [1], [1])
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Wywo%C5%82anie%20Funkcji	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+Łatwo zapomniec wywolac funckje:
+{{{#!python
+>>> f = open('filename')
+>>> data = f.read()
+>>> f.close
+<built-in method close of file object at 0xb786a020>
+>>> f.close()
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Za%20Du%C5%BCo%20w%20try	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,20 @@
+Źle:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> try:
+...     print foo['bar']
+...     baz = foo['bar'] + foo['foo']
+>>> except KeyError:
+...     print "nie ma bar"
+}}}
+
+Dobrze:
+{{{#!python
+>>> foo = {'bar': 2}
+>>> try:
+...     print foo['bar']
+>>> except KeyError:
+...     print "nie ma bar"
+>>> else:
+...     baz = foo['bar'] + foo['foo']
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Zen	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+{{{#!pycon
+
+>>> import this
+...
+}}}
+
+* http://docs.python.org/howto/doanddont.html
+* http://www.python.org/dev/peps/pep-0008/
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Zen%20c.d.	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,14 @@
+{{{#!pycon
+>>> import this
+...
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/ast.literal_eval	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,19 @@
+Źle:
+
+{{{#!python
+>>> data = open('config.cfg').read()
+>>> config = eval(data)
+}}}
+
+Tak sobie:
+{{{#!python
+>>> import ast
+>>> config = ast.literal_eval(data)
+}}}
+
+Dobrze:
+{{{#!python
+>>> import json
+>>> config = json.loads(data)
+>>> config = json.load(open('config.cfg'))
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/defaultdict	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,9 @@
+{{{#!python
+>>> import collections
+>>> d = collections.defaultdict(list)
+>>> for a, b in [(1, 'a'), (1, 'b'), (2, 'a')]:
+...     d[a].append(b)
+... 
+>>> d
+defaultdict(<type 'list'>, {1: ['a', 'b'], 2: ['a']})
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/enumerate%20i%20zip	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,23 @@
+{{{#!python
+>>> for i, c in enumerate('abc'): 
+...    print i, c
+... 
+0 a
+1 b
+2 c
+}}}
+
+{{{#!python
+>>> def integers():
+...     i = 0
+...     while True:
+...         yield i
+...         i += 1
+... 
+>>> for i, c in zip(integers(), 'abc'):
+...     print i, c
+... 
+0 a
+1 b
+2 c
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/finally	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,14 @@
+{{{#!python
+>>> f = open('filename')
+>>> try:
+...     data = f.read()
+>>> finally:
+...     f.close()
+}}}
+
+Po nowemu:
+{{{#!python
+>>> from __future__ import with_statement
+>>> with open('filename') as f:
+...     data = f.read()
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/for%20else	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+{{{#!python
+>>> for i in []:
+...     print 'a'
+... else:
+...     print 'b'
+... 
+b
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/gencomp	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,25 @@
+{{{#!python
+>>> g = (c for c in 'a.b.c.d' if c != '.')
+>>> list(g)
+['a', 'b', 'c']
+>>> list(g)
+[]
+}}}
+
+{{{#!python
+>>> g = iter('abcd')
+>>> for a, b in zip(g, g):
+...     print a, b
+... 
+a b
+c d
+}}}
+
+{{{#!python
+>>> g = iter('abcd')
+>>> for i in g:           
+...   print i, g.next()
+... 
+a b
+c d
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/getattr%2Fsetattr	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,18 @@
+Źle:
+{{{#!python
+>>> class Foo(object):
+...     foo = 1
+...     bar = 2
+...
+>>> foo = Foo()
+>>> attr = 'foo'
+>>> x = eval('foo.%s' % attr)
+>>> y = 3
+>>> exec('foo.%s = %s' % (attr, repr(y))
+}}}
+
+Dobrze:
+{{{#!python
+>>> x = getattr(foo, attr)
+>>> setattr(foo, attr, y)
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/is%20None	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,11 @@
+Zawsze używamy 'is' i 'is not' do porównywania z None:
+{{{#!python
+
+>>> class EqualToEverything(object):
+...     def __eq__(self, to_what):
+...         return True
+>>> EqualToEverything() == None
+True
+>>> EqualToEverything() is None
+False
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/jquery.js	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/listcomp	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,13 @@
+{{{#!python
+>>> [x+1 for x in (1, 2, 3)]
+[2, 3, 4]
+>>> [x for x in [1, 2, 3, 4] if x % 2]
+[1, 3]
+>>> ['(%s)' % c for c in 'a.b.c' if c != '.']
+['(a)', '(b)', '(c)']
+>>> [x for y in [[1, 2, 3], [4, 5, 6], [7, 8, 9]] for x in y]
+[1, 2, 3, 4, 5, 6, 7, 8, 9]
+>>> [part.strip() for part in
+...  'a, b, c, '.split(',') if part.strip()]
+['a', 'b', 'c']
+}}}
Binary file docs/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/property	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,15 @@
+{{{#!python
+>>> class Foo(object):
+...     bar = 3
+...     @property
+...     def foo(self):
+...         return self.bar + 1
+...
+...     @foo.setter
+...     def foo(self, value):
+...         self.bar = value - 1
+...
+...     @foo.deleter
+...     def foo(self):
+...         self.bar = 0
+}}}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/setdefault	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,8 @@
+{{{#!python
+>>> d = {}              
+>>> for a, b in [(1, 'a'), (1, 'b'), (2, 'a')]:
+...     d.setdefault(a, []).append(b)
+... 
+>>> d
+{1: ['a', 'b'], 2: ['a']}
+}}}
\ No newline at end of file
Binary file docs/slide-background.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/slides.css	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,47 @@
+
+html { background: #fff url(/+download/slide-background.png) no-repeat 95% 95%; color #444; height: 100%; }
+div.slide { padding: 1em 3em; position: absolute; top: 0; left: 0; bottom: 0; right: 0 }
+
+body {
+    line-height: 1.3;
+    font-family: DejaVu Sans, sans-serif;
+    color: #444;
+}
+
+h1, h2, h3, h4, h5 {
+    color: #9c81a0;
+    font-family: serif;
+    text-shadow: 0 0.0125em 0.25em rgba(0, 0, 0, 0.25);
+    letter-spacing: 0.05em;
+}
+
+div.highlight {
+    background: transparent;
+}
+
+a img {
+    border: none;
+}
+b img {
+    float: left;
+    margin: 0.25em 2em 0.25em 0;
+    display: block;
+}
+i img {
+    float: right;
+    margin: 0.25em 0 0.25em 2em;
+    display: block;
+}
+ul {
+    margin: 0;
+    padding: 0 0 0 2em;
+}
+li {
+    margin: 0.25em 0;
+    padding: 0;
+    list-style: square outside;
+}
+
+code {
+    font-size: 100%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/slides.js	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,25 @@
+    $(document).ready(function () {
+        function nextSlide() {
+            if ($('div.slide').last().is(':visible')) return;
+            $('div.slide:visible').hide().next('div.slide').show();
+        }
+        function prevSlide() {
+            if ($('div.slide').first().is(':visible')) return;
+            $('div.slide:visible').hide().prev('div.slide').show();
+        }
+        $('html').click(nextSlide).keydown(function (event) {
+            var k = event.keyCode;
+            if (k == 13 || k == 32 || k == 39) {
+                nextSlide();
+                event.preventDefault();
+            } else if (k == 8 || k == 37) {
+                prevSlide();
+                event.preventDefault();
+            } else if (k == 27) {
+                window.location = ('' + window.location).replace('+slides/', '');
+                event.preventDefault();
+            }
+        });
+        $('body').css('font-size', ''+$(window).height()/32+'px');
+        $('div.slide').hide().first().show();
+    });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/__init__.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# @copyright: 2008-2009 Radomir Dopieralski <hatta@sheep.art.pl>
+# @license: GNU GPL, see COPYING for details.
+
+"""
+Hatta Wiki is a wiki engine designed to be used with Mercurial repositories.
+It requires Mercurial and Werkzeug python modules.
+
+Hatta's pages are just plain text files (and also images, binaries, etc.) in
+some directory in your repository. For example, you can put it in your
+project's "docs" directory to keep documentation. The files can be edited both
+from the wiki or with a text editor -- in either case the changes committed to
+the repository will appear in the recent changes and in page's history.
+
+See hatta.py --help for usage.
+"""
+
+# Exposed API
+from wiki import Wiki, WikiResponse, WikiRequest
+from config import WikiConfig, read_config
+from __main__ import main
+from parser import WikiParser, WikiWikiParser
+from storage import WikiStorage, WikiSubdirectoryStorage
+from page import WikiPage, WikiPageText, WikiPageWiki
+from page import WikiPageColorText, WikiPageFile, WikiPageImage
+
+# Project's metainformation
+__version__ = '1.4.0dev'
+project_name = 'Hatta'
+project_url = 'http://hatta-wiki.org/'
+project_description = 'Wiki engine that lives in Mercurial repository.'
+
+# Make it work as Mercurial extension
+from hg_integration import cmdtable
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/__main__.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+from config import read_config
+from wiki import Wiki
+
+# Avoid WSGI errors, see http://mercurial.selenic.com/bts/issue1095
+sys.stdout = sys.__stdout__
+sys.stderr = sys.__stderr__
+
+def application(env, start):
+    """Detect that we are being run as WSGI application."""
+
+    global application
+    config = read_config()
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    if config.get('pages_path') is None:
+        config.set('pages_path', os.path.join(script_dir, 'docs'))
+    wiki = Wiki(config)
+    application = wiki.application
+    return application(env, start)
+
+def main(config=None, wiki=None):
+    """Start a standalone WSGI server."""
+
+    config = config or read_config()
+    wiki = wiki or Wiki(config)
+    app = wiki.application
+
+    host, port = (config.get('interface', '0.0.0.0'),
+                  int(config.get('port', 8080)))
+    try:
+        from cherrypy import wsgiserver
+    except ImportError:
+        try:
+            from cherrypy import _cpwsgiserver as wsgiserver
+        except ImportError:
+            import wsgiref.simple_server
+            server = wsgiref.simple_server.make_server(host, port, app)
+            try:
+                server.serve_forever()
+            except KeyboardInterrupt:
+                pass
+            return
+    apps = [('', app)]
+    name = wiki.site_name
+    server = wsgiserver.CherryPyWSGIServer((host, port), apps, server_name=name)
+    try:
+        server.start()
+    except KeyboardInterrupt:
+        server.stop()
+
+if __name__ == "__main__":
+    main()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/config.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+
+OPTIONS = []
+VALID_NAMES = set()
+
+def _add(short, long, dest, help, default=None, metavar=None, action=None, type=None):
+    OPTIONS.append((short, long, dest, help, default, metavar, action, type))
+    VALID_NAMES.add(dest)
+
+_add('-V', '--version', dest='show_version', default=False,
+    help='Display version and exit', action="store_true")
+_add('-d', '--pages-dir', dest='pages_path',
+    help='Store pages in DIR', metavar='DIR')
+_add('-t', '--cache-dir', dest='cache_path',
+    help='Store cache in DIR', metavar='DIR')
+_add('-i', '--interface', dest='interface',
+    help='Listen on interface INT', metavar='INT')
+_add('-p', '--port', dest='port', type='int',
+    help='Listen on port PORT', metavar='PORT')
+_add('-s', '--script-name', dest='script_name',
+    help='Override SCRIPT_NAME to NAME', metavar='NAME')
+_add('-n', '--site-name', dest='site_name',
+    help='Set the name of the site to NAME', metavar='NAME')
+_add('-m', '--front-page', dest='front_page',
+    help='Use PAGE as the front page', metavar='PAGE')
+_add('-e', '--encoding', dest='page_charset',
+    help='Use encoding ENC to read and write pages', metavar='ENC')
+_add('-c', '--config-file', dest='config_file',
+    help='Read configuration from FILE', metavar='FILE')
+_add('-l', '--language', dest='language',
+    help='Translate interface to LANG', metavar='LANG')
+_add('-r', '--read-only', dest='read_only',
+    help='Whether the wiki should be read-only', action="store_true")
+_add('-g', '--icon-page', dest='icon_page', metavar="PAGE",
+    help='Read icons graphics from PAGE.')
+_add('-w', '--hgweb', dest='hgweb',
+    help='Enable hgweb access to the repository', action="store_true")
+_add('-W', '--wiki-words', dest='wiki_words',
+    help='Enable WikiWord links', action="store_true")
+_add('-I', '--ignore-indent', dest='ignore_indent',
+    help='Treat indented lines as normal text', action="store_true")
+_add('-P', '--pygments-style', dest='pygments_style',
+    help='Use the STYLE pygments style for highlighting',
+    metavar='STYLE')
+_add('-D', '--subdirectories', dest='subdirectories',
+    action="store_true",
+    help='Store subpages as subdirectories in the filesystem')
+
+class WikiConfig(object):
+    """
+    Responsible for reading and storing site configuration. Contains the
+    default settings.
+
+    >>> config = WikiConfig(port='2080')
+    >>> config.sanitize()
+    >>> config.get('port')
+    2080
+    """
+
+    default_filename = u'hatta.conf'
+
+    def __init__(self, **kw):
+        self.config = dict(kw)
+        self.parse_environ()
+        self.options = list(OPTIONS)
+        self.valid_names = set(VALID_NAMES)
+
+    def sanitize(self):
+        """
+        Convert options to their required types.
+        """
+
+        try:
+            self.config['port'] = int(self.get('port', 0))
+        except ValueError:
+            self.config['port'] = 8080
+
+    def parse_environ(self):
+        """Check the environment variables for options."""
+
+        prefix = 'HATTA_'
+        for key, value in os.environ.iteritems():
+            if key.startswith(prefix):
+                name = key[len(prefix):].lower()
+                if name in self.valid_names:
+                    self.config[name] = value
+
+    def parse_args(self):
+        """Check the commandline arguments for options."""
+
+        import optparse
+
+        parser = optparse.OptionParser()
+        for short, long, dest, help, default, metavar, action, type in self.options:
+            parser.add_option(short, long, dest=dest, help=help, type=type,
+                              default=default, metavar=metavar, action=action)
+
+        options, args = parser.parse_args()
+        for option, value in options.__dict__.iteritems():
+            if value is not None:
+                self.config[option] = value
+        if args:
+            self.config['pages_path'] = args[0]
+
+    def parse_files(self, files=None):
+        """Check the config files for options."""
+
+        import ConfigParser
+
+        if files is None:
+            files = [self.get('config_file', self.default_filename)]
+        parser = ConfigParser.SafeConfigParser()
+        parser.read(files)
+        section = 'hatta'
+        for option, value in parser.items(section):
+            if option not in self.valid_names:
+                raise ValueError('Invalid option name "%s".' % option)
+            self.config[option] = value
+
+    def save_config(self, filename=None):
+        """Saves configuration to a given file."""
+        if filename is None:
+            filename = self.default_filename
+
+        import ConfigParser
+        parser = ConfigParser.RawConfigParser()
+        section = 'hatta'
+        parser.add_section(section)
+        for key, value in self.config.iteritems():
+            parser.set(section, str(key), str(value))
+
+        configfile = open(filename, 'wb')
+        try:
+            parser.write(configfile)
+        finally:
+            configfile.close()
+
+    def get(self, option, default_value=None):
+        """
+        Get the value of a config option or default if not set.
+
+        >>> config = WikiConfig(option=4)
+        >>> config.get("ziew", 3)
+        3
+        >>> config.get("ziew")
+        >>> config.get("ziew", "ziew")
+        'ziew'
+        >>> config.get("option")
+        4
+        """
+
+        return self.config.get(option, default_value)
+
+    def get_bool(self, option, default_value=False):
+        """
+        Like get, only convert the value to True or False.
+        """
+
+        value = self.get(option, default_value)
+        if value in (
+            1, True,
+            'True', 'true', 'TRUE',
+            '1',
+            'on', 'On', 'ON',
+            'yes', 'Yes', 'YES',
+            'enable', 'Enable', 'ENABLE',
+            'enabled', 'Enabled', 'ENABLED',
+        ):
+            return True
+        elif value in (
+            None, 0, False,
+            'False', 'false', 'FALSE',
+            '0',
+            'off', 'Off', 'OFF',
+            'no', 'No', 'NO',
+            'disable', 'Disable', 'DISABLE',
+            'disabled', 'Disabled', 'DISABLED',
+        ):
+            return False
+        else:
+            raise ValueError("expected boolean value")
+
+    def set(self, key, value):
+        self.config[key] = value
+
+
+def read_config():
+    """Read and parse the config."""
+
+    config = WikiConfig(
+        # Here you can modify the configuration: uncomment and change the ones
+        # you need. Note that it's better use environment variables or command
+        # line switches.
+
+        # interface='',
+        # port=8080,
+        # pages_path = 'docs',
+        # front_page = 'Home',
+        # site_name = 'Hatta Wiki',
+        # page_charset = 'UTF-8',
+    )
+    config.parse_args()
+    config.parse_files()
+    # config.sanitize()
+    return config
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/data.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import base64
+
+icon = base64.b64decode(
+'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhki'
+'AAAAAlwSFlzAAAEnQAABJ0BfDRroQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBo'
+'AAALWSURBVDiNbdNLaFxlFMDx//fd19x5JdNJm0lIImPaYm2MfSUggrssXBVaChUfi1JwpQtxK7gqu'
+'LMbQQQ3bipU0G3Rgg98DBpraWob00kzM6Z5TF7tdObm3vvd46K0TBo/OLtzfnychxIRut+Zo2/19vT'
+'kLxXze6biONbGJMRipL39MJyt33rvp+rVT7rzVTfw2vFzLxwcLf/V7oSq1W4hACIkIigUtnaoNecXG'
+'2u14T8blQRAd2v7yyN/RLFR6IRM1iedSeFnUvhpDydlI9ow0lcedG3348c1djeQz+WcThjgYZMgGBG'
+'SJMEYgzGGODLEoTBYGH4DeHcXoDSSzaRVogQjyaMwhtgYcoUco+Nl5qbnubFw7fr//uB2tXp78uj4c'
+'0YJsSTESUxsDCemjjH6YhnbtbA8xaVv7n/0uGZHDx48aH8+17iLJQrf9vCdFL7tkcn7/Pb7r8zdmWP'
+'2zqwopa7sAl4/cV4NlvrPbgch7aBN1vUIOw9ZWmmw2dqkb18fQSegOrOgfD9zahfQ37/3su+ljj1T6'
+'uCnAyxtoZVGa41tWSilULWfCZdaPD986MsjQxOHdwC9PdmT2tLk0oozpxfYf2SZwp4Iz1X4UZWBe1+'
+'z9+5X+OkiruWpYr744ZMmvjn5dvrwoVHLdRzWtobY2Kwx9soyz5ZXuV9fQ5pXCBabXKuXcBwbYwxYe'
+'kIppTXAF5VP2xutrVYmm8bzM1z9foSZik1z1SWMNLW1AtMrB/gnnMJxbSxbUV2a/QHQT8Y4c+vvC8V'
+'C74VCoZcodvnxux5Msg+THCSKHy2R48YgIb/crITrreZlEYl33MKrYycvvnx88p2BUkkpRyGSEBmDi'
+'WI6QcC95UUqM9PBzdqN99fbzc9EJNwBKKUoFw+8NDY8/sFQ/8CE57l5pZRdX6kHqxurW43mv98urM9'
+'fjJPouohE8NQ1dkEayAJ5wAe2gRawJSKmO/c/aERMn5m9/ksAAAAASUVORK5CYII=')
+
+scripts = r"""function hatta_dates(){var a=document.getElementsByTagName(
+'abbr');var p=function(i){return('00'+i).slice(-2)};for(var i=0;i<a.length;++i)
+{var n=a[i];if(n.className==='date'){var m=
+/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$/.exec(
+n.getAttribute('title'));var d=new Date(Date.UTC(+m[1],+m[2]-1,+m[3],+m[4],
++m[5],+m[6]));if(d){var b=-d.getTimezoneOffset()/60;if(b>=0){b="+"+b}
+n.textContent=""+d.getFullYear()+"-"+p(d.getMonth()+1)+"-"+p(d.getDate())+" "+
+p(d.getHours())+":"+p(d.getMinutes())+" GMT"+b}}}}function hatta_edit(){var b=
+document.getElementById('editortext');if(b){var c=0+
+document.location.hash.substring(1);var d=b.textContent.match(/(.*\n)/g);var
+f='';for(var i=0;i<d.length&&i<c;++i){f+=d[i]}b.focus();if(b.setSelectionRange)
+{b.setSelectionRange(f.length,f.length)}else if(b.createTextRange){var g=
+b.createTextRange();g.collapse(true);g.moveEnd('character',f.length);
+g.moveStart('character',f.length);g.select()}var h=document.createElement('pre'
+);b.parentNode.appendChild(h);var k=window.getComputedStyle(b,'');h.style.font=
+k.font;h.style.border=k.border;h.style.outline=k.outline;h.style.lineHeight=
+k.lineHeight;h.style.letterSpacing=k.letterSpacing;h.style.fontFamily=
+k.fontFamily;h.style.fontSize=k.fontSize;h.style.padding=0;h.style.overflow=
+'scroll';try{h.style.whiteSpace="-moz-pre-wrap"}catch(e){};try{
+h.style.whiteSpace="-o-pre-wrap"}catch(e){};try{h.style.whiteSpace="-pre-wrap"
+}catch(e){};try{h.style.whiteSpace="pre-wrap"}catch(e){};h.textContent=f;
+b.scrollTop=h.scrollHeight;h.parentNode.removeChild(h)}else{var l='';var m=
+document.getElementsByTagName('link');for(var i=0;i<m.length;++i){var n=m[i];
+if(n.getAttribute('type')==='application/wiki'){l=n.getAttribute('href')}}if(
+l===''){return}var o=['p','h1','h2','h3','h4','h5','h6','pre','ul','div',
+'span'];for(var j=0;j<o.length;++j){var m=document.getElementsByTagName(o[j]);
+for(var i=0;i<m.length;++i){var n=m[i];if(n.id&&n.id.match(/^line_\d+$/)){
+n.ondblclick=function(){var a=l+'#'+this.id.replace('line_','');
+document.location.href=a}}}}}}
+window.onload=function(){hatta_dates();hatta_edit()}"""
+
+style = """\
+html { background: #fff; color: #2e3436;
+    font-family: sans-serif; font-size: 96% }
+body { margin: 1em auto; line-height: 1.3; width: 40em }
+a { color: #3465a4; text-decoration: none }
+a:hover { text-decoration: underline }
+a.wiki:visited { color: #204a87 }
+a.nonexistent, a.nonexistent:visited { color: #a40000; }
+a.external { color: #3465a4; text-decoration: underline }
+a.external:visited { color: #75507b }
+a img { border: none }
+img.math, img.smiley { vertical-align: middle }
+pre { font-size: 100%; white-space: pre-wrap; word-wrap: break-word;
+    white-space: -moz-pre-wrap; white-space: -pre-wrap;
+    white-space: -o-pre-wrap; line-height: 1.2; color: #555753 }
+div.conflict pre.local { background: #fcaf3e; margin-bottom: 0; color: 000}
+div.conflict pre.other { background: #ffdd66; margin-top: 0; color: 000; border-top: #d80 dashed 1px; }
+pre.diff div.orig { font-size: 75%; color: #babdb6 }
+b.highlight, pre.diff ins { font-weight: bold; background: #fcaf3e;
+color: #ce5c00; text-decoration: none }
+pre.diff del { background: #eeeeec; color: #888a85; text-decoration: none }
+pre.diff div.change { border-left: 2px solid #fcaf3e }
+div.footer { border-top: solid 1px #babdb6; text-align: right }
+h1, h2, h3, h4 { color: #babdb6; font-weight: normal; letter-spacing: 0.125em}
+div.buttons { text-align: center }
+input.button, div.buttons input { font-weight: bold; font-size: 100%;
+    background: #eee; border: solid 1px #babdb6; margin: 0.25em; color: #888a85}
+.history input.button { font-size: 75% }
+.editor textarea { width: 100%; display: block; font-size: 100%;
+    border: solid 1px #babdb6; }
+.editor label { display:block; text-align: right }
+.editor .upload { margin: 2em auto; text-align: center }
+form.search input.search, .editor label input { font-size: 100%;
+    border: solid 1px #babdb6; margin: 0.125em 0 }
+.editor label.comment input  { width: 32em }
+a.logo { float: left; display: block; margin: 0.25em }
+div.header h1 { margin: 0; }
+div.content { clear: left }
+form.search { margin:0; text-align: right; font-size: 80% }
+div.snippet { font-size: 80%; color: #888a85 }
+div.header div.menu { float: right; margin-top: 1.25em }
+div.header div.menu a.current { color: #000 }
+hr { background: transparent; border:none; height: 0;
+     border-bottom: 1px solid #babdb6; clear: both }
+blockquote { border-left:.25em solid #ccc; padding-left:.5em; margin-left:0}
+abbr.date {border:none}
+dt {font-weight: bold; float: left; }
+dd {font-style: italic; }
+"""
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/error.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import werkzeug.exceptions
+
+
+class WikiError(werkzeug.exceptions.HTTPException):
+    """Base class for all error pages."""
+
+
+class BadRequest(WikiError):
+    code = 400
+
+
+class ForbiddenErr(WikiError):
+    code = 403
+
+
+class NotFoundErr(WikiError):
+    code = 404
+
+
+class RequestEntityTooLarge(WikiError):
+    code = 413
+
+
+class RequestURITooLarge(WikiError):
+    code = 414
+
+
+class UnsupportedMediaTypeErr(WikiError):
+    code = 415
+
+
+class NotImplementedErr(WikiError):
+    code = 501
+
+
+class ServiceUnavailableErr(WikiError):
+    code = 503
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/hg_integration.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+
+from config import WikiConfig
+from __main__ import main
+
+def run_wiki(ui, repo, directory=None, **opts):
+    """Start serving Hatta in the provided repository."""
+
+    config = WikiConfig()
+    config.set('pages_path', directory or os.path.join(repo.root, 'docs'))
+    ui.write('Starting wiki at http://127.0.0.1:8080\n')
+    main(config=config)
+
+cmdtable = {
+    'wiki': (
+        run_wiki, [
+        ],
+        "hg wiki [options] directory",
+    ),
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/page.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,767 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import difflib
+import mimetypes
+import re
+import os
+
+import werkzeug
+import werkzeug.contrib.atom
+
+try:
+    import pygments
+    import pygments.util
+    import pygments.lexers
+    import pygments.formatters
+    import pygments.styles
+except ImportError:
+    pygments = None
+
+try:
+    import Image
+except ImportError:
+    Image = None
+
+import parser
+import error
+
+
+def page_mime(title):
+    """
+    Guess page's mime type ased on corresponding file name.
+    Default ot text/x-wiki for files without an extension.
+
+    >>> page_mime(u'something.txt')
+    'text/plain'
+    >>> page_mime(u'SomePage')
+    'text/x-wiki'
+    >>> page_mime(u'ąęśUnicodePage')
+    'text/x-wiki'
+    >>> page_mime(u'image.png')
+    'image/png'
+    >>> page_mime(u'style.css')
+    'text/css'
+    >>> page_mime(u'archive.tar.gz')
+    'archive/gzip'
+    """
+
+    addr = title.encode('utf-8') # the encoding doesn't relly matter here
+    mime, encoding = mimetypes.guess_type(addr, strict=False)
+    if encoding:
+        mime = 'archive/%s' % encoding
+    if mime is None:
+        mime = 'text/x-wiki'
+    return mime
+
+
+class WikiPage(object):
+    """Everything needed for rendering a page."""
+
+    def __init__(self, wiki, request, title, mime):
+        self.request = request
+        self.title = title
+        self.mime = mime
+        # for now we just use the globals from wiki object
+        if request:
+            self.get_url = request.get_url
+            self.get_download_url = request.get_download_url
+        self.wiki = wiki
+        self.storage = self.wiki.storage
+        self.index = self.wiki.index
+        self.config = self.wiki.config
+
+    def date_html(self, date_time):
+        """
+        Create HTML for a date, according to recommendation at
+        http://microformats.org/wiki/date
+        """
+
+        text = date_time.strftime('%Y-%m-%d %H:%M')
+        # We are going for YYYY-MM-DDTHH:MM:SSZ
+        title = date_time.strftime('%Y-%m-%dT%H:%M:%SZ')
+        html = werkzeug.html.abbr(text, class_="date", title=title)
+        return html
+
+
+    def wiki_link(self, addr, label=None, class_=None, image=None, lineno=0):
+        """Create HTML for a wiki link."""
+
+        addr = addr.strip()
+        text = werkzeug.escape(label or addr)
+        chunk = ''
+        if class_ is not None:
+            classes = [class_]
+        else:
+            classes = []
+        if parser.external_link(addr):
+            if addr.startswith('mailto:'):
+                class_ = 'external email'
+                text = text.replace('@', '&#64;').replace('.', '&#46;')
+                href = addr.replace('@', '%40').replace('.', '%2E')
+            else:
+                classes.append('external')
+                href = werkzeug.escape(addr, quote=True)
+        else:
+            if '#' in addr:
+                addr, chunk = addr.split('#', 1)
+                chunk = '#'+chunk
+            if addr.startswith('+'):
+                href = '/'.join([self.request.script_root,
+                                 '+'+werkzeug.escape(addr[1:], quote=True)])
+                classes.append('special')
+            elif addr == u'':
+                href = chunk
+                classes.append('anchor')
+            else:
+                classes.append('wiki')
+                href = self.get_url(addr) + chunk
+                if addr not in self.storage:
+                    classes.append('nonexistent')
+        class_ = ' '.join(classes) or None
+        return werkzeug.html.a(image or text, href=href, class_=class_,
+                               title=addr+chunk)
+
+    def wiki_image(self, addr, alt, class_='wiki', lineno=0):
+        """Create HTML for a wiki image."""
+
+        addr = addr.strip()
+        html = werkzeug.html
+        chunk = ''
+        if parser.external_link(addr):
+            return html.img(src=werkzeug.url_fix(addr), class_="external",
+                            alt=alt)
+        if '#' in addr:
+            addr, chunk = addr.split('#', 1)
+        if addr == '':
+            return html.a(name=chunk)
+        if addr in self.storage:
+            mime = page_mime(addr)
+            if mime.startswith('image/'):
+                return html.img(src=self.get_download_url(addr), class_=class_,
+                                alt=alt)
+            else:
+                return html.img(href=self.get_download_url(addr), alt=alt)
+        else:
+            return html.a(html(alt), href=self.get_url(addr))
+
+    def search_form(self):
+        html = werkzeug.html
+        _ = self.wiki.gettext
+        return html.form(html.div(html.input(name="q", class_="search"),
+                html.input(class_="button", type_="submit", value=_(u'Search')),
+            ), method="GET", class_="search",
+            action=self.get_url(None, self.wiki.search))
+
+    def logo(self):
+        html = werkzeug.html
+        img = html.img(alt=u"[%s]" % self.wiki.front_page,
+                       src=self.get_download_url(self.wiki.logo_page))
+        return html.a(img, class_='logo', href=self.get_url(self.wiki.front_page))
+
+    def menu(self):
+        """Generate the menu items"""
+        _ = self.wiki.gettext
+        if self.wiki.menu_page in self.storage:
+            items = self.index.page_links_and_labels(self.wiki.menu_page)
+        else:
+            items = [
+                (self.wiki.front_page, self.wiki.front_page),
+                ('+history', _(u'Recent changes')),
+            ]
+        for link, label in items:
+            if link == self.title:
+                class_ = "current"
+            else:
+                class_ = None
+            yield self.wiki_link(link, label, class_=class_)
+
+    def header(self, special_title):
+        html = werkzeug.html
+        if self.wiki.logo_page in self.storage:
+            yield self.logo()
+        yield self.search_form()
+        yield html.div(u" ".join(self.menu()), class_="menu")
+        yield html.h1(html(special_title or self.title))
+
+    def html_header(self, special_title, edit_url):
+        e = lambda x: werkzeug.escape(x, quote=True)
+        h = werkzeug.html
+        yield h.title(u'%s - %s' % (e(special_title or self.title),
+                                    e(self.wiki.site_name)))
+        yield h.link(rel="stylesheet", type_="text/css",
+                     href=self.get_url(None, self.wiki.pygments_css))
+        yield h.link(rel="stylesheet", type_="text/css",
+                     href=self.get_url(None, self.wiki.style_css))
+        if special_title:
+            yield h.meta(name="robots", content="NOINDEX,NOFOLLOW")
+        if edit_url:
+            yield h.link(rel="alternate", type_="application/wiki",
+                         href=edit_url)
+        yield h.link(rel="shortcut icon", type_="image/x-icon",
+                     href=self.get_url(None, self.wiki.favicon_ico))
+        yield h.link(rel="alternate", type_="application/rss+xml",
+                     title=e("%s (ATOM)" % self.wiki.site_name),
+                     href=self.get_url(None, self.wiki.atom))
+        yield h.script(type_="text/javascript",
+                     src=self.get_url(None, self.wiki.scripts_js))
+
+    def footer(self, special_title, edit_url):
+        _ = self.wiki.gettext
+        if special_title:
+            footer_links = [
+                (_(u'Changes'), 'changes',
+                 self.get_url(None, self.wiki.recent_changes)),
+                (_(u'Index'), 'index',
+                 self.get_url(None, self.wiki.all_pages)),
+                (_(u'Orphaned'), 'orphaned',
+                 self.get_url(None, self.wiki.orphaned)),
+                (_(u'Wanted'), 'wanted',
+                 self.get_url(None, self.wiki.wanted)),
+            ]
+        else:
+            footer_links = [
+                (_(u'Edit'), 'edit', edit_url),
+                (_(u'History'), 'history',
+                 self.get_url(self.title, self.wiki.history)),
+                (_(u'Backlinks'), 'backlinks',
+                 self.get_url(self.title, self.wiki.backlinks))
+            ]
+        for label, class_, url in footer_links:
+            if url:
+                yield werkzeug.html.a(werkzeug.html(label), href=url,
+                                      class_=class_)
+                yield u'\n'
+
+    def render_content(self, content, special_title=None):
+        """The main page template."""
+
+        edit_url = None
+        if not special_title:
+            try:
+                self.wiki._check_lock(self.title)
+                edit_url = self.get_url(self.title, self.wiki.edit)
+            except error.ForbiddenErr:
+                pass
+
+        yield u"""\
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html><head>\n"""
+        for part in self.html_header(special_title, edit_url):
+            yield part
+        yield u'\n</head><body><div class="header">\n'
+        for part in self.header(special_title):
+            yield part
+        yield u'\n</div><div class="content">\n'
+        for part in content:
+            yield part
+        if not special_title or not self.title:
+            yield u'\n<div class="footer">\n'
+            for part in self.footer(special_title, edit_url):
+                yield part
+            yield u'</div>'
+        yield u'</div></body></html>'
+
+
+    def pages_list(self, pages, message=None, link=None, _class=None):
+        """Generate the content of a page list page."""
+
+        yield werkzeug.html.p(werkzeug.escape(message) % {'link': link})
+        yield u'<ul class="%s">' % werkzeug.escape(_class or 'pagelist')
+        for title in pages:
+            yield werkzeug.html.li(self.wiki_link(title))
+        yield u'</ul>'
+
+    def history_list(self):
+        """Generate the content of the history page."""
+
+        _ = self.wiki.gettext
+        h = werkzeug.html
+        max_rev = -1
+        title = self.title
+        link = self.wiki_link(title)
+        yield h.p(h(_(u'History of changes for %(link)s.')) % {'link': link})
+        url = self.request.get_url(title, self.wiki.undo, method='POST')
+        yield u'<form action="%s" method="POST"><ul class="history">' % url
+        try:
+            self.wiki._check_lock(title)
+            read_only = False
+        except error.ForbiddenErr:
+            read_only = True
+        for rev, date, author, comment in self.wiki.storage.page_history(title):
+            if max_rev < rev:
+                max_rev = rev
+            if rev > 0:
+                date_url = self.request.adapter.build(self.wiki.diff, {
+                    'title': title, 'from_rev': rev-1, 'to_rev': rev})
+            else:
+                date_url = self.request.adapter.build(self.wiki.revision, {
+                    'title': title, 'rev': rev})
+            if read_only:
+                button = u''
+            else:
+                button = h.input(type_="submit", name=str(rev),
+                                 value=h(_(u'Undo')))
+            yield h.li(h.a(self.date_html(date), href=date_url),
+                       button, ' . . . . ',
+                       h.i(self.wiki_link("~%s" % author, author)),
+                       h.div(h(comment), class_="comment"))
+        yield u'</ul>'
+        yield h.input(type_="hidden", name="parent", value=max_rev)
+        yield u'</form>'
+
+
+    def dependencies(self):
+        """Refresh the page when any of those pages was changed."""
+
+        dependencies = set()
+        for title in [self.wiki.logo_page, self.wiki.menu_page]:
+            if title not in self.storage:
+                dependencies.add(werkzeug.url_quote(title))
+        for title in [self.wiki.menu_page]:
+            if title in self.storage:
+                inode, size, mtime = self.storage.page_file_meta(title)
+                etag = '%s/%d-%d' % (werkzeug.url_quote(title), inode, mtime)
+                dependencies.add(etag)
+        return dependencies
+
+class WikiPageText(WikiPage):
+    """Pages of mime type text/* use this for display."""
+
+    def content_iter(self, lines):
+        yield '<pre>'
+        for line in lines:
+            yield werkzeug.html(line)
+        yield '</pre>'
+
+    def plain_text(self):
+        """
+        Get the content of the page with all markup removed, used for
+        indexing.
+        """
+
+        return self.storage.page_text(self.title)
+
+
+    def view_content(self, lines=None):
+        """Read the page content from storage or preview and return iterator."""
+
+        if lines is None:
+            f = self.storage.open_page(self.title)
+            lines = self.storage.page_lines(f)
+        return self.content_iter(lines)
+
+    def editor_form(self, preview=None):
+        """Generate the HTML for the editor."""
+
+        _ = self.wiki.gettext
+        author = self.request.get_author()
+        lines = []
+        try:
+            page_file = self.storage.open_page(self.title)
+            lines = self.storage.page_lines(page_file)
+            (rev, old_date, old_author,
+                old_comment) = self.storage.page_meta(self.title)
+            comment = _(u'modified')
+            if old_author == author:
+                comment = old_comment
+        except error.NotFoundErr:
+            comment = _(u'created')
+            rev = -1
+        except error.ForbiddenErr, e:
+            yield werkzeug.html.p(
+                werkzeug.html(_(unicode(e))))
+            return
+        if preview:
+            lines = preview
+            comment = self.request.form.get('comment', comment)
+        html = werkzeug.html
+        yield u'<form action="" method="POST" class="editor"><div>'
+        yield u'<textarea name="text" cols="80" rows="20" id="editortext">'
+        for line in lines:
+            yield werkzeug.escape(line)
+        yield u"""</textarea>"""
+        yield html.input(type_="hidden", name="parent", value=rev)
+        yield html.label(html(_(u'Comment')), html.input(name="comment",
+            value=comment), class_="comment")
+        yield html.label(html(_(u'Author')), html.input(name="author",
+            value=self.request.get_author()), class_="comment")
+        yield html.div(
+                html.input(type_="submit", name="save", value=_(u'Save')),
+                html.input(type_="submit", name="preview", value=_(u'Preview')),
+                html.input(type_="submit", name="cancel", value=_(u'Cancel')),
+                class_="buttons")
+        yield u'</div></form>'
+        if preview:
+            yield html.h1(html(_(u'Preview, not saved')), class_="preview")
+            for part in self.view_content(preview):
+                yield part
+
+    def diff_content(self, from_text, to_text, message=u''):
+        """Generate the HTML markup for a diff."""
+
+        def infiniter(iterator):
+            """Turn an iterator into an infinite one, padding it with None"""
+
+            for i in iterator:
+                yield i
+            while True:
+                yield None
+
+        diff = difflib._mdiff(from_text.split('\n'), to_text.split('\n'))
+        mark_re = re.compile('\0[-+^]([^\1\0]*)\1|([^\0\1])')
+        yield message
+        yield u'<pre class="diff">'
+        for old_line, new_line, changed in diff:
+            old_no, old_text = old_line
+            new_no, new_text = new_line
+            line_no = (new_no or old_no or 1)-1
+            if changed:
+                yield u'<div class="change" id="line_%d">' % line_no
+                old_iter = infiniter(mark_re.finditer(old_text))
+                new_iter = infiniter(mark_re.finditer(new_text))
+                old = old_iter.next()
+                new = new_iter.next()
+                buff = u''
+                while old or new:
+                    while old and old.group(1):
+                        if buff:
+                            yield werkzeug.escape(buff)
+                            buff = u''
+                        yield u'<del>%s</del>' % werkzeug.escape(old.group(1))
+                        old = old_iter.next()
+                    while new and new.group(1):
+                        if buff:
+                            yield werkzeug.escape(buff)
+                            buff = u''
+                        yield u'<ins>%s</ins>' % werkzeug.escape(new.group(1))
+                        new = new_iter.next()
+                    if new:
+                        buff += new.group(2)
+                    old = old_iter.next()
+                    new = new_iter.next()
+                if buff:
+                    yield werkzeug.escape(buff)
+                yield u'</div>'
+            else:
+                yield u'<div class="orig" id="line_%d">%s</div>' % (
+                    line_no, werkzeug.escape(old_text))
+        yield u'</pre>'
+
+class WikiPageColorText(WikiPageText):
+    """Text pages, but displayed colorized with pygments"""
+
+    def view_content(self, lines=None):
+        """Generate HTML for the content."""
+
+        if lines is None:
+            text = self.storage.page_text(self.title)
+        else:
+            text = ''.join(lines)
+        return self.highlight(text, mime=self.mime)
+
+    def highlight(self, text, mime=None, syntax=None, line_no=0):
+        """Colorize the source code."""
+
+        if pygments is None:
+            yield werkzeug.html.pre(werkzeug.html(text))
+            return
+
+        formatter = pygments.formatters.HtmlFormatter()
+        formatter.line_no = line_no
+
+        def wrapper(source, unused_outfile):
+            """Wrap each line of formatted output."""
+
+            yield 0, '<div class="highlight"><pre>'
+            for lineno, line in source:
+                yield (lineno,
+                       werkzeug.html.span(line, id_="line_%d" %
+                                         formatter.line_no))
+                formatter.line_no += 1
+            yield 0, '</pre></div>'
+
+        formatter.wrap = wrapper
+        try:
+            if mime:
+                lexer = pygments.lexers.get_lexer_for_mimetype(mime)
+            elif syntax:
+                lexer = pygments.lexers.get_lexer_by_name(syntax)
+            else:
+                lexer = pygments.lexers.guess_lexer(text)
+        except pygments.util.ClassNotFoundErr:
+            yield werkzeug.html.pre(werkzeug.html(text))
+            return
+        html = pygments.highlight(text, lexer, formatter)
+        yield html
+
+class WikiPageWiki(WikiPageColorText):
+    """Pages of with wiki markup use this for display."""
+
+    def __init__(self, *args, **kw):
+        super(WikiPageWiki, self).__init__(*args, **kw)
+        if self.config.get_bool('wiki_words', False):
+            self.parser = parser.WikiWikiParser
+        else:
+            self.parser = parser.WikiParser
+        if self.config.get_bool('ignore_indent', False):
+            try:
+                del self.parser.block['indent']
+            except KeyError:
+                pass
+
+    def extract_links(self, text=None):
+        """Extract all links from the page."""
+
+        if text is None:
+            try:
+                text = self.storage.page_text(self.title)
+            except error.NotFoundErr:
+                text = u''
+        return self.parser.extract_links(text)
+
+    def view_content(self, lines=None):
+        if lines is None:
+            f = self.storage.open_page(self.title)
+            lines = self.storage.page_lines(f)
+        if self.wiki.icon_page and self.wiki.icon_page in self.storage:
+            icons = self.index.page_links_and_labels(self.wiki.icon_page)
+            smilies = dict((emo, link) for (link, emo) in icons)
+        else:
+            smilies = None
+        content = self.parser(lines, self.wiki_link, self.wiki_image,
+                             self.highlight, self.wiki_math, smilies)
+        return content
+
+    def wiki_math(self, math):
+        math_url = self.config.get('math_url',
+                            'http://www.mathtran.org/cgi-bin/mathtran?tex=')
+        if '%s' in math_url:
+            url = math_url % werkzeug.url_quote(math)
+        else:
+            url = '%s%s' % (math_url, werkzeug.url_quote(math))
+        label = werkzeug.escape(math, quote=True)
+        return werkzeug.html.img(src=url, alt=label, class_="math")
+
+    def dependencies(self):
+        dependencies = WikiPage.dependencies(self)
+        for title in [self.wiki.icon_page]:
+            if title in self.storage:
+                inode, size, mtime = self.storage.page_file_meta(title)
+                etag = '%s/%d-%d' % (werkzeug.url_quote(title), inode, mtime)
+                dependencies.add(etag)
+        for link in self.index.page_links(self.title):
+            if link not in self.storage:
+                dependencies.add(werkzeug.url_quote(link))
+        return dependencies
+
+class WikiPageFile(WikiPage):
+    """Pages of all other mime types use this for display."""
+
+    def view_content(self, lines=None):
+        if self.title not in self.storage:
+            raise error.NotFoundErr()
+        content = ['<p>Download <a href="%s">%s</a> as <i>%s</i>.</p>' %
+                   (self.request.get_download_url(self.title),
+                    werkzeug.escape(self.title), self.mime)]
+        return content
+
+    def editor_form(self, preview=None):
+        _ = self.wiki.gettext
+        author = self.request.get_author()
+        if self.title in self.storage:
+            comment = _(u'changed')
+            (rev, old_date, old_author,
+                old_comment) = self.storage.page_meta(self.title)
+            if old_author == author:
+                comment = old_comment
+        else:
+            comment = _(u'uploaded')
+            rev = -1
+        html = werkzeug.html
+        yield html.p(html(
+                _(u"This is a binary file, it can't be edited on a wiki. "
+                  u"Please upload a new version instead.")))
+        yield html.form(html.div(
+            html.div(html.input(type_="file", name="data"), class_="upload"),
+            html.input(type_="hidden", name="parent", value=rev),
+            html.label(html(_(u'Comment')), html.input(name="comment",
+                       value=comment)),
+            html.label(html(_(u'Author')), html.input(name="author",
+                       value=author)),
+            html.div(html.input(type_="submit", name="save", value=_(u'Save')),
+                     html.input(type_="submit", name="cancel",
+                                value=_(u'Cancel')),
+            class_="buttons")), action="", method="POST", class_="editor",
+                                enctype="multipart/form-data")
+
+class WikiPageImage(WikiPageFile):
+    """Pages of mime type image/* use this for display."""
+
+    render_file = '128x128.png'
+
+    def view_content(self, lines=None):
+        if self.title not in self.storage:
+            raise error.NotFoundErr()
+        content = ['<img src="%s" alt="%s">'
+                   % (self.request.get_url(self.title, self.wiki.render),
+                      werkzeug.escape(self.title))]
+        return content
+
+    def render_mime(self):
+        """Give the filename and mime type of the rendered thumbnail."""
+
+        if not Image:
+            raise NotImplementedError('No Image library available')
+        return  self.render_file, 'image/png'
+
+    def render_cache(self, cache_dir):
+        """Render the thumbnail and save in the cache."""
+
+        if not Image:
+            raise NotImplementedError('No Image library available')
+        page_file = self.storage.open_page(self.title)
+        cache_path = os.path.join(cache_dir, self.render_file)
+        cache_file = open(cache_path, 'wb')
+        try:
+            im = Image.open(page_file)
+            im = im.convert('RGBA')
+            im.thumbnail((128, 128), Image.ANTIALIAS)
+            im.save(cache_file,'PNG')
+        except IOError:
+            raise error.UnsupportedMediaTypeErr('Image corrupted')
+        cache_file.close()
+        return cache_path
+
+class WikiPageCSV(WikiPageFile):
+    """Display class for type text/csv."""
+
+    def content_iter(self, lines=None):
+        import csv
+        _ = self.wiki.gettext
+        # XXX Add preview support
+        csv_file = self.storage.open_page(self.title)
+        reader = csv.reader(csv_file)
+        html_title = werkzeug.escape(self.title, quote=True)
+        yield u'<table id="%s" class="csvfile">' % html_title
+        try:
+            for row in reader:
+                yield u'<tr>%s</tr>' % (u''.join(u'<td>%s</td>' % cell
+                                                 for cell in row))
+        except csv.Error, e:
+            yield u'</table>'
+            yield werkzeug.html.p(werkzeug.html(
+                _(u'Error parsing CSV file %{file}s on line %{line}d: %{error}s')
+                % {'file': html_title, 'line': reader.line_num, 'error': e}))
+        finally:
+            csv_file.close()
+        yield u'</table>'
+
+    def view_content(self, lines=None):
+        if self.title not in self.storage:
+            raise error.NotFoundErr()
+        return self.content_iter(lines)
+
+class WikiPageRST(WikiPageText):
+    """
+    Display ReStructured Text.
+    """
+
+    def content_iter(self, lines):
+        try:
+            from docutils.core import publish_parts
+        except ImportError:
+            return super(WikiPageRST, self).content_iter(lines)
+        text = ''.join(lines)
+        SAFE_DOCUTILS = dict(file_insertion_enabled=False, raw_enabled=False)
+        content = publish_parts(text, writer_name='html',
+                                settings_overrides=SAFE_DOCUTILS)['html_body']
+        return [content]
+
+
+class WikiPageBugs(WikiPageText):
+    """
+    Display class for type text/x-bugs
+    Parse the ISSUES file in (roughly) format used by ciss
+    """
+
+    def content_iter(self, lines):
+        last_lines = []
+        in_header = False
+        in_bug = False
+        attributes = {}
+        title = None
+        for line_no, line in enumerate(lines):
+            if last_lines and line.startswith('----'):
+                title = ''.join(last_lines)
+                last_lines = []
+                in_header = True
+                attributes = {}
+            elif in_header and ':' in line:
+                attribute, value = line.split(':', 1)
+                attributes[attribute.strip()] = value.strip()
+            else:
+                if in_header:
+                    if in_bug:
+                        yield '</div>'
+                    #tags = [tag.strip() for tag in
+                    #        attributes.get('tags', '').split()
+                    #        if tag.strip()]
+                    yield '<div id="line_%d">' % (line_no)
+                    in_bug = True
+                    if title:
+                        yield werkzeug.html.h2(werkzeug.html(title))
+                    if attributes:
+                        yield '<dl>'
+                        for attribute, value in attributes.iteritems():
+                            yield werkzeug.html.dt(werkzeug.html(attribute))
+                            yield werkzeug.html.dd(werkzeug.html(value))
+                        yield '</dl>'
+                    in_header = False
+                if not line.strip():
+                    if last_lines:
+                        if last_lines[0][0] in ' \t':
+                            yield werkzeug.html.pre(werkzeug.html(
+                                            ''.join(last_lines)))
+                        else:
+                            yield werkzeug.html.p(werkzeug.html(
+                                            ''.join(last_lines)))
+                        last_lines = []
+                else:
+                    last_lines.append(line)
+        if last_lines:
+            if last_lines[0][0] in ' \t':
+                yield werkzeug.html.pre(werkzeug.html(
+                                ''.join(last_lines)))
+            else:
+                yield werkzeug.html.p(werkzeug.html(
+                                ''.join(last_lines)))
+        if in_bug:
+            yield '</div>'
+
+filename_map = {
+    'README': (WikiPageText, 'text/plain'),
+    'ISSUES': (WikiPageBugs, 'text/x-bugs'),
+    'ISSUES.txt': (WikiPageBugs, 'text/x-bugs'),
+    'COPYING': (WikiPageText, 'text/plain'),
+    'CHANGES': (WikiPageText, 'text/plain'),
+    'MANIFEST': (WikiPageText, 'text/plain'),
+    'favicon.ico': (WikiPageImage, 'image/x-icon'),
+}
+
+mime_map = {
+    'text': WikiPageColorText,
+    'application/x-javascript': WikiPageColorText,
+    'application/x-python': WikiPageColorText,
+    'text/csv': WikiPageCSV,
+    'text/x-rst': WikiPageRST,
+    'text/x-wiki': WikiPageWiki,
+    'image': WikiPageImage,
+    '': WikiPageFile,
+}
+
+mimetypes.add_type('application/x-python', '.wsgi')
+mimetypes.add_type('application/x-javascript', '.js')
+mimetypes.add_type('text/x-rst', '.rst')
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/parser.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,518 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+import unicodedata
+import itertools
+import werkzeug
+
+EXTERNAL_URL_RE = re.compile(ur'^[a-z]+://|^mailto:', re.I|re.U)
+
+def external_link(addr):
+    """
+    Decide whether a link is absolute or internal.
+
+    >>> external_link('http://example.com')
+    True
+    >>> external_link('https://example.com')
+    True
+    >>> external_link('ftp://example.com')
+    True
+    >>> external_link('mailto:user@example.com')
+    True
+    >>> external_link('PageTitle')
+    False
+    >>> external_link(u'ąęśćUnicodePage')
+    False
+
+    """
+
+    return EXTERNAL_URL_RE.match(addr)
+
+class WikiParser(object):
+    r"""
+    Responsible for generating HTML markup from the wiki markup.
+
+    The parser works on two levels. On the block level, it analyzes lines
+    of text and decides what kind of block element they belong to (block
+    elements include paragraphs, lists, headings, preformatted blocks).
+    Lines belonging to the same block are joined together, and a second
+    pass is made using regular expressions to parse line-level elements,
+    such as links, bold and italic text and smileys.
+
+    Some block-level elements, such as preformatted blocks, consume additional
+    lines from the input until they encounter the end-of-block marker, using
+    lines_until. Most block-level elements are just runs of marked up lines
+    though.
+
+
+    """
+
+    bullets_pat = ur"^\s*[*]+\s+"
+    heading_pat = ur"^\s*=+"
+    quote_pat = ur"^[>]+\s+"
+    block = {
+        # "name": (priority, ur"pattern"),
+        "bullets": (10, bullets_pat),
+        "code": (20, ur"^[{][{][{]+\s*$"),
+        "conflict": (30, ur"^<<<<<<< local\s*$"),
+        "empty": (40, ur"^\s*$"),
+        "heading": (50, heading_pat),
+        "indent": (60, ur"^[ \t]+"),
+        "macro":(70, ur"^<<\w+\s*$"),
+        "quote": (80, quote_pat),
+        "rule": (90, ur"^\s*---+\s*$"),
+        "syntax": (100, ur"^\{\{\{\#![\w+#.-]+\s*$"),
+        "table": (110, ur"^\|"),
+    }
+    image_pat = (ur"\{\{(?P<image_target>([^|}]|}[^|}])*)"
+                 ur"(\|(?P<image_text>([^}]|}[^}])*))?}}")
+    smilies = {
+        r':)': "smile.png",
+        r':(': "frown.png",
+        r':P': "tongue.png",
+        r':D': "grin.png",
+        r';)': "wink.png",
+    }
+    punct = {
+        r'...': "&hellip;",
+        r'--': "&ndash;",
+        r'---': "&mdash;",
+        r'~': "&nbsp;",
+        r'\~': "~",
+        r'~~': "&sim;",
+        r'(C)': "&copy;",
+        r'-->': "&rarr;",
+        r'<--': "&larr;",
+        r'(R)': "&reg;",
+        r'(TM)': "&trade;",
+        r'%%': "&permil;",
+        r'``': "&ldquo;",
+        r"''": "&rdquo;",
+        r",,": "&bdquo;",
+    }
+    markup = {
+        # "name": (priority, ur"pattern"),
+        "bold": (10, ur"[*][*]"),
+        "code": (20, ur"[{][{][{](?P<code_text>([^}]|[^}][}]|[^}][}][}])"
+                ur"*[}]*)[}][}][}]"),
+        "free_link": (30, ur"""[a-zA-Z]+://\S+[^\s.,:;!?()'"=+<>-]"""),
+        "italic": (40 , ur"//"),
+        "link": (50, ur"\[\[(?P<link_target>([^|\]]|\][^|\]])+)"
+                ur"(\|(?P<link_text>([^\]]|\][^\]])+))?\]\]"),
+        "image": (60, image_pat),
+        "linebreak": (70, ur"\\\\"),
+        "macro": (80, ur"[<][<](?P<macro_name>\w+)\s+"
+                 ur"(?P<macro_text>([^>]|[^>][>])+)[>][>]"),
+        "mail": (90, ur"""(mailto:)?\S+@\S+(\.[^\s.,:;!?()'"/=+<>-]+)+"""),
+        "math": (100, ur"\$\$(?P<math_text>[^$]+)\$\$"),
+        "mono": (110, ur"##"),
+        "newline": (120, ur"\n"),
+        "punct": (130, ur'(^|\b|(?<=\s))(%s)((?=[\s.,:;!?)/&=+"\'—-])|\b|$)' %
+                  ur"|".join(re.escape(k) for k in punct)),
+        "table": (140, ur"=?\|=?"),
+        "text": (150, ur".+?"),
+    }
+
+
+    def __init__(self, lines, wiki_link, wiki_image,
+                 wiki_syntax=None, wiki_math=None, smilies=None):
+        self.wiki_link = wiki_link
+        self.wiki_image = wiki_image
+        self.wiki_syntax = wiki_syntax
+        self.wiki_math = wiki_math
+        self.enumerated_lines = enumerate(lines)
+        if smilies is not None:
+            self.smilies = smilies
+        self.compile_patterns()
+        self.headings = {}
+        self.stack = []
+        self.line_no = 0
+
+    def compile_patterns(self):
+        self.quote_re = re.compile(self.quote_pat, re.U)
+        self.heading_re = re.compile(self.heading_pat, re.U)
+        self.bullets_re = re.compile(self.bullets_pat, re.U)
+        patterns = ((k, p) for (k, (x, p)) in
+                    sorted(self.block.iteritems(), key=lambda x: x[1][0]))
+        self.block_re = re.compile(ur"|".join("(?P<%s>%s)" % pat
+                                   for pat in patterns), re.U)
+        self.code_close_re = re.compile(ur"^\}\}\}\s*$", re.U)
+        self.macro_close_re = re.compile(ur"^>>\s*$", re.U)
+        self.conflict_close_re = re.compile(ur"^>>>>>>> other\s*$", re.U)
+        self.conflict_sep_re = re.compile(ur"^=======\s*$", re.U)
+        self.image_re = re.compile(self.image_pat, re.U)
+        smileys = ur"|".join(re.escape(k) for k in self.smilies)
+        smiley_pat = (ur"(^|\b|(?<=\s))(?P<smiley_face>%s)"
+                      ur"((?=[\s.,:;!?)/&=+-])|$)" % smileys)
+        self.markup['smiley'] = (125, smiley_pat)
+        patterns = ((k, p) for (k, (x, p)) in
+                    sorted(self.markup.iteritems(), key=lambda x: x[1][0]))
+        self.markup_re = re.compile(ur"|".join("(?P<%s>%s)" % pat
+                                    for pat in patterns), re.U)
+
+    def __iter__(self):
+        return self.parse()
+
+    @classmethod
+    def extract_links(cls, text):
+        links = []
+        def link(addr, label=None, class_=None, image=None, alt=None, lineno=0):
+            addr = addr.strip()
+            if external_link(addr):
+                return u''
+            if '#' in addr:
+                addr, chunk = addr.split('#', 1)
+            if addr == u'':
+                return u''
+            links.append((addr, label))
+            return u''
+        lines = text.split('\n')
+        for part in cls(lines, link, link):
+            for ret in links:
+                yield ret
+            links[:] = []
+
+    def parse(self):
+        """Parse a list of lines of wiki markup, yielding HTML for it."""
+
+        self.headings = {}
+        self.stack = []
+        self.line_no = 0
+
+        def key(enumerated_line):
+            line_no, line = enumerated_line
+            match = self.block_re.match(line)
+            if match:
+                return match.lastgroup
+            return "paragraph"
+
+        for kind, block in itertools.groupby(self.enumerated_lines, key):
+            func = getattr(self, "_block_%s" % kind)
+            for part in func(block):
+                yield part
+
+    def parse_line(self, line):
+        """
+        Find all the line-level markup and return HTML for it.
+
+        """
+
+        for match in self.markup_re.finditer(line):
+            func = getattr(self, "_line_%s" % match.lastgroup)
+            yield func(match.groupdict())
+
+    def pop_to(self, stop):
+        """
+            Pop from the stack until the specified tag is encoutered.
+            Return string containing closing tags of everything popped.
+        """
+        tags = []
+        tag = None
+        try:
+            while tag != stop:
+                tag = self.stack.pop()
+                tags.append(tag)
+        except IndexError:
+            pass
+        return u"".join(u"</%s>" % tag for tag in tags)
+
+    def lines_until(self, close_re):
+        """Get lines from input until the closing markup is encountered."""
+
+        self.line_no, line = self.enumerated_lines.next()
+        while not close_re.match(line):
+            yield line.rstrip()
+            line_no, line = self.enumerated_lines.next()
+
+# methods for the markup inside lines:
+
+    def _line_table(self, groups):
+        return groups["table"]
+
+    def _line_linebreak(self, groups):
+        return u'<br>'
+
+    def _line_smiley(self, groups):
+        smiley = groups["smiley_face"]
+        try:
+            url = self.smilies[smiley]
+        except KeyError:
+            url = ''
+        return self.wiki_image(url, smiley, class_="smiley")
+
+    def _line_bold(self, groups):
+        if 'b' in self.stack:
+            return self.pop_to('b')
+        else:
+            self.stack.append('b')
+            return u"<b>"
+
+    def _line_italic(self, groups):
+        if 'i' in self.stack:
+            return self.pop_to('i')
+        else:
+            self.stack.append('i')
+            return u"<i>"
+
+    def _line_mono(self, groups):
+        if 'tt' in self.stack:
+            return self.pop_to('tt')
+        else:
+            self.stack.append('tt')
+            return u"<tt>"
+
+    def _line_punct(self, groups):
+        text = groups["punct"]
+        return self.punct.get(text, text)
+
+    def _line_newline(self, groups):
+        return "\n"
+
+    def _line_text(self, groups):
+        return werkzeug.escape(groups["text"])
+
+    def _line_math(self, groups):
+        if self.wiki_math:
+            return self.wiki_math(groups["math_text"])
+        else:
+            return "<var>%s</var>" % werkzeug.escape(groups["math_text"])
+
+    def _line_code(self, groups):
+        return u'<code>%s</code>' % werkzeug.escape(groups["code_text"])
+
+    def _line_free_link(self, groups):
+        groups['link_target'] = groups['free_link']
+        return self._line_link(groups)
+
+    def _line_mail(self, groups):
+        addr = groups['mail']
+        groups['link_text'] = addr
+        if not addr.startswith(u'mailto:'):
+            addr = u'mailto:%s' % addr
+        groups['link_target'] = addr
+        return self._line_link(groups)
+
+    def _line_link(self, groups):
+        target = groups['link_target']
+        text = groups.get('link_text')
+        if not text:
+            text = target
+            if '#' in text:
+                text, chunk = text.split('#', 1)
+        match = self.image_re.match(text)
+        if match:
+            image = self._line_image(match.groupdict())
+            return self.wiki_link(target, text, image=image)
+        return self.wiki_link(target, text)
+
+    def _line_image(self, groups):
+        target = groups['image_target']
+        alt = groups.get('image_text')
+        if alt is None:
+            alt = target
+        return self.wiki_image(target, alt)
+
+    def _line_macro(self, groups):
+        name = groups['macro_name']
+        text = groups['macro_text'].strip()
+        return u'<span class="%s">%s</span>' % (
+            werkzeug.escape(name, quote=True),
+            werkzeug.escape(text))
+
+# methods for the block (multiline) markup:
+
+    def _block_code(self, block):
+        for self.line_no, part in block:
+            inside = u"\n".join(self.lines_until(self.code_close_re))
+            yield werkzeug.html.pre(werkzeug.html(inside), class_="code",
+                                    id="line_%d" % self.line_no)
+
+    def _block_syntax(self, block):
+        for self.line_no, part in block:
+            syntax = part.lstrip('{#!').strip()
+            inside = u"\n".join(self.lines_until(self.code_close_re))
+            if self.wiki_syntax:
+                return self.wiki_syntax(inside, syntax=syntax,
+                                        line_no=self.line_no)
+            else:
+                return [werkzeug.html.div(werkzeug.html.pre(
+                    werkzeug.html(inside), id="line_%d" % self.line_no),
+                    class_="highlight")]
+
+    def _block_macro(self, block):
+        for self.line_no, part in block:
+            name = part.lstrip('<').strip()
+            inside = u"\n".join(self.lines_until(self.macro_close_re))
+            yield u'<div class="%s">%s</div>' % (
+                werkzeug.escape(name, quote=True),
+                werkzeug.escape(inside))
+
+    def _block_paragraph(self, block):
+        parts = []
+        first_line = None
+        for self.line_no, part in block:
+            if first_line is None:
+                first_line = self.line_no
+            parts.append(part)
+        text = u"".join(self.parse_line(u"".join(parts)))
+        yield werkzeug.html.p(text, self.pop_to(""), id="line_%d" % first_line)
+
+    def _block_indent(self, block):
+        parts = []
+        first_line = None
+        for self.line_no, part in block:
+            if first_line is None:
+                first_line = self.line_no
+            parts.append(part.rstrip())
+        text = u"\n".join(parts)
+        yield werkzeug.html.pre(werkzeug.html(text), id="line_%d" % first_line)
+
+    def _block_table(self, block):
+        first_line = None
+        in_head = False
+        for self.line_no, line in block:
+            if first_line is None:
+                first_line = self.line_no
+                yield u'<table id="line_%d">' % first_line
+            table_row = line.strip()
+            is_header = table_row.startswith('|=') and table_row.endswith('=|')
+            if not in_head and is_header:
+                in_head = True
+                yield '<thead>'
+            elif in_head and not is_header:
+                in_head = False
+                yield '</thead>'
+            yield '<tr>'
+            in_cell = False
+            in_th = False
+
+            for part in self.parse_line(table_row):
+                if part in ('=|', '|', '=|=', '|='):
+                    if in_cell:
+                        if in_th:
+                            yield '</th>'
+                        else:
+                            yield '</td>'
+                        in_cell = False
+                    if part in ('=|=', '|='):
+                        in_th = True
+                    else:
+                        in_th = False
+                else:
+                    if not in_cell:
+                        if in_th:
+                            yield '<th>'
+                        else:
+                            yield '<td>'
+                        in_cell = True
+                    yield part
+            if in_cell:
+                if in_th:
+                    yield '</th>'
+                else:
+                    yield '</td>'
+            yield '</tr>'
+        yield u'</table>'
+
+    def _block_empty(self, block):
+        yield u''
+
+    def _block_rule(self, block):
+        for self.line_no, line in block:
+            yield werkzeug.html.hr()
+
+    def _block_heading(self, block):
+        for self.line_no, line in block:
+            level = min(len(self.heading_re.match(line).group(0).strip()), 5)
+            self.headings[level-1] = self.headings.get(level-1, 0)+1
+            label = u"-".join(str(self.headings.get(i, 0))
+                              for i in range(level))
+            yield werkzeug.html.a(name="head-%s" % label)
+            yield u'<h%d id="line_%d">%s</h%d>' % (level, self.line_no,
+                werkzeug.escape(line.strip("= \t\n\r\v")), level)
+
+    def _block_bullets(self, block):
+        level = 0
+        in_ul = False
+        for self.line_no, line in block:
+            nest = len(self.bullets_re.match(line).group(0).strip())
+            while nest > level:
+                if in_ul:
+                    yield '<li>'
+                yield '<ul id="line_%d">' % self.line_no
+                in_ul = True
+                level += 1
+            while nest < level:
+                yield '</li></ul>'
+                in_ul = False
+                level -= 1
+            if nest == level and not in_ul:
+                yield '</li>'
+            content = line.lstrip().lstrip('*').strip()
+            yield '<li>%s%s' % (u"".join(self.parse_line(content)),
+                                self.pop_to(""))
+            in_ul = False
+        yield '</li></ul>'*level
+
+    def _block_quote(self, block):
+        level = 0
+        in_p = False
+        for self.line_no, line in block:
+            nest = len(self.quote_re.match(line).group(0).strip())
+            if nest == level:
+                yield u'\n'
+            while nest > level:
+                if in_p:
+                    yield '%s</p>' % self.pop_to("")
+                    in_p = False
+                yield '<blockquote>'
+                level += 1
+            while nest < level:
+                if in_p:
+                    yield '%s</p>' % self.pop_to("")
+                    in_p = False
+                yield '</blockquote>'
+                level -= 1
+            content = line.lstrip().lstrip('>').strip()
+            if not in_p:
+                yield '<p id="line_%d">' % self.line_no
+                in_p = True
+            yield u"".join(self.parse_line(content))
+        if in_p:
+            yield '%s</p>' % self.pop_to("")
+        yield '</blockquote>'*level
+
+    def _block_conflict(self, block):
+        for self.line_no, part in block:
+            yield u'<div class="conflict">'
+            local = u"\n".join(self.lines_until(self.conflict_sep_re))
+            yield werkzeug.html.pre(werkzeug.html(local),
+                                    class_="local",
+                                    id="line_%d" % self.line_no)
+            other = u"\n".join(self.lines_until(self.conflict_close_re))
+            yield werkzeug.html.pre(werkzeug.html(other),
+                                    class_="other",
+                                    id="line_%d" % self.line_no)
+            yield u'</div>'
+
+
+class WikiWikiParser(WikiParser):
+    """A version of WikiParser that recognizes WikiWord links."""
+
+    markup = dict(WikiParser.markup)
+    camel_link = ur"\w+[%s]\w+" % re.escape(
+        u''.join(unichr(i) for i in xrange(sys.maxunicode)
+        if unicodedata.category(unichr(i))=='Lu'))
+    markup["camel_link"] = (105, camel_link)
+    markup["camel_nolink"] = (106, ur"[!~](?P<camel_text>%s)" % camel_link)
+
+    def _line_camel_link(self, groups):
+        groups['link_target'] = groups['camel_link']
+        return self._line_link(groups)
+
+    def _line_camel_nolink(self, groups):
+        return werkzeug.escape(groups["camel_text"])
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/search.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import sqlite3
+import re
+import os
+import thread
+
+import error
+
+
+class WikiSearch(object):
+    """
+    Responsible for indexing words and links, for fast searching and
+    backlinks. Uses a cache directory to store the index files.
+    """
+
+    word_pattern = re.compile(ur"""\w[-~&\w]+\w""", re.UNICODE)
+    jword_pattern = re.compile(
+ur"""[ヲ-゚]+|[ぁ-ん~ー]+|[ァ-ヶ~ー]+|[0-9A-Za-z]+|"""
+ur"""[0-9A-Za-zΑ-Ωα-ωА-я]+|"""
+ur"""[^- !"#$%&'()*+,./:;<=>?@\[\\\]^_`{|}"""
+ur"""‾。「」、・ 、。,.・:;?!゛゜´`¨"""
+ur"""^ ̄_/〜‖|…‥‘’“”"""
+ur"""()〔〕[]{}〈〉《》「」『』【】+−±×÷"""
+ur"""=≠<>≦≧∞∴♂♀°′″℃¥$¢£"""
+ur"""%#&*@§☆★○●◎◇◆□■△▲▽▼※〒"""
+ur"""→←↑↓〓∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∠∃∠⊥"""
+ur"""⌒∂∇≡≒≪≫√∽∝∵∫∬ʼn♯♭♪†‡¶◾"""
+ur"""─│┌┐┘└├┬┤┴┼"""
+ur"""━┃┏┓┛┗┣┫┻╋"""
+ur"""┠┯┨┷┿┝┰┥┸╂"""
+ur"""ヲ-゚ぁ-ん~ーァ-ヶ"""
+ur"""0-9A-Za-z0-9A-Za-zΑ-Ωα-ωА-я]+""", re.UNICODE)
+
+    def __init__(self, cache_path, lang, storage):
+        self._con = {}
+        self.path = cache_path
+        self.storage = storage
+        self.lang = lang
+        if lang == "ja":
+            self.split_text = self.split_japanese_text
+        self.filename = os.path.join(cache_path, 'index.sqlite3')
+        if not os.path.isdir(self.path):
+            self.empty = True
+            os.makedirs(self.path)
+        elif not os.path.exists(self.filename):
+            self.empty = True
+        else:
+            self.empty = False
+        self.init_db(self.con)
+
+    def init_db(self, con):
+        con.execute('CREATE TABLE IF NOT EXISTS titles '
+                '(id INTEGER PRIMARY KEY, title VARCHAR);')
+        con.execute('CREATE TABLE IF NOT EXISTS words '
+                '(word VARCHAR, page INTEGER, count INTEGER);')
+        con.execute('CREATE INDEX IF NOT EXISTS index1 '
+                         'ON words (page);')
+        con.execute('CREATE INDEX IF NOT EXISTS index2 '
+                         'ON words (word);')
+        con.execute('CREATE TABLE IF NOT EXISTS links '
+                '(src INTEGER, target INTEGER, label VARCHAR, number INTEGER);')
+        con.commit()
+
+    @property
+    def con(self):
+        """Keep one connection per thread."""
+
+        thread_id = thread.get_ident()
+        try:
+            return self._con[thread_id]
+        except KeyError:
+            connection = sqlite3.connect(self.filename)
+            connection.isolation_level = None
+            self._con[thread_id] = connection
+            return connection
+
+    def split_text(self, text):
+        """Splits text into words"""
+
+        for match in self.word_pattern.finditer(text):
+            word = match.group(0)
+            yield word.lower()
+
+    def split_japanese_text(self, text):
+        """Splits text into words, including rules for Japanese"""
+
+        for match in self.word_pattern.finditer(text):
+            word = match.group(0)
+            got_japanese = False
+            for m in self.jword_pattern.finditer(word):
+                w = m.group(0)
+                got_japanese = True
+                yield w.lower()
+            if not got_japanese:
+                yield word.lower()
+
+    def count_words(self, words):
+        count = {}
+        for word in words:
+            count[word] = count.get(word, 0)+1
+        return count
+
+    def title_id(self, title, con):
+        c = con.execute('SELECT id FROM titles WHERE title=?;', (title,))
+        idents = c.fetchone()
+        if idents is None:
+            con.execute('INSERT INTO titles (title) VALUES (?);', (title,))
+            c = con.execute('SELECT LAST_INSERT_ROWID();')
+            idents = c.fetchone()
+        return idents[0]
+
+    def update_words(self, title, text, cursor):
+        title_id = self.title_id(title, cursor)
+        cursor.execute('DELETE FROM words WHERE page=?;', (title_id,))
+        if not text:
+            return
+        words = self.count_words(self.split_text(text))
+        title_words = self.count_words(self.split_text(title))
+        for word, count in title_words.iteritems():
+            words[word] = words.get(word, 0) + count
+        for word, count in words.iteritems():
+            cursor.execute('INSERT INTO words VALUES (?, ?, ?);',
+                             (word, title_id, count))
+
+    def update_links(self, title, links_and_labels, cursor):
+        title_id = self.title_id(title, cursor)
+        cursor.execute('DELETE FROM links WHERE src=?;', (title_id,))
+        for number, (link, label) in enumerate(links_and_labels):
+            cursor.execute('INSERT INTO links VALUES (?, ?, ?, ?);',
+                             (title_id, link, label, number))
+
+    def orphaned_pages(self):
+        """Gives all pages with no links to them."""
+
+        con = self.con
+        try:
+            sql = ('SELECT title FROM titles '
+                   'WHERE NOT EXISTS '
+                   '(SELECT * FROM links WHERE target=title) '
+                   'ORDER BY title;')
+            for (title,) in con.execute(sql):
+                yield unicode(title)
+        finally:
+            con.commit()
+
+    def wanted_pages(self):
+        """Gives all pages that are linked to, but don't exist, together with
+        the number of links."""
+
+        con = self.con
+        try:
+            sql = ('SELECT COUNT(*), target FROM links '
+                   'WHERE NOT EXISTS '
+                   '(SELECT * FROM titles WHERE target=title) '
+                   'GROUP BY target ORDER BY -COUNT(*);')
+            for (refs, db_title,) in con.execute(sql):
+                title = unicode(db_title)
+                yield refs, title
+        finally:
+            con.commit()
+
+
+    def page_backlinks(self, title):
+        """Gives a list of pages linking to specified page."""
+
+        con = self.con # sqlite3.connect(self.filename)
+        try:
+            sql = ('SELECT DISTINCT(titles.title) '
+                   'FROM links, titles '
+                   'WHERE links.target=? AND titles.id=links.src '
+                   'ORDER BY titles.title;')
+            for (backlink,) in con.execute(sql, (title,)):
+                yield unicode(backlink)
+        finally:
+            con.commit()
+
+    def page_links(self, title):
+        """Gives a list of links on specified page."""
+
+        con = self.con # sqlite3.connect(self.filename)
+        try:
+            title_id = self.title_id(title, con)
+            sql = 'SELECT target FROM links WHERE src=? ORDER BY number;'
+            for (link,) in con.execute(sql, (title_id,)):
+                yield unicode(link)
+        finally:
+            con.commit()
+
+    def page_links_and_labels (self, title):
+        con = self.con # sqlite3.connect(self.filename)
+        try:
+            title_id = self.title_id(title, con)
+            sql = 'SELECT target, label FROM links WHERE src=? ORDER BY number;'
+            for link, label in con.execute(sql, (title_id,)):
+                yield unicode(link), unicode(label)
+        finally:
+            con.commit()
+
+    def find(self, words):
+        """Iterator of all pages containing the words, and their scores."""
+
+        con = self.con
+        try:
+            ranks = []
+            for word in words:
+                # Calculate popularity of each word.
+                sql = 'SELECT SUM(words.count) FROM words WHERE word LIKE ?;'
+                rank = con.execute(sql, ('%%%s%%' % word,)).fetchone()[0]
+                # If any rank is 0, there will be no results anyways
+                if not rank:
+                    return
+                ranks.append((rank, word))
+            ranks.sort()
+            # Start with the least popular word. Get all pages that contain it.
+            first_rank, first = ranks[0]
+            rest = ranks[1:]
+            sql = ('SELECT words.page, titles.title, SUM(words.count) '
+                   'FROM words, titles '
+                   'WHERE word LIKE ? AND titles.id=words.page '
+                   'GROUP BY words.page;')
+            first_counts = con.execute(sql, ('%%%s%%' % first,))
+            # Check for the rest of words
+            for title_id, title, first_count in first_counts:
+                # Score for the first word
+                score = float(first_count)/first_rank
+                for rank, word in rest:
+                    sql = ('SELECT SUM(count) FROM words '
+                           'WHERE page=? AND word LIKE ?;')
+                    count = con.execute(sql,
+                        (title_id, '%%%s%%' % word)).fetchone()[0]
+                    if not count:
+                        # If page misses any of the words, its score is 0
+                        score = 0
+                        break
+                    score += float(count)/rank
+                if score > 0:
+                    yield int(100*score), unicode(title)
+        finally:
+            con.commit()
+
+    def reindex_page(self, page, title, cursor, text=None):
+        """Updates the content of the database, needs locks around."""
+
+        if text is None:
+            get_text = getattr(page, 'plain_text', lambda: u'')
+            try:
+                text = get_text()
+            except error.NotFoundErr:
+                text = None
+                title_id = self.title_id(title, cursor)
+                if not list(self.page_backlinks(title)):
+                    cursor.execute("DELETE FROM titles WHERE id=?;", (title_id,))
+        extract_links = getattr(page, 'extract_links', None)
+        if extract_links and text:
+            links = extract_links(text)
+        else:
+            links = []
+        self.update_links(title, links, cursor=cursor)
+        self.update_words(title, text or u'', cursor=cursor)
+
+    def update_page(self, page, title, data=None, text=None):
+        """Updates the index with new page content, for a single page."""
+
+        if text is None and data is not None:
+            text = unicode(data, self.storage.charset, 'replace')
+        cursor = self.con.cursor()
+        cursor.execute('BEGIN IMMEDIATE TRANSACTION;')
+        try:
+            self.set_last_revision(self.storage.repo_revision())
+            self.reindex_page(page, title, cursor, text)
+            self.con.commit()
+        except:
+            self.con.rollback()
+            raise
+
+    def reindex(self, wiki, pages):
+        """Updates specified pages in bulk."""
+
+        cursor = self.con.cursor()
+        cursor.execute('BEGIN IMMEDIATE TRANSACTION;')
+        try:
+            for title in pages:
+                page = wiki.get_page(None, title)
+                self.reindex_page(page, title, cursor)
+            self.con.commit()
+            self.empty = False
+        except:
+            self.con.rollback()
+            raise
+
+    def set_last_revision(self, rev):
+        """Store the last indexed repository revision."""
+
+        # We use % here because the sqlite3's substitiution doesn't work
+        # We store revision 0 as 1, 1 as 2, etc. because 0 means "no revision"
+        self.con.execute('PRAGMA USER_VERSION=%d;' % (int(rev+1),))
+
+    def get_last_revision(self):
+        """Retrieve the last indexed repository revision."""
+
+        con = self.con
+        c = con.execute('PRAGMA USER_VERSION;')
+        rev = c.fetchone()[0]
+        # -1 means "no revision", 1 means revision 0, 2 means revision 1, etc.
+        return rev-1
+
+    def update(self, wiki):
+        """Reindex al pages that changed since last indexing."""
+
+        last_rev = self.get_last_revision()
+        if last_rev == -1:
+            changed = self.storage.all_pages()
+        else:
+            changed = self.storage.changed_since(last_rev)
+        self.reindex(wiki, changed)
+        rev = self.storage.repo_revision()
+        self.set_last_revision(rev)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/storage.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,548 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+import tempfile
+import thread
+import re
+import werkzeug
+import datetime
+
+# Note: we have to set these before importing Mercurial
+os.environ['HGENCODING'] = 'utf-8'
+os.environ['HGMERGE'] = "internal:merge"
+
+import mercurial.hg
+import mercurial.ui
+import mercurial.revlog
+import mercurial.util
+import mercurial.hgweb
+import mercurial.commands
+
+
+import error
+
+def locked_repo(func):
+    """A decorator for locking the repository when calling a method."""
+
+    def new_func(self, *args, **kwargs):
+        """Wrap the original function in locks."""
+
+        wlock = self.repo.wlock()
+        lock = self.repo.lock()
+        try:
+            func(self, *args, **kwargs)
+        finally:
+            lock.release()
+            wlock.release()
+
+    return new_func
+
+def _find_repo_path(path):
+    """Go up the directory tree looking for a repository."""
+
+    while not os.path.isdir(os.path.join(path, ".hg")):
+        old_path, path = path, os.path.dirname(path)
+        if path == old_path:
+            return None
+    return path
+
+def _get_ui():
+    try:
+        ui = mercurial.ui.ui(report_untrusted=False,
+                                  interactive=False, quiet=True)
+    except TypeError:
+        # Mercurial 1.3 changed the way we setup the ui object.
+        ui = mercurial.ui.ui()
+        ui.quiet = True
+        ui._report_untrusted = False
+        ui.setconfig('ui', 'interactive', False)
+    return ui
+
+
+class WikiStorage(object):
+    """
+    Provides means of storing wiki pages and keeping track of their
+    change history, using Mercurial repository as the storage method.
+    """
+
+    def __init__(self, path, charset=None, _=lambda x:x):
+        """
+        Takes the path to the directory where the pages are to be kept.
+        If the directory doen't exist, it will be created. If it's inside
+        a Mercurial repository, that repository will be used, otherwise
+        a new repository will be created in it.
+        """
+
+        self._ = _
+        self.charset = charset or 'utf-8'
+        self.path = os.path.abspath(path)
+        if not os.path.exists(self.path):
+            os.makedirs(self.path)
+        self.ui = _get_ui()
+        self.repo_path = _find_repo_path(self.path)
+        if self.repo_path is None:
+            # Create the repository if needed.
+            self.repo_path = self.path
+            mercurial.hg.repository(self.ui, self.repo_path, create=True)
+        self.repo_prefix = self.path[len(self.repo_path):].strip('/')
+        self._repos = {}
+
+    def reopen(self):
+        """Close and reopen the repo, to make sure we are up to date."""
+
+        self._repos = {}
+
+    @property
+    def repo(self):
+        """Keep one open repository per thread."""
+
+        thread_id = thread.get_ident()
+        try:
+            return self._repos[thread_id]
+        except KeyError:
+            repo = mercurial.hg.repository(self.ui, self.repo_path)
+            self._repos[thread_id] = repo
+            return repo
+
+
+    def _check_path(self, path):
+        """
+        Ensure that the path is within allowed bounds.
+        """
+
+        _ = self._
+        abspath = os.path.abspath(path)
+        if os.path.islink(path) or os.path.isdir(path):
+            raise error.ForbiddenErr(
+                _(u"Can't use symbolic links or directories as pages"))
+        if not abspath.startswith(self.path):
+            raise error.ForbiddenErr(
+                _(u"Can't read or write outside of the pages repository"))
+
+    def _file_path(self, title):
+        return os.path.join(self.repo_path, self._title_to_file(title))
+
+    def _title_to_file(self, title):
+        title = unicode(title).strip()
+        filename = werkzeug.url_quote(title, safe='')
+        # Escape special windows filenames and dot files
+        _windows_device_files = ('CON', 'AUX', 'COM1', 'COM2', 'COM3',
+                                 'COM4', 'LPT1', 'LPT2', 'LPT3', 'PRN',
+                                 'NUL')
+        if (filename.split('.')[0].upper() in _windows_device_files or
+            filename.startswith('_') or filename.startswith('.')):
+            filename = '_' + filename
+        return os.path.join(self.repo_prefix, filename)
+
+    def _file_to_title(self, filepath):
+        if not filepath.startswith(self.repo_prefix):
+            raise error.ForbiddenErr(
+                _(u"Can't read or write outside of the pages repository"))
+        name = filepath[len(self.repo_prefix):].strip('/')
+        # Unescape special windows filenames and dot files
+        if name.startswith('_') and len(name)>1:
+            name = name[1:]
+        return werkzeug.url_unquote(name)
+
+    def __contains__(self, title):
+        if title:
+            file_path = self._file_path(title)
+            return os.path.isfile(file_path) and not os.path.islink(file_path)
+
+    def __iter__(self):
+        return self.all_pages()
+
+    def merge_changes(self, changectx, repo_file, text, user, parent):
+        """Commits and merges conflicting changes in the repository."""
+
+        _ = self._
+        tip_node = changectx.node()
+        filectx = changectx[repo_file].filectx(parent)
+        parent_node = filectx.changectx().node()
+
+        self.repo.dirstate.setparents(parent_node)
+        node = self._commit([repo_file], text, user)
+
+        partial = lambda filename: repo_file == filename
+        try:
+            mercurial.merge.update(self.repo, tip_node, True, True, partial)
+            msg = _(u'merge of edit conflict')
+        except mercurial.util.Abort:
+            msg = _(u'failed merge of edit conflict')
+        self.repo.dirstate.setparents(tip_node, node)
+        # Mercurial 1.1 and later need updating the merge state
+        try:
+            mercurial.merge.mergestate(self.repo).mark(repo_file, "r")
+        except (AttributeError, KeyError):
+            pass
+        return msg
+
+    @locked_repo
+    def save_file(self, title, file_name, author=u'', comment=u'', parent=None):
+        """Save an existing file as specified page."""
+
+        _ = self._
+        user = author.encode('utf-8') or _(u'anon').encode('utf-8')
+        text = comment.encode('utf-8') or _(u'comment').encode('utf-8')
+        repo_file = self._title_to_file(title)
+        file_path = self._file_path(title)
+        self._check_path(file_path)
+        try:
+            mercurial.util.rename(file_name, file_path)
+        except OSError, e:
+            if e.errno == 36: # File name too long
+                raise error.RequestURITooLarge()
+            else:
+                raise
+        changectx = self._changectx()
+        try:
+            # Mercurial 1.5 and earlier have .add() on the repo
+            add = self.repo.add
+        except AttributeError:
+            # Mercurial 1.6
+            add = self.repo[None].add
+        try:
+            filectx_tip = changectx[repo_file]
+            current_page_rev = filectx_tip.filerev()
+        except mercurial.revlog.LookupError:
+            add([repo_file])
+            current_page_rev = -1
+        if parent is not None and current_page_rev != parent:
+            msg = self.merge_changes(changectx, repo_file, text, user, parent)
+            user = '<wiki>'
+            text = msg.encode('utf-8')
+        self._commit([repo_file], text, user)
+
+    def _commit(self, files, text, user):
+        try:
+            return self.repo.commit(files=files, text=text, user=user,
+                                    force=True, empty_ok=True)
+        except TypeError:
+            # Mercurial 1.3 doesn't accept empty_ok or files parameter
+            match = mercurial.match.exact(self.repo_path, '', list(files))
+            return self.repo.commit(match=match, text=text, user=user,
+                                    force=True)
+
+    def save_data(self, title, data, author=u'', comment=u'', parent=None):
+        """Save data as specified page."""
+
+        try:
+            temp_path = tempfile.mkdtemp(dir=self.path)
+            file_path = os.path.join(temp_path, 'saved')
+            f = open(file_path, "wb")
+            f.write(data)
+            f.close()
+            self.save_file(title, file_path, author, comment, parent)
+        finally:
+            try:
+                os.unlink(file_path)
+            except OSError:
+                pass
+            try:
+                os.rmdir(temp_path)
+            except OSError:
+                pass
+
+    def save_text(self, title, text, author=u'', comment=u'', parent=None):
+        """Save text as specified page, encoded to charset."""
+
+        data = text.encode(self.charset)
+        self.save_data(title, data, author, comment, parent)
+
+    def page_text(self, title):
+        """Read unicode text of a page."""
+
+        data = self.open_page(title).read()
+        text = unicode(data, self.charset, 'replace')
+        return text
+
+    def page_lines(self, page):
+        for data in page.xreadlines():
+            yield unicode(data, self.charset, 'replace')
+
+    @locked_repo
+    def delete_page(self, title, author=u'', comment=u''):
+        user = author.encode('utf-8') or 'anon'
+        text = comment.encode('utf-8') or 'deleted'
+        repo_file = self._title_to_file(title)
+        file_path = self._file_path(title)
+        self._check_path(file_path)
+        try:
+            # Mercurial 1.5 and earlier have .remove() on the repo
+            remove = self.repo.remove
+        except AttributeError:
+            # Mercurial 1.6
+            remove = self.repo[None].remove
+        try:
+            os.unlink(file_path)
+        except OSError:
+            pass
+        remove([repo_file])
+        self._commit([repo_file], text, user)
+
+    def open_page(self, title):
+        """Open the page and return a file-like object with its contents."""
+
+        file_path = self._file_path(title)
+        self._check_path(file_path)
+        try:
+            return open(file_path, "rb")
+        except IOError:
+            raise error.NotFoundErr()
+
+    def page_file_meta(self, title):
+        """Get page's inode number, size and last modification time."""
+
+        try:
+            (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
+             st_atime, st_mtime, st_ctime) = os.stat(self._file_path(title))
+        except OSError:
+            return 0, 0, 0
+        return st_ino, st_size, st_mtime
+
+    def page_meta(self, title):
+        """Get page's revision, date, last editor and his edit comment."""
+
+        filectx_tip = self._find_filectx(title)
+        if filectx_tip is None:
+            raise error.NotFoundErr()
+            #return -1, None, u'', u''
+        rev = filectx_tip.filerev()
+        filectx = filectx_tip.filectx(rev)
+        date = datetime.datetime.fromtimestamp(filectx.date()[0])
+        author = unicode(filectx.user(), "utf-8",
+                         'replace').split('<')[0].strip()
+        comment = unicode(filectx.description(), "utf-8", 'replace')
+        return rev, date, author, comment
+
+    def repo_revision(self):
+        """Give the latest revision of the repository."""
+
+        return self._changectx().rev()
+
+
+    def _changectx(self):
+        """Get the changectx of the tip."""
+
+        try:
+            # This is for Mercurial 1.0
+            return self.repo.changectx()
+        except TypeError:
+            # Mercurial 1.3 (and possibly earlier) needs an argument
+            return self.repo.changectx('tip')
+
+    def _find_filectx(self, title):
+        """Find the last revision in which the file existed."""
+
+        repo_file = self._title_to_file(title)
+        stack = [self._changectx()]
+        while stack:
+            changectx = stack.pop()
+            if repo_file in changectx:
+                return changectx[repo_file]
+            if changectx.rev() == 0:
+                return None
+            for parent in changectx.parents():
+                if parent != changectx:
+                    stack.append(parent)
+        return None
+
+    def page_history(self, title):
+        """Iterate over the page's history."""
+
+        filectx_tip = self._find_filectx(title)
+        if filectx_tip is None:
+            return
+        maxrev = filectx_tip.filerev()
+        minrev = 0
+        for rev in range(maxrev, minrev-1, -1):
+            filectx = filectx_tip.filectx(rev)
+            date = datetime.datetime.fromtimestamp(filectx.date()[0])
+            author = unicode(filectx.user(), "utf-8",
+                             'replace').split('<')[0].strip()
+            comment = unicode(filectx.description(), "utf-8", 'replace')
+            yield rev, date, author, comment
+
+    def page_revision(self, title, rev):
+        """Get binary content of the specified revision of the page."""
+
+        filectx_tip = self._find_filectx(title)
+        if filectx_tip is None:
+            raise error.NotFoundErr()
+        try:
+            data = filectx_tip.filectx(rev).data()
+        except IndexError:
+            raise error.NotFoundErr()
+        return data
+
+    def revision_text(self, title, rev):
+        """Get unicode text of the specified revision of the page."""
+
+        data = self.page_revision(title, rev)
+        text = unicode(data, self.charset, 'replace')
+        return text
+
+    def history(self):
+        """Iterate over the history of entire wiki."""
+
+        changectx = self._changectx()
+        maxrev = changectx.rev()
+        minrev = 0
+        for wiki_rev in range(maxrev, minrev-1, -1):
+            change = self.repo.changectx(wiki_rev)
+            date = datetime.datetime.fromtimestamp(change.date()[0])
+            author = unicode(change.user(), "utf-8",
+                             'replace').split('<')[0].strip()
+            comment = unicode(change.description(), "utf-8", 'replace')
+            for repo_file in change.files():
+                if repo_file.startswith(self.repo_prefix):
+                    title = self._file_to_title(repo_file)
+                    try:
+                        rev = change[repo_file].filerev()
+                    except mercurial.revlog.LookupError:
+                        rev = -1
+                    yield title, rev, date, author, comment
+
+    def all_pages(self):
+        """Iterate over the titles of all pages in the wiki."""
+
+        for filename in os.listdir(self.path):
+            file_path = os.path.join(self.path, filename)
+            if (os.path.isfile(file_path)
+                and not os.path.islink(file_path)
+                and not filename.startswith('.')):
+                yield werkzeug.url_unquote(filename)
+
+    def changed_since(self, rev):
+        """Return all pages that changed since specified repository revision."""
+
+        try:
+            last = self.repo.lookup(int(rev))
+        except IndexError:
+            for page in self.all_pages():
+                yield page
+            return
+        current = self.repo.lookup('tip')
+        status = self.repo.status(current, last)
+        modified, added, removed, deleted, unknown, ignored, clean = status
+        for filename in modified+added+removed+deleted:
+            if filename.startswith(self.repo_prefix):
+                yield self._file_to_title(filename)
+
+
+class WikiSubdirectoryStorage(WikiStorage):
+    """
+    A version of WikiStorage that keeps the subpages in real subdirectories in
+    the filesystem. Indexes supported.
+
+    """
+
+    periods_re = re.compile(r'^[.]|(?<=/)[.]')
+    slashes_re = re.compile(r'^[/]|(?<=/)[/]')
+
+    # TODO: make them configurable
+    index = "Index"
+
+    def _title_to_file(self, title):
+        """
+        Modified escaping allowing (some) slashes and spaces.
+        If the entry is a directory, use an index file.
+        """
+
+        title = unicode(title).strip()
+        escaped = werkzeug.url_quote(title, safe='/ ')
+        escaped = self.periods_re.sub('%2E', escaped)
+        escaped = self.slashes_re.sub('%2F', escaped)
+        path = os.path.join(self.repo_prefix, escaped)
+        if os.path.isdir(os.path.join(self.repo_path, path)):
+            path = os.path.join(path, self.index)
+        return path
+
+    def _file_to_title(self, filepath):
+        """If the path points to an index file, use the directory."""
+
+        if os.path.basename(filepath) == self.index:
+            filepath = os.path.dirname(filepath)
+        return super(WikiSubdirectoryStorage, self)._file_to_title(filepath)
+
+    def turn_into_subdirectory(self, path):
+        """Turn a single-file page into an index page inside a subdirectory."""
+
+        _ = self._
+        self._check_path(path)
+        dir_path = os.path.dirname(path)
+        if not os.path.isdir(dir_path):
+            self.turn_into_subdirectory(dir_path)
+        if not os.path.exists(path):
+            os.mkdir(path)
+            return
+        try:
+            temp_dir = tempfile.mkdtemp(dir=self.path)
+            temp_path = os.path.join(temp_dir, 'saved')
+            mercurial.commands.rename(self.ui, self.repo, path, temp_path)
+            os.makedirs(path)
+            index_path = os.path.join(path, self.index)
+            mercurial.commands.rename(self.ui, self.repo, temp_path, index_path)
+        finally:
+            try:
+                os.rmdir(temp_dir)
+            except OSError:
+                pass
+        self._commit([index_path, path], _(u"made subdirectory page"), "<wiki>")
+
+    @locked_repo
+    def save_file(self, title, file_name, author=u'', comment=u'', parent=None):
+        """Save the file and make the subdirectories if needed."""
+
+        path = self._file_path(title)
+        dir_path = os.path.dirname(path)
+        if not os.path.isdir(dir_path):
+            self.turn_into_subdirectory(dir_path)
+        try:
+            os.makedirs(os.path.join(self.repo_path, dir_path))
+        except OSError, e:
+            if e.errno != 17:
+                raise
+        super(WikiSubdirectoryStorage, self).save_file(title, file_name,
+                                                       author, comment, parent)
+
+    @locked_repo
+    def delete_page(self, title, author=u'', comment=u''):
+        """
+        Remove empty directories after deleting a page.
+
+        Note that Mercurial doesn't track directories, so we don't have to
+        commit after removing empty directories.
+        """
+
+        super(WikiSubdirectoryStorage, self).delete_page(title, author, comment)
+        file_path = self._file_path(title)
+        self._check_path(file_path)
+        dir_path = os.path.dirname(file_path)
+        try:
+            os.removedirs(dir_path)
+        except OSError, e:
+            if e.errno != 39: # ignore "(39) Directory not empty" error on removing Index
+                raise
+
+    def all_pages(self):
+        """
+        Iterate over the titles of all pages in the wiki.
+        Include subdirectories but skip over index.
+        """
+
+        for (dirpath, dirnames, filenames) in os.walk(self.path):
+            path = dirpath[len(self.path)+1:]
+            for name in filenames:
+                if os.path.basename(name) == self.index:
+                    filename = os.path.join(path, os.path.dirname(name))
+                    yield werkzeug.url_unquote(filename)
+                else:
+                    filename = os.path.join(path, name)
+                    if (os.path.isfile(os.path.join(self.path, filename))
+                        and not filename.startswith('.')):
+                        yield werkzeug.url_unquote(filename)
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hatta/wiki.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,941 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import gettext
+import os
+import sys
+import re
+import tempfile
+import itertools
+
+import werkzeug
+import werkzeug.routing
+
+try:
+    import pygments
+except ImportError:
+    pygments = None
+
+import hatta
+import storage
+import search
+import page
+import parser
+import error
+import data
+
+import mercurial # import it after storage!
+
+class WikiResponse(werkzeug.BaseResponse, werkzeug.ETagResponseMixin,
+                   werkzeug.CommonResponseDescriptorsMixin):
+    """A typical HTTP response class made out of Werkzeug's mixins."""
+
+    def make_conditional(self, request):
+        ret = super(WikiResponse, self).make_conditional(request)
+        # Remove all headers if it's 304, according to
+        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
+        if self.status.startswith('304'):
+            self.response = []
+            try:
+                del self.content_type
+            except (AttributeError, KeyError, IndexError):
+                pass
+            try:
+                del self.content_length
+            except (AttributeError, KeyError, IndexError):
+                pass
+            try:
+                del self.headers['Content-length']
+            except (AttributeError, KeyError, IndexError):
+                pass
+            try:
+                del self.headers['Content-type']
+            except (AttributeError, KeyError, IndexError):
+                pass
+        return ret
+
+
+class WikiTempFile(object):
+    """Wrap a file for uploading content."""
+
+    def __init__(self, tmppath):
+        self.tmppath = tempfile.mkdtemp(dir=tmppath)
+        self.tmpname = os.path.join(self.tmppath, 'saved')
+        self.f = open(self.tmpname, "wb")
+
+    def read(self, *args, **kw):
+        return self.f.read(*args, **kw)
+
+    def readlines(self, *args, **kw):
+        return self.f.readlines(*args, **kw)
+
+    def write(self, *args, **kw):
+        return self.f.write(*args, **kw)
+
+    def seek(self, *args, **kw):
+        return self.f.seek(*args, **kw)
+
+    def truncate(self, *args, **kw):
+        return self.f.truncate(*args, **kw)
+
+    def close(self, *args, **kw):
+        ret = self.f.close(*args, **kw)
+        try:
+            os.unlink(self.tmpname)
+        except OSError:
+            pass
+        try:
+            os.rmdir(self.tmppath)
+        except OSError:
+            pass
+        return ret
+
+
+class WikiRequest(werkzeug.BaseRequest, werkzeug.ETagRequestMixin):
+    """
+    A Werkzeug's request with additional functions for handling file
+    uploads and wiki-specific link generation.
+    """
+
+    charset = 'utf-8'
+    encoding_errors = 'ignore'
+
+    def __init__(self, wiki, adapter, environ, **kw):
+        werkzeug.BaseRequest.__init__(self, environ, shallow=False, **kw)
+        self.wiki = wiki
+        self.adapter = adapter
+        self.tmpfiles = []
+        self.tmppath = wiki.path
+        # Whether to print the css for highlighting
+        self.print_highlight_styles = True
+
+    def get_url(self, title=None, view=None, method='GET',
+                external=False, **kw):
+        if view is None:
+            view = self.wiki.view
+        if title is not None:
+            kw['title'] = title.strip()
+        return self.adapter.build(view, kw, method=method,
+                                  force_external=external)
+
+    def get_download_url(self, title):
+        return self.get_url(title, view=self.wiki.download)
+
+    def get_author(self):
+        """Try to guess the author name. Use IP address as last resort."""
+
+        try:
+            cookie = werkzeug.url_unquote(self.cookies.get("author", ""))
+        except UnicodeError:
+            cookie = None
+        try:
+            auth = werkzeug.url_unquote(self.environ.get('REMOTE_USER', ""))
+        except UnicodeError:
+            auth = None
+        author = (self.form.get("author") or cookie or auth or self.remote_addr)
+        return author
+
+    def _get_file_stream(self, total_content_length=None, content_type=None,
+                         filename=None, content_length=None):
+        """Save all the POSTs to temporary files."""
+
+        temp_file = WikiTempFile(self.tmppath)
+        self.tmpfiles.append(temp_file)
+        return temp_file
+
+    def cleanup(self):
+        """Clean up the temporary files created by POSTs."""
+
+        for temp_file in self.tmpfiles:
+            temp_file.close()
+        self.tmpfiles = []
+
+
+class WikiTitleConverter(werkzeug.routing.PathConverter):
+    """Behaves like the path converter, but doesn't match the "+ pages"."""
+
+    def to_url(self, value):
+        return werkzeug.url_quote(value.strip(), self.map.charset, safe="/")
+
+    regex = '([^+%]|%[^2]|%2[^Bb]).*'
+
+
+class WikiAllConverter(werkzeug.routing.BaseConverter):
+    """Matches everything."""
+
+    regex = '.*'
+
+
+class URL(object):
+    """A decorator for marking methods as endpoints for URLs."""
+
+    urls = []
+
+    def __init__(self, url, methods=None):
+        """Create a decorator with specified parameters."""
+
+        self.url = url
+        self.methods = methods or ['GET', 'HEAD']
+
+    def __call__(self, func):
+        """The actual decorator only records the data."""
+
+        self.urls.append((func.__name__, self.url, self.methods))
+        return func
+
+    @classmethod
+    def rules(cls, app):
+        """Returns the routing rules, using app's bound methods."""
+
+        for name, url, methods in cls.urls:
+            func = getattr(app, name, None)
+            if not callable(func):
+                continue
+            yield werkzeug.routing.Rule(url, endpoint=func, methods=methods)
+
+
+class Wiki(object):
+    """
+    The main class of the wiki, handling initialization of the whole
+    application and most of the logic.
+    """
+    storage_class = storage.WikiStorage
+    index_class = search.WikiSearch
+    filename_map = page.filename_map
+    mime_map = page.mime_map
+    icon = data.icon
+    scripts = data.scripts
+    style = data.style
+
+    def __init__(self, config):
+        if config.get_bool('show_version', False):
+            sys.stdout.write("Hatta %s\n" % hatta.__version__)
+            sys.exit()
+        self.dead = False
+        self.config = config
+
+        self.language = config.get('language', None)
+        if self.language is not None:
+            try:
+                self.gettext = gettext.translation('hatta', 'locale',
+                                        languages=[self.language]).ugettext
+            except IOError:
+                self.gettext = gettext.translation('hatta', fallback=True,
+                                        languages=[self.language]).ugettext
+        else:
+            self.gettext = gettext.translation('hatta', fallback=True).ugettext
+
+        self.path = os.path.abspath(config.get('pages_path', 'docs'))
+        self.page_charset = config.get('page_charset', 'utf-8')
+        self.menu_page = self.config.get('menu_page', u'Menu')
+        self.front_page = self.config.get('front_page', u'Home')
+        self.logo_page = self.config.get('logo_page', u'logo.png')
+        self.locked_page = self.config.get('locked_page', u'Locked')
+        self.site_name = self.config.get('site_name', u'Hatta Wiki')
+        self.read_only = self.config.get_bool('read_only', False)
+        self.icon_page = self.config.get('icon_page', None)
+        self.pygments_style = self.config.get('pygments_style', 'tango')
+        self.subdirectories = self.config.get_bool('subdirectories', False)
+        if self.subdirectories:
+            self.storage = storage.WikiSubdirectoryStorage(self.path,
+                                                self.page_charset, self.gettext)
+        else:
+            self.storage = self.storage_class(self.path, self.page_charset,
+                                              self.gettext)
+        self.cache = os.path.abspath(config.get('cache_path',
+                                     os.path.join(self.storage.repo_path,
+                                                  '.hg', 'hatta', 'cache')))
+        self.index = self.index_class(self.cache, self.language, self.storage)
+        self.index.update(self)
+        self.url_rules = URL.rules(self)
+        self.url_map = werkzeug.routing.Map(self.url_rules, converters={
+            'title':WikiTitleConverter,
+            'all':WikiAllConverter
+        })
+
+    def add_url_rule(self, rule):
+        """Let plugins add additional url rules."""
+
+        self.url_rules.append(rule)
+        self.url_map = werkzeug.routing.Map(self.url_rules, converters={
+            'title':WikiTitleConverter,
+            'all':WikiAllConverter
+        })
+
+    def get_page(self, request, title):
+        """Creates a page object based on page's mime type"""
+
+        if title:
+            try:
+                page_class, mime = self.filename_map[title]
+            except KeyError:
+                mime = page.page_mime(title)
+                major, minor = mime.split('/', 1)
+                try:
+                    page_class = self.mime_map[mime]
+                except KeyError:
+                    try:
+                        plus_pos = minor.find('+')
+                        if plus_pos > 0:
+                            minor_base = minor[plus_pos:]
+                        else:
+                            minor_base = ''
+                        base_mime = '/'.join([major, minor_base])
+                        page_class = self.mime_map[base_mime]
+                    except KeyError:
+                        try:
+                            page_class = self.mime_map[major]
+                        except KeyError:
+                            page_class = self.mime_map['']
+        else:
+            page_class = page.WikiPage
+            mime = ''
+        return page_class(self, request, title, mime)
+
+    def response(self, request, title, content, etag='', mime='text/html',
+                 rev=None, size=None):
+        """Create a WikiResponse for a page."""
+
+        response = WikiResponse(content, mimetype=mime)
+        user = getattr(request, 'remote_user', '')
+        if rev is None:
+            inode, _size, mtime = self.storage.page_file_meta(title)
+            response.set_etag(u'%s/%s/%d-%d/%s' % (etag, werkzeug.url_quote(title),
+                                                   inode, mtime, user))
+            if size == -1:
+                size = _size
+        else:
+            response.set_etag(u'%s/%s/%s/%s' % (etag, werkzeug.url_quote(title),
+                                                rev, user))
+        if size:
+            response.content_length = size
+        response.make_conditional(request)
+        return response
+
+    def _check_lock(self, title):
+        _ = self.gettext
+        restricted_pages = [
+            'scripts.js',
+            'robots.txt',
+        ]
+        if self.read_only:
+            raise error.ForbiddenErr(_(u"This site is read-only."))
+        if title in restricted_pages:
+            raise error.ForbiddenErr(_(u"""Can't edit this page.
+It can only be edited by the site admin directly on the disk."""))
+        if title in self.index.page_links(self.locked_page):
+            raise error.ForbiddenErr(_(u"This page is locked."))
+
+    def _serve_default(self, request, title, content, mime):
+        """Some pages have their default content."""
+
+        if title in self.storage:
+            return self.download(request, title)
+        response = werkzeug.Response(content, mimetype=mime)
+        response.set_etag('/%s/-1' % title)
+        response.make_conditional(request)
+        return response
+
+    @URL('/<title:title>')
+    @URL('/')
+    def view(self, request, title=None):
+        if title is None:
+            title = self.front_page
+        page = self.get_page(request, title)
+        try:
+            content = page.view_content()
+        except error.NotFoundErr:
+            url = request.get_url(title, self.edit, external=True)
+            return werkzeug.routing.redirect(url, code=303)
+        html = page.render_content(content)
+        dependencies = page.dependencies()
+        etag = '/(%s)' % u','.join(dependencies)
+        return self.response(request, title, html, etag=etag)
+
+    @URL('/+history/<title:title>/<int:rev>')
+    def revision(self, request, title, rev):
+        _ = self.gettext
+        text = self.storage.revision_text(title, rev)
+        link = werkzeug.html.a(werkzeug.html(title),
+                               href=request.get_url(title))
+        content = [
+            werkzeug.html.p(
+                werkzeug.html(
+                    _(u'Content of revision %(rev)d of page %(title)s:'))
+                % {'rev': rev, 'title': link }),
+            werkzeug.html.pre(werkzeug.html(text)),
+        ]
+        special_title = _(u'Revision of "%(title)s"') % {'title': title}
+        page = self.get_page(request, title)
+        html = page.render_content(content, special_title)
+        response = self.response(request, title, html, rev=rev, etag='/old')
+        return response
+
+    @URL('/+version/')
+    @URL('/+version/<title:title>')
+    def version(self, request, title=None):
+        if title is None:
+            version = self.storage.repo_revision()
+        else:
+            try:
+                version, x, x, x = self.storage.page_history(title).next()
+            except StopIteration:
+                version = 0
+        return werkzeug.Response('%d' % version, mimetype="text/plain")
+
+    @URL('/+edit/<title:title>', methods=['POST'])
+    def save(self, request, title):
+        _ = self.gettext
+        self._check_lock(title)
+        url = request.get_url(title)
+        if request.form.get('cancel'):
+            if title not in self.storage:
+                url = request.get_url(self.front_page)
+        if request.form.get('preview'):
+            text = request.form.get("text")
+            if text is not None:
+                lines = text.split('\n')
+            else:
+                lines = [werkzeug.html.p(werkzeug.html(
+                    _(u'No preview for binaries.')))]
+            return self.edit(request, title, preview=lines)
+        elif request.form.get('save'):
+            comment = request.form.get("comment", "")
+            author = request.get_author()
+            text = request.form.get("text")
+            try:
+                parent = int(request.form.get("parent"))
+            except (ValueError, TypeError):
+                parent = None
+            self.storage.reopen()
+            self.index.update(self)
+            page = self.get_page(request, title)
+            if text is not None:
+                if title == self.locked_page:
+                    for link, label in page.extract_links(text):
+                        if title == link:
+                            raise error.ForbiddenErr(
+                                _(u"This page is locked."))
+                if u'href="' in comment or u'http:' in comment:
+                    raise error.ForbiddenErr()
+                if text.strip() == '':
+                    self.storage.delete_page(title, author, comment)
+                    url = request.get_url(self.front_page)
+                else:
+                    self.storage.save_text(title, text, author, comment, parent)
+            else:
+                text = u''
+                upload = request.files['data']
+                f = upload.stream
+                if f is not None and upload.filename is not None:
+                    try:
+                        self.storage.save_file(title, f.tmpname, author,
+                                               comment, parent)
+                    except AttributeError:
+                        self.storage.save_data(title, f.read(), author,
+                                               comment, parent)
+                else:
+                    self.storage.delete_page(title, author, comment)
+                    url = request.get_url(self.front_page)
+            self.index.update_page(page, title, text=text)
+        response = werkzeug.routing.redirect(url, code=303)
+        response.set_cookie('author',
+                            werkzeug.url_quote(request.get_author()),
+                            max_age=604800)
+        return response
+
+    @URL('/+edit/<title:title>', methods=['GET'])
+    def edit(self, request, title, preview=None):
+        _ = self.gettext
+        self._check_lock(title)
+        exists = title in self.storage
+        if exists:
+            self.storage.reopen()
+        page = self.get_page(request, title)
+        content = page.editor_form(preview)
+        special_title = _(u'Editing "%(title)s"') % {'title': title}
+        html = page.render_content(content, special_title)
+        if not exists:
+            response = werkzeug.Response(html, mimetype="text/html",
+                                     status='404 Not found')
+
+        elif preview:
+            response = werkzeug.Response(html, mimetype="text/html")
+        else:
+            response = self.response(request, title, html, '/edit')
+        response.headers.add('Cache-Control', 'no-cache')
+        return response
+
+    @URL('/+feed/atom')
+    @URL('/+feed/rss')
+    def atom(self, request):
+        _ = self.gettext
+        feed = werkzeug.contrib.atom.AtomFeed(self.site_name,
+            feed_url=request.url,
+            url=request.adapter.build(self.view, force_external=True),
+            subtitle=_(u'Track the most recent changes to the wiki '
+                       u'in this feed.'))
+        history = itertools.islice(self.storage.history(), None, 10, None)
+        unique_titles = set()
+        for title, rev, date, author, comment in history:
+            if title in unique_titles:
+                continue
+            unique_titles.add(title)
+            if rev > 0:
+                url = request.adapter.build(self.diff, {
+                    'title': title,
+                    'from_rev': rev-1,
+                    'to_rev': rev
+                }, force_external=True)
+            else:
+                url = request.adapter.build(self.revision, {
+                    'title': title,
+                    'rev': rev,
+                }, force_external=True)
+            feed.add(title, comment, content_type="text", author=author,
+                     url=url, updated=date)
+        rev = self.storage.repo_revision()
+        response = self.response(request, 'atom', feed.generate(), '/+feed',
+                                 'application/xml', rev)
+        response.make_conditional(request)
+        return response
+
+    @URL('/+download/<title:title>')
+    def download(self, request, title):
+        """Serve the raw content of a page directly from disk."""
+
+        mime = page.page_mime(title)
+        if mime == 'text/x-wiki':
+            mime = 'text/plain'
+        try:
+            wrap_file = werkzeug.wrap_file
+        except AttributeError:
+            wrap_file = lambda x, y:y
+        f = wrap_file(request.environ, self.storage.open_page(title))
+        response = self.response(request, title, f, '/download', mime, size=-1)
+        response.direct_passthrough = True
+        return response
+
+    @URL('/+render/<title:title>')
+    def render(self, request, title):
+        """Serve a thumbnail or otherwise rendered content."""
+
+        def file_time_and_size(file_path):
+            """Get file's modification timestamp and its size."""
+
+            try:
+                (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
+                 st_atime, st_mtime, st_ctime) = os.stat(file_path)
+            except OSError:
+                st_mtime = 0
+                st_size = None
+            return st_mtime, st_size
+
+        def rm_temp_dir(dir_path):
+            """Delete the directory with subdirectories."""
+
+            for root, dirs, files in os.walk(dir_path, topdown=False):
+                for name in files:
+                    try:
+                        os.remove(os.path.join(root, name))
+                    except OSError:
+                        pass
+                for name in dirs:
+                    try:
+                        os.rmdir(os.path.join(root, name))
+                    except OSError:
+                        pass
+            try:
+                os.rmdir(dir_path)
+            except OSError:
+                pass
+
+        page = self.get_page(request, title)
+        try:
+            cache_filename, cache_mime = page.render_mime()
+            render = page.render_cache
+        except (AttributeError, NotImplementedError):
+            return self.download(request, title)
+
+        cache_dir = os.path.join(self.cache, 'render',
+                                  werkzeug.url_quote(title, safe=''))
+        cache_file = os.path.join(cache_dir, cache_filename)
+        page_inode, page_size, page_mtime = self.storage.page_file_meta(title)
+        cache_mtime, cache_size = file_time_and_size(cache_file)
+        if page_mtime > cache_mtime:
+            if not os.path.exists(cache_dir):
+                os.makedirs(cache_dir)
+            try:
+                temp_dir = tempfile.mkdtemp(dir=cache_dir)
+                result_file = render(temp_dir)
+                mercurial.util.rename(result_file, cache_file)
+            finally:
+                rm_temp_dir(temp_dir)
+        try:
+            wrap_file = werkzeug.wrap_file
+        except AttributeError:
+            wrap_file = lambda x, y:y
+        f = wrap_file(request.environ, open(cache_file))
+        response = self.response(request, title, f, '/render', cache_mime,
+                                 size=cache_size)
+        response.direct_passthrough = True
+        return response
+
+    @URL('/+undo/<title:title>', methods=['POST'])
+    def undo(self, request, title):
+        """Revert a change to a page."""
+
+        _ = self.gettext
+        self._check_lock(title)
+        rev = None
+        for key in request.form:
+            try:
+                rev = int(key)
+            except ValueError:
+                pass
+        author = request.get_author()
+        if rev is not None:
+            try:
+                parent = int(request.form.get("parent"))
+            except (ValueError, TypeError):
+                parent = None
+            self.storage.reopen()
+            self.index.update(self)
+            if rev == 0:
+                comment = _(u'Delete page %(title)s') % {'title': title}
+                data = ''
+                self.storage.delete_page(title, author, comment)
+            else:
+                comment = _(u'Undo of change %(rev)d of page %(title)s') % {
+                    'rev': rev, 'title': title}
+                data = self.storage.page_revision(title, rev-1)
+                self.storage.save_data(title, data, author, comment, parent)
+            page = self.get_page(request, title)
+            self.index.update_page(page, title, data=data)
+        url = request.adapter.build(self.history, {'title': title},
+                                    method='GET', force_external=True)
+        return werkzeug.redirect(url, 303)
+
+    @URL('/+history/<title:title>')
+    def history(self, request, title):
+        """Display history of changes of a page."""
+
+        _ = self.gettext
+        page = self.get_page(request, title)
+        content = page.render_content(page.history_list(),
+            _(u'History of "%(title)s"') % {'title': title})
+        response = self.response(request, title, content, '/history')
+        return response
+
+    @URL('/+history/')
+    def recent_changes(self, request):
+        """Serve the recent changes page."""
+
+        _ = self.gettext
+        def changes_list(page):
+            """Generate the content of the recent changes page."""
+
+            h = werkzeug.html
+            yield u'<ul>'
+            last = {}
+            lastrev = {}
+            count = 0
+            for title, rev, date, author, comment in self.storage.history():
+                if (author, comment) == last.get(title, (None, None)):
+                    continue
+                count += 1
+                if count > 100:
+                    break
+                if rev > 0:
+                    date_url = request.adapter.build(self.diff, {
+                        'title': title,
+                        'from_rev': rev-1,
+                        'to_rev': lastrev.get(title, rev)
+                    })
+                elif rev == 0:
+                    date_url = request.adapter.build(self.revision, {
+                        'title': title, 'rev': rev})
+                else:
+                    date_url = request.adapter.build(self.history, {
+                        'title': title})
+                last[title] = author, comment
+                lastrev[title] = rev
+
+                yield h.li(h.a(page.date_html(date), href=date_url), ' ',
+                    h.b(page.wiki_link(title)), u' . . . . ',
+                    h.i(page.wiki_link('~%s' % author, author)),
+                    h.div(h(comment), class_="comment")
+                )
+            yield u'</ul>'
+
+        page = self.get_page(request, '')
+        content = page.render_content(changes_list(page), _(u'Recent changes'))
+        response = WikiResponse(content, mimetype='text/html')
+        response.set_etag('/history/%d' % self.storage.repo_revision())
+        response.make_conditional(request)
+        return response
+
+    @URL('/+history/<title:title>/<int:from_rev>:<int:to_rev>')
+    def diff(self, request, title, from_rev, to_rev):
+        """Show the differences between specified revisions."""
+
+        _ = self.gettext
+        page = self.get_page(request, title)
+        build = request.adapter.build
+        from_url = build(self.revision, {'title': title, 'rev': from_rev})
+        to_url = build(self.revision, {'title': title, 'rev': to_rev})
+        a = werkzeug.html.a
+        links = {
+            'link1': a(str(from_rev), href=from_url),
+            'link2': a(str(to_rev), href=to_url),
+            'link': a(werkzeug.html(title), href=request.get_url(title)),
+        }
+        message = werkzeug.html(_(
+            u'Differences between revisions %(link1)s and %(link2)s '
+            u'of page %(link)s.')) % links
+        diff_content = getattr(page, 'diff_content', None)
+        if diff_content:
+            from_text = self.storage.revision_text(page.title, from_rev)
+            to_text = self.storage.revision_text(page.title, to_rev)
+            content = page.diff_content(from_text, to_text, message)
+        else:
+            content = [werkzeug.html.p(werkzeug.html(
+                _(u"Diff not available for this kind of pages.")))]
+        special_title = _(u'Diff for "%(title)s"') % {'title': title}
+        html = page.render_content(content, special_title)
+        response = werkzeug.Response(html, mimetype='text/html')
+        return response
+
+
+    @URL('/+index')
+    def all_pages(self, request):
+        """Show index of all pages in the wiki."""
+
+        _ = self.gettext
+        page = self.get_page(request, '')
+        all_pages = sorted(self.storage.all_pages())
+        content = page.pages_list(all_pages, _(u'Index of all pages'))
+        html = page.render_content(content, _(u'Page Index'))
+        response = WikiResponse(html, mimetype='text/html')
+        response.set_etag('/+index/%d' % self.storage.repo_revision())
+        response.make_conditional(request)
+        return response
+
+    @URL('/+orphaned')
+    def orphaned(self, request):
+        """Show all pages that don't have backlinks."""
+
+        _ = self.gettext
+        page = self.get_page(request, '')
+        pages = self.index.orphaned_pages()
+        content = page.pages_list(pages,
+                                  _(u'List of pages with no links to them'))
+        html = page.render_content(content, _(u'Orphaned pages'))
+        response = WikiResponse(html, mimetype='text/html')
+        response.set_etag('/+orphaned/%d' % self.storage.repo_revision())
+        response.make_conditional(request)
+        return response
+
+    @URL('/+wanted')
+    def wanted(self, request):
+        """Show all pages that don't exist yet, but are linked."""
+
+        _ = self.gettext
+        def wanted_pages_list(page):
+            """Generate the content of wanted pages page."""
+
+            h = werkzeug.html
+            yield h.p(h(
+                _(u"List of pages that are linked to, but don't exist yet.")))
+            yield u'<ol class="wanted">'
+            for refs, title in self.index.wanted_pages():
+                if not parser.external_link(title) and not title.startswith('+'):
+                    url = page.get_url(title, self.backlinks)
+                    yield h.li(h.b(page.wiki_link(title)),
+                               h.i(u' (',
+                                  h.a(h(_(u"%d references") % refs),
+                                      href=url, class_="backlinks"), ')'))
+            yield u'</ol>'
+
+        page = self.get_page(request, '')
+        content = wanted_pages_list(page)
+        html = page.render_content(content, _(u'Wanted pages'))
+        response = WikiResponse(html, mimetype='text/html')
+        response.set_etag('/+wanted/%d' % self.storage.repo_revision())
+        response.make_conditional(request)
+        return response
+
+    @URL('/+search', methods=['GET', 'POST'])
+    def search(self, request):
+        """Serve the search results page."""
+
+        _ = self.gettext
+        def search_snippet(title, words):
+            """Extract a snippet of text for search results."""
+
+            try:
+                text = self.storage.page_text(title)
+            except error.NotFoundErr:
+                return u''
+            regexp = re.compile(u"|".join(re.escape(w) for w in words),
+                                re.U|re.I)
+            match = regexp.search(text)
+            if match is None:
+                return u""
+            position = match.start()
+            min_pos = max(position - 60, 0)
+            max_pos = min(position + 60, len(text))
+            snippet = werkzeug.escape(text[min_pos:max_pos])
+            highlighted = werkzeug.html.b(match.group(0), class_="highlight")
+            html = regexp.sub(highlighted, snippet)
+            return html
+
+        def page_search(words, page, request):
+            """Display the search results."""
+
+            h = werkzeug.html
+            self.storage.reopen()
+            self.index.update(self)
+            result = sorted(self.index.find(words), key=lambda x:-x[0])
+            yield werkzeug.html.p(h(_(u'%d page(s) containing all words:')
+                                  % len(result)))
+            yield u'<ol class="search">'
+            for number, (score, title) in enumerate(result):
+                yield h.li(h.b(page.wiki_link(title)), u' ', h.i(str(score)),
+                           h.div(search_snippet(title, words),
+                                 _class="snippet"),
+                           id_="search-%d" % (number+1))
+            yield u'</ol>'
+
+        query = request.values.get('q', u'').strip()
+        page = self.get_page(request, '')
+        if not query:
+            url = request.get_url(view=self.all_pages, external=True)
+            return werkzeug.routing.redirect(url, code=303)
+        words = tuple(self.index.split_text(query))
+        if not words:
+            words = (query,)
+        title = _(u'Searching for "%s"') % u" ".join(words)
+        content = page_search(words, page, request)
+        html = page.render_content(content, title)
+        return WikiResponse(html, mimetype='text/html')
+
+    @URL('/+search/<title:title>', methods=['GET', 'POST'])
+    def backlinks(self, request, title):
+        """Serve the page with backlinks."""
+
+        _ = self.gettext
+        self.storage.reopen()
+        self.index.update(self)
+        page = self.get_page(request, title)
+        message = _(u'Pages that contain a link to %(link)s.')
+        link = page.wiki_link(title)
+        pages = self.index.page_backlinks(title)
+        content = page.pages_list(pages, message, link, _class='backlinks')
+        html = page.render_content(content, _(u'Links to "%s"') % title)
+        response = WikiResponse(html, mimetype='text/html')
+        response.set_etag('/+search/%d' % self.storage.repo_revision())
+        response.make_conditional(request)
+        return response
+
+    @URL('/+download/scripts.js')
+    def scripts_js(self, request):
+        """Server the default scripts"""
+
+        return self._serve_default(request, 'scripts.js', self.scripts,
+                                   'text/javascript')
+
+    @URL('/+download/style.css')
+    def style_css(self, request):
+        """Serve the default style"""
+
+        return self._serve_default(request, 'style.css', self.style,
+                                   'text/css')
+
+    @URL('/+download/pygments.css')
+    def pygments_css(self, request):
+        """Serve the default pygments style"""
+
+        _ = self.gettext
+        if pygments is None:
+            raise error.NotImplementedErr(
+                _(u"Code highlighting is not available."))
+
+        pygments_style = self.pygments_style
+        if pygments_style not in pygments.styles.STYLE_MAP:
+            pygments_style = 'default'
+        formatter = pygments.formatters.HtmlFormatter(style=pygments_style)
+        style_defs = formatter.get_style_defs('.highlight')
+        return self._serve_default(request, 'pygments.css', style_defs,
+                                   'text/css')
+
+    @URL('/favicon.ico')
+    def favicon_ico(self, request):
+        """Serve the default favicon."""
+
+        return self._serve_default(request, 'favicon.ico', self.icon,
+                                   'image/x-icon')
+
+    @URL('/robots.txt')
+    def robots_txt(self, request):
+        """Serve the robots directives."""
+
+        robots = ('User-agent: *\r\n'
+                  'Disallow: /+*\r\n'
+                  'Disallow: /%2B*\r\n'
+                  'Disallow: /+edit\r\n'
+                  'Disallow: /+feed\r\n'
+                  'Disallow: /+history\r\n'
+                  'Disallow: /+search\r\n'
+                  'Disallow: /+hg\r\n'
+                 )
+        return self._serve_default(request, 'robots.txt', robots,
+                                   'text/plain')
+
+    @URL('/+hg<all:path>', methods=['GET', 'POST', 'HEAD'])
+    def hgweb(self, request, path=None):
+        """Serve the pages repository on the web like a normal hg repository."""
+
+        _ = self.gettext
+        if not self.config.get_bool('hgweb', False):
+            raise error.ForbiddenErr(_(u'Repository access disabled.'))
+        app = mercurial.hgweb.request.wsgiapplication(
+            lambda: mercurial.hgweb.hgweb(self.storage.repo, self.site_name))
+        def hg_app(env, start):
+            env = request.environ
+            prefix = '/+hg'
+            if env['PATH_INFO'].startswith(prefix):
+                env["PATH_INFO"] = env["PATH_INFO"][len(prefix):]
+                env["SCRIPT_NAME"] += prefix
+            return app(env, start)
+        return hg_app
+
+    @URL('/off-with-his-head', methods=['GET'])
+    def die(self, request):
+        """Terminate the standalone server if invoked from localhost."""
+
+        _ = self.gettext
+        if not request.remote_addr.startswith('127.'):
+            raise error.ForbiddenErr(_(u'This URL can only be called locally.'))
+        def agony():
+            yield u'Oh dear!'
+            self.dead = True
+        return werkzeug.Response(agony(), mimetype='text/plain')
+
+    @werkzeug.responder
+    def application(self, environ, start):
+        """The main application loop."""
+
+        adapter = self.url_map.bind_to_environ(environ)
+        request = WikiRequest(self, adapter, environ)
+        try:
+            try:
+                endpoint, values = adapter.match()
+                return endpoint(request, **values)
+            except werkzeug.exceptions.HTTPException, err:
+                return err
+        finally:
+            request.cleanup()
+            del request
+            del adapter
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/start.py	Thu Oct 07 23:00:29 2010 +0200
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+An example of extending Hatta: adding a slideshow functionality.
+"""
+
+import webbrowser
+import werkzeug
+import os
+
+import hatta
+
+_ = lambda x: x
+
+class WikiPageWiki(hatta.WikiPageWiki):
+    def footer(self, special_title, edit_url):
+        for part in super(WikiPageWiki, self).footer(special_title, edit_url):
+            yield part
+        yield werkzeug.html.a(werkzeug.html(_('Slides')),
+            href=self.get_url(self.title, self.wiki.slides),
+            class_='slides')
+        yield u'\n'
+
+
+class Wiki(hatta.Wiki):
+    @hatta.wiki.URL('/+slides/<title:title>')
+    def slides(self, request, title):
+        """Dump the HTML content of the pages listed."""
+
+        items = self.index.page_links_and_labels(title)
+        contents = []
+        for t, label in items:
+            page = self.get_page(request, t)
+            try:
+                html = ''.join(page.view_content())
+            except hatta.error.NotFoundErr:
+                continue
+            slide_title = (u'<h1>%s</h1>' % werkzeug.escape(label))
+            contents.append(slide_title + html)
+        content = ('<div class="slide">%s</div>'
+                   % '</div><div class="slide">'.join(contents))
+        html = """<!DOCTYPE html>
+<link type="text/css" href="../+download/slides.css" rel="stylesheet">
+<link type="text/css" href="../+download/pygments.css" rel="stylesheet">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>%s</title>
+%s
+<script src="../+download/jquery.js"></script>
+<script src="../+download/slides.js"></script>
+""" % (werkzeug.escape(title), content)
+        response = hatta.WikiResponse(html, mimetype='text/html')
+        return response
+
+
+if __name__=='__main__':
+    config = hatta.WikiConfig()
+    wiki = Wiki(config)
+    wiki.mime_map['text/x-wiki'] = WikiPageWiki
+    webbrowser.open('http://localhost:8080')
+    hatta.main(wiki=wiki, config=config)