#!/usr/bin/env python # -*- coding: utf-8 -*- """ Instant File Provider ===================== Browse and serve local files via HTTP. Sole requirements are Python 2.5 and Werkzeug_ 0.4 as of this version. The first release (0.1, 24-Sep-2007) was based on Paste, Paste Deploy, Paste Script, and Genshi. Version 0.2.1 introduced the use of unicode objects for most paths. This results in unicode objects being returned from path functions and allows for handling files with non-ASCII characters. Usage:: python instantfileprovider.py runserver .. _Werkzeug: http://werkzeug.pocoo.org/ :Copyright: 2007-2009 Jochen Kupperschmidt :Date: 05-Jan-2009 :License: GNU General Public License :Version: 0.2.1 """ from fnmatch import fnmatch from itertools import starmap import os from werkzeug.exceptions import abort, HTTPException from werkzeug import script from werkzeug.utils import escape, SharedDataMiddleware, url_fix, xhtml from werkzeug.wrappers import BaseResponse as Response # The path you want to publish. DOCUMENT_ROOT = '/path/to/publish' # File patterns to exclude. HIDE = [u'.hg', u'.svn', u'*.pyc', u'*.pyo'] class Application(object): """The WSGI application""" def __init__(self, document_root, hide): self.document_root = os.path.abspath(document_root) self.hide = hide def __call__(self, environ, start_response): try: # Make sure the document root exists # (yes, on *every* request). if not os.path.isdir(self.document_root): abort(503, 'Document root does not exist,' 'please correct your setup.') path_info = environ.get('PATH_INFO', u'') path = os.path.abspath( os.path.join(self.document_root, path_info[1:])) if not os.path.isdir(path): # Directory doesn't exist. abort(404) # Fetch directory entries and return them as XHTML. entries = list(get_entries(path, self.hide)) xhtml = xhtmlize(path_info, entries) response = Response(xhtml, mimetype='text/html') except HTTPException, exc: response = exc return response(environ, start_response) def get_entries(path, hide): """Yield directory entries.""" for entry in os.listdir(path): # Check if the entry should be hidden. if not any(fnmatch(entry, pattern) for pattern in hide): # Categorize entry as directory or file. yield entry, os.path.isdir(os.path.join(path, entry)) def xhtmlize(path, entries): """Build a XHTML document to browse a directory.""" def generate_path_anchors(): stack = [] for segment in filter(None, path.split('/')): stack.append(segment) yield xhtml.a(stack[-1], href=u'/'.join([''] + map(url_fix, stack))) if not path.endswith(u'/'): path += u'/' # Add an entry point towards the higher directory. if path != u'/': entries.insert(0, (u'..', True)) # Sort directory entries by type (reversed), then by name. entries.sort(key=lambda entry: (not entry[1], entry[0])) def itemize(item, is_dir): label = escape(item) if is_dir: label = xhtml.strong(label + u'/') item += u'/' return xhtml.li(xhtml.a(label, href=url_fix(path + item))) return xhtml.html( xhtml.body( xhtml.h1(u'/' + u'/'.join(generate_path_anchors())), xhtml.ul(*list(starmap(itemize, entries))), ) ) def make_shared_app(): # The application needs a unicode path. app = Application(unicode(DOCUMENT_ROOT), HIDE) # The middleware is buggy so feed it a non-unicode string. app = SharedDataMiddleware(app, {'/': DOCUMENT_ROOT}) return app action_runserver = script.make_runserver(make_shared_app) if __name__ == '__main__': script.run()