# Path: bustard/exceptions.py
# class NotFound(HTTPException):
#     def __init__(self):
#         self.response = Response(NOTFOUND_HTML, status_code=404)
# 
# Path: bustard/http.py
# class Response:
# 
#     def __init__(self, content=b'', status_code=200,
#                  content_type='text/html; charset=utf-8',
#                  headers=None):
#         self._content = content
#         self._status_code = status_code
#         _headers = headers or {}
#         _headers.setdefault('Content-Type', content_type)
#         if isinstance(_headers, Headers):
#             self._headers = _headers
#         else:
#             self._headers = Headers(_headers)
#         self._cookies = SimpleCookie()
#         self._load_cookies_from_headers()
# 
#     def _load_cookies_from_headers(self):
#         cookies = self._headers.to_dict().pop('Set-Cookie', [])
#         for cookie in cookies:
#             self._cookies.load(cookie)
# 
#     @property
#     def content(self):
#         return self._content
# 
#     @content.setter
#     def content(self, value):
#         if isinstance(value, str):
#             value = value.encode('utf-8')
#         self._content = value
#     body = data = content
# 
#     def get_data(self):
#         return self._content
# 
#     @property
#     def content_type(self, value):
#         return self.headers.get('Content-Type', '')
# 
#     @content_type.setter
#     def content_type(self, value):
#         self.headers['Content-Type'] = value
# 
#     @property
#     def content_length(self):
#         return int(self.headers.get('Content-Length', '0'))
# 
#     @property
#     def status_code(self):
#         return self._status_code
# 
#     @status_code.setter
#     def status_code(self, value):
#         self._status_code = value
# 
#     @property
#     def status(self):
#         code = self._status_code
#         return response_status_string(code)
# 
#     @property
#     def headers(self):
#         return self._headers
# 
#     @headers.setter
#     def headers(self, value):
#         self._headers = Headers(value)
# 
#     @property
#     def content_type(self):
#         return self._headers.get('Content-Type', '')
# 
#     @content_type.setter
#     def content_type(self, value):
#         self._headers['Content-Type'] = value
# 
#     @property
#     def cookies(self):
#         return self._cookies
# 
#     def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
#                    domain=None, secure=False, httponly=False):
#         cookie = cookie_dump(
#             key, value=value, max_age=max_age, expires=expires, path=path,
#             domain=domain, secure=secure, httponly=httponly
#         )
#         self._cookies.load(cookie)
# 
#     def delete_cookie(self, key, max_age=0,
#                       expires='Thu, 01-Jan-1970 00:00:00 GMT'):
#         self.set_cookie(key, value='', max_age=max_age, expires=expires)
# 
#     @property
#     def headers_list(self):
#         # normal headers
#         headers_list = list(self.headers.to_list())
# 
#         # set-cookies
#         headers_list.extend(
#             ('Set-Cookie', value.OutputString())
#             for value in self.cookies.values()
#         )
#         return headers_list
# 
#     def json(self):
#         return json.loads(to_text(self.data))
# 
#     def __repr__(self):
#         return '<{} [{}]>'.format(self.__class__.__name__, self.status_code)


import mimetypes
import os
from .exceptions import NotFound
from .http import Response
            view = decorator(view)

        view.view_class = cls
        view.__name__ = name or cls.__name__
        methods = []
        for method in http_methods:
            if hasattr(cls, method):
                methods.append(method.upper())
        methods = frozenset(methods)
        cls.methods = methods
        view.methods = methods
        return view


class StaticFilesView(View):

    def __init__(self, static_dir):
        self.static_dir = os.path.abspath(static_dir)

    def get(self, request, path):
        file_path = os.path.abspath(os.path.join(self.static_dir, path))
        if not file_path.startswith(self.static_dir):
            raise NotFound()
        if not os.path.isfile(file_path):
            raise NotFound()

        with open(file_path, 'rb') as fp:
            content = fp.read()
        content_type = mimetypes.guess_type(file_path)[0]
        content_type = content_type or 'application/octet-stream'

# Path: bustard/exceptions.py
# class NotFound(HTTPException):
#     def __init__(self):
#         self.response = Response(NOTFOUND_HTML, status_code=404)
# 
# Path: bustard/http.py
# class Response:
# 
#     def __init__(self, content=b'', status_code=200,
#                  content_type='text/html; charset=utf-8',
#                  headers=None):
#         self._content = content
#         self._status_code = status_code
#         _headers = headers or {}
#         _headers.setdefault('Content-Type', content_type)
#         if isinstance(_headers, Headers):
#             self._headers = _headers
#         else:
#             self._headers = Headers(_headers)
#         self._cookies = SimpleCookie()
#         self._load_cookies_from_headers()
# 
#     def _load_cookies_from_headers(self):
#         cookies = self._headers.to_dict().pop('Set-Cookie', [])
#         for cookie in cookies:
#             self._cookies.load(cookie)
# 
#     @property
#     def content(self):
#         return self._content
# 
#     @content.setter
#     def content(self, value):
#         if isinstance(value, str):
#             value = value.encode('utf-8')
#         self._content = value
#     body = data = content
# 
#     def get_data(self):
#         return self._content
# 
#     @property
#     def content_type(self, value):
#         return self.headers.get('Content-Type', '')
# 
#     @content_type.setter
#     def content_type(self, value):
#         self.headers['Content-Type'] = value
# 
#     @property
#     def content_length(self):
#         return int(self.headers.get('Content-Length', '0'))
# 
#     @property
#     def status_code(self):
#         return self._status_code
# 
#     @status_code.setter
#     def status_code(self, value):
#         self._status_code = value
# 
#     @property
#     def status(self):
#         code = self._status_code
#         return response_status_string(code)
# 
#     @property
#     def headers(self):
#         return self._headers
# 
#     @headers.setter
#     def headers(self, value):
#         self._headers = Headers(value)
# 
#     @property
#     def content_type(self):
#         return self._headers.get('Content-Type', '')
# 
#     @content_type.setter
#     def content_type(self, value):
#         self._headers['Content-Type'] = value
# 
#     @property
#     def cookies(self):
#         return self._cookies
# 
#     def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
#                    domain=None, secure=False, httponly=False):
#         cookie = cookie_dump(
#             key, value=value, max_age=max_age, expires=expires, path=path,
#             domain=domain, secure=secure, httponly=httponly
#         )
#         self._cookies.load(cookie)
# 
#     def delete_cookie(self, key, max_age=0,
#                       expires='Thu, 01-Jan-1970 00:00:00 GMT'):
#         self.set_cookie(key, value='', max_age=max_age, expires=expires)
# 
#     @property
#     def headers_list(self):
#         # normal headers
#         headers_list = list(self.headers.to_list())
# 
#         # set-cookies
#         headers_list.extend(
#             ('Set-Cookie', value.OutputString())
#             for value in self.cookies.values()
#         )
#         return headers_list
# 
#     def json(self):
#         return json.loads(to_text(self.data))
# 
#     def __repr__(self):
#         return '<{} [{}]>'.format(self.__class__.__name__, self.status_code)


# Path: bustard/views.py
import mimetypes
import os
from .exceptions import NotFound
from .http import Response

            view = decorator(view)

        view.view_class = cls
        view.__name__ = name or cls.__name__
        methods = []
        for method in http_methods:
            if hasattr(cls, method):
                methods.append(method.upper())
        methods = frozenset(methods)
        cls.methods = methods
        view.methods = methods
        return view


class StaticFilesView(View):

    def __init__(self, static_dir):
        self.static_dir = os.path.abspath(static_dir)

    def get(self, request, path):
        file_path = os.path.abspath(os.path.join(self.static_dir, path))
        if not file_path.startswith(self.static_dir):
            raise NotFound()
        if not os.path.isfile(file_path):
            raise NotFound()

        with open(file_path, 'rb') as fp:
            content = fp.read()
        content_type = mimetypes.guess_type(file_path)[0]
        content_type = content_type or 'application/octet-stream'

        return Response(content, content_type=content_type)