+class TreeFileApp:
+ """
+ Map a directory tree base_dir to a base_uri and serve it statically.
+ Map index.html files to their relative roots:
+ index.html to "/" (root uri) to index.html and
+ "/a" (directory uri) to a/index.html
+ """
+ @staticmethod
+ def is_ignored_filename(path):
+ return path.name.startswith(".")
+
+ @classmethod
+ def scan_files(cls, base_dir):
+ stack = [base_dir.iterdir()]
+ while len(stack):
+ try:
+ path = next(stack[-1])
+ except StopIteration:
+ stack.pop()
+ continue
+ if path.is_dir():
+ stack.append(path.iterdir())
+ elif not cls.is_ignored_filename(path):
+ yield path
+
+ def get_file(self, path: Path) -> StaticFile:
+ return StaticFile(path)
+
+ def uri_tail(self, path: Path) -> str:
+ """
+ Return the "local" path, relative to self.base_dir, if applicable
+ """
+ assert isinstance(path, Path)
+ if path.is_absolute():
+ path = path.relative_to(self.base_dir)
+ return str(path)
+
+ def uri(self, path: Path) -> str:
+ uri_tail = self.uri_tail(path)
+ if uri_tail == "index.html":
+ return self.base_uri or "/"
+ index_suffix = "/index.html"
+ if uri_tail.endswith(index_suffix):
+ uri_tail = uri_tail[:-len(index_suffix)]
+ return f"{self.base_uri}/{uri_tail.lstrip('/')}"
+
+ def __init__(self, app, base_dir, base_uri="/"):
+ self.app = app
+ self.base_dir = base_dir
+ self.base_uri = base_uri.rstrip("/")
+ self.name = self.base_uri.replace("/", ".").strip(".") or "root"
+ self.files_per_uris = {}
+ for path in self.scan_files(base_dir):
+ static_file = self.get_file(path)
+ uri = self.uri(static_file.path)
+ self.files_per_uris[uri] = static_file
+ app.add_route(uri, self)
+
+ async def on_get(self, req, resp):
+ resource = self.files_per_uris[req.path]
+ resp.content_type = resource.media_type
+ resp.data = resource.get().encode()
+ resp.status = HTTP_OK
+
+