minimaws web mode enhancements:

* Support serving static assets, use for stylesheet, script and images
* Better error pages, reject unsupported HTTP methods
* Replace lists with sortable tables with more detail (click headings to sort)
* Add pages for exploring source files, link from machine pages
 - Can start from full source file list at http://localhost:8080/sourcefile/

(nw) JavaScript performance can drop when sorting really big tables,
e.g. the list of all source files, or the list of machines in some of
the fruit machine drivers.  This update doesn't expose machine/device
information, just consolidating what's there.  The wsgiref server is
adding headers to prevent caching, I'll look for a workaround.
This commit is contained in:
Vas Crabb 2017-08-02 00:41:09 +10:00
parent 46a221b95e
commit 14642adc5a
8 changed files with 488 additions and 86 deletions

View File

@ -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));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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 }

View File

@ -167,20 +167,48 @@ class QueryCursor(object):
def get_devices_referenced(self, machine): def get_devices_referenced(self, machine):
return self.dbcurs.execute( return self.dbcurs.execute(
'SELECT devicereference.device AS shortname, machine.description AS description ' \ 'SELECT devicereference.device AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \
'FROM devicereference LEFT JOIN machine ON devicereference.device = machine.shortname ' \ 'FROM devicereference LEFT JOIN machine ON devicereference.device = machine.shortname LEFT JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
'WHERE devicereference.machine = ? ' \ 'WHERE devicereference.machine = ?',
'ORDER BY machine.description ASC, devicereference.device ASC',
(machine, )) (machine, ))
def get_device_references(self, shortname): def get_device_references(self, shortname):
return self.dbcurs.execute( return self.dbcurs.execute(
'SELECT shortname, description ' \ 'SELECT machine.shortname AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \
'FROM machine ' \ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
'WHERE id IN (SELECT machine FROM devicereference WHERE device = ?) ' \ 'WHERE machine.id IN (SELECT machine FROM devicereference WHERE device = ?)',
'ORDER BY description ASC',
(shortname, )) (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): class UpdateCursor(object):
def __init__(self, dbconn, **kwargs): def __init__(self, dbconn, **kwargs):

View File

@ -6,17 +6,115 @@
import string 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( MACHINE_PROLOGUE = string.Template(
'<!DOCTYPE html>\n' \ '<!DOCTYPE html>\n' \
'<html>\n' \ '<html>\n' \
'<head>\n' \ '<head>\n' \
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\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' \ '</head>\n' \
'<body>\n' \ '<body>\n' \
'<h1>${description}</h1>\n' \ '<h1>${description}</h1>\n' \
'<table>\n' \ '<table class="sysinfo">\n' \
' <tr><th style="text-align: right">Short name:</th><td>${shortname}</td></tr>\n' \ ' <tr><th>Short name:</th><td>${shortname}</td></tr>\n' \
' <tr><th style="text-align: right">Is device:</th><td>${isdevice}</td></tr>\n' \ ' <tr><th>Is device:</th><td>${isdevice}</td></tr>\n' \
' <tr><th style="text-align: right">Runnable:</th><td>${runnable}</td></tr>\n' \ ' <tr><th>Runnable:</th><td>${runnable}</td></tr>\n' \
' <tr><th style="text-align: right">Source file:</th><td>${sourcefile}</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')

View File

@ -7,6 +7,9 @@ from . import dbaccess
from . import htmltmpl from . import htmltmpl
import cgi import cgi
import inspect
import mimetypes
import os.path
import sys import sys
import wsgiref.simple_server import wsgiref.simple_server
import wsgiref.util import wsgiref.util
@ -17,113 +20,323 @@ else:
import urlparse 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): 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.application_uri = application_uri
self.environ = environ self.environ = environ
self.start_response = start_response 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() 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) self.shortname = wsgiref.util.shift_path_info(environ)
def __iter__(self): def __iter__(self):
if not self.shortname: if not self.shortname:
# could probably list machines here or something # could probably list machines here or something
self.start_response('403 Forbidden', [('Content-type', 'text/plain')]) self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8')])
yield '403 Forbidden'.encode('utf-8') return self.error_page(403)
elif self.environ['PATH_INFO']: elif self.environ['PATH_INFO']:
# subdirectory of a machine # subdirectory of a machine
self.start_response('404 Not Found', [('Content-type', 'text/plain')]) self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')])
yield '404 Not Found'.encode('utf-8') return self.error_page(404)
else: else:
machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone() machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone()
if not machine_info: if not machine_info:
self.start_response('404 Not Found', [('Content-type', 'text/plain')]) self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')])
yield '404 Not Found'.encode('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: else:
self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')]) self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')])
description = machine_info['description'] return self.machine_page(machine_info)
yield htmltmpl.MACHINE_PROLOGUE.substitute(
description=cgi.escape(description), def machine_page(self, machine_info):
shortname=cgi.escape(self.shortname), description = machine_info['description']
isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'), yield htmltmpl.MACHINE_PROLOGUE.substitute(
runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'), assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8') sourcehref=self.sourcefile_href(machine_info['sourcefile']),
if machine_info['year'] is not None: description=cgi.escape(description),
yield ( shortname=cgi.escape(self.shortname),
' <tr><th style="text-align: right">Year:</th><td>%s</td></tr>\n' \ isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'),
' <tr><th style="text-align: right">Manufacturer:</th><td>%s</td></tr>\n' % runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
(cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8') sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8')
if machine_info['cloneof'] is not None: if machine_info['year'] is not None:
parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone() yield (
if parent: ' <tr><th>Year:</th><td>%s</td></tr>\n' \
yield ( ' <tr><th>Manufacturer:</th><td>%s</td></tr>\n' %
' <tr><th style="text-align: right">Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % (cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8')
(cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).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: else:
yield ( self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')])
' <tr><th style="text-align: right">Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' % return self.sourcefile_listing_page(self.filename)
(cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8') else:
if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']): self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8')])
parent = self.dbcurs.listfull(machine_info['romof']).fetchone() return self.error_page(404)
if parent: elif self.environ['REQUEST_METHOD'] != 'GET':
yield ( self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS')])
' <tr><th style="text-align: right">Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' % return self.error_page(405)
(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:
else: self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8')])
yield ( return self.sourcefile_page(id)
' <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']) def sourcefile_listing_page(self, pattern):
first = True if not pattern:
for name, desc in devices: title = heading = 'All Source Files'
if first: else:
yield '<h2>Devices Referenced</h2>\n<ul>\n'.encode('utf-8') heading = self.linked_title(pattern)
first = False title = 'Source Files: ' + cgi.escape(pattern)
if desc is not None: yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute(
yield ( assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
' <li><a href="%s">%s (%s)</a></li>\n' % title=title,
(self.machine_href(name), cgi.escape(desc), cgi.escape(name))).encode('utf-8') heading=heading).encode('utf-8')
else: for filename, machines in self.dbcurs.get_sourcefiles(pattern):
yield ( yield htmltmpl.SOURCEFILE_LIST_ROW.substitute(
' <li><a href="%s">%s</a></li>\n' % sourcefile=self.linked_title(filename, True),
(self.machine_href(name), cgi.escape(name))).encode('utf-8') machines=cgi.escape('%d' % machines)).encode('utf-8')
if not first: yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-sourcefiles"));</script>\n</body>\n</html>\n'.encode('utf-8')
yield '</ul>\n'.encode('utf-8')
devices = self.dbcurs.get_device_references(self.shortname) def sourcefile_page(self, id):
first = True yield htmltmpl.SOURCEFILE_PROLOGUE.substitute(
for name, desc in devices: assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
if first: filename=cgi.escape(self.filename),
yield '<h2>Referenced By</h2>\n<ul>\n'.encode('utf-8') title=self.linked_title(self.filename)).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') 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')
def machine_href(self, shortname): yield '</body>\n</html>\n'.encode('utf-8')
return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)
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): class MiniMawsApp(object):
def __init__(self, dbfile, **kwargs): def __init__(self, dbfile, **kwargs):
super(MiniMawsApp, self).__init__(**kwargs) super(MiniMawsApp, self).__init__(**kwargs)
self.dbconn = dbaccess.QueryConnection(dbfile) 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): def __call__(self, environ, start_response):
application_uri = wsgiref.util.application_uri(environ) application_uri = wsgiref.util.application_uri(environ)
module = wsgiref.util.shift_path_info(environ) module = wsgiref.util.shift_path_info(environ)
if module == 'machine': if module == 'machine':
return MachineHandler(self, application_uri, environ, start_response) 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: else:
start_response('200 OK', [('Content-type', 'text/plain')]) return ErrorPageHandler(404, self, application_uri, environ, start_response)
return ('Module is %s\n' % (module, ), )
def run_server(options): def run_server(options):