Add "minimaws" sample script demonstrating how to do some tasks with

output from -listxml verb.  Compatible with Python 2.7 or Python 3.
Requires at least SQLite 3.6.19 for foreign key support.

This serves a few purposes:
* Demonstrating some things that can be done with -listxml output
* Providing a reference implementation for useful queries
* Helping ensure our XML output isn't completely useless
* Providing additional queries over MAME's auxiliary verbs
* Proper glob support unlike the broken implementation in MAME right now

Right now, it's a bit ugly to use.  You can only load into a completely
clean database, and you need to manually create the schema.  I'll
address this later.  The default database filename is minimaws.sqlite3
(you can override this with --database before the verb on the command
line).  Loading isn't particularly fast, but query performance is very
good.

Create a database first:
rm -f minimaws.sqlite3
sqlite3 minimaws.sqlite3 < scripts/minimaws/schema.sql

Now you can load it using a MAME binary or XML output (use one of these
options, not both):
python scripts/minimaws/minimaws.py load --executable ./mame
python scripts/minimaws/minimaws.py load --file mame0188.xml

Once that's done you can do queries:
python scripts/minimaws/minimaws.py listfull
python scripts/minimaws/minimaws.py listclones "*cmast*"
python scripts/minimaws/minimaws.py listsource "*mous*"
python scripts/minimaws/minimaws.py listbrothers "intl*"

These work much like the equivalent MAME verbs, but without the overhead
of loading MAME's static data.  But there's one already query that you
can't easily do with MAME:
python scripts/minimaws/minimaws.py listaffected "src/devices/cpu/m6805/*" src/devices/sound/qsound.cpp

This will list all runnable systems that use a device defined in any
file under devices/cpu/m6805 or in devices/sound/qsound.cpp (you can
specify and arbitrary number of files or glob patterns).  This may be
useful for planning regression tests.

Another thing this does (that gives rise to the name) is serving
information over HTTP.  It's implemented as a WSGI, and it mainly uses
GET requests.  This means it can run hosted in Apache mod_wsgi, or
cached by Apache mod_proxy, Squid, nginx, or something else.  It can
also run out-of-the-box using wsgiref.simple_server components.  The
default port is 8080 but this can be changed with the --port option.

Start the web server with the serve verb (stop it with keyboard
interrupt ^C or similar):
python scripts/minimaws/minimaws.py serve

Right now it's rather crude, and doesn't list devices for you.  This
means you have to know the shortname of a machine to get a useful URL.

For example, you can look at a driver and see its parent set and the
devices it references:
http://localhost:8080/machine/kof2000n

Or you can look at a device, and see the devices it refereces, as well
as the devices/systems that reference it:
http://localhost:8080/machine/zac1b11142

The links between devices/systems are clickable.  They might 404 on you
if you used a single-driver build with broken parent/clone
relationships, but they should all work in a full build that passes
validation.

There's still a lot to do.  In particular I want to demonstrate how to
do live DIP switch preview and dynamic slot discovery.  But I've already
discovered stuff in the -listxml output that's less than ideal with
this, so it's helping.
This commit is contained in:
Vas Crabb 2017-08-01 02:55:25 +10:00
parent 456fd5e422
commit 8981b40bd9
12 changed files with 952 additions and 30 deletions

View File

@ -0,0 +1,4 @@
#!/usr/bin/python
##
## license:BSD-3-Clause
## copyright-holders:Vas Crabb

View File

@ -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()

View File

@ -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()

View File

@ -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')

View File

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

View File

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

59
scripts/minimaws/minimaws.py Executable file
View File

@ -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, ))

View File

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

View File

@ -7,8 +7,8 @@
#include <algorithm>
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 {

View File

@ -353,7 +353,7 @@ void cli_frontend::listfull(const std::vector<std::string> &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<std::string> &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<std::string> &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<std::string> &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<std::string> &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);

View File

@ -123,18 +123,26 @@ const char info_xml_creator::s_dtd_string[] =
"\t\t\t\t<!ATTLIST control ways CDATA #IMPLIED>\n"
"\t\t\t\t<!ATTLIST control ways2 CDATA #IMPLIED>\n"
"\t\t\t\t<!ATTLIST control ways3 CDATA #IMPLIED>\n"
"\t\t<!ELEMENT dipswitch (dipvalue*)>\n"
"\t\t<!ELEMENT dipswitch (diplocation*, dipvalue*)>\n"
"\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n"
"\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n"
"\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n"
"\t\t\t<!ELEMENT diplocation EMPTY>\n"
"\t\t\t\t<!ATTLIST diplocation name CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST diplocation number CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST diplocation inverted (yes|no) \"no\">\n"
"\t\t\t<!ELEMENT dipvalue EMPTY>\n"
"\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n"
"\t\t<!ELEMENT configuration (confsetting*)>\n"
"\t\t<!ELEMENT configuration (conflocation*, confsetting*)>\n"
"\t\t\t<!ATTLIST configuration name CDATA #REQUIRED>\n"
"\t\t\t<!ATTLIST configuration tag CDATA #REQUIRED>\n"
"\t\t\t<!ATTLIST configuration mask CDATA #REQUIRED>\n"
"\t\t\t<!ELEMENT conflocation EMPTY>\n"
"\t\t\t\t<!ATTLIST conflocation name CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST conflocation number CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST conflocation inverted (yes|no) \"no\">\n"
"\t\t\t<!ELEMENT confsetting EMPTY>\n"
"\t\t\t\t<!ATTLIST confsetting name CDATA #REQUIRED>\n"
"\t\t\t\t<!ATTLIST confsetting value CDATA #REQUIRED>\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<description>%s</description>\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<device_ref name=\"%s\"/>\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</%s>\n", outertag);

View File

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