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( + '\n' \ + '\n' \ + '\n' \ + ' \n' \ + ' ${description} (${shortname})\n' \ + '\n' \ + '\n' \ + '

${description}

\n' \ + '\n' \ + ' \n' \ + ' \n' \ + ' \n' \ + ' \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 ( + ' \n' \ + ' \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 ( + ' \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 ( + ' \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 ( + ' \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 ( + ' \n' % + (cgi.escape('%s/machine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8') + yield '
Short name:${shortname}
Is device:${isdevice}
Runnable:${runnable}
Source file:${sourcefile}
Year:%s
Manufacturer:%s
Parent Machine:%s (%s)
Parent Machine:%s
Parent ROM set:%s (%s)
Parent Machine:%s
\n'.encode('utf-8') + + devices = self.dbcurs.get_devices_referenced(machine_info['id']) + first = True + for name, desc in devices: + if first: + yield '

Devices Referenced

\n\n'.encode('utf-8') + + devices = self.dbcurs.get_device_references(self.shortname) + first = True + for name, desc in devices: + if first: + yield '

Referenced By

\n\n'.encode('utf-8') + + yield '\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='', default='minimaws.sqlite3', help='SQLite 3 info database file (defaults to minimaws.sqlite3)') + subparsers = parser.add_subparsers(title='commands', dest='command', metavar='') + + subparser = subparsers.add_parser('listfull', help='list short names and full names') + subparser.add_argument('pattern', nargs='?', metavar='', help='short name glob pattern') + + subparser = subparsers.add_parser('listsource', help='list short names and source files') + subparser.add_argument('pattern', nargs='?', metavar='', help='short name glob pattern') + + subparser = subparsers.add_parser('listclones', help='show clones') + subparser.add_argument('pattern', nargs='?', metavar='', 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='', help='short name glob pattern') + + subparser = subparsers.add_parser('listaffected', help='show drivers affected by source change(s)') + subparser.add_argument('pattern', nargs='+', metavar='', help='source file glob pattern') + + subparser = subparsers.add_parser('serve', help='serve over HTTP') + subparser.add_argument('--port', metavar='', default=8080, type=int, help='server TCP port') + subparser.add_argument('--host', metavar='', 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='', help='emulator executable') + group.add_argument('--file', metavar='', 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)); diff --git a/src/devices/bus/intellec4/intellec4.cpp b/src/devices/bus/intellec4/intellec4.cpp index 65ea12e743f..7034d4c7b27 100644 --- a/src/devices/bus/intellec4/intellec4.cpp +++ b/src/devices/bus/intellec4/intellec4.cpp @@ -7,8 +7,8 @@ #include -DEFINE_DEVICE_TYPE_NS(INTELLEC4_UNIV_SLOT, bus::intellec4, univ_slot_device, "intlc4univslot", "INTELLEC 4/MOD 40 Universal Slot") -DEFINE_DEVICE_TYPE_NS(INTELLEC4_UNIV_BUS, bus::intellec4, univ_bus_device, "intlc4univbus", "INTELLEC 4/MOD 40 Universal Bus") +DEFINE_DEVICE_TYPE_NS(INTELLEC4_UNIV_SLOT, bus::intellec4, univ_slot_device, "intlc4univslot", "INTELLEC 4 Universal Slot") +DEFINE_DEVICE_TYPE_NS(INTELLEC4_UNIV_BUS, bus::intellec4, univ_bus_device, "intlc4univbus", "INTELLEC 4 Universal Bus") namespace bus { namespace intellec4 { diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp index 872d0e7df29..dc488903cb0 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -353,7 +353,7 @@ void cli_frontend::listfull(const std::vector &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); // print the header osd_printf_info("Name: Description:\n"); @@ -377,7 +377,7 @@ void cli_frontend::listsource(const std::vector &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); // iterate through drivers and output the info while (drivlist.next()) @@ -396,13 +396,13 @@ void cli_frontend::listclones(const std::vector &args) // start with a filtered list of drivers driver_enumerator drivlist(m_options, gamename); - int original_count = drivlist.count(); + int const original_count = drivlist.count(); // iterate through the remaining ones to see if their parent matches while (drivlist.next_excluded()) { // if we have a non-bios clone and it matches, keep it - int clone_of = drivlist.clone(); + int const clone_of = drivlist.clone(); if ((clone_of >= 0) && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT)) if (drivlist.matches(gamename, drivlist.driver(clone_of).name)) drivlist.include(); @@ -413,9 +413,9 @@ void cli_frontend::listclones(const std::vector &args) { // see if we match but just weren't a clone if (original_count == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); else - osd_printf_info("Found %lu matches for '%s' but none were clones\n", (unsigned long)drivlist.count(), gamename); + osd_printf_info("Found %lu match(es) for '%s' but none were clones\n", (unsigned long)drivlist.count(), gamename); // FIXME: this never gets hit return; } @@ -446,7 +446,7 @@ void cli_frontend::listbrothers(const std::vector &args) // start with a filtered list of drivers; return an error if none found driver_enumerator initial_drivlist(m_options, gamename); if (initial_drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); // for the final list, start with an empty driver list driver_enumerator drivlist(m_options); diff --git a/src/frontend/mame/info.cpp b/src/frontend/mame/info.cpp index a6d63c34847..01872db6a51 100644 --- a/src/frontend/mame/info.cpp +++ b/src/frontend/mame/info.cpp @@ -123,18 +123,26 @@ const char info_xml_creator::s_dtd_string[] = "\t\t\t\t\n" "\t\t\t\t\n" "\t\t\t\t\n" -"\t\t\n" +"\t\t\n" "\t\t\t\n" "\t\t\t\n" "\t\t\t\n" +"\t\t\t\n" +"\t\t\t\t\n" +"\t\t\t\t\n" +"\t\t\t\t\n" "\t\t\t\n" "\t\t\t\t\n" "\t\t\t\t\n" "\t\t\t\t\n" -"\t\t\n" +"\t\t\n" "\t\t\t\n" "\t\t\t\n" "\t\t\t\n" +"\t\t\t\n" +"\t\t\t\t\n" +"\t\t\t\t\n" +"\t\t\t\t\n" "\t\t\t\n" "\t\t\t\t\n" "\t\t\t\t\n" @@ -466,14 +474,14 @@ void info_xml_creator::output_one(driver_enumerator &drivlist, device_type_set * // now print various additional information output_bios(driver); output_rom(&drivlist, config->root_device()); - output_device_roms(config->root_device()); + output_device_refs(config->root_device()); output_sample(config->root_device()); output_chips(config->root_device(), ""); output_display(config->root_device(), &drivlist.driver().flags, ""); output_sound(config->root_device()); output_input(portlist); - output_switches(portlist, "", IPT_DIPSWITCH, "dipswitch", "dipvalue"); - output_switches(portlist, "", IPT_CONFIG, "configuration", "confsetting"); + output_switches(portlist, "", IPT_DIPSWITCH, "dipswitch", "diplocation", "dipvalue"); + output_switches(portlist, "", IPT_CONFIG, "configuration", "conflocation", "confsetting"); output_ports(portlist); output_adjusters(portlist); output_driver(driver, overall_unemulated, overall_imperfect); @@ -535,6 +543,7 @@ void info_xml_creator::output_one_device(machine_config &config, device_t &devic fprintf(m_output, "\t\t%s\n", util::xml::normalize_string(device.name())); output_rom(nullptr, device); + output_device_refs(device); if (device.type().type() != typeid(samples_device)) // ignore samples_device itself output_sample(device); @@ -545,8 +554,8 @@ void info_xml_creator::output_one_device(machine_config &config, device_t &devic output_sound(device); if (has_input) output_input(portlist); - output_switches(portlist, devtag, IPT_DIPSWITCH, "dipswitch", "dipvalue"); - output_switches(portlist, devtag, IPT_CONFIG, "configuration", "confsetting"); + output_switches(portlist, devtag, IPT_DIPSWITCH, "dipswitch", "diplocation", "dipvalue"); + output_switches(portlist, devtag, IPT_CONFIG, "configuration", "conflocation", "confsetting"); output_adjusters(portlist); output_features(device.type(), overall_unemulated, overall_imperfect); output_images(device, devtag); @@ -600,14 +609,14 @@ void info_xml_creator::output_devices(device_type_set const *filter) //------------------------------------------------ -// output_device_roms - when a driver uses roms -// included in a device set, print a reference +// output_device_refs - when a machine uses a +// subdevice, print a reference //------------------------------------------------- -void info_xml_creator::output_device_roms(device_t &root) +void info_xml_creator::output_device_refs(device_t &root) { for (device_t &device : device_iterator(root)) - if (device.owner()) + if (&device != &root) fprintf(m_output, "\t\t\n", util::xml::normalize_string(device.shortname())); } @@ -1448,11 +1457,11 @@ void info_xml_creator::output_input(const ioport_list &portlist) // DIP switch settings //------------------------------------------------- -void info_xml_creator::output_switches(const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *innertag) +void info_xml_creator::output_switches(const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *loctag, const char *innertag) { // iterate looking for DIP switches for (auto &port : portlist) - for (ioport_field &field : port.second->fields()) + for (ioport_field const &field : port.second->fields()) if (field.type() == type) { std::ostringstream output; @@ -1461,15 +1470,22 @@ void info_xml_creator::output_switches(const ioport_list &portlist, const char * newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); // output the switch name information - std::string normalized_field_name(util::xml::normalize_string(field.name())); - std::string normalized_newtag(util::xml::normalize_string(newtag.c_str())); - util::stream_format(output,"\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask()); + std::string const normalized_field_name(util::xml::normalize_string(field.name())); + std::string const normalized_newtag(util::xml::normalize_string(newtag.c_str())); + util::stream_format(output, "\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask()); + + // loop over locations + for (ioport_diplocation const &diploc : field.diplocations()) + { + util::stream_format(output, "\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, util::xml::normalize_string(diploc.name()), diploc.number()); + if (diploc.inverted()) + output << " inverted=\"yes\""; + output << "/>\n"; + } // loop over settings - for (ioport_setting &setting : field.settings()) - { + for (ioport_setting const &setting : field.settings()) util::stream_format(output,"\t\t\t<%s name=\"%s\" value=\"%u\"%s/>\n", innertag, util::xml::normalize_string(setting.name()), setting.value(), setting.value() == field.defvalue() ? " default=\"yes\"" : ""); - } // terminate the switch entry util::stream_format(output,"\t\t\n", outertag); diff --git a/src/frontend/mame/info.h b/src/frontend/mame/info.h index 635084a3a3b..f210ade6320 100644 --- a/src/frontend/mame/info.h +++ b/src/frontend/mame/info.h @@ -49,13 +49,13 @@ private: void output_sampleof(device_t &device); void output_bios(game_driver const &driver); void output_rom(driver_enumerator *drivlist, device_t &device); - void output_device_roms(device_t &root); + void output_device_refs(device_t &root); void output_sample(device_t &device); void output_chips(device_t &device, const char *root_tag); void output_display(device_t &device, machine_flags::type const *flags, const char *root_tag); void output_sound(device_t &device); void output_input(const ioport_list &portlist); - void output_switches(const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *innertag); + void output_switches(const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *loctag, const char *innertag); void output_ports(const ioport_list &portlist); void output_adjusters(const ioport_list &portlist); void output_driver(game_driver const &driver, device_t::feature_type unemulated, device_t::feature_type imperfect);