Fix browser support.
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 20 Jan 2010 02:07:49 +0000 (12:37 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 20 Jan 2010 02:07:49 +0000 (12:37 +1030)
Makefile-web
web/bzrbrowse/bzrbrowse.cgi [new file with mode: 0755]
web/bzrbrowse/file.png [new file with mode: 0644]
web/bzrbrowse/folder.png [new file with mode: 0644]

index 2258584f921ec524a0acf07888ca18098c5cabb0..8d1f0c691d297c82782ae2a43ee9ddf41d4b7532 100644 (file)
@@ -13,7 +13,7 @@ upload: check webpages
        bzr push
        send-web # Rusty's upload script.
 
-webpages: $(WEB_SUBDIRS) $(WEBDIR)/junkcode $(WEBDIR)/list.html $(WEBDIR)/index.html $(WEBDIR)/upload.html $(WEBDIR)/uploader.php $(WEBDIR)/example-config.h $(WEBDIR)/ccan.jpg $(DIRECT_TARBALLS) $(DEPEND_TARBALLS) $(WEBDIR)/ccan.tar.bz2 $(WEBDIR)/Makefile-ccan $(ALL_PAGES) $(JUNKPAGES) $(JUNKBALLS)
+webpages: $(WEB_SUBDIRS) $(WEBDIR)/junkcode $(WEBDIR)/list.html $(WEBDIR)/index.html $(WEBDIR)/upload.html $(WEBDIR)/uploader.php $(WEBDIR)/example-config.h $(WEBDIR)/ccan.jpg $(BZRBROWSE) $(DIRECT_TARBALLS) $(DEPEND_TARBALLS) $(WEBDIR)/ccan.tar.bz2 $(WEBDIR)/Makefile-ccan $(ALL_PAGES) $(JUNKPAGES) $(JUNKBALLS)
 
 $(WEB_SUBDIRS):
        mkdir -p $@
@@ -53,6 +53,10 @@ $(WEBDIR)/Makefile-ccan: Makefile-ccan
 $(WEBDIR)/ccan.jpg: web/ccan.jpg
        cp $< $@
 
+BZRBROWSE=$(WEBDIR)/bzrbrowse.cgi $(WEBDIR)/file.png $(WEBDIR)/folder.png
+$(BZRBROWSE): %: web/bzrbrowse/%
+       cp $< $@
+
 $(WEBDIR)/info/%.html: $(WEBDIR)/tarballs/%.tar.bz2 $(WEBDIR)/tarballs/with-deps/%.tar.bz2
        @URLPREFIX=../ php5 web/staticmoduleinfo.php ccan/$* > $@
 
diff --git a/web/bzrbrowse/bzrbrowse.cgi b/web/bzrbrowse/bzrbrowse.cgi
new file mode 100755 (executable)
index 0000000..334ce50
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2008 Lukas Lalinsky <lalinsky@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+# CHANGE THIS:
+config = {
+    'root': '/home/lukas/projects',
+    'base_url': '/~lukas/bzrbrowse.cgi',
+    'images_url': '/~lukas/bzrbrowse',
+    'branch_url': 'http://bzr.oxygene.sk',
+}
+
+import os, sys
+from bzrlib.branch import Branch
+from bzrlib.errors import NotBranchError
+from bzrlib import urlutils, osutils
+
+__version__ = '0.0.1'
+
+
+class HTTPError(Exception):
+
+    def __init__(self, code, message):
+        self.code = code
+        self.message = message
+
+
+class NotFound(HTTPError):
+
+    def __init__(self, message):
+        super(NotFound, self).__init__('404 Not Found', message)
+
+
+def escape_html(text):
+    return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace("\n", '<br />')
+
+
+class BzrBrowse(object):
+
+    icons = {
+        'file': 'file.png',
+        'directory': 'folder.png',
+    }
+
+    page_tmpl = '''<!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><title>%(title)s</title><style type="text/css">
+body { font-family: sans-serif; font-size: 10pt; }
+div#page { padding: 0.5em; border: solid 1px #444; background: #FAFAFA; }
+#footer { font-size: 70%%; background-color: #444; color: #FFF; margin: 0; padding: 0.1em 0.3em; }
+#footer hr { display: none; }
+h1 { margin: 0; font-size: 14pt; background-color: #444; color: #FFF; padding: 0.1em 0.3em; }
+h1 a { color: #FFF; }
+h1 a:hover { color: #8CB1D8; }
+#page a { color: #244B7C; }
+#page a:hover { color: #B12319; }
+pre { margin: 0; font-size: 90%%; }
+.linenumbers { text-align: right; padding-right: 0.5em; border-right: solid 1px #444; }
+.text { padding-left: 0.5em; }
+.msg { margin: 0; margin-bottom: 0.5em; padding: 0.3em 0em; border-bottom: solid 1px #444;}
+code { background-color: #000; color: #FFF; font-size: 90%%;}
+</style>
+</head><body>
+<h1>%(header)s</h1>
+<div id="page">%(contents)s</div>
+<div id="footer"><hr />bzrbrowse/%(version)s</div>
+</body></html>'''
+
+    def __init__(self, config):
+        self.config = config
+        self.base_url = None
+
+    def list_to_html(self, entries):
+        content = []
+        for entry in entries:
+            line = '<img src="%(images_url)s/%(icon)s" /> <a href="%(base_url)s/%(path)s">%(name)s</a><br />' % {
+                    'base_url': self.config['base_url'],
+                    'images_url': self.config['images_url'],
+                    'path': entry['path'],
+                    'name': entry['name'],
+                    'icon': self.icons.get(entry['kind'], self.icons['file'])
+                }
+            content.append(line)
+        return ''.join(content)
+
+    def list_fs_directory(self, path):
+        entries = []
+        if path:
+            entries.append({
+                'name': '..',
+                'path': os.path.dirname(path),
+                'kind': 'directory',
+            })
+        if path:
+            prefix = path + '/'
+        else:
+            prefix = ''
+        try:
+            filelist = os.listdir(os.path.join(self.config['root'], path))
+        except OSError:
+            raise NotFound('Path not found: ' + path)
+        for name in sorted(filelist):
+            if name.startswith('.'):
+                continue
+            abspath = os.path.join(path, name)
+            if os.path.isdir(os.path.join(self.config['root'], abspath)):
+                entries.append({
+                    'name': name,
+                    'path': prefix + name,
+                    'kind': 'directory',
+                })
+        return self.list_to_html(entries)
+
+    def view_branch_file(self, tree, ie):
+        if ie.text_size > 1024 * 1024:
+            return 'File too big. (%d bytes)' % (ie.text_size)
+        tree.lock_read()
+        try:
+            text = tree.get_file_text(ie.file_id)
+        finally:
+            tree.unlock()
+        if '\0' in text:
+            return 'Binary file. (%d bytes)' % (ie.text_size)
+        try:
+            text = text.decode('utf-8')
+        except UnicodeDecodeError:
+            text = text.decode('latin-1')
+        linenumbers = []
+        for i in range(1, text.count('\n') + 1):
+            linenumbers.append('<a id="l-%d" href="#l-%d">%d</a>' % (i, i, i))
+        linenumbers = '\n'.join(linenumbers)
+        return ('<table cellspacing="0" cellpadding="0"><tr><td class="linenumbers"><pre>' +
+                linenumbers + '</pre></td><td class="text"><pre>' + escape_html(text) +
+                '</pre></td></tr></table>')
+
+    def list_branch_directory(self, branch, path, relpath):
+        tree = branch.basis_tree()
+        file_id = tree.path2id(relpath)
+        ie = tree.inventory[file_id]
+        if ie.kind == 'file':
+            return self.view_branch_file(tree, ie)
+        entries = []
+        if path:
+            entries.append({
+                'name': '..',
+                'path': urlutils.dirname(path),
+                'kind': 'directory',
+            })
+        if path:
+            prefix = path + '/'
+        else:
+            prefix = ''
+        for name, child in sorted(ie.children.iteritems()):
+            entries.append({
+                'name': name,
+                'path': prefix + name,
+                'kind': child.kind,
+            })
+        html = self.list_to_html(entries)
+        base = self.config['branch_url'] + '/' + osutils.relpath(self.config['root'], urlutils.local_path_from_url(branch.base))
+        html = ('<p class="msg">This is a <a href="http://bazaar-vcs.org/">Bazaar</a> branch. ' +
+                'Use <code>bzr branch ' + base + '</code> to download it.</p>' + html)
+        return html
+
+    def request(self, path):
+        abspath = os.path.join(self.config['root'], path)
+        try:
+            branch, relpath = Branch.open_containing(abspath)
+        except NotBranchError:
+            return self.list_fs_directory(path)
+        return self.list_branch_directory(branch, path, relpath)
+
+    def title(self, path):
+        return '/' + path
+
+    def header(self, path):
+        title = []
+        p = ''
+        title.append('<a href="%s%s">root</a>' % (self.config['base_url'], p))
+        for name in path.split('/'):
+            p += '/' + name
+            title.append('<a href="%s%s">%s</a>' % (self.config['base_url'], p, name))
+        return '/'.join(title)
+
+    def __call__(self, environ, start_response):
+        try:
+            path = '/'.join(filter(bool, environ.get('PATH_INFO', '').split('/')))
+            contents = self.page_tmpl % {
+                'title': self.title(path),
+                'header': self.header(path),
+                'contents': self.request(path),
+                'version': __version__
+            }
+            contents = contents.encode('utf-8')
+            headers = [('Content-type','text/html; charset=UTF-8')]
+            start_response('200 OK', headers)
+            return [contents]
+        except HTTPError, e:
+            headers = [('Content-type','text/html; charset=UTF-8')]
+            start_response(e.code, headers)
+            return [e.message]
+        except:
+            import cgitb, sys
+            traceback_html = cgitb.html(sys.exc_info())
+            headers = [('Content-type','text/html; charset=UTF-8')]
+            start_response('200 OK', headers)
+            return [traceback_html]
+
+
+from wsgiref.handlers import CGIHandler
+CGIHandler().run(BzrBrowse(config))
diff --git a/web/bzrbrowse/file.png b/web/bzrbrowse/file.png
new file mode 100644 (file)
index 0000000..df545eb
Binary files /dev/null and b/web/bzrbrowse/file.png differ
diff --git a/web/bzrbrowse/folder.png b/web/bzrbrowse/folder.png
new file mode 100644 (file)
index 0000000..901edc9
Binary files /dev/null and b/web/bzrbrowse/folder.png differ