summaryrefslogtreecommitdiffstatshomepage
path: root/scripts/minimaws
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/minimaws')
-rw-r--r--scripts/minimaws/lib/__init__.py4
-rw-r--r--scripts/minimaws/lib/auxverbs.py83
-rw-r--r--scripts/minimaws/lib/dbaccess.py292
-rw-r--r--scripts/minimaws/lib/htmltmpl.py22
-rw-r--r--scripts/minimaws/lib/lxparse.py227
-rw-r--r--scripts/minimaws/lib/wsgiserve.py135
-rwxr-xr-xscripts/minimaws/minimaws.py59
-rw-r--r--scripts/minimaws/schema.sql84
8 files changed, 906 insertions, 0 deletions
diff --git a/scripts/minimaws/lib/__init__.py b/scripts/minimaws/lib/__init__.py
new file mode 100644
index 00000000000..d89cb7f81ba
--- /dev/null
+++ b/scripts/minimaws/lib/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
diff --git a/scripts/minimaws/lib/auxverbs.py b/scripts/minimaws/lib/auxverbs.py
new file mode 100644
index 00000000000..c68c528af6f
--- /dev/null
+++ b/scripts/minimaws/lib/auxverbs.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+from . import dbaccess
+
+import sys
+
+
+def do_listfull(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, description in dbcurs.listfull(options.pattern):
+ if first:
+ sys.stdout.write('Name: Description:\n')
+ first = False
+ sys.stdout.write('%-16s "%s"\n' % (shortname, description))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listsource(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ shortname = None
+ for shortname, sourcefile in dbcurs.listsource(options.pattern):
+ sys.stdout.write('%-16s %s\n' % (shortname, sourcefile))
+ if shortname is None:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listclones(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, parent in dbcurs.listclones(options.pattern):
+ if first:
+ sys.stdout.write('Name: Clone of:\n')
+ first = False
+ sys.stdout.write('%-16s %s\n' % (shortname, parent))
+ if first:
+ count = dbcurs.count_systems(options.pattern).fetchone()[0]
+ if count:
+ sys.stderr.write('Found %d match(es) for \'%s\' but none were clones\n' % (count, options.pattern))
+ else:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listbrothers(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for sourcefile, shortname, parent in dbcurs.listbrothers(options.pattern):
+ if first:
+ sys.stdout.write('%-20s %-16s %s\n' % ('Source file:', 'Name:', 'Parent:'))
+ first = False
+ sys.stdout.write('%-20s %-16s %s\n' % (sourcefile, shortname, parent or ''))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+def do_listaffected(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, description in dbcurs.listaffected(*options.pattern):
+ if first:
+ sys.stdout.write('Name: Description:\n')
+ first = False
+ sys.stdout.write('%-16s "%s"\n' % (shortname, description))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
diff --git a/scripts/minimaws/lib/dbaccess.py b/scripts/minimaws/lib/dbaccess.py
new file mode 100644
index 00000000000..3358b538b3c
--- /dev/null
+++ b/scripts/minimaws/lib/dbaccess.py
@@ -0,0 +1,292 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+import sqlite3
+
+
+class SchemaQueries(object):
+ INDEX_MACHINE_ISDEVICE_SHORTNAME = 'CREATE INDEX machine_isdevice_shortname ON machine (isdevice ASC, shortname ASC)'
+ INDEX_MACHINE_ISDEVICE_DESCRIPTION = 'CREATE INDEX machine_isdevice_description ON machine (isdevice ASC, description ASC)'
+ INDEX_MACHINE_RUNNABLE_SHORTNAME = 'CREATE INDEX machine_runnable_shortname ON machine (runnable ASC, shortname ASC)'
+ INDEX_MACHINE_RUNNABLE_DESCRIPTION = 'CREATE INDEX machine_runnable_description ON machine (runnable ASC, description ASC)'
+
+ INDEX_SYSTEM_YEAR = 'CREATE INDEX system_year ON system (year ASC)'
+ INDEX_SYSTEM_MANUFACTURER = 'CREATE INDEX system_manufacturer ON system (manufacturer ASC)'
+
+ INDEX_ROMOF_PARENT = 'CREATE INDEX romof_parent ON romof (parent ASC)'
+
+ INDEX_CLONEOF_PARENT = 'CREATE INDEX cloneof_parent ON cloneof (parent ASC)'
+
+ INDEX_DEVICEREFERENCE_DEVICE = 'CREATE INDEX devicereference_device ON devicereference (device ASC)'
+
+ INDEX_DIPSWITCH_MACHINE_ISCONFIG = 'CREATE INDEX dipswitch_machine_isconfig ON dipswitch (machine ASC, isconfig ASC)'
+
+ DROP_MACHINE_ISDEVICE_SHORTNAME = 'DROP INDEX IF EXISTS machine_isdevice_shortname'
+ DROP_MACHINE_ISDEVICE_DESCRIPTION = 'DROP INDEX IF EXISTS machine_isdevice_description'
+ DROP_MACHINE_RUNNABLE_SHORTNAME = 'DROP INDEX IF EXISTS machine_runnable_shortname'
+ DROP_MACHINE_RUNNABLE_DESCRIPTION = 'DROP INDEX IF EXISTS machine_runnable_description'
+
+ DROP_SYSTEM_YEAR = 'DROP INDEX IF EXISTS system_year'
+ DROP_SYSTEM_MANUFACTURER = 'DROP INDEX IF EXISTS system_manufacturer'
+
+ DROP_ROMOF_PARENT = 'DROP INDEX IF EXISTS romof_parent'
+
+ DROP_CLONEOF_PARENT = 'DROP INDEX IF EXISTS cloneof_parent'
+
+ DROP_DEVICEREFERENCE_DEVICE = 'DROP INDEX IF EXISTS devicereference_device'
+
+ DROP_DIPSWITCH_MACHINE_ISCONFIG = 'DROP INDEX IF EXISTS dipswitch_machine_isconfig'
+
+
+class UpdateQueries(object):
+ ADD_FEATURETYPE = 'INSERT OR IGNORE INTO featuretype (name) VALUES (?)'
+ ADD_SOURCEFILE = 'INSERT OR IGNORE INTO sourcefile (filename) VALUES (?)'
+ ADD_MACHINE = 'INSERT INTO machine (shortname, description, sourcefile, isdevice, runnable) SELECT ?, ?, id, ?, ? FROM sourcefile WHERE filename = ?'
+ ADD_SYSTEM = 'INSERT INTO system (id, year, manufacturer) VALUES (?, ?, ?)'
+ ADD_CLONEOF = 'INSERT INTO cloneof (id, parent) VALUES (?, ?)'
+ ADD_ROMOF = 'INSERT INTO romof (id, parent) VALUES (?, ?)'
+ ADD_DEVICEREFERENCE = 'INSERT OR IGNORE INTO devicereference (machine, device) VALUES (?, ?)'
+ ADD_DIPSWITCH = 'INSERT INTO dipswitch (machine, isconfig, name, tag, mask) VALUES (?, ?, ?, ?, ?)'
+ ADD_DIPLOCATION = 'INSERT INTO diplocation (dipswitch, bit, name, num, inverted) VALUES (?, ?, ?, ?, ?)'
+ ADD_DIPVALUE = 'INSERT INTO dipvalue (dipswitch, name, value, isdefault) VALUES (?, ?, ?, ?)'
+ ADD_FEATURE = 'INSERT INTO feature (machine, featuretype, status, overall) SELECT ?, id, ?, ? FROM featuretype WHERE name = ?'
+
+
+class QueryCursor(object):
+ def __init__(self, dbconn, **kwargs):
+ super(QueryCursor, self).__init__(**kwargs)
+ self.dbcurs = dbconn.cursor()
+
+ def close(self):
+ self.dbcurs.close()
+
+ def is_glob(self, *patterns):
+ for pattern in patterns:
+ if any(ch in pattern for ch in '?*['):
+ return True
+ return False
+
+ def count_systems(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT COUNT(*) ' \
+ 'FROM machine WHERE isdevice = 0 AND shortname GLOB ? ',
+ (pattern, ))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT COUNT(*) ' \
+ 'FROM machine WHERE isdevice = 0 ')
+
+ def listfull(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT shortname, description ' \
+ 'FROM machine WHERE isdevice = 0 AND shortname GLOB ? ' \
+ 'ORDER BY shortname ASC',
+ (pattern, ))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT shortname, description ' \
+ 'FROM machine WHERE isdevice = 0 ' \
+ 'ORDER BY shortname ASC')
+
+ def listsource(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname, sourcefile.filename ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
+ 'WHERE machine.isdevice = 0 AND machine.shortname GLOB ? ' \
+ 'ORDER BY machine.shortname ASC',
+ (pattern, ))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname, sourcefile.filename ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
+ 'WHERE machine.isdevice = 0 ORDER BY machine.shortname ASC')
+
+ def listclones(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname, cloneof.parent ' \
+ 'FROM machine JOIN cloneof ON machine.id = cloneof.id ' \
+ 'WHERE machine.shortname GLOB ? OR cloneof.parent GLOB ? ' \
+ 'ORDER BY machine.shortname ASC',
+ (pattern, pattern))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname, cloneof.parent ' \
+ 'FROM machine JOIN cloneof ON machine.id = cloneof.id ' \
+ 'ORDER BY machine.shortname ASC')
+
+ def listbrothers(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT sourcefile.filename, machine.shortname, cloneof.parent ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id LEFT JOIN cloneof ON machine.id = cloneof.id ' \
+ 'WHERE machine.isdevice = 0 AND sourcefile.id IN (SELECT sourcefile FROM machine WHERE shortname GLOB ?)' \
+ 'ORDER BY machine.shortname ASC',
+ (pattern, ))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT sourcefile.filename, machine.shortname, cloneof.parent ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id LEFT JOIN cloneof ON machine.id = cloneof.id ' \
+ 'WHERE machine.isdevice = 0 ' \
+ 'ORDER BY machine.shortname ASC')
+
+ def listaffected(self, *patterns):
+ if 1 == len(patterns):
+ return self.dbcurs.execute(
+ 'SELECT shortname, description ' \
+ 'FROM machine ' \
+ 'WHERE id IN (SELECT machine FROM devicereference WHERE device IN (SELECT shortname FROM machine WHERE sourcefile IN (SELECT id FROM sourcefile WHERE filename GLOB ?))) AND runnable = 1 ' \
+ 'ORDER BY shortname ASC',
+ patterns)
+ elif self.is_glob(*patterns):
+ return self.dbcurs.execute(
+ 'SELECT shortname, description ' \
+ 'FROM machine ' \
+ 'WHERE id IN (SELECT machine FROM devicereference WHERE device IN (SELECT shortname FROM machine WHERE sourcefile IN (SELECT id FROM sourcefile WHERE filename GLOB ?' + (' OR filename GLOB ?' * (len(patterns) - 1)) + '))) AND runnable = 1 ' \
+ 'ORDER BY shortname ASC',
+ patterns)
+ else:
+ return self.dbcurs.execute(
+ 'SELECT shortname, description ' \
+ 'FROM machine ' \
+ 'WHERE id IN (SELECT machine FROM devicereference WHERE device IN (SELECT shortname FROM machine WHERE sourcefile IN (SELECT id FROM sourcefile WHERE filename IN (?' + (', ?' * (len(patterns) - 1)) + ')))) AND runnable = 1 ' \
+ 'ORDER BY shortname ASC',
+ patterns)
+
+ def get_machine_info(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT machine.id AS id, 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.shortname = ?',
+ (machine, ))
+
+ 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',
+ (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',
+ (shortname, ))
+
+
+class UpdateCursor(object):
+ def __init__(self, dbconn, **kwargs):
+ super(UpdateCursor, self).__init__(**kwargs)
+ self.dbcurs = dbconn.cursor()
+
+ def close(self):
+ self.dbcurs.close()
+
+ def add_featuretype(self, name):
+ self.dbcurs.execute(UpdateQueries.ADD_FEATURETYPE, (name, ))
+
+ def add_sourcefile(self, filename):
+ self.dbcurs.execute(UpdateQueries.ADD_SOURCEFILE, (filename, ))
+
+ def add_machine(self, shortname, description, sourcefile, isdevice, runnable):
+ self.dbcurs.execute(UpdateQueries.ADD_MACHINE, (shortname, description, isdevice, runnable, sourcefile))
+ return self.dbcurs.lastrowid
+
+ def add_system(self, machine, year, manufacturer):
+ self.dbcurs.execute(UpdateQueries.ADD_SYSTEM, (machine, year, manufacturer))
+ return self.dbcurs.lastrowid
+
+ def add_cloneof(self, machine, parent):
+ self.dbcurs.execute(UpdateQueries.ADD_CLONEOF, (machine, parent))
+ return self.dbcurs.lastrowid
+
+ def add_romof(self, machine, parent):
+ self.dbcurs.execute(UpdateQueries.ADD_ROMOF, (machine, parent))
+ return self.dbcurs.lastrowid
+
+ def add_devicereference(self, machine, device):
+ self.dbcurs.execute(UpdateQueries.ADD_DEVICEREFERENCE, (machine, device))
+
+ def add_dipswitch(self, machine, isconfig, name, tag, mask):
+ self.dbcurs.execute(UpdateQueries.ADD_DIPSWITCH, (machine, isconfig, name, tag, mask))
+ return self.dbcurs.lastrowid
+
+ def add_diplocation(self, dipswitch, bit, name, num, inverted):
+ self.dbcurs.execute(UpdateQueries.ADD_DIPLOCATION, (dipswitch, bit, name, num, inverted))
+ return self.dbcurs.lastrowid
+
+ def add_dipvalue(self, dipswitch, name, value, isdefault):
+ self.dbcurs.execute(UpdateQueries.ADD_DIPVALUE, (dipswitch, name, value, isdefault))
+ return self.dbcurs.lastrowid
+
+ def add_feature(self, machine, featuretype, status, overall):
+ self.dbcurs.execute(UpdateQueries.ADD_FEATURE, (machine, status, overall, featuretype))
+ return self.dbcurs.lastrowid
+
+
+class QueryConnection(object):
+ def __init__(self, database, **kwargs):
+ # TODO: detect python versions that allow URL-based read-only connection
+ super(QueryConnection, self).__init__(**kwargs)
+ self.dbconn = sqlite3.connect(database)
+ self.dbconn.row_factory = sqlite3.Row
+ self.dbconn.execute('PRAGMA foreign_keys = ON')
+
+ def close(self):
+ self.dbconn.close()
+
+ def cursor(self):
+ return QueryCursor(self.dbconn)
+
+
+class UpdateConnection(object):
+ def __init__(self, database, **kwargs):
+ super(UpdateConnection, self).__init__(**kwargs)
+ self.dbconn = sqlite3.connect(database)
+ self.dbconn.execute('PRAGMA foreign_keys = ON')
+
+ def commit(self):
+ self.dbconn.commit()
+
+ def rollback(self):
+ self.dbconn.rollback()
+
+ def close(self):
+ self.dbconn.close()
+
+ def cursor(self):
+ return UpdateCursor(self.dbconn)
+
+ def drop_indexes(self):
+ self.dbconn.execute(SchemaQueries.DROP_MACHINE_ISDEVICE_SHORTNAME)
+ self.dbconn.execute(SchemaQueries.DROP_MACHINE_ISDEVICE_DESCRIPTION)
+ self.dbconn.execute(SchemaQueries.DROP_MACHINE_RUNNABLE_SHORTNAME)
+ self.dbconn.execute(SchemaQueries.DROP_MACHINE_RUNNABLE_DESCRIPTION)
+ self.dbconn.execute(SchemaQueries.DROP_SYSTEM_YEAR)
+ self.dbconn.execute(SchemaQueries.DROP_SYSTEM_MANUFACTURER)
+ self.dbconn.execute(SchemaQueries.DROP_ROMOF_PARENT)
+ self.dbconn.execute(SchemaQueries.DROP_CLONEOF_PARENT)
+ self.dbconn.execute(SchemaQueries.DROP_DEVICEREFERENCE_DEVICE)
+ self.dbconn.execute(SchemaQueries.DROP_DIPSWITCH_MACHINE_ISCONFIG)
+ self.dbconn.commit()
+
+ def create_indexes(self):
+ self.dbconn.execute(SchemaQueries.INDEX_MACHINE_ISDEVICE_SHORTNAME)
+ self.dbconn.execute(SchemaQueries.INDEX_MACHINE_ISDEVICE_DESCRIPTION)
+ self.dbconn.execute(SchemaQueries.INDEX_MACHINE_RUNNABLE_SHORTNAME)
+ self.dbconn.execute(SchemaQueries.INDEX_MACHINE_RUNNABLE_DESCRIPTION)
+ self.dbconn.execute(SchemaQueries.INDEX_SYSTEM_YEAR)
+ self.dbconn.execute(SchemaQueries.INDEX_SYSTEM_MANUFACTURER)
+ self.dbconn.execute(SchemaQueries.INDEX_ROMOF_PARENT)
+ self.dbconn.execute(SchemaQueries.INDEX_CLONEOF_PARENT)
+ self.dbconn.execute(SchemaQueries.INDEX_DEVICEREFERENCE_DEVICE)
+ self.dbconn.execute(SchemaQueries.INDEX_DIPSWITCH_MACHINE_ISCONFIG)
+ self.dbconn.commit()
diff --git a/scripts/minimaws/lib/htmltmpl.py b/scripts/minimaws/lib/htmltmpl.py
new file mode 100644
index 00000000000..2c71fea9fb9
--- /dev/null
+++ b/scripts/minimaws/lib/htmltmpl.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+import string
+
+
+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' \
+ '</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')
diff --git a/scripts/minimaws/lib/lxparse.py b/scripts/minimaws/lib/lxparse.py
new file mode 100644
index 00000000000..f9061bcb798
--- /dev/null
+++ b/scripts/minimaws/lib/lxparse.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+from . import dbaccess
+
+import subprocess
+import xml.sax
+import xml.sax.saxutils
+
+
+class ElementHandlerBase(object):
+ def __init__(self, parent, **kwargs):
+ super(ElementHandlerBase, self).__init__(**kwargs)
+ self.dbconn = parent.dbconn if parent is not None else None
+ self.locator = parent.locator if parent is not None else None
+ self.depth = 0
+ self.childhandler = None
+ self.childdepth = 0
+
+ def startMainElement(self, name, attrs):
+ pass
+
+ def endMainElement(self, name):
+ pass
+
+ def mainCharacters(self, content):
+ pass
+
+ def mainIgnorableWitespace(self, whitespace):
+ pass
+
+ def startChildElement(self, name, attrs):
+ pass
+
+ def endChildElement(self, name):
+ pass
+
+ def childCharacters(self, content):
+ pass
+
+ def childIgnorableWitespace(self, whitespace):
+ pass
+
+ def endChildHandler(self, name, handler):
+ pass
+
+ def setChildHandler(self, name, attrs, handler):
+ self.depth -= 1
+ self.childhandler = handler
+ self.childdepth += 1
+ handler.startElement(name, attrs)
+
+ def setDocumentLocator(self, locator):
+ self.locator = locator
+
+ def startElement(self, name, attrs):
+ if self.childhandler is not None:
+ self.childdepth += 1
+ self.childhandler.startElement(name, attrs)
+ else:
+ self.depth += 1
+ if 1 == self.depth:
+ self.startMainElement(name, attrs)
+ else:
+ self.startChildElement(name, attrs)
+
+ def endElement(self, name):
+ if self.childhandler is not None:
+ self.childdepth -= 1
+ self.childhandler.endElement(name)
+ if 0 == self.childdepth:
+ self.endChildHandler(name, self.childhandler)
+ self.childhandler = None
+ else:
+ self.depth -= 1
+ if 0 == self.depth:
+ self.endMainElement(name)
+ else:
+ self.endChildElement(name)
+
+ def characters(self, content):
+ if self.childhandler is not None:
+ self.childhandler.characters(content)
+ elif 1 < self.depth:
+ self.childCharacters(content)
+ else:
+ self.mainCharacters(content)
+
+ def ignorableWhitespace(self, content):
+ if self.childhandler is not None:
+ self.childhandler.ignorableWhitespace(content)
+ elif 1 < self.depth:
+ self.childIgnorableWitespace(content)
+ else:
+ self.mainIgnorableWitespace(content)
+
+
+class ElementHandler(ElementHandlerBase):
+ IGNORE = ElementHandlerBase(parent=None)
+
+
+class TextAccumulator(ElementHandler):
+ def __init__(self, parent, **kwargs):
+ super(TextAccumulator, self).__init__(parent=parent, **kwargs)
+ self.text = ''
+
+ def mainCharacters(self, content):
+ self.text += content
+
+
+class DipSwitchHandler(ElementHandler):
+ def __init__(self, parent, **kwargs):
+ super(DipSwitchHandler, self).__init__(parent=parent, **kwargs)
+ self.dbcurs = parent.dbcurs
+ self.machine = parent.id
+
+ def startMainElement(self, name, attrs):
+ self.mask = int(attrs['mask'])
+ self.bit = 0
+ self.id = self.dbcurs.add_dipswitch(self.machine, name == 'configuration', attrs['name'], attrs['tag'], self.mask)
+
+ def startChildElement(self, name, attrs):
+ if (name == 'diplocation') or (name == 'conflocation'):
+ while (0 != self.mask) and not (self.mask & 1):
+ self.mask >>= 1
+ self.bit += 1
+ self.dbcurs.add_diplocation(self.id, self.bit, attrs['name'], attrs['number'], attrs['inverted'] == 'yes' if 'inverted' in attrs else False)
+ self.mask >>= 1
+ self.bit += 1
+ elif (name == 'dipvalue') or (name == 'confsetting'):
+ self.dbcurs.add_dipvalue(self.id, attrs['name'], attrs['value'], attrs['default'] == 'yes' if 'default' in attrs else False)
+ self.setChildHandler(name, attrs, self.IGNORE)
+
+
+class MachineHandler(ElementHandler):
+ def __init__(self, parent, **kwargs):
+ super(MachineHandler, self).__init__(parent=parent, **kwargs)
+ self.dbcurs = self.dbconn.cursor()
+
+ def startMainElement(self, name, attrs):
+ self.shortname = attrs['name']
+ self.sourcefile = attrs['sourcefile']
+ self.isdevice = attrs['isdevice'] == 'yes' if 'isdevice' in attrs else False
+ self.runnable = attrs['runnable'] == 'yes' if 'runnable' in attrs else True
+ self.cloneof = attrs.get('cloneof')
+ self.romof = attrs.get('romof')
+ self.dbcurs.add_sourcefile(self.sourcefile)
+
+ def startChildElement(self, name, attrs):
+ if (name == 'description') or (name == 'year') or (name == 'manufacturer'):
+ self.setChildHandler(name, attrs, TextAccumulator(self))
+ elif (name == 'dipswitch') or (name == 'configuration'):
+ self.setChildHandler(name, attrs, DipSwitchHandler(self))
+ else:
+ if name == 'device_ref':
+ self.dbcurs.add_devicereference(self.id, attrs['name'])
+ elif name == 'feaure':
+ self.dbcurs.add_featuretype(attrs['type'])
+ status = 0 if 'status' not in attrs else 2 if attrs['status'] == 'unemulated' else 1
+ overall = status if 'overall' not in attrs else 2 if attrs['overall'] == 'unemulated' else 1
+ self.dbcurs.add_feature(self.id, attrs['type'], status, overall)
+ self.setChildHandler(name, attrs, self.IGNORE)
+
+ def endChildHandler(self, name, handler):
+ if name == 'description':
+ self.description = handler.text
+ self.id = self.dbcurs.add_machine(self.shortname, self.description, self.sourcefile, self.isdevice, self.runnable)
+ if self.cloneof is not None:
+ self.dbcurs.add_cloneof(self.id, self.cloneof)
+ if self.romof is not None:
+ self.dbcurs.add_romof(self.id, self.romof)
+ elif name == 'year':
+ self.year = handler.text
+ elif name == 'manufacturer':
+ self.manufacturer = handler.text
+ self.dbcurs.add_system(self.id, self.year, self.manufacturer)
+
+ def endMainElement(self, name):
+ self.dbconn.commit()
+ self.dbcurs.close()
+
+
+class ListXmlHandler(ElementHandler):
+ def __init__(self, dbconn, **kwargs):
+ super(ListXmlHandler, self).__init__(parent=None, **kwargs)
+ self.dbconn = dbconn
+
+ def startDocument(self):
+ pass
+
+ def endDocument(self):
+ pass
+
+ def startMainElement(self, name, attrs):
+ if name != 'mame':
+ raise xml.sax.SAXParseException(
+ msg=('Expected "mame" element but found "%s"' % (name, )),
+ exception=None,
+ locator=self.locator)
+ self.dbconn.drop_indexes()
+
+ def endMainElement(self, name):
+ # TODO: build index by first letter or whatever
+ self.dbconn.create_indexes()
+
+ def startChildElement(self, name, attrs):
+ if name != 'machine':
+ raise xml.sax.SAXParseException(
+ msg=('Expected "machine" element but found "%s"' % (name, )),
+ exception=None,
+ locator=self.locator)
+ self.setChildHandler(name, attrs, MachineHandler(self))
+
+ def processingInstruction(self, target, data):
+ pass
+
+
+def load_info(options):
+ parser = xml.sax.make_parser()
+ parser.setContentHandler(ListXmlHandler(dbaccess.UpdateConnection(options.database)))
+ if options.executable is not None:
+ task = subprocess.Popen([options.executable, '-listxml'], stdout=subprocess.PIPE)
+ parser.parse(task.stdout)
+ else:
+ parser.parse(options.file)
diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py
new file mode 100644
index 00000000000..96ea43f35c5
--- /dev/null
+++ b/scripts/minimaws/lib/wsgiserve.py
@@ -0,0 +1,135 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+from . import dbaccess
+from . import htmltmpl
+
+import cgi
+import sys
+import wsgiref.simple_server
+import wsgiref.util
+
+if sys.version_info > (3, ):
+ import urllib.parse as urlparse
+else:
+ import urlparse
+
+
+class MachineHandler(object):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(MachineHandler, self).__init__(**kwargs)
+ self.application_uri = application_uri
+ self.environ = environ
+ self.start_response = start_response
+ self.dbcurs = app.dbconn.cursor()
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+
+ def machine_href(self, shortname):
+ return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)
+
+
+class MiniMawsApp(object):
+ def __init__(self, dbfile, **kwargs):
+ super(MiniMawsApp, self).__init__(**kwargs)
+ self.dbconn = dbaccess.QueryConnection(dbfile)
+
+ 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)
+ else:
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ return ('Module is %s\n' % (module, ), )
+
+
+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
diff --git a/scripts/minimaws/minimaws.py b/scripts/minimaws/minimaws.py
new file mode 100755
index 00000000000..12ddc865b56
--- /dev/null
+++ b/scripts/minimaws/minimaws.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+import lib.auxverbs
+import lib.lxparse
+import lib.wsgiserve
+
+import argparse
+import sys
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--database', metavar='<dbfile>', default='minimaws.sqlite3', help='SQLite 3 info database file (defaults to minimaws.sqlite3)')
+ subparsers = parser.add_subparsers(title='commands', dest='command', metavar='<command>')
+
+ subparser = subparsers.add_parser('listfull', help='list short names and full names')
+ subparser.add_argument('pattern', nargs='?', metavar='<pat>', help='short name glob pattern')
+
+ subparser = subparsers.add_parser('listsource', help='list short names and source files')
+ subparser.add_argument('pattern', nargs='?', metavar='<pat>', help='short name glob pattern')
+
+ subparser = subparsers.add_parser('listclones', help='show clones')
+ subparser.add_argument('pattern', nargs='?', metavar='<pat>', help='short name/parent glob pattern')
+
+ subparser = subparsers.add_parser('listbrothers', help='show drivers from the same source file(s)')
+ subparser.add_argument('pattern', nargs='?', metavar='<pat>', help='short name glob pattern')
+
+ subparser = subparsers.add_parser('listaffected', help='show drivers affected by source change(s)')
+ subparser.add_argument('pattern', nargs='+', metavar='<pat>', help='source file glob pattern')
+
+ subparser = subparsers.add_parser('serve', help='serve over HTTP')
+ subparser.add_argument('--port', metavar='<port>', default=8080, type=int, help='server TCP port')
+ subparser.add_argument('--host', metavar='<host>', default='', help='server TCP hostname')
+
+ subparser = subparsers.add_parser('load', help='load machine information')
+ group = subparser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--executable', metavar='<exe>', help='emulator executable')
+ group.add_argument('--file', metavar='<xmlfile>', help='XML machine information file')
+
+ options = parser.parse_args()
+ if options.command == 'listfull':
+ lib.auxverbs.do_listfull(options)
+ elif options.command == 'listsource':
+ lib.auxverbs.do_listsource(options)
+ elif options.command == 'listclones':
+ lib.auxverbs.do_listclones(options)
+ elif options.command == 'listbrothers':
+ lib.auxverbs.do_listbrothers(options)
+ elif options.command == 'listaffected':
+ lib.auxverbs.do_listaffected(options)
+ elif options.command == 'serve':
+ lib.wsgiserve.run_server(options)
+ elif options.command == 'load':
+ lib.lxparse.load_info(options)
+ else:
+ print('%s' % (options, ))
diff --git a/scripts/minimaws/schema.sql b/scripts/minimaws/schema.sql
new file mode 100644
index 00000000000..7633441a123
--- /dev/null
+++ b/scripts/minimaws/schema.sql
@@ -0,0 +1,84 @@
+PRAGMA page_size = 4096;
+PRAGMA foreign_keys = ON;
+
+CREATE TABLE featuretype (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ UNIQUE (name ASC));
+
+CREATE TABLE sourcefile (
+ id INTEGER PRIMARY KEY,
+ filename TEXT NOT NULL,
+ UNIQUE (filename ASC));
+
+CREATE TABLE machine (
+ id INTEGER PRIMARY KEY,
+ shortname TEXT NOT NULL,
+ description TEXT NOT NULL,
+ sourcefile INTEGER NOT NULL,
+ isdevice INTEGER NOT NULL,
+ runnable INTEGER NOT NULL,
+ UNIQUE (shortname ASC),
+ UNIQUE (description ASC),
+ FOREIGN KEY (sourcefile) REFERENCES sourcefile (id));
+
+CREATE TABLE system (
+ id INTEGER PRIMARY KEY,
+ year TEXT NOT NULL,
+ manufacturer TEXT NOT NULL,
+ FOREIGN KEY (id) REFERENCES machine (id));
+
+CREATE TABLE cloneof (
+ id INTEGER PRIMARY KEY,
+ parent TEXT NOT NULL,
+ FOREIGN KEY (id) REFERENCES machine (id));
+
+CREATE TABLE romof (
+ id INTEGER PRIMARY KEY,
+ parent TEXT NOT NULL,
+ FOREIGN KEY (id) REFERENCES machine (id));
+
+CREATE TABLE devicereference (
+ id INTEGER PRIMARY KEY,
+ machine INTEGER NOT NULL,
+ device TEXT NOT NULL,
+ UNIQUE (machine ASC, device ASC),
+ FOREIGN KEY (machine) REFERENCES machine (id));
+
+CREATE TABLE dipswitch (
+ id INTEGER PRIMARY KEY,
+ machine INTEGER NOT NULL,
+ isconfig INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ tag TEXT NOT NULL,
+ mask INTEGER NOT NULL,
+ --UNIQUE (machine ASC, tag ASC, mask ASC), not necessarily true, need to expose port conditions
+ FOREIGN KEY (machine) REFERENCES machine (id));
+
+CREATE TABLE diplocation (
+ id INTEGER PRIMARY KEY,
+ dipswitch INTEGER NOT NULL,
+ bit INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ num INTEGER NOT NULL,
+ inverted INTEGER NOT NULL,
+ UNIQUE (dipswitch ASC, bit ASC),
+ FOREIGN KEY (dipswitch) REFERENCES dipswitch (id));
+
+CREATE TABLE dipvalue (
+ id INTEGER PRIMARY KEY,
+ dipswitch INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ value INTEGER NOT NULL,
+ isdefault INTEGER NOT NULL,
+ FOREIGN KEY (dipswitch) REFERENCES dipswitch (id));
+
+CREATE TABLE feature (
+ id INTEGER PRIMARY KEY,
+ machine INTEGER NOT NULL,
+ featuretype INTEGER NOT NULL,
+ status INTEGER NOT NULL,
+ overall INTEGER NOT NULL,
+ UNIQUE (machine ASC, featuretype ASC),
+ FOREIGN KEY (machine) REFERENCES machine (id),
+ FOREIGN KEY (featuretype) REFERENCES featuretype (id));