diff options
Diffstat (limited to 'docs/release/scripts/minimaws/lib/wsgiserve.py')
-rw-r--r-- | docs/release/scripts/minimaws/lib/wsgiserve.py | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/docs/release/scripts/minimaws/lib/wsgiserve.py b/docs/release/scripts/minimaws/lib/wsgiserve.py new file mode 100644 index 00000000000..b30828e01e0 --- /dev/null +++ b/docs/release/scripts/minimaws/lib/wsgiserve.py @@ -0,0 +1,526 @@ +#!/usr/bin/python +## +## license:BSD-3-Clause +## copyright-holders:Vas Crabb + +from . import dbaccess +from . import htmltmpl + +import cgi +import inspect +import json +import mimetypes +import os.path +import re +import sys +import wsgiref.simple_server +import wsgiref.util + +if sys.version_info >= (3, ): + import urllib.parse as urlparse +else: + import urlparse + + +class HandlerBase(object): + STATUS_MESSAGE = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported' } + + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(HandlerBase, self).__init__(**kwargs) + self.app = app + self.js_escape = app.js_escape + self.application_uri = application_uri + self.environ = environ + self.start_response = start_response + + def error_page(self, code): + yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % code), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8') + + +class ErrorPageHandler(HandlerBase): + def __init__(self, code, app, application_uri, environ, start_response, **kwargs): + super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.code = code + self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + + def __iter__(self): + return self.error_page(self.code) + + +class AssetHandler(HandlerBase): + def __init__(self, directory, app, application_uri, environ, start_response, **kwargs): + super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.directory = directory + self.asset = wsgiref.util.shift_path_info(environ) + + def __iter__(self): + if not self.asset: + self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(403) + elif self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + else: + path = os.path.join(self.directory, self.asset) + if not os.path.isfile(path): + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + try: + f = open(path, 'rb') + type, encoding = mimetypes.guess_type(path) + self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')]) + return wsgiref.util.FileWrapper(f) + except: + self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(500) + + +class QueryPageHandler(HandlerBase): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.dbcurs = app.dbconn.cursor() + + def machine_href(self, shortname): + return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True) + + def sourcefile_href(self, sourcefile): + return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (sourcefile, )), True) + + +class MachineRpcHandlerBase(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.shortname = wsgiref.util.shift_path_info(environ) + + def __iter__(self): + if not self.shortname: + self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(403) + elif self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + else: + machine = self.dbcurs.get_machine_id(self.shortname) + if machine is None: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.data_page(machine) + + +class MachineHandler(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.shortname = wsgiref.util.shift_path_info(environ) + + def __iter__(self): + if not self.shortname: + # could probably list machines here or something + self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(403) + elif self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + else: + machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone() + if not machine_info: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.machine_page(machine_info) + + def machine_page(self, machine_info): + id = machine_info['id'] + description = machine_info['description'] + yield htmltmpl.MACHINE_PROLOGUE.substitute( + app=self.js_escape(cgi.escape(self.application_uri, True)), + assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)), + sourcehref=self.sourcefile_href(machine_info['sourcefile']), + description=cgi.escape(description), + shortname=cgi.escape(self.shortname), + isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'), + runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'), + sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8') + if machine_info['year'] is not None: + yield ( + ' <tr><th>Year:</th><td>%s</td></tr>\n' \ + ' <tr><th>Manufacturer:</th><td>%s</td></tr>\n' % + (cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8') + if machine_info['cloneof'] is not None: + parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone() + if parent: + yield ( + ' <tr><th>Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % + (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8') + else: + yield ( + ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' % + (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8') + if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']): + parent = self.dbcurs.listfull(machine_info['romof']).fetchone() + if parent: + yield ( + ' <tr><th>Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % + (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8') + else: + yield ( + ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' % + (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8') + unemulated = [] + imperfect = [] + for feature, status, overall in self.dbcurs.get_feature_flags(id): + if overall == 1: + imperfect.append(feature) + elif overall > 1: + unemulated.append(feature) + if (unemulated): + unemulated.sort() + yield( + (' <tr><th>Unemulated Features:</th><td>%s' + (', %s' * (len(unemulated) - 1)) + '</td></tr>\n') % + tuple(unemulated)).encode('utf-8'); + if (imperfect): + yield( + (' <tr><th>Imperfect Features:</th><td>%s' + (', %s' * (len(imperfect) - 1)) + '</td></tr>\n') % + tuple(imperfect)).encode('utf-8'); + yield '</table>\n'.encode('utf-8') + + # allow system BIOS selection + haveoptions = False + for name, desc, isdef in self.dbcurs.get_biossets(id): + if not haveoptions: + haveoptions = True; + yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') + yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8') + yield htmltmpl.MACHINE_BIOS_OPTION.substitute( + name=cgi.escape(name, True), + description=cgi.escape(desc), + isdefault=('yes' if isdef else 'no')).encode('utf-8') + if haveoptions: + yield '</select>\n<script>set_default_system_bios();</script>\n'.encode('utf-8') + + # allow RAM size selection + first = True + for name, size, isdef in self.dbcurs.get_ram_options(id): + if first: + if not haveoptions: + haveoptions = True; + yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') + yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8') + first = False + yield htmltmpl.MACHINE_RAM_OPTION.substitute( + name=cgi.escape(name, True), + size=cgi.escape('{:,}'.format(size)), + isdefault=('yes' if isdef else 'no')).encode('utf-8') + if not first: + yield '</select>\n<script>set_default_ram_option();</script>\n'.encode('utf-8') + + # placeholder for machine slots - populated by client-side JavaScript + if self.dbcurs.count_slots(id): + if not haveoptions: + haveoptions = True + yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') + yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute( + machine=self.js_escape(self.shortname)).encode('utf=8') + + # list devices referenced by this system/device + first = True + for name, desc, src in self.dbcurs.get_devices_referenced(id): + if first: + yield \ + '<h2>Devices Referenced</h2>\n' \ + '<table id="tbl-dev-refs">\n' \ + ' <thead>\n' \ + ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n'.encode('utf-8') + first = False + yield self.machine_row(name, desc, src) + if not first: + yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8') + + # list slots where this device is an option + first = True + for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id): + if (first): + yield \ + '<h2>Compatible Slots</h2>\n' \ + '<table id="tbl-comp-slots">\n' \ + ' <thead>\n' \ + ' <tr><th>Short name</th><th>Description</th><th>Slot</th><th>Choice</th><th>Source file</th></tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n'.encode('utf-8') + first = False + yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute( + machinehref=self.machine_href(name), + sourcehref=self.sourcefile_href(src), + shortname=cgi.escape(name), + description=cgi.escape(desc), + sourcefile=cgi.escape(src), + slot=cgi.escape(slot), + slotoption=cgi.escape(opt)).encode('utf-8') + if not first: + yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8') + + # list systems/devices that reference this device + first = True + for name, desc, src in self.dbcurs.get_device_references(id): + if first: + yield \ + '<h2>Referenced By</h2>\n' \ + '<table id="tbl-ref-by">\n' \ + ' <thead>\n' \ + ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n'.encode('utf-8') + first = False + yield self.machine_row(name, desc, src) + if not first: + yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8') + + yield '</html>\n'.encode('utf-8') + + def machine_row(self, shortname, description, sourcefile): + return (htmltmpl.MACHINE_ROW if description is not None else htmltmpl.EXCL_MACHINE_ROW).substitute( + machinehref=self.machine_href(shortname), + sourcehref=self.sourcefile_href(sourcefile), + shortname=cgi.escape(shortname), + description=cgi.escape(description or ''), + sourcefile=cgi.escape(sourcefile or '')).encode('utf-8') + + +class SourceFileHandler(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(SourceFileHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + + def __iter__(self): + self.filename = self.environ['PATH_INFO'] + if self.filename and (self.filename[0] == '/'): + self.filename = self.filename[1:] + if not self.filename: + if self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.sourcefile_listing_page(None) + else: + id = self.dbcurs.get_sourcefile_id(self.filename) + if id is None: + if ('*' not in self.filename) and ('?' not in self.filename) and ('?' not in self.filename): + self.filename += '*' if self.filename[-1] == '/' else '/*' + if not self.dbcurs.count_sourcefiles(self.filename): + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.sourcefile_listing_page(self.filename) + else: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.sourcefile_page(id) + + def sourcefile_listing_page(self, pattern): + if not pattern: + title = heading = 'All Source Files' + else: + heading = self.linked_title(pattern) + title = 'Source Files: ' + cgi.escape(pattern) + yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute( + assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), + title=title, + heading=heading).encode('utf-8') + for filename, machines in self.dbcurs.get_sourcefiles(pattern): + yield htmltmpl.SOURCEFILE_LIST_ROW.substitute( + sourcefile=self.linked_title(filename, True), + machines=cgi.escape('%d' % machines)).encode('utf-8') + yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-sourcefiles"));</script>\n</body>\n</html>\n'.encode('utf-8') + + def sourcefile_page(self, id): + yield htmltmpl.SOURCEFILE_PROLOGUE.substitute( + assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True), + filename=cgi.escape(self.filename), + title=self.linked_title(self.filename)).encode('utf-8') + + first = True + for machine_info in self.dbcurs.get_sourcefile_machines(id): + if first: + yield \ + '<table id="tbl-machines">\n' \ + ' <thead>\n' \ + ' <tr>\n' \ + ' <th>Short name</th>\n' \ + ' <th>Description</th>\n' \ + ' <th>Year</th>\n' \ + ' <th>Manufacturer</th>\n' \ + ' <th>Runnable</th>\n' \ + ' <th>Parent</th>\n' \ + ' </tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n'.encode('utf-8') + first = False + yield self.machine_row(machine_info) + if first: + yield '<p>No machines found.</p>\n'.encode('utf-8') + else: + yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-machines"));</script>\n'.encode('utf-8') + + yield '</body>\n</html>\n'.encode('utf-8') + + def linked_title(self, filename, linkfinal=False): + parts = filename.split('/') + final = parts[-1] + del parts[-1] + uri = urlparse.urljoin(self.application_uri, 'sourcefile') + title = '' + for part in parts: + uri = urlparse.urljoin(uri + '/', part) + title += '<a href="{0}">{1}</a>/'.format(cgi.escape(uri, True), cgi.escape(part)) + if linkfinal: + uri = urlparse.urljoin(uri + '/', final) + return title + '<a href="{0}">{1}</a>'.format(cgi.escape(uri, True), cgi.escape(final)) + else: + return title + final + + def machine_row(self, machine_info): + return (htmltmpl.SOURCEFILE_ROW_PARENT if machine_info['cloneof'] is None else htmltmpl.SOURCEFILE_ROW_CLONE).substitute( + machinehref=self.machine_href(machine_info['shortname']), + parenthref=self.machine_href(machine_info['cloneof'] or '__invalid'), + shortname=cgi.escape(machine_info['shortname']), + description=cgi.escape(machine_info['description']), + year=cgi.escape(machine_info['year'] or ''), + manufacturer=cgi.escape(machine_info['manufacturer'] or ''), + runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'), + parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8') + + +class BiosRpcHandler(MachineRpcHandlerBase): + def data_page(self, machine): + result = { } + for name, description, isdefault in self.dbcurs.get_biossets(machine): + result[name] = { 'description': description, 'isdefault': True if isdefault else False } + yield json.dumps(result).encode('utf-8') + + +class FlagsRpcHandler(MachineRpcHandlerBase): + def data_page(self, machine): + result = { 'features': { } } + for feature, status, overall in self.dbcurs.get_feature_flags(machine): + detail = { } + if status == 1: + detail['status'] = 'imperfect' + elif status > 1: + detail['status'] = 'unemulated' + if overall == 1: + detail['overall'] = 'imperfect' + elif overall > 1: + detail['overall'] = 'unemulated' + result['features'][feature] = detail + yield json.dumps(result).encode('utf-8') + + +class SlotsRpcHandler(MachineRpcHandlerBase): + def data_page(self, machine): + result = { 'defaults': { }, 'slots': { } } + + # get defaults and slot options + for slot, default in self.dbcurs.get_slot_defaults(machine): + result['defaults'][slot] = default + prev = None + for slot, option, shortname, description in self.dbcurs.get_slot_options(machine): + if slot != prev: + if slot in result['slots']: + options = result['slots'][slot] + else: + options = { } + result['slots'][slot] = options + prev = slot + options[option] = { 'device': shortname, 'description': description } + + # remove slots that come from default cards in other slots + for slot in tuple(result['slots'].keys()): + slot += ':' + for candidate in tuple(result['slots'].keys()): + if candidate.startswith(slot): + del result['slots'][candidate] + + yield json.dumps(result).encode('utf-8') + + +class MiniMawsApp(object): + JS_ESCAPE = re.compile('([\"\'\\\\])') + RPC_SERVICES = { + 'bios': BiosRpcHandler, + 'flags': FlagsRpcHandler, + 'slots': SlotsRpcHandler } + + def __init__(self, dbfile, **kwargs): + super(MiniMawsApp, self).__init__(**kwargs) + self.dbconn = dbaccess.QueryConnection(dbfile) + self.assetsdir = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), 'assets') + if not mimetypes.inited: + mimetypes.init() + + def __call__(self, environ, start_response): + application_uri = wsgiref.util.application_uri(environ) + module = wsgiref.util.shift_path_info(environ) + if module == 'machine': + return MachineHandler(self, application_uri, environ, start_response) + elif module == 'sourcefile': + return SourceFileHandler(self, application_uri, environ, start_response) + elif module == 'static': + return AssetHandler(self.assetsdir, self, application_uri, environ, start_response) + elif module == 'rpc': + service = wsgiref.util.shift_path_info(environ) + if not service: + return ErrorPageHandler(403, self, application_uri, environ, start_response) + elif service in self.RPC_SERVICES: + return self.RPC_SERVICES[service](self, application_uri, environ, start_response) + else: + return ErrorPageHandler(404, self, application_uri, environ, start_response) + elif not module: + return ErrorPageHandler(403, self, application_uri, environ, start_response) + else: + return ErrorPageHandler(404, self, application_uri, environ, start_response) + + def js_escape(self, str): + return self.JS_ESCAPE.sub('\\\\\\1', str).replace('\0', '\\0') + + +def run_server(options): + application = MiniMawsApp(options.database) + server = wsgiref.simple_server.make_server(options.host, options.port, application) + try: + server.serve_forever() + except KeyboardInterrupt: + pass |