diff options
Diffstat (limited to 'scripts/minimaws')
-rw-r--r-- | scripts/minimaws/lib/assets/common.js | 55 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/sortasc.png | bin | 0 -> 1157 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/sortdesc.png | bin | 0 -> 1195 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/sortind.png | bin | 0 -> 1247 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/style.css | 8 | ||||
-rw-r--r-- | scripts/minimaws/lib/dbaccess.py | 44 | ||||
-rw-r--r-- | scripts/minimaws/lib/htmltmpl.py | 110 | ||||
-rw-r--r-- | scripts/minimaws/lib/wsgiserve.py | 363 |
8 files changed, 491 insertions, 89 deletions
diff --git a/scripts/minimaws/lib/assets/common.js b/scripts/minimaws/lib/assets/common.js new file mode 100644 index 00000000000..8ab57941127 --- /dev/null +++ b/scripts/minimaws/lib/assets/common.js @@ -0,0 +1,55 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +function sort_table(tbl, col, dir, numeric) +{ + var tbody = tbl.tBodies[0]; + var trows = Array.prototype.slice.call(tbody.rows, 0).sort( + function (x, y) + { + if (numeric) + return dir * (parseInt(x.cells[col].textContent) - parseInt(y.cells[col].textContent)); + else + return dir * x.cells[col].textContent.localeCompare(y.cells[col].textContent); + }) + trows.forEach(function (row) { tbody.appendChild(row); }); +} + + +function make_table_sortable(tbl) +{ + var headers = tbl.tHead.rows[0].cells; + var i; + for (i = 0; i < headers.length; i++) + { + (function (col) + { + var dir = 1; + var sorticon = document.createElement("img"); + sorticon.setAttribute("src", assetsurl + "/sortind.png"); + sorticon.style.cssFloat = "right"; + sorticon.style.marginLeft = "0.5em"; + headers[col].appendChild(sorticon); + headers[col].addEventListener( + 'click', + function () + { + imgsrc = sorticon.getAttribute("src"); + imgsrc = imgsrc.substr(imgsrc.lastIndexOf('/') + 1); + if (imgsrc != 'sortind.png') + dir = -dir; + if (dir < 0) + sorticon.setAttribute("src", assetsurl + "/sortdesc.png"); + else + sorticon.setAttribute("src", assetsurl + "/sortasc.png"); + var i; + for (i = 0; i < headers.length; i++) + { + if (i != col) + headers[i].lastChild.setAttribute("src", assetsurl + "/sortind.png"); + } + sort_table(tbl, col, dir, headers[col].getAttribute("class") == "numeric"); + }); + }(i)); + } +} diff --git a/scripts/minimaws/lib/assets/sortasc.png b/scripts/minimaws/lib/assets/sortasc.png Binary files differnew file mode 100644 index 00000000000..29b893cdd02 --- /dev/null +++ b/scripts/minimaws/lib/assets/sortasc.png diff --git a/scripts/minimaws/lib/assets/sortdesc.png b/scripts/minimaws/lib/assets/sortdesc.png Binary files differnew file mode 100644 index 00000000000..63f48fc7213 --- /dev/null +++ b/scripts/minimaws/lib/assets/sortdesc.png diff --git a/scripts/minimaws/lib/assets/sortind.png b/scripts/minimaws/lib/assets/sortind.png Binary files differnew file mode 100644 index 00000000000..773dd6aec02 --- /dev/null +++ b/scripts/minimaws/lib/assets/sortind.png diff --git a/scripts/minimaws/lib/assets/style.css b/scripts/minimaws/lib/assets/style.css new file mode 100644 index 00000000000..7e9e0125cd2 --- /dev/null +++ b/scripts/minimaws/lib/assets/style.css @@ -0,0 +1,8 @@ +/* license:BSD-3-Clause + * copyright-holders:Vas Crabb + */ + +th { text-align: left; background-color: #ddd; padding: 0.25em } +td { padding-left: 0.25em; padding-right: 0.25em } + +table[class=sysinfo] th { text-align: right } diff --git a/scripts/minimaws/lib/dbaccess.py b/scripts/minimaws/lib/dbaccess.py index 3358b538b3c..e6ff07261fe 100644 --- a/scripts/minimaws/lib/dbaccess.py +++ b/scripts/minimaws/lib/dbaccess.py @@ -167,20 +167,48 @@ class QueryCursor(object): def get_devices_referenced(self, machine): return self.dbcurs.execute( - 'SELECT devicereference.device AS shortname, machine.description AS description ' \ - 'FROM devicereference LEFT JOIN machine ON devicereference.device = machine.shortname ' \ - 'WHERE devicereference.machine = ? ' \ - 'ORDER BY machine.description ASC, devicereference.device ASC', + 'SELECT devicereference.device AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \ + 'FROM devicereference LEFT JOIN machine ON devicereference.device = machine.shortname LEFT JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \ + 'WHERE devicereference.machine = ?', (machine, )) def get_device_references(self, shortname): return self.dbcurs.execute( - 'SELECT shortname, description ' \ - 'FROM machine ' \ - 'WHERE id IN (SELECT machine FROM devicereference WHERE device = ?) ' \ - 'ORDER BY description ASC', + 'SELECT machine.shortname AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \ + 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \ + 'WHERE machine.id IN (SELECT machine FROM devicereference WHERE device = ?)', (shortname, )) + def get_sourcefile_id(self, filename): + return (self.dbcurs.execute('SELECT id FROM sourcefile WHERE filename = ?', (filename, )).fetchone() or (None, ))[0] + + def get_sourcefile_machines(self, id): + return self.dbcurs.execute( + 'SELECT machine.shortname AS shortname, machine.description AS description, machine.isdevice AS isdevice, machine.runnable AS runnable, sourcefile.filename AS sourcefile, system.year AS year, system.manufacturer AS manufacturer, cloneof.parent AS cloneof, romof.parent AS romof ' \ + 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id LEFT JOIN system ON machine.id = system.id LEFT JOIN cloneof ON system.id = cloneof.id LEFT JOIN romof ON system.id = romof.id ' \ + 'WHERE machine.sourcefile = ?', + (id, )) + + def get_sourcefiles(self, pattern): + if pattern is not None: + return self.dbcurs.execute( + 'SELECT sourcefile.filename AS filename, COUNT(machine.id) AS machines ' \ + 'FROM sourcefile LEFT JOIN machine ON sourcefile.id = machine.sourcefile ' \ + 'WHERE sourcefile.filename GLOB ?' \ + 'GROUP BY sourcefile.id ', + (pattern, )) + else: + return self.dbcurs.execute( + 'SELECT sourcefile.filename AS filename, COUNT(machine.id) AS machines ' \ + 'FROM sourcefile LEFT JOIN machine ON sourcefile.id = machine.sourcefile ' \ + 'GROUP BY sourcefile.id') + + def count_sourcefiles(self, pattern): + if pattern is not None: + return self.dbcurs.execute('SELECT COUNT(*) FROM sourcefile WHERE filename GLOB ?', (pattern, )).fetchone()[0] + else: + return self.dbcurs.execute('SELECT COUNT(*) FROM sourcefile').fetchone()[0] + class UpdateCursor(object): def __init__(self, dbconn, **kwargs): diff --git a/scripts/minimaws/lib/htmltmpl.py b/scripts/minimaws/lib/htmltmpl.py index 2c71fea9fb9..106f9615e9d 100644 --- a/scripts/minimaws/lib/htmltmpl.py +++ b/scripts/minimaws/lib/htmltmpl.py @@ -6,17 +6,115 @@ import string +ERROR_PAGE = string.Template( + '<!DOCTYPE html>\n' \ + '<html>\n' \ + '<head>\n' \ + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \ + ' <title>${code} ${message}</title>\n' \ + '</head>\n' \ + '<body>\n' \ + '<h1>${message}</h1>\n' \ + '</body>\n' \ + '</html>\n') + + MACHINE_PROLOGUE = string.Template( '<!DOCTYPE html>\n' \ '<html>\n' \ '<head>\n' \ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \ - ' <title>${description} (${shortname})</title>\n' \ + ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \ + ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \ + ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \ + ' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \ + ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \ + ' <title>Machine: ${description} (${shortname})</title>\n' \ '</head>\n' \ '<body>\n' \ '<h1>${description}</h1>\n' \ - '<table>\n' \ - ' <tr><th style="text-align: right">Short name:</th><td>${shortname}</td></tr>\n' \ - ' <tr><th style="text-align: right">Is device:</th><td>${isdevice}</td></tr>\n' \ - ' <tr><th style="text-align: right">Runnable:</th><td>${runnable}</td></tr>\n' \ - ' <tr><th style="text-align: right">Source file:</th><td>${sourcefile}</td></tr>\n') + '<table class="sysinfo">\n' \ + ' <tr><th>Short name:</th><td>${shortname}</td></tr>\n' \ + ' <tr><th>Is device:</th><td>${isdevice}</td></tr>\n' \ + ' <tr><th>Runnable:</th><td>${runnable}</td></tr>\n' \ + ' <tr><th>Source file:</th><td><a href="${sourcehref}">${sourcefile}</a></td></tr>\n') + +MACHINE_ROW = string.Template( + ' <tr>\n' \ + ' <td><a href="${machinehref}">${shortname}</a></td>\n' \ + ' <td><a href="${machinehref}">${description}</a></td>\n' \ + ' <td><a href="${sourcehref}">${sourcefile}</a></td>\n' \ + ' </tr>\n') + +EXCL_MACHINE_ROW = string.Template( + ' <tr>\n' \ + ' <td><a href="${machinehref}">${shortname}</a></td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' </tr>\n') + + +SOURCEFILE_PROLOGUE = string.Template( + '<!DOCTYPE html>\n' \ + '<html>\n' \ + '<head>\n' \ + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \ + ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \ + ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \ + ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \ + ' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \ + ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \ + ' <title>Source File: ${filename}</title>\n' \ + '</head>\n' \ + '<body>\n' \ + '<h1>${title}</h1>\n') + +SOURCEFILE_ROW_PARENT = string.Template( + ' <tr>\n' \ + ' <td><a href="${machinehref}">${shortname}</a></td>\n' \ + ' <td><a href="${machinehref}">${description}</a></td>\n' \ + ' <td>${year}</td>\n' \ + ' <td>${manufacturer}</td>\n' \ + ' <td>${runnable}</td>\n' \ + ' <td></td>\n' \ + ' </tr>\n') + +SOURCEFILE_ROW_CLONE = string.Template( + ' <tr>\n' \ + ' <td><a href="${machinehref}">${shortname}</a></td>\n' \ + ' <td><a href="${machinehref}">${description}</a></td>\n' \ + ' <td>${year}</td>\n' \ + ' <td>${manufacturer}</td>\n' \ + ' <td>${runnable}</td>\n' \ + ' <td><a href="${parenthref}">${parent}</a></td>\n' \ + ' </tr>\n') + + +SOURCEFILE_LIST_PROLOGUE = string.Template( + '<!DOCTYPE html>\n' \ + '<html>\n' \ + '<head>\n' \ + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \ + ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \ + ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \ + ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \ + ' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \ + ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \ + ' <title>${title}</title>\n' \ + '</head>\n' \ + '<body>\n' \ + '<h1>${heading}</h1>\n' \ + '<table id="tbl-sourcefiles">\n' \ + ' <thead>\n' \ + ' <tr>\n' \ + ' <th>Source file</th>\n' \ + ' <th class="numeric">Machines</th>\n' \ + ' </tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n') + +SOURCEFILE_LIST_ROW = string.Template( + ' <tr>\n' \ + ' <td>${sourcefile}</td>\n' \ + ' <td style="text-align: right">${machines}</td>\n' \ + ' </tr>\n') diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py index 96ea43f35c5..641c535c54b 100644 --- a/scripts/minimaws/lib/wsgiserve.py +++ b/scripts/minimaws/lib/wsgiserve.py @@ -7,6 +7,9 @@ from . import dbaccess from . import htmltmpl import cgi +import inspect +import mimetypes +import os.path import sys import wsgiref.simple_server import wsgiref.util @@ -17,113 +20,323 @@ else: import urlparse -class MachineHandler(object): +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(MachineHandler, self).__init__(**kwargs) + super(HandlerBase, self).__init__(**kwargs) + self.app = app 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')]) + + 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')]) + 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')]) + 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')]) + 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')]) + 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')]) + return wsgiref.util.FileWrapper(f) + except: + self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8')]) + 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 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 Forbidden', [('Content-type', 'text/plain')]) - yield '403 Forbidden'.encode('utf-8') + self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8')]) + return self.error_page(403) elif self.environ['PATH_INFO']: # subdirectory of a machine - self.start_response('404 Not Found', [('Content-type', 'text/plain')]) - yield '404 Not Found'.encode('utf-8') + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')]) + return self.error_page(404) else: machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone() if not machine_info: - self.start_response('404 Not Found', [('Content-type', 'text/plain')]) - yield '404 Not Found'.encode('utf-8') + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')]) + 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')]) + return self.error_page(405) else: self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')]) - description = machine_info['description'] - yield htmltmpl.MACHINE_PROLOGUE.substitute( - 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 style="text-align: right">Year:</th><td>%s</td></tr>\n' \ - ' <tr><th style="text-align: right">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 style="text-align: right">Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % - (cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8') - else: - yield ( - ' <tr><th style="text-align: right">Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' % - (cgi.escape('%s/machine/%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 style="text-align: right">Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % - (cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8') - else: - yield ( - ' <tr><th style="text-align: right">Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' % - (cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8') - yield '</table>\n'.encode('utf-8') - - devices = self.dbcurs.get_devices_referenced(machine_info['id']) - first = True - for name, desc in devices: - if first: - yield '<h2>Devices Referenced</h2>\n<ul>\n'.encode('utf-8') - first = False - if desc is not None: - yield ( - ' <li><a href="%s">%s (%s)</a></li>\n' % - (self.machine_href(name), cgi.escape(desc), cgi.escape(name))).encode('utf-8') + return self.machine_page(machine_info) + + def machine_page(self, machine_info): + description = machine_info['description'] + yield htmltmpl.MACHINE_PROLOGUE.substitute( + assets=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('%s/machine/%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('%s/machine/%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('%s/machine/%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('%s/machine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8') + yield '</table>\n'.encode('utf-8') + + first = True + for name, desc, src in self.dbcurs.get_devices_referenced(machine_info['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 ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-dev-refs"));</script>\n'.encode('utf-8') + + first = True + for name, desc, src in self.dbcurs.get_device_references(self.shortname): + 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 ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-ref-by"));</script>\n'.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')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')]) + 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')]) + 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')]) + return self.error_page(405) else: - yield ( - ' <li><a href="%s">%s</a></li>\n' % - (self.machine_href(name), cgi.escape(name))).encode('utf-8') - if not first: - yield '</ul>\n'.encode('utf-8') - - devices = self.dbcurs.get_device_references(self.shortname) - first = True - for name, desc in devices: - if first: - yield '<h2>Referenced By</h2>\n<ul>\n'.encode('utf-8') - first = False - yield ( - ' <li><a href="%s">%s (%s)</a></li>\n' % - (self.machine_href(name), cgi.escape(desc), cgi.escape(name))).encode('utf-8') - if not first: - yield '</ul>\n'.encode('utf-8') - - yield '</html>\n'.encode('utf-8') + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')]) + return self.sourcefile_listing_page(self.filename) + else: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')]) + 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')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')]) + return self.sourcefile_page(id) - def machine_href(self, shortname): - return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True) + 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 MiniMawsApp(object): 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 not module: + return ErrorPageHandler(403, self, application_uri, environ, start_response) else: - start_response('200 OK', [('Content-type', 'text/plain')]) - return ('Module is %s\n' % (module, ), ) + return ErrorPageHandler(404, self, application_uri, environ, start_response) def run_server(options): |