mirror of
https://github.com/holub/mame
synced 2025-04-19 23:12:11 +03:00
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:
parent
456fd5e422
commit
8981b40bd9
4
scripts/minimaws/lib/__init__.py
Normal file
4
scripts/minimaws/lib/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
##
|
||||
## license:BSD-3-Clause
|
||||
## copyright-holders:Vas Crabb
|
83
scripts/minimaws/lib/auxverbs.py
Normal file
83
scripts/minimaws/lib/auxverbs.py
Normal 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()
|
292
scripts/minimaws/lib/dbaccess.py
Normal file
292
scripts/minimaws/lib/dbaccess.py
Normal 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()
|
22
scripts/minimaws/lib/htmltmpl.py
Normal file
22
scripts/minimaws/lib/htmltmpl.py
Normal 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')
|
227
scripts/minimaws/lib/lxparse.py
Normal file
227
scripts/minimaws/lib/lxparse.py
Normal 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)
|
135
scripts/minimaws/lib/wsgiserve.py
Normal file
135
scripts/minimaws/lib/wsgiserve.py
Normal 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
59
scripts/minimaws/minimaws.py
Executable 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, ))
|
84
scripts/minimaws/schema.sql
Normal file
84
scripts/minimaws/schema.sql
Normal 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));
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user