Commits

Friedrich Kastner-Masilko committed 3b1f1cc

Added wiki extension.

  • Participants
  • Parent commits 6ea28b2

Comments (0)

Files changed (9)

wiki/.wiki.footer

+</div><div class="cb"></div></div><div class="cb footer-placeholder"></div></div></body></html>

wiki/.wiki.header

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
+	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head><link rel="stylesheet" type="text/css" href="../css/layout.css" /></head><body class=""><div id="main-wrapper"><div id="header-wrapper"></div><div id="content-wrapper"><div id="wiki">
+'''integration with Bitbucket WikiCreole
+
+wiki is an extension for Mercurial that allows it to act as a offline
+BitBucket wiki renderer.
+
+'''
+from mercurial.i18n import _
+from mercurial.util import opener, makedirs
+
+import os, sys, shutil
+from creole import Parser
+from creole2html import HtmlEmitter
+
+def wiki(ui, repo, *args, **opts):
+    '''see detailed help for list of subcommands'''
+    modified, added, removed, deleted, unknown, ignored, clean = [
+            n for n in repo.status(clean=True)]
+
+    files = modified + added + clean
+
+    o = opener(os.path.abspath(args[0]))
+
+    for fn in files:        
+        if fn.endswith('.wiki'):
+            ui.debug('Converting %s' % fn)
+            f = open(repo.wjoin(fn), 'r')
+            document = Parser(unicode(f.read(), 'utf-8', 'ignore')).parse()
+            f.close()
+            fnt = fn + '.html'
+            ui.debug(' to %s\n' % fnt)
+            f = o(fnt, 'w')
+            part = open(repo.wjoin('.wiki.header'), 'r')
+            f.write(part.read())
+            part.close()
+            f.write(HtmlEmitter(document).emit().encode('utf-8', 'ignore'))
+            part = open(repo.wjoin('.wiki.footer'), 'r')
+            f.write(part.read())
+            part.close()
+            f.close()
+        else:
+            ui.debug('Copying %s' % fn)
+            fnt = os.path.abspath(os.path.join(args[0], fn))
+            ui.debug(' to %s\n' % fnt)
+            try:
+                shutil.copy(fn, fnt)
+            except:
+                makedirs(os.path.dirname(fnt))
+                shutil.copy(fn, fnt)
+
+cmdtable = {
+    "wiki":
+        (wiki, [], _('generates html from .wiki file')),
+}
+# -*- coding: iso-8859-1 -*-
+"""
+    Creole wiki markup parser
+
+    See http://wikicreole.org/ for latest specs.
+
+    Notes:
+    * No markup allowed in headings.
+      Creole 1.0 does not require us to support this.
+    * No markup allowed in table headings.
+      Creole 1.0 does not require us to support this.
+    * No (non-bracketed) generic url recognition: this is "mission impossible"
+      except if you want to risk lots of false positives. Only known protocols
+      are recognized.
+    * We do not allow ":" before "//" italic markup to avoid urls with
+      unrecognized schemes (like wtf://server/path) triggering italic rendering
+      for the rest of the paragraph.
+
+    @copyright: 2007 MoinMoin:RadomirDopieralski (creole 0.5 implementation),
+                2007 MoinMoin:ThomasWaldmann (updates)
+    @license: GNU GPL, see COPYING for details.
+    @license: BSD, see COPYING for details.
+"""
+
+import re
+import sys
+
+__version__ = '1.1'
+
+
+class Rules:
+    """Hold all the rules for generating regular expressions."""
+
+    # For the inline elements:
+    proto = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
+    link = r'''(?P<link>
+            \[\[
+            (?P<link_target>.+?) \s*
+            ([|] \s* (?P<link_text>.+?) \s*)?
+            ]]
+        )'''
+    image = r'''(?P<image>
+            {{
+            (?P<image_target>.+?) \s*
+            ([|] \s* (?P<image_text>.+?) \s*)?
+            }}
+        )'''
+    macro = r'''(?P<macro>
+            <<
+            (?P<macro_name> \w+)
+            (\( (?P<macro_args> .*?) \))? \s*
+            ([|] \s* (?P<macro_text> .+?) \s* )?
+            >>
+        )'''
+    code = r'(?P<code> {{{ (?P<code_text>.*?) }}} )'
+    emph = r'(?P<emph> (?<!:)// )' # there must be no : in front of the //
+                                   # avoids italic rendering in urls with
+                                   # unknown protocols
+    strong = r'(?P<strong> \*\* )'
+    linebreak = r'(?P<break> \\\\ )'
+    escape = r'(?P<escape> ~ (?P<escaped_char>\S) )'
+    char =  r'(?P<char> . )'
+
+    # For the block elements:
+    separator = r'(?P<separator> ^ \s* ---- \s* $ )' # horizontal line
+    line = r'(?P<line> ^ \s* $ )' # empty line that separates paragraphs
+    head = r'''(?P<head>
+            ^ \s*
+            (?P<head_head>=+) \s*
+            (?P<head_text> .*? ) \s*
+            (?P<head_tail>=*) \s*
+            $
+        )'''
+    text = r'(?P<text> .+ )'
+    list = r'''(?P<list>
+            ^ [ \t]* ([*][^*\#]|[\#][^\#*]).* $
+            ( \n[ \t]* [*\#]+.* $ )*
+        )''' # Matches the whole list, separate items are parsed later. The
+             # list *must* start with a single bullet.
+    item = r'''(?P<item>
+            ^ \s*
+            (?P<item_head> [\#*]+) \s*
+            (?P<item_text> .*?)
+            $
+        )''' # Matches single list items
+    pre = r'''(?P<pre>
+            ^{{{ \s* $
+            (\n)?
+            (?P<pre_text>
+                ([\#]!(?P<pre_kind>\w*?)(\s+.*)?$)?
+                (.|\n)+?
+            )
+            (\n)?
+            ^}}} \s*$
+        )'''
+    pre_escape = r' ^(?P<indent>\s*) ~ (?P<rest> \}\}\} \s*) $'
+    table = r'''(?P<table>
+            ^ \s*
+            [|].*? \s*
+            [|]? \s*
+            $
+        )'''
+
+    # For splitting table cells:
+    cell = r'''
+            \| \s*
+            (
+                (?P<head> [=][^|]+ ) |
+                (?P<cell> (  %s | [^|])+ )
+            ) \s*
+        ''' % '|'.join([link, macro, image, code])
+
+    def __init__(self, bloglike_lines=False, url_protocols=None,
+                 wiki_words=False):
+        c = re.compile
+        # For pre escaping, in creole 1.0 done with ~:
+        self.pre_escape_re = c(self.pre_escape, re.M | re.X)
+        # for link descriptions
+        self.link_re = c('|'.join([self.image, self.linebreak,
+                                   self.char]), re.X | re.U)
+        # for list items
+        self.item_re = c(self.item, re.X | re.U | re.M)
+        # for table cells
+        self.cell_re = c(self.cell, re.X | re.U)
+
+        # For block elements:
+        if bloglike_lines:
+            self.text = r'(?P<text> .+ ) (?P<break> (?<!\\)$\n(?!\s*$) )?'
+        self.block_re = c('|'.join([self.line, self.head, self.separator,
+                                    self.pre, self.list, self.table,
+                                    self.text]), re.X | re.U | re.M)
+
+        # For inline elements:
+        if url_protocols is not None:
+            self.proto = '|'.join(re.escape(p) for p in url_protocols)
+        self.url =  r'''(?P<url>
+            (^ | (?<=\s | [.,:;!?()/=]))
+            (?P<escaped_url>~)?
+            (?P<url_target> (?P<url_proto> %s ):\S+? )
+            ($ | (?=\s | [,.:;!?()] (\s | $))))''' % self.proto
+        inline_elements = [self.link, self.url, self.macro,
+                           self.code, self.image, self.strong,
+                           self.emph, self.linebreak,
+                           self.escape, self.char]
+        if wiki_words:
+            import unicodedata
+            up_case = u''.join(unichr(i) for i in xrange(sys.maxunicode)
+                               if unicodedata.category(unichr(i))=='Lu')
+            self.wiki = ur'''(?P<wiki>[%s]\w+[%s]\w+)''' % (up_case, up_case)
+            inline_elements.insert(3, self.wiki)
+        self.inline_re = c('|'.join(inline_elements), re.X | re.U)
+
+class Parser:
+    """
+    Parse the raw text and create a document object
+    that can be converted into output using Emitter.
+
+    A separate instance should be created for parsing a new document.
+    The first parameter is the raw text to be parsed. An optional second
+    argument is the Rules object to use. You can customize the parsing
+    rules to enable optional features or extend the parser.
+    """
+
+    def __init__(self, raw, rules=None):
+        self.rules = rules or Rules()
+        self.raw = raw
+        self.root = DocNode('document', None)
+        self.cur = self.root        # The most recent document node
+        self.text = None            # The node to add inline characters to
+
+    def _upto(self, node, kinds):
+        """
+        Look up the tree to the first occurence
+        of one of the listed kinds of nodes or root.
+        Start at the node node.
+        """
+        while node.parent is not None and not node.kind in kinds:
+            node = node.parent
+        return node
+
+    # The _*_repl methods called for matches in regexps. Sometimes the
+    # same method needs several names, because of group names in regexps.
+
+    def _url_repl(self, groups):
+        """Handle raw urls in text."""
+
+        if not groups.get('escaped_url'):
+            # this url is NOT escaped
+            target = groups.get('url_target', '')
+            node = DocNode('link', self.cur)
+            node.content = target
+            DocNode('text', node, node.content)
+            self.text = None
+        else:
+            # this url is escaped, we render it as text
+            if self.text is None:
+                self.text = DocNode('text', self.cur, u'')
+            self.text.content += groups.get('url_target')
+    _url_target_repl = _url_repl
+    _url_proto_repl = _url_repl
+    _escaped_url = _url_repl
+
+    def _link_repl(self, groups):
+        """Handle all kinds of links."""
+
+        target = groups.get('link_target', '')
+        text = (groups.get('link_text', '') or '').strip()
+        parent = self.cur
+        self.cur = DocNode('link', self.cur)
+        self.cur.content = target
+        self.text = None
+        re.sub(self.rules.link_re, self._replace, text)
+        self.cur = parent
+        self.text = None
+    _link_target_repl = _link_repl
+    _link_text_repl = _link_repl
+
+    def _wiki_repl(self, groups):
+        """Handle WikiWord links, if enabled."""
+
+        text = groups.get('wiki', '')
+        node = DocNode('link', self.cur)
+        node.content = text
+        DocNode('text', node, node.content)
+        self.text = None
+
+    def _macro_repl(self, groups):
+        """Handles macros using the placeholder syntax."""
+
+        name = groups.get('macro_name', '')
+        text = (groups.get('macro_text', '') or '').strip()
+        node = DocNode('macro', self.cur, name)
+        node.args = groups.get('macro_args', '') or ''
+        DocNode('text', node, text or name)
+        self.text = None
+    _macro_name_repl = _macro_repl
+    _macro_args_repl = _macro_repl
+    _macro_text_repl = _macro_repl
+
+    def _image_repl(self, groups):
+        """Handles images and attachemnts included in the page."""
+
+        target = groups.get('image_target', '').strip()
+        text = (groups.get('image_text', '') or '').strip()
+        node = DocNode("image", self.cur, target)
+        DocNode('text', node, text or node.content)
+        self.text = None
+    _image_target_repl = _image_repl
+    _image_text_repl = _image_repl
+
+    def _separator_repl(self, groups):
+        self.cur = self._upto(self.cur, ('document', 'section', 'blockquote'))
+        DocNode('separator', self.cur)
+
+    def _item_repl(self, groups):
+        bullet = groups.get('item_head', u'')
+        text = groups.get('item_text', u'')
+        if bullet[-1] == '#':
+            kind = 'number_list'
+        else:
+            kind = 'bullet_list'
+        level = len(bullet)
+        lst = self.cur
+        # Find a list of the same kind and level up the tree
+        while (lst and
+                   not (lst.kind in ('number_list', 'bullet_list') and
+                        lst.level == level) and
+                    not lst.kind in ('document', 'section', 'blockquote')):
+            lst = lst.parent
+        if lst and lst.kind == kind:
+            self.cur = lst
+        else:
+            # Create a new level of list
+            self.cur = self._upto(self.cur,
+                ('list_item', 'document', 'section', 'blockquote'))
+            self.cur = DocNode(kind, self.cur)
+            self.cur.level = level
+        self.cur = DocNode('list_item', self.cur)
+        self.parse_inline(text)
+        self.text = None
+    _item_text_repl = _item_repl
+    _item_head_repl = _item_repl
+
+    def _list_repl(self, groups):
+        text = groups.get('list', u'')
+        self.rules.item_re.sub(self._replace, text)
+
+    def _head_repl(self, groups):
+        self.cur = self._upto(self.cur, ('document', 'section', 'blockquote'))
+        node = DocNode('header', self.cur, groups.get('head_text', '').strip())
+        node.level = len(groups.get('head_head', ' '))
+    _head_head_repl = _head_repl
+    _head_text_repl = _head_repl
+
+    def _text_repl(self, groups):
+        text = groups.get('text', '')
+        if self.cur.kind in ('table', 'table_row', 'bullet_list',
+            'number_list'):
+            self.cur = self._upto(self.cur,
+                ('document', 'section', 'blockquote'))
+        if self.cur.kind in ('document', 'section', 'blockquote'):
+            self.cur = DocNode('paragraph', self.cur)
+        else:
+            text = u' ' + text
+        self.parse_inline(text)
+        if groups.get('break') and self.cur.kind in ('paragraph',
+            'emphasis', 'strong', 'code'):
+            DocNode('break', self.cur, '')
+        self.text = None
+    _break_repl = _text_repl
+
+    def _table_repl(self, groups):
+        row = groups.get('table', '|').strip()
+        self.cur = self._upto(self.cur, (
+            'table', 'document', 'section', 'blockquote'))
+        if self.cur.kind != 'table':
+            self.cur = DocNode('table', self.cur)
+        tb = self.cur
+        tr = DocNode('table_row', tb)
+
+        text = ''
+        for m in self.rules.cell_re.finditer(row):
+            cell = m.group('cell')
+            if cell:
+                self.cur = DocNode('table_cell', tr)
+                self.text = None
+                self.parse_inline(cell)
+            else:
+                cell = m.group('head')
+                self.cur = DocNode('table_head', tr)
+                self.text = DocNode('text', self.cur, u'')
+                self.text.content = cell.strip('=')
+        self.cur = tb
+        self.text = None
+
+    def _pre_repl(self, groups):
+        self.cur = self._upto(self.cur, ('document', 'section', 'blockquote'))
+        kind = groups.get('pre_kind', None)
+        text = groups.get('pre_text', u'')
+        def remove_tilde(m):
+            return m.group('indent') + m.group('rest')
+        text = self.rules.pre_escape_re.sub(remove_tilde, text)
+        node = DocNode('preformatted', self.cur, text)
+        node.sect = kind or ''
+        self.text = None
+    _pre_text_repl = _pre_repl
+    _pre_head_repl = _pre_repl
+    _pre_kind_repl = _pre_repl
+
+    def _line_repl(self, groups):
+        self.cur = self._upto(self.cur, ('document', 'section', 'blockquote'))
+
+    def _code_repl(self, groups):
+        DocNode('code', self.cur, groups.get('code_text', u'').strip())
+        self.text = None
+    _code_text_repl = _code_repl
+    _code_head_repl = _code_repl
+
+    def _emph_repl(self, groups):
+        if self.cur.kind != 'emphasis':
+            self.cur = DocNode('emphasis', self.cur)
+        else:
+            self.cur = self._upto(self.cur, ('emphasis', )).parent
+        self.text = None
+
+    def _strong_repl(self, groups):
+        if self.cur.kind != 'strong':
+            self.cur = DocNode('strong', self.cur)
+        else:
+            self.cur = self._upto(self.cur, ('strong', )).parent
+        self.text = None
+
+    def _break_repl(self, groups):
+        DocNode('break', self.cur, None)
+        self.text = None
+
+    def _escape_repl(self, groups):
+        if self.text is None:
+            self.text = DocNode('text', self.cur, u'')
+        self.text.content += groups.get('escaped_char', u'')
+
+    def _char_repl(self, groups):
+        if self.text is None:
+            self.text = DocNode('text', self.cur, u'')
+        self.text.content += groups.get('char', u'')
+
+    def _replace(self, match):
+        """Invoke appropriate _*_repl method. Called for every matched group."""
+
+        groups = match.groupdict()
+        for name, text in groups.iteritems():
+            if text is not None:
+                replace = getattr(self, '_%s_repl' % name)
+                replace(groups)
+                return
+
+    def parse_inline(self, raw):
+        """Recognize inline elements inside blocks."""
+
+        re.sub(self.rules.inline_re, self._replace, raw)
+
+    def parse_block(self, raw):
+        """Recognize block elements."""
+
+        re.sub(self.rules.block_re, self._replace, raw)
+
+    def parse(self):
+        """Parse the text given as self.raw and return DOM tree."""
+
+        self.parse_block(self.raw)
+        return self.root
+
+#################### Helper classes
+
+### The document model
+
+class DocNode:
+    """
+    A node in the document.
+    """
+
+    def __init__(self, kind='', parent=None, content=None):
+        self.children = []
+        self.parent = parent
+        self.kind = kind
+        self.content = content
+        if self.parent is not None:
+            self.parent.children.append(self)
+
+

wiki/creole2html.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+ur"""
+WikiCreole to HTML converter
+This program is an example of how the creole.py WikiCreole parser
+can be used.
+
+@copyright: 2007 MoinMoin:RadomirDopieralski
+@license: BSD, see COPYING for details.
+
+Test cases contributed by Jan Klopper (janklopper@underdark.nl),
+modified by Radomir Dopieralski (MoinMoin:RadomirDopieralski).
+
+>>> import lxml.html.usedoctest
+>>> import creole
+>>> def parse(text):
+...     print HtmlEmitter(creole.Parser(text).parse()).emit()
+>>> def wiki_parse(text):
+...     rules = creole.Rules(wiki_words=True)
+...     print HtmlEmitter(creole.Parser(text, rules).parse()).emit()
+
+>>> parse(u'test')
+<p>test</p>
+
+>>> parse(u'test\ntest')
+<p>test test</p>
+
+>>> HtmlEmitter(Parser(u'test\ntest').parse()).emit()
+u'<p>test test</p>\n'
+
+>>> parse(u'test\n\ntest')
+<p>test</p><p>test</p>
+
+>>> parse(u'test\\\\test')
+<p>test<br>test</p>
+
+>>> parse(u'ÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿŒœ%0A')
+<p>ÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿŒœ%0A</p>
+
+>>> parse(u'----')
+<hr>
+
+>>> parse(u'==test==')
+<h2>test</h2>
+
+>>> parse(u'== test')
+<h2>test</h2>
+
+>>> parse(u'==test====')
+<h2>test</h2>
+
+>>> parse(u'=====test')
+<h5>test</h5>
+
+>>> parse(u'==test==\ntest\n===test===')
+<h2>test</h2>
+<p>test</p>
+<h3>test</h3>
+
+>>> parse(u'test\n* test line one\n * test line two\ntest\n\ntest')
+<p>test</p>
+<ul>
+    <li>test line one</li>
+    <li>test line two test</li>
+</ul>
+<p>test</p>
+
+>>> parse(u'* test line one\n* test line two\n** Nested item')
+<ul>
+    <li>test line one</li>
+    <li>test line two<ul>
+        <li>Nested item</li>
+    </ul></li>
+</ul>
+
+>>> parse(u'* test line one\n* test line two\n# Nested item')
+<ul>
+    <li>test line one</li>
+    <li>test line two<ol>
+        <li>Nested item</li>
+    </ol></li>
+</ul>
+
+>>> parse(u'test //test test// test **test test** test')
+<p>test <i>test test</i> test <b>test test</b> test</p>
+
+>>> parse(u'test //test **test// test** test')
+<p>test <i>test <b>test<i> test<b> test</b></i></b></i></p>
+
+>>> parse(u'**test')
+<p><b>test</b></p>
+
+>>> parse(u'|x|y|z|\n|a|b|c|\n|d|e|f|\ntest')
+<table>
+    <tr><td>x</td><td>y</td><td>z</td></tr>
+    <tr><td>a</td><td>b</td><td>c</td></tr>
+    <tr><td>d</td><td>e</td><td>f</td></tr>
+</table>
+<p>test</p>
+
+>>> parse(u'|=x|y|=z=|\n|a|b|c|\n|d|e|f|')
+<table>
+    <tr><th>x</th><td>y</td><th>z</th></tr>
+    <tr><td>a</td><td>b</td><td>c</td></tr>
+    <tr><td>d</td><td>e</td><td>f</td></tr>
+</table>
+
+>>> parse(u'test http://example.com/test test')
+<p>test <a href="http://example.com/test">http://example.com/test</a> test</p>
+
+>>> parse(u'http://example.com/,test, test')
+<p><a href="http://example.com/,test">http://example.com/,test</a>, test</p>
+
+>>> parse(u'(http://example.com/test)')
+<p>(<a href="http://example.com/test">http://example.com/test</a>)</p>
+
+XXX This might be considered a bug, but it's impossible to detect in general.
+>>> parse(u'http://example.com/(test)')
+<p><a href="http://example.com/(test">http://example.com/(test</a>)</p>
+
+>>> parse(u'http://example.com/test?test&test=1')
+<p><a href="http://example.com/test?test&amp;test=1">http://example.com/test?test&amp;test=1</a></p>
+
+>>> parse(u'~http://example.com/test')
+<p>http://example.com/test</p>
+
+>>> parse(u'http://example.com/~test')
+<p><a href="http://example.com/~test">http://example.com/~test</a></p>
+
+>>> parse(u'[[test]] [[tset|test]]')
+<p><a href="test">test</a> <a href="tset">test</a></p>
+
+>>> parse(u'[[http://example.com|test]]')
+<p><a href="http://example.com">test</a></p>
+
+>>> wiki_parse(u'Lorem WikiWord iPsum sit ameT.')
+<p>Lorem <a href="WikiWord">WikiWord</a> iPsum sit ameT.</p>
+
+"""
+
+import re
+from creole import Parser, Rules
+
+class Rules:
+    # For the link targets:
+    proto = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
+    extern = r'(?P<extern_addr>(?P<extern_proto>%s):.*)' % proto
+    interwiki = r'''
+            (?P<inter_wiki> [A-Z][a-zA-Z]+ ) :
+            (?P<inter_page> .* )
+        '''
+
+class HtmlEmitter:
+    """
+    Generate HTML output for the document
+    tree consisting of DocNodes.
+    """
+
+    addr_re = re.compile('|'.join([
+            Rules.extern,
+            Rules.interwiki,
+        ]), re.X | re.U) # for addresses
+
+    def __init__(self, root):
+        self.root = root
+
+    def get_text(self, node):
+        """Try to emit whatever text is in the node."""
+
+        try:
+            return node.children[0].content or ''
+        except:
+            return node.content or ''
+
+    def html_escape(self, text):
+        return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
+
+    def attr_escape(self, text):
+        return self.html_escape(text).replace('"', '&quot')
+
+    # *_emit methods for emitting nodes of the document:
+
+    def document_emit(self, node):
+        return self.emit_children(node)
+
+    def text_emit(self, node):
+        return self.html_escape(node.content)
+
+    def separator_emit(self, node):
+        return u'<hr>';
+
+    def paragraph_emit(self, node):
+        return u'<p>%s</p>\n' % self.emit_children(node)
+
+    def bullet_list_emit(self, node):
+        return u'<ul>\n%s</ul>\n' % self.emit_children(node)
+
+    def number_list_emit(self, node):
+        return u'<ol>\n%s</ol>\n' % self.emit_children(node)
+
+    def list_item_emit(self, node):
+        return u'<li>%s</li>\n' % self.emit_children(node)
+
+    def table_emit(self, node):
+        return u'<table>\n%s</table>\n' % self.emit_children(node)
+
+    def table_row_emit(self, node):
+        return u'<tr>%s</tr>\n' % self.emit_children(node)
+
+    def table_cell_emit(self, node):
+        return u'<td>%s</td>' % self.emit_children(node)
+
+    def table_head_emit(self, node):
+        return u'<th>%s</th>' % self.emit_children(node)
+
+    def emphasis_emit(self, node):
+        return u'<i>%s</i>' % self.emit_children(node)
+
+    def strong_emit(self, node):
+        return u'<b>%s</b>' % self.emit_children(node)
+
+    def header_emit(self, node):
+        return u'<h%d>%s</h%d>\n' % (
+            node.level, self.html_escape(node.content), node.level)
+
+    def code_emit(self, node):
+        return u'<tt>%s</tt>' % self.html_escape(node.content)
+
+    def link_emit(self, node):
+        target = node.content
+        if node.children:
+            inside = self.emit_children(node)
+        else:
+            inside = self.html_escape(target)
+        m = self.addr_re.match(target)
+        if m:
+            if m.group('extern_addr'):
+                return u'<a href="%s">%s</a>' % (
+                    self.attr_escape(target), inside)
+            elif m.group('inter_wiki'):
+                raise NotImplementedError
+        return u'<a href="%s.wiki.html">%s</a>' % (
+            self.attr_escape(target).replace(' ', '_'), inside)
+
+    def image_emit(self, node):
+        target = node.content
+        text = self.get_text(node)
+        m = self.addr_re.match(target)
+        if m:
+            if m.group('extern_addr'):
+                return u'<img src="%s" alt="%s">' % (
+                    self.attr_escape(target), self.attr_escape(text))
+            elif m.group('inter_wiki'):
+                raise NotImplementedError
+        return u'<img src="%s" alt="%s">' % (
+            self.attr_escape(target), self.attr_escape(text))
+
+    def macro_emit(self, node):
+        raise NotImplementedError
+
+    def break_emit(self, node):
+        return u"<br>"
+
+    def preformatted_emit(self, node):
+        return u"<pre>%s</pre>" % self.html_escape(node.content)
+
+    def default_emit(self, node):
+        """Fallback function for emitting unknown nodes."""
+
+        raise TypeError
+
+    def emit_children(self, node):
+        """Emit all the children of a node."""
+
+        return u''.join([self.emit_node(child) for child in node.children])
+
+    def emit_node(self, node):
+        """Emit a single node."""
+
+        emit = getattr(self, '%s_emit' % node.kind, self.default_emit)
+        return emit(node)
+
+    def emit(self):
+        """Emit the document represented by self.root DOM tree."""
+
+        return self.emit_node(self.root)
+
+if __name__=="__main__":
+    import sys
+    document = Parser(unicode(sys.stdin.read(), 'utf-8', 'ignore')).parse()
+    sys.stdout.write(HtmlEmitter(document).emit().encode('utf-8', 'ignore'))
+
+

wiki/css/layout.css

+@import "tabs.css";
+
+/* Our stuff */
+html, body {
+  color: white;
+  height: 100%;
+}
+
+body {
+	margin: 0;
+	padding: 0;
+	background: #fff url('../img/layout/bg_header_new.png') repeat-x left top;
+}
+
+body,input,select {
+	font: 75% "Lucida Grande", Verdana, Arial, sans-serif;
+	line-height: 16px;
+	color: #525252;
+}
+
+form {
+	margin-bottom: 0;
+}
+
+select {
+	background: #fff;
+	color: #525252;
+}
+
+input[type=text], input[type=password], textarea {
+	font-size: 1.1em !important;
+	background: #fff;
+	color: #525252;
+	padding: 3px;
+}
+
+input[type=radio], input[type=checkbox], input[type=submit], input[type=reset], input[type=button], input[type=file], select {
+	font-size: 1em;
+}
+
+h1,h2,h3,h4,h5,h6 {
+	margin-top: 0;
+	padding-top: 0;
+	margin-bottom: 3px;
+}
+
+hr {
+	padding: 0;
+	margin: 5px 0;
+	height: 1px;
+	border: 0;
+	border-bottom: 1px solid #ddd;
+}
+
+pre, textarea {
+	font: 11px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+}
+
+a img {
+	border: 0;
+}
+
+a, a:link, a:active, a:visited {
+	color: #2B547D;
+	text-decoration: none;
+}
+
+a:hover {
+	text-decoration: underline;
+}
+
+fieldset {
+	border: 0;
+	margin: 0;
+	padding: 0;
+}
+
+h1 a {
+	color: #525252 !important;
+}
+
+div {
+	margin: 0;
+	padding: 0;
+}
+
+p {
+	margin: 0;
+	padding: 0 0 10px 0;
+}
+
+table {
+	padding: 0;
+	margin: 0;
+}
+
+th {
+	vertical-align: top;
+	text-align: left;
+}
+
+/* STANDARD LAYOUT-BOX */
+.layout-box {
+	width: 918px;
+	padding: 15px 20px;
+	border: 1px solid #ddd;
+	margin: 0 auto;
+}
+
+/* USERINFO PAGE */
+.userinfo-repositories {
+	padding: 10px 20px;
+	border: 1px solid #ddd;
+	background: #dde7ef;
+}
+
+.userlist {
+	padding-bottom: 5px !important;
+}
+
+.userlist img {
+	margin-bottom: 10px;
+}
+
+.userinfo-namedetails {
+	font-weight: bold;
+	padding-bottom: 0 !important;
+}
+
+/* FORM BUTTONS */
+.primary-button {
+	border-top: 1px solid #ddd;
+	border-left: 1px solid #ddd;
+	border-bottom: 1px solid #bbb;
+	border-right: 1px solid #bbb;
+	background: #2B547D;
+	padding: 3px 10px !important;
+	color: #fff;
+}
+
+.delete-button {
+	border-top: 1px solid #ddd;
+	border-left: 1px solid #ddd;
+	border-bottom: 1px solid #bbb;
+	border-right: 1px solid #bbb;
+	background: #ff4444;
+	padding: 2px 8px !important;
+	color: #fff;
+}
+
+.secondary-button {
+	border-top: 1px solid #ddd;
+	border-left: 1px solid #ddd;
+	border-bottom: 1px solid #bbb;
+	border-right: 1px solid #bbb;
+	padding: 2px 8px !important;
+	background: #eee;
+}
+
+.secondary-button-darkbg {
+	background: #fff !important;
+}
+
+.bump-button {
+	float: right; 
+	margin-right: 14px;
+}
+
+/* FULL WIDTH TABLE (used in Source and Overview) */
+table.maintable {
+	width: 960px;
+	border-collapse: collapse;
+	font: 12px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+}
+
+table.maintable tr {
+	height: 35px;
+}
+
+table.maintable tr.header {
+	background: #eee;
+}
+
+table.maintable td {
+	border-bottom: 1px solid #ddd;
+	height: 32px !important;
+}
+
+table.maintable th {
+	vertical-align: middle;
+	font-weight: normal !important;
+	border-bottom: 1px solid #ddd;
+	height: 32px !important;
+}
+
+.downloads td, .downloads th {
+	padding: 4px 0 0 8px;
+}
+
+td.archives a {
+	border-bottom: 1px dotted #666;
+}
+
+tr.highlight-file {
+	background: lightyellow !important;
+}
+
+.highlight {
+	position: relative;
+}
+
+.highlight pre a:target {
+	display: block;
+	position: absolute;
+	left: 0;
+	z-index: -1;
+	background: #ffffbe;
+	height: 15px;
+	width: 100%;
+}
+
+/* GLOBAL REUSABLE CLASSES/IDS */
+.repo-private {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/lock.png') no-repeat left 50%;
+	padding: 2px 0 2px 16px;
+}
+
+.repo-public {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/lock_disable_unlock.png') no-repeat left 50%;
+	padding: 2px 0 2px 16px;
+}
+
+.editable-title:hover {
+	padding: 2px 20px 2px 0;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/pencil_small.png') no-repeat right 50%;
+}
+
+.editable-icon {
+	font-size: 0.8em;
+	color: #525252 !important;
+	padding: 2px 0 2px 16px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/pencil_small.png') no-repeat left 50%;
+}
+
+.is-spam {
+	background: #eee url('http://media-cdn.bitbucket.org/img/diagonal_transparent_bg.png') repeat left top !important;
+}
+
+.spam {
+	font-size: 0.8em;
+	color: #525252 !important;
+	padding: 2px 0 2px 14px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/minus_small_circle.png') no-repeat left 65%;
+}
+
+.ham {
+	font-size: 0.8em;
+	color: #525252 !important;
+	padding: 2px 0 2px 14px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/plus_small_circle.png') no-repeat left 65%;
+}
+
+.delete {
+	font-size: 0.8em;
+	color: #525252 !important;
+	padding: 2px 0 2px 14px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/cross_small_circle.png') no-repeat left 65%;
+}
+
+div.cb {
+	clear: both;
+}
+
+.crow2 {
+	background: #fcfcfc !important;
+}
+
+.text-hl {
+	font-weight: bold;
+}
+
+.noborder {
+	border: 0 !important;
+}
+
+.nopad-bottom {
+	padding-bottom: 0 !important;
+}
+
+.nomargin-bottom {
+	margin-bottom: 0 !important;
+}
+
+.nowrap {
+	white-space: nowrap !important;
+}
+
+.file-icon {
+	padding: 2px 20px 2px 0;
+	background: transparent url(http://media-cdn.bitbucket.org/img/icons/fugue/document.png) no-repeat right 50%;
+}
+
+.global-divider-header {
+	width: 920px;
+	margin: 0 auto;
+	padding: 0;
+}
+
+.global-divider-header h3, .global-divider-header h2 {
+	margin-bottom: 7px !important;
+}
+
+.global-event-wrapper {
+	padding: 6px 7px;
+	border: 1px solid #ddd;
+	background: #fff;
+}
+
+.follow, .following, .following-user {
+	padding-left: 20px !important;
+}
+
+.follow {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/heart__plus.png') no-repeat left 50% !important;
+}
+
+.following {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/heart.png') no-repeat left 50% !important;
+}
+
+.following-user {
+	background: transparent url(http://media-cdn.bitbucket.org/img/icons/silver/user.gif) no-repeat left 50%;
+}
+
+.avatar {
+	width: 32px;
+	height: 32px;
+	border: 1px solid #ece4ea;
+	padding: 2px;
+}
+
+.left {
+	float: left;
+	margin-right: 10px !important;
+}
+
+.right {
+	float: right;
+	margin-left: 10px !important;
+}
+
+.info-box-lightgrey {
+	font-size: 1em;
+	line-height: 1.4em;
+	background: #eee;
+	border: 1px solid #ddd;
+	padding: 15px 20px;
+	margin-bottom: 10px;
+}
+
+.scroll-x {
+	overflow-x: auto;
+}
+
+.button-repo-new {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/plus.png') no-repeat 10px 50%;
+}
+
+/* INBOX ICON */
+.inbox-no-messages {
+	padding-left: 19px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/mail.png') no-repeat left 50%;
+}
+
+.inbox-has-messages {
+	padding-left: 19px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/mail_open.png') no-repeat left 50%;
+}
+
+/* WARNINGS AND INFO */
+.warning {
+	padding: 15px 40px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/icons/orange/warning.gif) no-repeat 15px 15px;
+	border: 1px solid #ddd;
+	margin-bottom: 10px;
+}
+
+.errors {
+	padding: 15px 40px;
+	background: #eee url(http://media-cdn.bitbucket.org/img/icons/fugue/exclamation.png) no-repeat 15px 15px;
+	border: 1px solid #ddd;
+	margin-bottom: 10px;
+}
+
+.errors .error {
+	display: block;
+	padding-top: 3px;
+}
+
+.errorlist {
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+}
+
+.errorlist ul {
+	list-style-type: none;
+	padding: 0 !important;
+	margin: 0 !important;
+}
+
+.errorlist li {
+	padding: 2px 0 2px 20px !important;
+	background: transparent url(http://media-cdn.bitbucket.org/img/icons/red/warning.gif) no-repeat left 50%;
+	margin: 5px 0;
+	font: 12px "Lucida Grande", Verdana, Arial, sans-serif !important;
+}
+
+.info-paragraph {
+	padding-bottom: 0;
+}
+
+/* HEADER */
+#header-wrapper {
+	width: 960px;
+	height: 60px;
+	margin: 0 auto 10px auto;
+	position: relative;
+}
+
+#header-wrapper #header-wrapper-logo {
+	float: left;
+	margin-top: 5px;
+	margin-left: 0;
+}
+
+#header-wrapper #header, #header-wrapper #header-loggedin {
+	width: 960px;
+	height: 66px;
+	margin: 0 auto;
+	position: relative;
+}
+
+#header-wrapper #header h1, #header-wrapper #header-loggedin h1 {
+	display: block;
+	margin: 0 0 0 70px;
+	padding-top: 16px;
+	font: 24px "Lucida Grande", "Trebuchet MS", Tahoma, Arial, sans-serif;
+	letter-spacing: -1px;
+}
+
+#header-wrapper .header-wrapper-slogan {
+	color: #969696;
+	padding-left: 85px;
+}
+
+#header-wrapper #header #header-nav {
+	position: absolute;
+	top: 15px;
+	right: 0;
+	width: 700px;
+	text-align: right;
+}
+
+#header-wrapper #header-loggedin #header-nav {
+	position: absolute;
+	top: 15px;
+	right: 52px;
+	border: 1px solid transparent;
+}
+
+#header-wrapper #header #header-nav a, #header-wrapper #header-loggedin #header-nav a {
+	font-size: 1.1em;
+	color: #525252;
+}
+
+/* HEADER LOGGED IN */
+#header-wrapper #header-loggedin #userimage {
+	border: 1px solid #ccc;
+	padding: 1px;
+	position: absolute;
+	top: 5px;
+	right: 0;
+	width: 35px;
+	height: 35px;
+}
+
+#header-nav ul {
+	margin: -5px 0 0 0;
+	padding: 0;
+	list-style-type: none;
+}
+
+#header-nav > ul > li {
+	float: left;
+	margin-left: 5px;
+	position: relative;
+	padding: 5px 6px;
+}
+
+#header-nav ul li ul {
+	display: none;
+	margin: 0;
+	padding: 0;
+	list-style-type: none;
+	position: absolute;
+	top: 24px;
+	left: -1px;
+	background: #fff;
+	z-index: 200 !important;
+	border-left: 1px solid #ccc;
+	border-bottom: 1px solid #747474;
+	border-right: 1px solid #747474;
+}
+
+#header-nav ul li ul li {
+	padding: 4px 8px;
+	margin-left: 0;
+	position: relative;
+}
+
+#header-nav ul li.has-dropdown a {
+	padding: 2px 20px 2px 0;
+	background: transparent url(http://media-cdn.bitbucket.org/img/dropdown-off.png) no-repeat right 50%;
+}
+
+#header-nav ul li.has-dropdown-on {
+	background: #fff;
+	border-left: 1px solid #ccc;
+	border-top: 1px solid #ccc;
+	border-right: 1px solid #747474;
+	margin-top: -1px;
+	margin-right: -1px;
+	margin-left: 4px !important;
+}
+
+#header-nav ul li.has-dropdown-on a {
+	background: #fff url(http://media-cdn.bitbucket.org/img/dropdown-on.png) no-repeat right 50%;
+}
+
+#header-nav ul li ul li a {
+	white-space: nowrap;
+	color: #525252 !important;
+	display: block;
+	background: #fff !important;
+	padding: 0 20px 2px 0 !important;
+}
+
+#header-nav ul li ul li img {
+	position: absolute;
+	right: 8px;
+	top: 3px;
+}
+
+#header-nav form {
+	margin: 0 0 5px 0;
+	padding: 0;
+}
+
+#header-nav-login-forms {
+	float: right;
+	margin-top: -13px;
+}
+
+#header-nav-login-forms form {
+	margin: 0 0 0 10px !important;
+	padding: 0 !important;
+}
+
+#header-nav form input.login {
+	font-size: 0.9em !important;
+	width: 58px;
+	height: 16px;
+	padding-top: 1px !important;
+	padding-bottom: 1px !important;
+	color: #999 !important;
+	border: 1px solid #ccc;
+}
+
+#header-nav form input.openid {
+	font-size: 0.9em !important;
+	width: 111px;
+	height: 16px;
+	padding-top: 1px !important;
+	padding-bottom: 1px !important;
+	color: #999 !important;
+	margin-top: 3px;
+	border: 1px solid #ccc;
+}
+
+#header-nav form input.normaltext {
+	color: #525252 !important;
+}
+
+.header-nav-login-separator {
+	display: block;
+	margin: 3px 0;
+	padding: 0;
+}
+
+.header-nav-repo {
+	z-index: 10;
+	clear: right;
+}
+
+.header-nav-repo-button-new {
+	border-top: 1px solid #ddd;
+}
+
+.header-nav-repo-segmenter-theirs {
+	font-size: 0.9em;
+	margin-left: 8px;
+	font-style: italic;
+}
+
+/* MAIN WRAPPER */
+#main-wrapper {
+	min-height: 100%;
+	height: auto !important;
+	height: 100%;
+}
+
+/* CONTENT */
+#content-wrapper {
+	width: 960px;
+	margin: 0 auto;
+}
+
+#content-wrapper h2 {
+	font: 1.3em "Lucida Grande", "Trebuchet MS", Tahoma, Arial, sans-serif;
+	letter-spacing: -1px;
+	margin-bottom: 8px;
+}
+
+#content-wrapper h3 {
+	font: 1.3em "Lucida Grande", "Trebuchet MS", Tahoma, Arial, sans-serif;
+	letter-spacing: -1px;
+}
+
+#content-wrapper .container {
+	width: 960px;
+	margin: 0 auto;
+}
+
+#content-wrapper #twocols1, #content-wrapper #twocols2 {
+	width: 470px;
+	float: left;
+}
+
+#content-wrapper #twocols21 {
+	width: 200px;
+	float: right;
+	text-align: right;
+}
+
+#twocols21 ul {
+    list-style-type: none;
+}
+
+#content-wrapper #twocols1 {
+	margin-right: 20px;
+}
+
+/* FOOTER */
+.footer-placeholder {
+	height: 71px;
+}
+
+#footer-wrapper {
+	clear: both;
+	width: 100%;
+	height: 71px;
+	background: #fff url('http://media-cdn.bitbucket.org/img/layout/bg_footer.gif') repeat-x left top;
+	margin: -71px 0 0 0;
+}
+
+#footer-wrapper #footer {
+	width: 960px;
+	height: 44px;
+	padding-top: 27px;
+	margin: 0 auto;
+	position: relative;
+    text-align: center;
+}
+
+#footer-wrapper #footer a {
+	font-size: 1.1em;
+	color: #525252 !important;
+}
+
+/* REPOSITORY HEADER/MENU (tabs.html) */
+#repo-menu {
+	width: 918px;
+	margin: 0 auto;
+	padding: 8px 20px;
+	border-left: 1px solid #ddd;
+	border-right: 1px solid #ddd;
+	border-bottom: 1px solid #ddd;
+	background: #fff;
+	z-index: 1 !important;
+}
+
+#repo-menu-links {
+	float: right;
+	padding-top: 2px;
+	z-index: 1 !important;
+}
+
+#repo-menu-links-mini {
+	display: none;
+}
+
+#repo-menu-links-mini > ul > li > a {
+	width: 16px;
+	height: 16px;
+	float: left;
+	margin-left: 7px;
+	margin-right: 0;
+	padding-right: 0 !important;
+	padding-left: 0 !important;
+}
+
+.link-request-pull,
+.link-fork,
+.link-hack,
+.link-hack-pq,
+.link-follow,
+.link-download,
+.repo-menu-atom,
+.repo-menu-rss {
+	display: block;
+	padding: 2px 0 2px 20px;
+	margin-right: 15px;
+	color: #525252 !important;
+	text-decoration: none !important;
+}
+
+.repo-avatar {
+	margin-left: 20px !important;
+	padding-top: 2px;
+}
+
+/* REPO MENU ICONS */
+.repo-menu-rss {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/feed.png') no-repeat left 50%;
+}
+
+.repo-menu-atom {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/atom.png') no-repeat left 50%;
+}
+
+.link-request-pull {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/arrow_180.png') no-repeat left 50%;
+}
+
+.link-fork {
+	z-index: 1 !important;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/arrow_045.png') no-repeat left 50%;
+}
+
+.link-hack {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/bandaid.png') no-repeat left 50%;
+	z-index: 1 !important;
+}
+
+.link-follow {
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/heart__plus.png') no-repeat left 50%;
+}
+
+.link-download {
+	margin-right: 0 !important;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/download-arrow.png') no-repeat left 50%;
+}
+
+#repo-menu-links-mini ul, #repo-menu-links ul {
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+	z-index: 5 !important;
+}
+
+#repo-menu-links-mini ul li, #repo-menu-links ul li {
+	float: left;
+	position: relative;
+	z-index: 5 !important;
+}
+
+#repo-menu-links-mini ul li ul, #repo-menu-links ul li ul {
+	display: none;
+	border: 1px solid #ddd;
+	width: 110px;
+	z-index: 5 !important;
+}
+
+#repo-menu-links-mini ul li:hover ul, #repo-menu-links ul li:hover ul {
+	display: block;
+	position: absolute;
+	top: 18px;
+	left: 0;
+}
+
+#repo-menu-links-mini ul li ul li, #repo-menu-links ul li ul li {
+	clear: left;
+	z-index: 5 !important;
+	background: #fff url(http://media-cdn.bitbucket.org/img/layout/bg_lists_small.png) repeat-x left top;
+}
+
+#repo-menu-links-mini ul li ul li:hover, #repo-menu-links ul li ul li:hover {
+	background: #fff !important;
+}
+
+#repo-menu-links-mini ul li a.compressed, #repo-menu-links ul li a.compressed {
+	display: block;
+	width: 87px;
+	padding: 5px 0 5px 23px;
+	border-top: 1px solid #ddd;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/document_zipper.png') no-repeat 3px 50%;
+}
+
+#repo-menu-links-mini ul li a.zip, #repo-menu-links ul li a.zip {
+	display: block;
+	width: 87px;
+	padding: 5px 0 5px 23px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/document_zipper.png') no-repeat 3px 50%;
+}
+
+#repo-desc {
+	position: relative;
+	margin: 0 auto 10px auto;
+	padding-top: 13px !important;
+	padding-bottom: 13px !important;
+	border-top: 0 !important;
+	font-size: 1.1em;
+	line-height: 1.4em;
+	z-index: 1 !important;
+	background: #fff;
+}
+
+#repo-desc pre {
+	padding: 0 !important;
+	margin: 3px 0 !important;
+}
+
+#repo-desc h3 {
+	display: inline;
+}
+
+#repo-desc h3 span {
+	font-size: 0.8em !important;
+}
+
+.repo-desc-description {
+	padding-top: 2px !important;
+	padding-bottom: 2px !important;
+	width: 750px;
+}
+
+.repo-desc-private {
+	border-top: 0 !important;
+	background: #fff !important;
+}
+
+#repo-tag {
+	height: 30px;
+	padding: 0;
+	position: absolute;
+	top: 0;
+	right: 20px;
+}
+
+#repo-tag .blue-left {
+	height: 30px;
+	width: 7px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/blue_left.png) no-repeat left top;
+	float: left;
+}
+
+#repo-tag .blue-content {
+	height: 25px;
+	padding: 5px 15px 0 15px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/blue_content.png) repeat-x left top;
+	float: left;
+}
+
+#repo-tag .blue-right {
+	height: 30px;
+	width: 7px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/blue_right.png) no-repeat left top;
+	float: left;
+}
+
+#repo-tag .green-left {
+	height: 30px;
+	width: 7px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/green_left.png) no-repeat left top;
+	float: left;
+}
+
+#repo-tag .green-content {
+	height: 25px;
+	padding: 5px 15px 0 15px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/green_content.png) repeat-x left top;
+	float: left;
+}
+
+#repo-tag .green-right {
+	height: 30px;
+	width: 7px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/rounded/green_right.png) no-repeat left top;
+	float: left;
+}
+
+#repo-menu-branches-tags ul {
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+	z-index: 10 !important;
+}
+
+#repo-menu-branches-tags ul li {
+	float: left;
+	position: relative;
+	z-index: 10 !important;
+}
+
+#repo-menu-branches-tags ul li ul {
+	display: none;
+	border: 1px solid #ddd;
+	border-top: 0;
+	background: #eee;
+	width: 150px;
+	z-index: 10 !important;
+}
+
+#repo-menu-branches-tags ul li:hover ul {
+	display: block;
+	position: absolute;
+	top: 20px;
+	left: 0;
+}
+
+#repo-menu-branches-tags ul li ul li {
+	clear: left;
+	z-index: 20 !important;
+	background: #fff url(http://media-cdn.bitbucket.org/img/layout/bg_lists_small.png) repeat-x left top !important;
+}
+
+#repo-menu-branches-tags ul li ul li:hover {
+	background: #fff !important;
+}
+
+#repo-menu-branches-tags ul li a {
+	display: block;
+	width: 130px;
+	padding: 5px 10px;
+	border-top: 1px solid #ddd;
+}
+
+#repo-menu-branches-tags ul li.icon-branches {
+	z-index: 20 !important;
+	padding: 3px 8px 3px 23px;
+	margin-right: 5px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/arrow_135.png') no-repeat 3px 50%;
+}
+
+#repo-menu-branches-tags ul li.icon-tags {
+	padding: 3px 8px 3px 23px;
+	background: transparent url('http://media-cdn.bitbucket.org/img/icons/fugue/tags.png') no-repeat 3px 50%;
+}
+
+#toggle-repo-content {
+	display: block;
+	width: 958px;
+	height: 5px;
+	background-color: #eee;
+	border-top: 1px solid #ddd;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	text-align: center;
+}
+
+#toggle-repo-content:hover {
+	background-color: #ddd;
+}
+
+/* SOURCE LIST/TABLE */
+#sourcelist {
+	margin: 0 auto 10px auto;
+	width: 960px;
+	font-size: 1.1em;
+	border: 1px solid #ddd;
+	border-bottom: 0;
+}
+
+th.sourcelist-icon {
+	width: 50px;
+}
+
+th.sourcelist-filename {
+	width: 200px;
+}
+
+th.sourcelist-modified, td.sourcelist-modified {
+	width: 120px;
+	padding-left: 10px;
+}
+
+th.sourcelist-filesize, td.sourcelist-filesize {
+	width: 75px;
+	border-right: 1px solid #ddd;
+	padding-right: 10px;
+	text-align: right;
+}
+
+th.sourcelist-message {
+	width: 550px;
+}
+
+td.sourcelist-img img {
+	margin-left: 20px;
+}
+
+td.sourcelist-mp {
+	font: 12px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+}
+
+td.apply-mq-patch {
+	padding-right: 8px;
+}
+
+#readme-viewer {
+	font-size: 1.1em;
+	margin: 0 auto;
+	padding: 0;
+	border: 0;
+	width: 920px !important;
+}
+
+#readme-viewer h1 {
+	margin-bottom: 15px !important!
+}
+
+#source-path {
+	font-size: 1.1em;
+	font-weight: bold;
+	border-bottom: 0;
+	margin: 0 auto;
+}
+
+#view-at {
+	font-size: 0.85em;
+	margin: -4px -10px 0 0;
+}
+
+#view-at input {
+	margin-left: 4px;
+	font-size: 0.85em !important;
+	width: 120px;
+}
+
+.info-table {
+	width: 100%;
+	background: #fafafa;
+	border-bottom: 1px solid #ddd;
+}
+
+.info-table th {
+	vertical-align: middle !important;
+}
+
+/* SHORTLOG/OVERVIEW */
+#shortlogs-changes {
+	border: 1px solid #ddd;
+	border-bottom: 0 !important;
+	width: 960px;
+	margin: 0 auto;
+}
+
+.shortlogs-changes-diff {
+	width: 1px;
+	text-align: center;
+}
+
+.shortlogs-cna {
+	text-align: right;
+}
+
+.diff-radio input, .shortlogs-changes-diff input {
+	padding: 0;
+	margin: 0;
+}
+
+.changes-added, .changes-modified, .changes-removed {
+	display: block;
+	font-size: 0.75em;
+	text-align: center;
+	width: 15px;
+	float: right;
+	border: 1px solid #ddd;
+}
+
+.changes-added, .changes-modified {
+	border-right: 0 !important;
+}
+
+.changes-removed, .changes-modified {
+	border-left: 0 !important;
+}
+
+.shortlogs-changes-added span, .shortlogs-changes-modified span, .shortlogs-changes-deleted-box span {
+	display: block;
+	font-size: 0.8em;
+	text-align: center;
+	border: 1px solid #ddd;
+	width: 15px;
+	float: right;
+}
+
+.shortlogs-changes-added span, .changes-added {
+	background: #bbffbb;
+	text-align: center;
+}
+
+.shortlogs-changes-modified span, .changes-modified {
+	background: #ffdd88;
+	text-align: center;
+}
+
+.shortlogs-changes-deleted-box span, .changes-removed {
+	background: #ff8888;
+	text-align: center;
+}
+
+.shortlogs-changes-date {
+	padding-left: 15px;
+}
+
+th.shortlogs-changes-diff-head {
+	width: 1px;
+	padding-right: 5px;
+}
+
+.shortlogs-changes-deleted span {
+	margin-right: 10px;
+}
+
+.shortlogs-changes-date {
+	width: 80px;
+}
+
+.shortlogs-changes-author {
+	width: 140px;
+}
+
+.shortlogs-changes-msg {
+	width: 560px;
+}
+
+.shortlogs-changes-added, .shortlogs-changes-modified, .shortlogs-changes-deleted {
+	width: 20px;
+}
+
+.shortlogs-changes-diff-head {
+	width: 30px;
+}
+
+.shortlogs-changes-avatar {
+	width: 20px;
+	text-align: left;
+}
+
+.shortlogs-changes-avatar img {
+	width: 14px;
+	padding-top: 2px;
+}
+
+/* CHANGESET */
+#changeset-changed {
+	font-size: 1.1em;
+	margin: 0 auto;
+}
+
+#changeset-diff {
+	font-size: 1.1em;
+	width: 960px;
+	margin: 0 auto;
+}
+
+#changeset-diff div {
+	width: 958px;
+	border: 1px solid #ddd;
+	margin-bottom: 10px;
+}
+
+#changeset-diff p {
+	padding: 15px 20px;
+}
+
+#changeset-diff .changeset-top-link {
+	padding: 2px 0 2px 20px;
+	background: transparent url(http://media-cdn.bitbucket.org/img/icons/fugue/arrow_skip_090.png) no-repeat left 50%;
+}
+
+#changeset-diff table {
+	font-size: 1em !important;
+	width: 958px;
+	border-collapse: collapse;
+}
+
+#changeset-diff table td {
+	padding: 2px 3px;
+	margin: 0;
+}
+
+#changeset-diff table td.linenr {
+	background: #eee !important;
+	border-right: 1px solid #ddd;
+	padding-right: 5px;
+	padding-left: 5px;
+	width: 25px;
+	text-align: right;
+}
+
+#changeset-diff table td:target + td.code, #changeset-diff table td:target + td + td.code {
+	background: #ffffbe !important;
+}
+
+#changeset-diff table td.hashnr {
+	width: 80px;
+	padding-left: 2px;
+	border-right: 1px solid #ddd;
+}
+
+#changeset-diff table td.linenr pre {
+	width: 25px;
+	text-align: right;
+	color: #747474 !important;
+	font: 11px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace !important;
+	letter-spacing: -1px;
+}
+
+#changeset-diff table td pre {
+	padding: 0;
+	margin: 0;
+}
+
+#changeset-diff table td.linenr a {
+	color: #747474 !important;
+}
+
+#changeset-diff tr.ellipsis {
+    background: #efefef;
+    text-align: center;
+}
+
+#changeset-diff tr.linecontext {
+	text-align: left !important;
+	font: 11px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace !important;
+	color: #747474 !important;
+	margin: 3px;
+	letter-spacing: -1px;
+	background: #dde7ef;
+}
+
+#changeset-diff tr.del {
+	background: #ffdddd;
+}
+
+#changeset-diff tr.add {
+	background: #ddffdd;
+}
+
+#changeset-diff tr.add ins {
+    background: #aaffaa;
+    text-decoration: none;
+}
+
+#changeset-diff tr.del del {
+    background: #ffaaaa;
+    text-decoration: none;
+}
+
+/* CHANGESETS */
+#changesets-desc {
+	padding: 0 0 10px 0;
+}
+
+.changesets-date {
+	font-size: 1.1em;
+	font-weight: bold;
+	border-bottom: 0;
+	background: #fff url(http://media-cdn.bitbucket.org/img/layout/bg_general.png) repeat-x left top;
+	margin: 0 auto;
+	padding: 10px 20px !important;
+}
+
+.changeset-description {
+	white-space: pre-wrap;
+	width: 710px;
+	margin-bottom: 10px;
+}
+
+.chg-more-less {
+	float: right;
+	padding: 4px;
+	font-size: 0.85em;
+	margin-right: 40px;
+}
+
+#changesets-outer {
+	width: 962px;
+	margin: 0 auto;
+}
+
+#changesets-graph {
+	float: left;
+	margin-top: 20px;
+	width: 160px;
+	margin-left: -160px;
+}
+
+.changesets-changeset {
+	border-bottom: 0;
+	font: 12px/16px "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+	margin: 0 auto;
+}
+
+.changesets-changeset .dropdown-container {
+	margin-top: -10px !important;
+}
+
+.changesets-changeset h4 {
+	font-size: 1em;
+	font-weight: bold;
+}
+
+.changesets-changeset span {
+	font-family: "Lucida Grande", "Trebuchet MS", Tahoma, Arial, sans-serif;
+}
+
+#changesets-footer {
+	margin: 0 auto;
+}
+
+.changesets-footer-nopages {
+	border-left: 0 !important;
+	border-bottom: 0 !important;
+	border-right: 0 !important;
+}
+
+/* CODE VIEWS */
+.highlighttable {
+	border-collapse: collapse;
+	width: 958px;
+}
+
+.highlighttable .linenos {
+	background: #eee;
+	border-right: 1px solid #ddd;
+	padding-left: 5px;
+	padding-right: 5px;
+	width: 20px;
+	text-align: right;
+}
+
+.highlighttable .code {
+	padding-left: 3px;
+	padding-right: 3px;
+	width: 938px;
+}
+
+.highlighttable .code-diff {
+	border: 1px solid #ddd;
+	padding: 0;
+	width: 920px;
+}
+
+.highlighttable pre {
+	line-height: 1.4em !important;
+}
+
+.removed, .added, .command {
+	padding: 0;
+	margin: 0;
+}
+
+.removed {
+	background: #ffdddd;
+}
+
+.added {
+	background: #ddffdd;
+}
+
+.command {
+	background: #dde7ef;
+}
+
+/* SOURCEVIEWER */
+#source-view {
+	width: 960px;
+	border: 1px solid #ddd;
+	margin: 0 auto;
+}
+
+.source-view-links {
+	text-align: right;
+}
+
+.source-view-form {
+	display: inline;
+}
+
+#inline-image {
+	padding: 14px;
+}
+
+#inline-image img {
+	margin-bottom: 5px;
+}