From 6968dc34e9e6b00b08d312385011bbaac68c5a49 Mon Sep 17 00:00:00 2001 From: Sebastian Silva Date: Fri, 09 Dec 2011 00:20:29 +0000 Subject: Moved to devel version of Hatta. Implemented shutdown. --- (limited to 'websdk/hatta/page.py') diff --git a/websdk/hatta/page.py b/websdk/hatta/page.py new file mode 100644 index 0000000..3992552 --- /dev/null +++ b/websdk/hatta/page.py @@ -0,0 +1,656 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import difflib +import mimetypes +import re +import os + +import werkzeug +import werkzeug.contrib.atom + +pygments = None +try: + import pygments + import pygments.util + import pygments.lexers + import pygments.formatters + import pygments.styles +except ImportError: + pass + +Image = None +try: + import Image +except ImportError: + pass + +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 + + +def date_html(date_time): + """ + Create HTML for a date, according to recommendation at + http://microformats.org/wiki/date + """ + + return date_time.strftime( + '%Y-%m-%d %H:%M') + + +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 + if self.wiki.alias_page and self.wiki.alias_page in self.storage: + self.aliases = dict( + self.index.page_links_and_labels(self.wiki.alias_page)) + else: + self.aliases = {} + + def link_alias(self, addr): + """Find a target address for an alias.""" + + try: + alias, target = addr.split(':', 1) + except ValueError: + return self.wiki.alias_page + try: + pattern = self.aliases[alias] + except KeyError: + return self.wiki.alias_page + try: + link = pattern % target + except TypeError: + link = pattern + target + return link + + 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): + classes.append('external') + if addr.startswith('mailto:'): + # Obfuscate e-mails a little bit. + classes.append('mail') + text = text.replace('@', '@').replace('.', '.') + href = werkzeug.escape(addr, + quote=True).replace('@', '%40').replace('.', '%2E') + else: + href = werkzeug.escape(werkzeug.url_fix(addr), quote=True) + else: + if '#' in addr: + addr, chunk = addr.split('#', 1) + chunk = '#' + werkzeug.url_fix(chunk) + if addr.startswith(':'): + alias = self.link_alias(addr[1:]) + href = werkzeug.escape(werkzeug.url_fix(alias) + chunk, True) + classes.append('external') + classes.append('alias') + elif addr.startswith('+'): + href = '/'.join([self.request.script_root, + '+' + werkzeug.escape(addr[1:], quote=True)]) + classes.append('special') + elif addr == u'': + href = werkzeug.escape(chunk, True) + classes.append('anchor') + else: + classes.append('wiki') + href = werkzeug.escape(self.get_url(addr) + chunk, True) + if addr not in self.storage: + classes.append('nonexistent') + class_ = werkzeug.escape(' '.join(classes) or '', True) + # We need to output HTML on our own to prevent escaping of href + return '%s' % ( + href, class_, werkzeug.escape(addr + chunk, True), + image or text) + + 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) + elif addr.startswith(':'): + if chunk: + chunk = '#' + chunk + alias = self.link_alias(addr[1:]) + href = werkzeug.url_fix(alias + chunk) + return html.img(src=href, class_="external alias", alt=alt) + elif 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 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 template(self, template_name, **kwargs): + template = self.wiki.template_env.get_template(template_name) + edit_url = None + if self.title: + try: + self.wiki._check_lock(self.title) + edit_url = self.get_url(self.title, self.wiki.edit) + except error.ForbiddenErr: + pass + context = { + 'request': self.request, + 'wiki': self.wiki, + 'title': self.title, + 'mime': self.mime, + 'url': self.get_url, + 'download_url': self.get_download_url, + 'config': self.config, + 'page': self, + 'edit_url': edit_url, + } + context.update(kwargs) + stream = template.stream(**context) + stream.enable_buffering(10) + return stream + + 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 + + def render_editor(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 + return self.template('edit_file.html', comment=comment, + author=author, parent=rev) + + +class WikiPageSpecial(WikiPage): + """Special pages, like recent changes, index, etc.""" + + +class WikiPageText(WikiPage): + """Pages of mime type text/* use this for display.""" + + def content_iter(self, lines): + yield '
'
+        for line in lines:
+            yield werkzeug.html(line)
+        yield '
' + + 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 render_editor(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: + return werkzeug.html.p(werkzeug.html(_(unicode(e)))) + if preview: + lines = preview + comment = self.request.form.get('comment', comment) + return self.template('edit_text.html', comment=comment, + preview=preview, + author=author, parent=rev, lines=lines) + + 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'
'
+        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'
' % 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'%s' % 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'%s' % 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'
' + else: + yield u'
%s
' % ( + line_no, werkzeug.escape(old_text)) + yield u'
' + + +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, '
'
+            for lineno, line in source:
+                yield (lineno,
+                       werkzeug.html.span(line, id_="line_%d" %
+                                         formatter.line_no))
+                formatter.line_no += 1
+            yield 0, '
' + + 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, self.wiki.alias_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 = ['

Download %s as %s.

' % + (self.request.get_download_url(self.title), + werkzeug.escape(self.title), self.mime)] + return content + + +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 = ['%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'' % html_title + try: + for row in reader: + yield u'%s' % (u''.join(u'' % cell + for cell in row)) + except csv.Error, e: + yield u'
%s
' + yield werkzeug.html.p(werkzeug.html( + _(u'Error parsing CSV file %{file}s on' + u'line %{line}d: %{error}s') % + {'file': html_title, 'line': reader.line_num, 'error': e})) + finally: + csv_file.close() + yield u'' + + 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 '' + #tags = [tag.strip() for tag in + # attributes.get('tags', '').split() + # if tag.strip()] + yield '
' % (line_no) + in_bug = True + if title: + yield werkzeug.html.h2(werkzeug.html(title)) + if attributes: + yield '
' + for attribute, value in attributes.iteritems(): + yield werkzeug.html.dt(werkzeug.html(attribute)) + yield werkzeug.html.dd(werkzeug.html(value)) + yield '
' + 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 '
' + +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') -- cgit v0.9.1