Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Support handlers altering the set of files that appear in a directory listing. #115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions wptserve/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,34 @@ def __call__(self, request, response):
%(items)s
</ul>
""" % {"path": cgi.escape(url_path),
"items": "\n".join(self.list_items(url_path, path))} # flake8: noqa
"items": "\n".join(self.list_items(request, url_path, path))} # flake8: noqa

def list_items(self, base_path, path):
def list_items(self, request, base_path, path):
assert base_path.endswith("/")

# TODO: this won't actually list all routes, only the
# ones that correspond to a real filesystem path. It's
# not possible to list every route that will match
# something, but it should be possible to at least list the
# statically defined ones

if base_path != "/":
link = urljoin(base_path, "..")
yield ("""<li class="dir"><a href="%(link)s">%(name)s</a></li>""" %
{"link": link, "name": ".."})
for item in sorted(os.listdir(path)):
link = cgi.escape(quote(item))
if os.path.isdir(os.path.join(path, item)):
link += "/"
class_ = "dir"
else:
class_ = "file"
yield ("""<li class="%(class)s"><a href="%(link)s">%(name)s</a></li>""" %
{"link": link, "name": cgi.escape(item), "class": class_})

dir_items = set((item, os.path.isdir(os.path.join(path, item))) for item in os.listdir(path))
dirs = list(name for name, is_dir in dir_items if is_dir)
files = set(name for name, is_dir in dir_items if not is_dir)

for fn in reversed(request.router.listing_fixups):
files = fn(request, files)
files = list(files)

dirs.sort()
files.sort()

template = """<li class="%(class)s"><a href="%(link)s">%(name)s</a></li>"""

for class_, item_list in [("dir", dirs),
("file", files)]:
for item in item_list:
link = cgi.escape(quote(item))
yield template % {"link": link, "name": cgi.escape(item), "class": class_}


def wrap_pipeline(path, request, response):
Expand Down
2 changes: 2 additions & 0 deletions wptserve/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,11 @@ class Request(object):
.. attribute:: server

Server object containing information about the server environment.

"""

def __init__(self, request_handler):
self.router = request_handler.server.router
self.doc_root = request_handler.server.router.doc_root
self.route_match = None # Set by the router

Expand Down
32 changes: 21 additions & 11 deletions wptserve/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ class Router(object):
def __init__(self, doc_root, routes):
self.doc_root = doc_root
self.routes = []
self.listing_fixups = []
self.logger = get_logger()
for route in reversed(routes):
self.register(*route)

def register(self, methods, path, handler):
def register(self, methods, path, handler, listing_fixup=None):
"""Register a handler for a set of paths.

:param methods: Set of methods this should match. "*" is a
Expand Down Expand Up @@ -130,28 +131,39 @@ def register(self, methods, path, handler):

{"resource": "test", "*": "data.json"}

:param handler: Function that will be called to process matching
requests. This must take two parameters, the request
object and the response object.
:param handler: Function that will be called to process
matching requests. This must take two
parameters, the request object and the
response object.

:param listing_fixup: Optional function that takes a request
and a set of names under a particuar
directory and returns an updated set of
names under that directory. Used to fix
up directory listings when a route adds
resources under a path that don't map
directly to a file.

"""
if type(methods) in types.StringTypes or methods in (any_method, "*"):
methods = [methods]
for method in methods:
self.routes.append((method, compile_path_match(path), handler))
self.logger.debug("Route pattern: %s" % self.routes[-1][1].pattern)
if listing_fixup is not None:
self.listing_fixups.append(listing_fixup)

def get_handler(self, request):
def get_handler(self, request_method, request_path):
"""Get a handler for a request or None if there is no handler.

:param request: Request to get a handler for.
:rtype: Callable or None
"""
for method, regexp, handler in reversed(self.routes):
if (request.method == method or
if (request_method == method or
method in (any_method, "*") or
(request.method == "HEAD" and method == "GET")):
m = regexp.match(request.url_parts.path)
(request_method == "HEAD" and method == "GET")):
m = regexp.match(request_path)
if m:
if not hasattr(handler, "__class__"):
name = handler.__name__
Expand All @@ -162,7 +174,5 @@ def get_handler(self, request):
match_parts = m.groupdict().copy()
if len(match_parts) < len(m.groups()):
match_parts["*"] = m.groups()[-1]
request.route_match = match_parts

return handler
return match_parts, handler
return None
3 changes: 2 additions & 1 deletion wptserve/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ def handle_one_request(self):
return

self.logger.debug("%s %s" % (request.method, request.request_path))
handler = self.server.router.get_handler(request)
match_parts, handler = self.server.router.get_handler(request.method, request.url_parts.path)
request.match_parts = match_parts

# If the handler we used for the request had a non-default base path
# set update the doc_root of the request to reflect this
Expand Down