summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/minimaws/lib/assets/common.js55
-rw-r--r--scripts/minimaws/lib/assets/sortasc.pngbin0 -> 1157 bytes
-rw-r--r--scripts/minimaws/lib/assets/sortdesc.pngbin0 -> 1195 bytes
-rw-r--r--scripts/minimaws/lib/assets/sortind.pngbin0 -> 1247 bytes
-rw-r--r--scripts/minimaws/lib/assets/style.css8
-rw-r--r--scripts/minimaws/lib/dbaccess.py44
-rw-r--r--scripts/minimaws/lib/htmltmpl.py110
-rw-r--r--scripts/minimaws/lib/wsgiserve.py363
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
new file mode 100644
index 00000000000..29b893cdd02
--- /dev/null
+++ b/scripts/minimaws/lib/assets/sortasc.png
Binary files differ
diff --git a/scripts/minimaws/lib/assets/sortdesc.png b/scripts/minimaws/lib/assets/sortdesc.png
new file mode 100644
index 00000000000..63f48fc7213
--- /dev/null
+++ b/scripts/minimaws/lib/assets/sortdesc.png
Binary files differ
diff --git a/scripts/minimaws/lib/assets/sortind.png b/scripts/minimaws/lib/assets/sortind.png
new file mode 100644
index 00000000000..773dd6aec02
--- /dev/null
+++ b/scripts/minimaws/lib/assets/sortind.png
Binary files differ
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):