summaryrefslogtreecommitdiffstats
path: root/docs/release/scripts/minimaws/lib
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/scripts/minimaws/lib')
-rw-r--r--docs/release/scripts/minimaws/lib/__init__.py4
-rw-r--r--docs/release/scripts/minimaws/lib/assets/common.js78
-rw-r--r--docs/release/scripts/minimaws/lib/assets/machine.js525
-rw-r--r--docs/release/scripts/minimaws/lib/assets/sortasc.pngbin0 -> 1157 bytes
-rw-r--r--docs/release/scripts/minimaws/lib/assets/sortdesc.pngbin0 -> 1195 bytes
-rw-r--r--docs/release/scripts/minimaws/lib/assets/sortind.pngbin0 -> 1247 bytes
-rw-r--r--docs/release/scripts/minimaws/lib/assets/style.css11
-rw-r--r--docs/release/scripts/minimaws/lib/auxverbs.py83
-rw-r--r--docs/release/scripts/minimaws/lib/dbaccess.py606
-rw-r--r--docs/release/scripts/minimaws/lib/htmltmpl.py165
-rw-r--r--docs/release/scripts/minimaws/lib/lxparse.py280
-rw-r--r--docs/release/scripts/minimaws/lib/wsgiserve.py526
12 files changed, 2278 insertions, 0 deletions
diff --git a/docs/release/scripts/minimaws/lib/__init__.py b/docs/release/scripts/minimaws/lib/__init__.py
new file mode 100644
index 00000000000..d89cb7f81ba
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
diff --git a/docs/release/scripts/minimaws/lib/assets/common.js b/docs/release/scripts/minimaws/lib/assets/common.js
new file mode 100644
index 00000000000..79200217ddb
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/common.js
@@ -0,0 +1,78 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+
+function sort_table(tbl, col, dir, numeric)
+{
+ var tbody = tbl.tBodies[0];
+ var trows = Array.prototype.slice.call(tbody.rows, 0).sort(
+ function (x, y)
+ {
+ if (numeric)
+ return dir * (parseInt(x.cells[col].textContent) - parseInt(y.cells[col].textContent));
+ else
+ return dir * x.cells[col].textContent.localeCompare(y.cells[col].textContent);
+ })
+ trows.forEach(function (row) { tbody.appendChild(row); });
+}
+
+
+function make_table_sortable(tbl)
+{
+ var headers = tbl.tHead.rows[0].cells;
+ for (var i = 0; i < headers.length; i++)
+ {
+ (function (col)
+ {
+ var dir = 1;
+ var sorticon = document.createElement('img');
+ sorticon.setAttribute('src', assetsurl + '/sortind.png');
+ sorticon.style.cssFloat = 'right';
+ sorticon.style.marginLeft = '0.5em';
+ headers[col].appendChild(sorticon);
+ headers[col].addEventListener(
+ 'click',
+ function (event)
+ {
+ var imgsrc = sorticon.getAttribute('src');
+ imgsrc = imgsrc.substr(imgsrc.lastIndexOf('/') + 1);
+ if (imgsrc != 'sortind.png')
+ dir = -dir;
+ if (dir < 0)
+ sorticon.setAttribute('src', assetsurl + '/sortdesc.png');
+ else
+ sorticon.setAttribute('src', assetsurl + '/sortasc.png');
+ for (var i = 0; i < headers.length; i++)
+ {
+ if (i != col)
+ headers[i].lastChild.setAttribute('src', assetsurl + '/sortind.png');
+ }
+ sort_table(tbl, col, dir, headers[col].getAttribute('class') == 'numeric');
+ });
+ }(i));
+ }
+}
+
+
+function make_restore_default_handler(popup, index)
+{
+ return function (event)
+ {
+ if (popup.selectedIndex != index)
+ {
+ popup.selectedIndex = index;
+ popup.dispatchEvent(new Event('change'));
+ }
+ }
+}
+
+
+function make_restore_default_button(title, id, popup, index)
+{
+ var btn = document.createElement('button');
+ btn.setAttribute('id', id);
+ btn.setAttribute('type', 'button');
+ btn.disabled = popup.selectedIndex == index;
+ btn.textContent = title;
+ btn.onclick = make_restore_default_handler(popup, index);
+ return btn;
+}
diff --git a/docs/release/scripts/minimaws/lib/assets/machine.js b/docs/release/scripts/minimaws/lib/assets/machine.js
new file mode 100644
index 00000000000..d0774d02c0a
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/machine.js
@@ -0,0 +1,525 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+
+var slot_info = Object.create(null);
+var bios_sets = Object.create(null);
+var machine_flags = Object.create(null);
+
+
+function make_slot_popup_id(name) { return ('select-slot-choice-' + name).replace(/:/g, '-'); }
+function get_slot_popup(name) { return document.getElementById(make_slot_popup_id(name)); }
+
+
+function update_cmd_preview()
+{
+ var inifmt = document.getElementById('select-options-format').value == 'ini';
+ var result = '';
+ var first = true;
+ function add_option(flag, value)
+ {
+ if (first)
+ first = false;
+ else if (inifmt)
+ result += '\n';
+ else
+ result += ' ';
+
+ if (inifmt)
+ {
+ result += flag + ' ';
+ if (flag.length < 25)
+ result += ' '.repeat(25 - flag.length);
+ result += value;
+ }
+ else
+ {
+ result += '-' + flag + ' ';
+ if (value == '')
+ result += '""';
+ else
+ result += value;
+ }
+ }
+ var elide_defaults = !document.getElementById('check-explicit-defaults').checked;
+
+ // add system BIOS if applicable
+ var sysbios = document.getElementById('select-system-bios');
+ if (sysbios && (!elide_defaults || (sysbios.selectedOptions[0].getAttribute('data-isdefault') != 'yes')))
+ add_option('bios', sysbios.value);
+
+ // add RAM option if applicable
+ var ramopt = document.getElementById('select-ram-option');
+ if (ramopt && (!elide_defaults || (ramopt.selectedOptions[0].getAttribute('data-isdefault') != 'yes')))
+ add_option('ramsize', ramopt.value);
+
+ var slotslist = document.getElementById('list-slot-options');
+ if (slotslist)
+ {
+ for (var item = slotslist.firstChild; item; item = item.nextSibling)
+ {
+ if (item.nodeName == 'DT')
+ {
+ // need to set the slot option if it has non-default card and/or non-default card BIOS
+ var slotname = item.getAttribute('data-slotname');
+ var selection = get_slot_popup(slotname).selectedOptions[0];
+ var biospopup = document.getElementById(('select-slot-bios-' + slotname).replace(/:/g, '-'));
+ var defcard = selection.getAttribute('data-isdefault') == 'yes';
+ var defbios = !biospopup || (biospopup.selectedOptions[0].getAttribute('data-isdefault') == 'yes');
+ if (!elide_defaults || !defcard || !defbios)
+ {
+ var card = selection.value;
+ add_option(slotname, card + ((biospopup && (!elide_defaults || !defbios)) ? (',bios=' + biospopup.value) : ''));
+ }
+ }
+ }
+ }
+
+ // replace the preview with appropriate element
+ var target = document.getElementById('para-cmd-preview');
+ var replacement = document.createElement(inifmt ? 'pre' : 'tt');
+ replacement.setAttribute('id', 'para-cmd-preview');
+ replacement.textContent = result;
+ target.parentNode.replaceChild(replacement, target);
+}
+
+
+function set_default_system_bios()
+{
+ // search for an explicit default option
+ var sysbios = document.getElementById('select-system-bios');
+ var len = sysbios.options.length;
+ for (var i = 0; i < len; i++)
+ {
+ if (sysbios.options[i].getAttribute('data-isdefault') == 'yes')
+ {
+ // select it and add a button for restoring it
+ sysbios.selectedIndex = i;
+ var dflt = make_restore_default_button('default', 'btn-def-system-bios', sysbios, i);
+ sysbios.onchange = make_slot_bios_change_handler(dflt);
+ sysbios.parentNode.appendChild(document.createTextNode(' '));
+ sysbios.parentNode.appendChild(dflt);
+ break;
+ }
+ }
+ update_cmd_preview();
+}
+
+
+function set_default_ram_option()
+{
+ // search for an explicit default option
+ var ramopt = document.getElementById('select-ram-option');
+ var len = ramopt.options.length;
+ for (var i = 0; i < len; i++)
+ {
+ if (ramopt.options[i].getAttribute('data-isdefault') == 'yes')
+ {
+ // select it and add a button for restoring it
+ ramopt.selectedIndex = i;
+ var dflt = make_restore_default_button('default', 'btn-def-ram-option', ramopt, i);
+ ramopt.onchange = make_slot_bios_change_handler(dflt);
+ ramopt.parentNode.appendChild(document.createTextNode(' '));
+ ramopt.parentNode.appendChild(dflt);
+ break;
+ }
+ }
+ update_cmd_preview();
+}
+
+
+var fetch_bios_sets = (function ()
+ {
+ var pending = Object.create(null);
+ return function (device)
+ {
+ if (!Object.prototype.hasOwnProperty.call(bios_sets, device) && !Object.prototype.hasOwnProperty.call(pending, device))
+ {
+ pending[device] = true;
+ var req = new XMLHttpRequest();
+ req.open('GET', appurl + 'rpc/bios/' + device, true);
+ req.responseType = 'json';
+ req.onload =
+ function ()
+ {
+ delete pending[device];
+ if (req.status == 200)
+ {
+ bios_sets[device] = req.response;
+ var slotslist = document.getElementById('list-slot-options');
+ if (slotslist)
+ {
+ for (var item = slotslist.firstChild; item; item = item.nextSibling)
+ {
+ if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device))
+ add_bios_row(item.getAttribute('data-slotname'), item.nextSibling.firstChild, device);
+ }
+ }
+ }
+ };
+ req.send();
+ }
+ };
+ })();
+
+
+var fetch_machine_flags = (function ()
+ {
+ var pending = Object.create(null);
+ return function (device)
+ {
+ if (!Object.prototype.hasOwnProperty.call(machine_flags, device) && !Object.prototype.hasOwnProperty.call(pending, device))
+ {
+ pending[device] = true;
+ var req = new XMLHttpRequest();
+ req.open('GET', appurl + 'rpc/flags/' + device, true);
+ req.responseType = 'json';
+ req.onload =
+ function ()
+ {
+ delete pending[device];
+ if (req.status == 200)
+ {
+ machine_flags[device] = req.response;
+ var slotslist = document.getElementById('list-slot-options');
+ if (slotslist)
+ {
+ for (var item = slotslist.firstChild; item; item = item.nextSibling)
+ {
+ if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device))
+ add_flag_rows(item.nextSibling.firstChild, device);
+ }
+ }
+ }
+ };
+ req.send();
+ }
+ };
+ })();
+
+
+function add_flag_rows(table, device)
+{
+ var sorted_features = Object.keys(machine_flags[device].features).sort();
+ var imperfect = [], unemulated = [];
+ var len = sorted_features.length;
+ for (var i = 0; i < len; i++)
+ ((machine_flags[device].features[sorted_features[i]].overall == 'unemulated') ? unemulated : imperfect).push(sorted_features[i]);
+
+ function add_one(flags, title)
+ {
+ var len = flags.length;
+ if (len > 0)
+ {
+ var row = document.createElement('tr');
+ row.appendChild(document.createElement('th')).textContent = title;
+ var cell = row.appendChild(document.createElement('td'));
+ cell.textContent = flags[0];
+ for (i = 1; i < len; i++)
+ cell.textContent += ', ' + flags[i];
+ if (table.lastChild.getAttribute('class') == 'devbios')
+ table.insertBefore(row, table.lastChild);
+ else
+ table.appendChild(row);
+ }
+ }
+
+ add_one(unemulated, 'Unemulated features:');
+ add_one(imperfect, 'Imperfect features:');
+}
+
+
+function add_bios_row(slot, table, device)
+{
+ var sorted_sets = Object.keys(bios_sets[device]).sort();
+ var len = sorted_sets.length;
+ if (len > 0)
+ {
+ // create table row, add heading
+ var row = document.createElement('tr');
+ row.setAttribute('class', 'devbios');
+ row.appendChild(document.createElement('th')).textContent = 'BIOS:';
+ var cell = document.createElement('td');
+
+ // make the BIOS popul itself
+ var popup = document.createElement('select');
+ popup.setAttribute('id', ('select-slot-bios-' + slot).replace(/:/g, '-'));
+ for (var i = 0; i < len; i++)
+ {
+ var set = sorted_sets[i];
+ var detail = bios_sets[device][set];
+ var option = document.createElement('option');
+ option.setAttribute('value', set);
+ option.setAttribute('data-isdefault', detail.isdefault ? 'yes' : 'no');
+ option.textContent = set + ' - ' + detail.description;
+ popup.appendChild(option);
+ if (detail.isdefault)
+ popup.selectedIndex = i;
+ }
+ cell.appendChild(popup);
+
+ // make a button to restore the default
+ var dflt;
+ if (popup.selectedOptions[0].getAttribute('data-isdefault') == 'yes')
+ {
+ dflt = make_restore_default_button('default', ('btn-def-slot-bios-' + slot).replace(/:/g, '-'), popup, popup.selectedIndex);
+ cell.appendChild(document.createTextNode(' '))
+ cell.appendChild(dflt);
+ }
+
+ // drop the controls into a cell, add it to the table, keep the command line preview up-to-date
+ popup.onchange = make_slot_bios_change_handler(dflt);
+ row.appendChild(cell);
+ table.appendChild(row);
+ update_cmd_preview();
+ }
+}
+
+
+function make_slot_term(name, slot, defaults)
+{
+ var len, i;
+
+ // see if we can find a default, outer layers take precedence
+ var defcard = '';
+ len = defaults.length;
+ for (i = 0; (i < len) && (defcard == ''); i++)
+ {
+ if (Object.prototype.hasOwnProperty.call(defaults[i], name))
+ defcard = defaults[i][name];
+ }
+
+ // create a container with the slot name and popup
+ var term = document.createElement('dt');
+ term.setAttribute('id', ('item-slot-choice-' + name).replace(/:/g, '-'));
+ term.setAttribute('data-slotname', name);
+ term.setAttribute('data-slotcard', '');
+ term.textContent = name + ': ';
+ var popup = document.createElement('select');
+ popup.setAttribute('id', make_slot_popup_id(name));
+ term.appendChild(popup);
+
+ // add the empty slot option
+ var option = document.createElement('option');
+ option.setAttribute('value', '');
+ option.setAttribute('data-isdefault', ('' == defcard) ? 'yes' : 'no');
+ option.textContent = '-';
+ popup.appendChild(option);
+ popup.selectedIndex = 0;
+
+ // add options for the cards
+ var sorted_choices = Object.keys(slot).sort();
+ len = sorted_choices.length;
+ for (i = 0; i < len; i++)
+ {
+ var choice = sorted_choices[i];
+ var card = slot[choice];
+ option = document.createElement('option');
+ option.setAttribute('value', choice);
+ option.setAttribute('data-isdefault', (choice == defcard) ? 'yes' : 'no');
+ option.textContent = choice + ' - ' + card.description;
+ popup.appendChild(option);
+ if (choice == defcard)
+ popup.selectedIndex = i + 1;
+ }
+
+ // make a button for restoring the default and hook up events
+ var dflt = make_restore_default_button('default', ('btn-def-slot-choice-' + name).replace(/:/g, '-'), popup, popup.selectedIndex);
+ term.appendChild(document.createTextNode(' '));
+ term.appendChild(dflt);
+ popup.onchange = make_slot_change_handler(name, slot, defaults, dflt);
+ return term;
+}
+
+
+function add_slot_items(root, device, defaults, slotslist, pos)
+{
+ // add another layer of defaults for this device
+ var defvals = Object.create(null);
+ for (var key in slot_info[device].defaults)
+ defvals[root + key] = slot_info[device].defaults[key];
+ defaults = defaults.slice();
+ defaults.push(defvals);
+ var defcnt = defaults.length;
+
+ // add controls for each subslot
+ var slots = slot_info[device].slots;
+ var sorted_slots = Object.keys(slots).sort();
+ var len = sorted_slots.length;
+ for (var i = 0; i < len; i++)
+ {
+ var slotname = sorted_slots[i];
+ var slotabs = root + slotname;
+ var slot = slots[slotname];
+ var term = make_slot_term(slotabs, slot, defaults);
+ var def = document.createElement('dd');
+ def.setAttribute('id', ('item-slot-detail-' + slotabs).replace(/:/g, '-'));
+ if (pos)
+ {
+ slotslist.insertBefore(term, pos);
+ slotslist.insertBefore(def, pos);
+ }
+ else
+ {
+ slotslist.appendChild(term);
+ slotslist.appendChild(def);
+ }
+
+ // force a change event to populate subslot controls if the default isn't empty
+ get_slot_popup(slotabs).dispatchEvent(new Event('change'));
+ }
+
+ update_cmd_preview();
+}
+
+
+function make_slot_change_handler(name, slot, defaults, dfltbtn)
+{
+ var selection = null;
+ return function (event)
+ {
+ var choice = event.target.value;
+ var slotslist = event.target.parentNode.parentNode;
+ var def = event.target.parentNode.nextSibling;
+ var slotname = event.target.parentNode.getAttribute('data-slotname');
+ selection = (choice == '') ? null : slot[choice];
+ dfltbtn.disabled = event.target.selectedOptions[0].getAttribute('data-isdefault') == 'yes';
+
+ // clear out any subslots from previous selection
+ var prefix = slotname + ':';
+ for (var candidate = def.nextSibling; candidate && candidate.getAttribute('data-slotname').startsWith(prefix); )
+ {
+ var next = candidate.nextSibling;
+ slotslist.removeChild(candidate);
+ candidate = next.nextSibling;
+ slotslist.removeChild(next);
+ }
+
+ if (selection === null)
+ {
+ // no selection, remove the slot card details table
+ event.target.parentNode.setAttribute('data-slotcard', '');
+ if (def.firstChild)
+ def.removeChild(def.firstChild);
+ }
+ else
+ {
+ // stash the selected device where it's easy to find
+ event.target.parentNode.setAttribute('data-slotcard', selection.device);
+
+ // create details table and add a link to the device's page
+ var tbl = document.createElement('table');
+ tbl.setAttribute('class', 'sysinfo');
+ var row = tbl.appendChild(document.createElement('tr'));
+ row.appendChild(document.createElement('th')).textContent = 'Short name:';
+ var link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a'));
+ link.textContent = selection.device;
+ link.setAttribute('href', appurl + 'machine/' + selection.device);
+
+ // if we have emulation flags, populate now, otherwise fetch asynchronously
+ if (!Object.prototype.hasOwnProperty.call(machine_flags, selection.device))
+ fetch_machine_flags(selection.device);
+ else
+ add_flag_rows(tbl, selection.device);
+
+ // if we have BIOS details, populate now, otherwise fetch asynchronously
+ if (!Object.prototype.hasOwnProperty.call(bios_sets, selection.device))
+ fetch_bios_sets(selection.device);
+ else
+ add_bios_row(slotname, tbl, selection.device);
+
+ // drop the details table into the list
+ if (def.firstChild)
+ def.replaceChild(tbl, def.firstChild);
+ else
+ def.appendChild(tbl);
+
+ // create controls for subslots
+ add_slot_items(slotname + ':' + choice, selection.device, defaults, slotslist, def.nextSibling);
+ }
+ update_cmd_preview();
+ };
+}
+
+
+function make_slot_bios_change_handler(dflt)
+{
+ return function (event)
+ {
+ if (dflt)
+ dflt.disabled = event.target.selectedOptions[0].getAttribute('data-isdefault') == 'yes';
+ update_cmd_preview();
+ }
+}
+
+
+function populate_slots(machine)
+{
+ var placeholder = document.getElementById('para-slots-placeholder');
+ var slotslist = document.createElement('dl');
+ slotslist.setAttribute('id', 'list-slot-options');
+ placeholder.parentNode.replaceChild(slotslist, placeholder);
+ add_slot_items('', machine, [], slotslist, null);
+}
+
+
+function slot_retrieve_error(device)
+{
+ var errors;
+ var placeholder = document.getElementById('para-slots-placeholder');
+ if (placeholder)
+ {
+ errors = document.createElement('div');
+ errors.setAttribute('id', 'div-slots-errors');
+ placeholder.parentNode.replaceChild(errors, placeholder);
+ }
+ else
+ {
+ errors = document.getElementById('div-slots-errors');
+ }
+ var message = document.createElement('p');
+ message.textContent = 'Error retrieving slot information for ' + device + '.';
+ errors.appendChild(message);
+}
+
+
+function fetch_slots(machine)
+{
+ function make_request(device)
+ {
+ var req = new XMLHttpRequest();
+ req.open('GET', appurl + 'rpc/slots/' + device, true);
+ req.responseType = 'json';
+ req.onload =
+ function ()
+ {
+ if (req.status == 200)
+ {
+ slot_info[device] = req.response;
+ delete pending[device];
+ for (var slotname in req.response.slots)
+ {
+ var slot = req.response.slots[slotname];
+ for (var choice in slot)
+ {
+ var card = slot[choice].device
+ if (!Object.prototype.hasOwnProperty.call(slot_info, card) && !Object.prototype.hasOwnProperty.call(pending, card))
+ {
+ pending[card] = true;
+ make_request(card);
+ }
+ }
+ }
+ if (!Object.keys(pending).length)
+ populate_slots(machine);
+ }
+ else
+ {
+ slot_retrieve_error(device);
+ }
+ };
+ req.send();
+ }
+ var pending = Object.create(null);
+ pending[machine] = true;
+ make_request(machine);
+}
diff --git a/docs/release/scripts/minimaws/lib/assets/sortasc.png b/docs/release/scripts/minimaws/lib/assets/sortasc.png
new file mode 100644
index 00000000000..29b893cdd02
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/sortasc.png
Binary files differ
diff --git a/docs/release/scripts/minimaws/lib/assets/sortdesc.png b/docs/release/scripts/minimaws/lib/assets/sortdesc.png
new file mode 100644
index 00000000000..63f48fc7213
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/sortdesc.png
Binary files differ
diff --git a/docs/release/scripts/minimaws/lib/assets/sortind.png b/docs/release/scripts/minimaws/lib/assets/sortind.png
new file mode 100644
index 00000000000..773dd6aec02
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/sortind.png
Binary files differ
diff --git a/docs/release/scripts/minimaws/lib/assets/style.css b/docs/release/scripts/minimaws/lib/assets/style.css
new file mode 100644
index 00000000000..75ea0fa93de
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/assets/style.css
@@ -0,0 +1,11 @@
+/* license:BSD-3-Clause
+ * copyright-holders:Vas Crabb
+ */
+
+th { text-align: left; background-color: #ddd; padding: 0.25em }
+td { padding-left: 0.25em; padding-right: 0.25em }
+
+table[class=sysinfo] th { text-align: right }
+
+dl[id=list-slot-options] dt { font-weight: bold; margin-top: 1em }
+dl[id=list-slot-options] dd table { margin-top: 0.5em; margin-bottom: 1em }
diff --git a/docs/release/scripts/minimaws/lib/auxverbs.py b/docs/release/scripts/minimaws/lib/auxverbs.py
new file mode 100644
index 00000000000..c68c528af6f
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/auxverbs.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+from . import dbaccess
+
+import sys
+
+
+def do_listfull(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, description in dbcurs.listfull(options.pattern):
+ if first:
+ sys.stdout.write('Name: Description:\n')
+ first = False
+ sys.stdout.write('%-16s "%s"\n' % (shortname, description))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listsource(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ shortname = None
+ for shortname, sourcefile in dbcurs.listsource(options.pattern):
+ sys.stdout.write('%-16s %s\n' % (shortname, sourcefile))
+ if shortname is None:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listclones(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, parent in dbcurs.listclones(options.pattern):
+ if first:
+ sys.stdout.write('Name: Clone of:\n')
+ first = False
+ sys.stdout.write('%-16s %s\n' % (shortname, parent))
+ if first:
+ count = dbcurs.count_systems(options.pattern).fetchone()[0]
+ if count:
+ sys.stderr.write('Found %d match(es) for \'%s\' but none were clones\n' % (count, options.pattern))
+ else:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+
+def do_listbrothers(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for sourcefile, shortname, parent in dbcurs.listbrothers(options.pattern):
+ if first:
+ sys.stdout.write('%-20s %-16s %s\n' % ('Source file:', 'Name:', 'Parent:'))
+ first = False
+ sys.stdout.write('%-20s %-16s %s\n' % (sourcefile, shortname, parent or ''))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
+
+def do_listaffected(options):
+ dbconn = dbaccess.QueryConnection(options.database)
+ dbcurs = dbconn.cursor()
+ first = True
+ for shortname, description in dbcurs.listaffected(*options.pattern):
+ if first:
+ sys.stdout.write('Name: Description:\n')
+ first = False
+ sys.stdout.write('%-16s "%s"\n' % (shortname, description))
+ if first:
+ sys.stderr.write('No matching systems found for \'%s\'\n' % (options.pattern, ))
+ dbcurs.close()
+ dbconn.close()
diff --git a/docs/release/scripts/minimaws/lib/dbaccess.py b/docs/release/scripts/minimaws/lib/dbaccess.py
new file mode 100644
index 00000000000..57de6aeb236
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/dbaccess.py
@@ -0,0 +1,606 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+import sqlite3
+import sys
+
+if sys.version_info >= (3, 4):
+ import urllib.request
+
+
+class SchemaQueries(object):
+ CREATE_FEATURETYPE = \
+ 'CREATE TABLE featuretype (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' UNIQUE (name ASC))'
+ CREATE_SOURCEFILE = \
+ 'CREATE TABLE sourcefile (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' filename TEXT NOT NULL,\n' \
+ ' UNIQUE (filename ASC))'
+ CREATE_MACHINE = \
+ 'CREATE TABLE machine (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' shortname TEXT NOT NULL,\n' \
+ ' description TEXT NOT NULL,\n' \
+ ' sourcefile INTEGER NOT NULL,\n' \
+ ' isdevice INTEGER NOT NULL,\n' \
+ ' runnable INTEGER NOT NULL,\n' \
+ ' UNIQUE (shortname ASC),\n' \
+ ' UNIQUE (description ASC),\n' \
+ ' FOREIGN KEY (sourcefile) REFERENCES sourcefile (id))'
+ CREATE_SYSTEM = \
+ 'CREATE TABLE system (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' year TEXT NOT NULL,\n' \
+ ' manufacturer TEXT NOT NULL,\n' \
+ ' FOREIGN KEY (id) REFERENCES machine (id))'
+ CREATE_CLONEOF = \
+ 'CREATE TABLE cloneof (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' parent TEXT NOT NULL,\n' \
+ ' FOREIGN KEY (id) REFERENCES machine (id))'
+ CREATE_ROMOF = \
+ 'CREATE TABLE romof (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' parent TEXT NOT NULL,\n' \
+ ' FOREIGN KEY (id) REFERENCES machine (id))'
+ CREATE_BIOSSET = \
+ 'CREATE TABLE biosset (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' description TEXT NOT NULL,\n' \
+ ' UNIQUE (machine ASC, name ASC),\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id))'
+ CREATE_BIOSSETDEFAULT = \
+ 'CREATE TABLE biossetdefault (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' FOREIGN KEY (id) REFERENCES biosset (id))'
+ CREATE_DEVICEREFERENCE = \
+ 'CREATE TABLE devicereference (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' device INTEGER NOT NULL,\n' \
+ ' UNIQUE (machine ASC, device ASC),\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id),\n' \
+ ' FOREIGN KEY (device) REFERENCES machine (id))'
+ CREATE_DIPSWITCH = \
+ 'CREATE TABLE dipswitch (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' isconfig INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' tag TEXT NOT NULL,\n' \
+ ' mask INTEGER NOT NULL,\n' \
+ ' --UNIQUE (machine ASC, tag ASC, mask ASC), not necessarily true, need to expose port conditions\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id))'
+ CREATE_DIPLOCATION = \
+ 'CREATE TABLE diplocation (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' dipswitch INTEGER NOT NULL,\n' \
+ ' bit INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' num INTEGER NOT NULL,\n' \
+ ' inverted INTEGER NOT NULL,\n' \
+ ' UNIQUE (dipswitch ASC, bit ASC),\n' \
+ ' FOREIGN KEY (dipswitch) REFERENCES dipswitch (id))'
+ CREATE_DIPVALUE = \
+ 'CREATE TABLE dipvalue (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' dipswitch INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' value INTEGER NOT NULL,\n' \
+ ' isdefault INTEGER NOT NULL,\n' \
+ ' FOREIGN KEY (dipswitch) REFERENCES dipswitch (id))'
+ CREATE_FEATURE = \
+ 'CREATE TABLE feature (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' featuretype INTEGER NOT NULL,\n' \
+ ' status INTEGER NOT NULL,\n' \
+ ' overall INTEGER NOT NULL,\n' \
+ ' UNIQUE (machine ASC, featuretype ASC),\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id),\n' \
+ ' FOREIGN KEY (featuretype) REFERENCES featuretype (id))'
+ CREATE_SLOT = \
+ 'CREATE TABLE slot (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' UNIQUE (machine ASC, name ASC),\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id))'
+ CREATE_SLOTOPTION = \
+ 'CREATE TABLE slotoption (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' slot INTEGER NOT NULL,\n' \
+ ' device INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' UNIQUE (slot ASC, name ASC),\n' \
+ ' FOREIGN KEY (slot) REFERENCES slot (id),\n' \
+ ' FOREIGN KEY (device) REFERENCES machine (id))'
+ CREATE_SLOTDEFAULT = \
+ 'CREATE TABLE slotdefault (\n' \
+ ' id INTEGER PRIMARY KEY,\n' \
+ ' slotoption INTEGER NOT NULL,\n' \
+ ' FOREIGN KEY (id) REFERENCES slot (id),\n' \
+ ' FOREIGN KEY (slotoption) REFERENCES slotoption (id))'
+ CREATE_RAMOPTION = \
+ 'CREATE TABLE ramoption (\n' \
+ ' machine INTEGER NOT NULL,\n' \
+ ' size INTEGER NOT NULL,\n' \
+ ' name TEXT NOT NULL,\n' \
+ ' PRIMARY KEY (machine ASC, size ASC),\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id))'
+ CREATE_RAMDEFAULT = \
+ 'CREATE TABLE ramdefault (\n' \
+ ' machine INTEGER PRIMARY KEY,\n' \
+ ' size INTEGER NOT NULL,\n' \
+ ' FOREIGN KEY (machine) REFERENCES machine (id),\n' \
+ ' FOREIGN KEY (machine, size) REFERENCES ramoption (machine, size))'
+
+ CREATE_TEMPORARY_DEVICEREFERENCE = 'CREATE TEMPORARY TABLE temp_devicereference (id INTEGER PRIMARY KEY, machine INTEGER NOT NULL, device TEXT NOT NULL, UNIQUE (machine, device))'
+ CREATE_TEMPORARY_SLOTOPTION = 'CREATE TEMPORARY TABLE temp_slotoption (id INTEGER PRIMARY KEY, slot INTEGER NOT NULL, device TEXT NOT NULL, name TEXT NOT NULL)'
+ CREATE_TEMPORARY_SLOTDEFAULT = 'CREATE TEMPORARY TABLE temp_slotdefault (id INTEGER PRIMARY KEY, slotoption INTEGER NOT NULL)'
+
+ DROP_TEMPORARY_DEVICEREFERENCE = 'DROP TABLE IF EXISTS temp_devicereference'
+ DROP_TEMPORARY_SLOTOPTION = 'DROP TABLE IF EXISTS temp_slotoption'
+ DROP_TEMPORARY_SLOTDEFAULT = 'DROP TABLE IF EXISTS temp_slotdefault'
+
+ 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_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_DIPSWITCH_MACHINE_ISCONFIG = 'DROP INDEX IF EXISTS dipswitch_machine_isconfig'
+
+ CREATE_TABLES = (
+ CREATE_FEATURETYPE,
+ CREATE_SOURCEFILE,
+ CREATE_MACHINE,
+ CREATE_SYSTEM,
+ CREATE_CLONEOF,
+ CREATE_ROMOF,
+ CREATE_BIOSSET,
+ CREATE_BIOSSETDEFAULT,
+ CREATE_DEVICEREFERENCE,
+ CREATE_DIPSWITCH,
+ CREATE_DIPLOCATION,
+ CREATE_DIPVALUE,
+ CREATE_FEATURE,
+ CREATE_SLOT,
+ CREATE_SLOTOPTION,
+ CREATE_SLOTDEFAULT,
+ CREATE_RAMOPTION,
+ CREATE_RAMDEFAULT)
+
+ CREATE_TEMPORARY_TABLES = (
+ CREATE_TEMPORARY_DEVICEREFERENCE,
+ CREATE_TEMPORARY_SLOTOPTION,
+ CREATE_TEMPORARY_SLOTDEFAULT)
+
+ CREATE_INDEXES = (
+ INDEX_MACHINE_ISDEVICE_SHORTNAME,
+ INDEX_MACHINE_ISDEVICE_DESCRIPTION,
+ INDEX_MACHINE_RUNNABLE_SHORTNAME,
+ INDEX_MACHINE_RUNNABLE_DESCRIPTION,
+ INDEX_SYSTEM_YEAR,
+ INDEX_SYSTEM_MANUFACTURER,
+ INDEX_ROMOF_PARENT,
+ INDEX_CLONEOF_PARENT,
+ INDEX_DIPSWITCH_MACHINE_ISCONFIG)
+
+ DROP_INDEXES = (
+ DROP_MACHINE_ISDEVICE_SHORTNAME,
+ DROP_MACHINE_ISDEVICE_DESCRIPTION,
+ DROP_MACHINE_RUNNABLE_SHORTNAME,
+ DROP_MACHINE_RUNNABLE_DESCRIPTION,
+ DROP_SYSTEM_YEAR,
+ DROP_SYSTEM_MANUFACTURER,
+ DROP_ROMOF_PARENT,
+ DROP_CLONEOF_PARENT,
+ DROP_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_BIOSSET = 'INSERT INTO biosset (machine, name, description) VALUES (?, ?, ?)'
+ ADD_BIOSSETDEFAULT = 'INSERT INTO biossetdefault (id) 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 = ?'
+ ADD_SLOT = 'INSERT INTO slot (machine, name) VALUES (?, ?)'
+ ADD_RAMOPTION = 'INSERT INTO ramoption (machine, size, name) VALUES (?, ?, ?)'
+ ADD_RAMDEFAULT = 'INSERT INTO ramdefault (machine, size) VALUES (?, ?)'
+
+ ADD_TEMPORARY_DEVICEREFERENCE = 'INSERT OR IGNORE INTO temp_devicereference (machine, device) VALUES (?, ?)'
+ ADD_TEMPORARY_SLOTOPTION = 'INSERT INTO temp_slotoption (slot, device, name) VALUES (?, ?, ?)'
+ ADD_TEMPORARY_SLOTDEFAULT = 'INSERT INTO temp_slotdefault (id, slotoption) VALUES (?, ?)'
+
+ FINALISE_DEVICEREFERENCES = 'INSERT INTO devicereference (id, machine, device) SELECT temp_devicereference.id, temp_devicereference.machine, machine.id FROM temp_devicereference LEFT JOIN machine ON temp_devicereference.device = machine.shortname'
+ FINALISE_SLOTOPTIONS = 'INSERT INTO slotoption (id, slot, device, name) SELECT temp_slotoption.id, temp_slotoption.slot, machine.id, temp_slotoption.name FROM temp_slotoption LEFT JOIN machine ON temp_slotoption.device = machine.shortname'
+ FINALISE_SLOTDEFAULTS = 'INSERT INTO slotdefault (id, slotoption) SELECT id, slotoption FROM temp_slotdefault'
+
+
+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 id 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 id 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 id 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_id(self, machine):
+ return (self.dbcurs.execute('SELECT id FROM machine WHERE shortname = ?', (machine, )).fetchone() or (None, ))[0]
+
+ 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_biossets(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT biosset.name AS name, biosset.description AS description, COUNT(biossetdefault.id) AS isdefault ' \
+ 'FROM biosset LEFT JOIN biossetdefault USING (id) ' \
+ 'WHERE biosset.machine = ? ' \
+ 'GROUP BY biosset.id',
+ (machine, ))
+
+ def get_devices_referenced(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \
+ 'FROM devicereference LEFT JOIN machine ON devicereference.device = machine.id LEFT JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
+ 'WHERE devicereference.machine = ?',
+ (machine, ))
+
+ def get_device_references(self, device):
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname AS shortname, machine.description AS description, sourcefile.filename AS sourcefile ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id ' \
+ 'WHERE machine.id IN (SELECT machine FROM devicereference WHERE device = ?)',
+ (device, ))
+
+ def get_compatible_slots(self, device):
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname AS shortname, machine.description AS description, slot.name AS slot, slotoption.name AS slotoption, sourcefile.filename AS sourcefile ' \
+ 'FROM slotoption JOIN slot ON slotoption.slot = slot.id JOIN machine on slot.machine = machine.id JOIN sourcefile ON machine.sourcefile = sourcefile.id '
+ 'WHERE slotoption.device = ?',
+ (device, ))
+
+ def get_sourcefile_id(self, filename):
+ return (self.dbcurs.execute('SELECT id FROM sourcefile WHERE filename = ?', (filename, )).fetchone() or (None, ))[0]
+
+ def get_sourcefile_machines(self, id):
+ return self.dbcurs.execute(
+ 'SELECT machine.shortname AS shortname, machine.description AS description, machine.isdevice AS isdevice, machine.runnable AS runnable, sourcefile.filename AS sourcefile, system.year AS year, system.manufacturer AS manufacturer, cloneof.parent AS cloneof, romof.parent AS romof ' \
+ 'FROM machine JOIN sourcefile ON machine.sourcefile = sourcefile.id LEFT JOIN system ON machine.id = system.id LEFT JOIN cloneof ON system.id = cloneof.id LEFT JOIN romof ON system.id = romof.id ' \
+ 'WHERE machine.sourcefile = ?',
+ (id, ))
+
+ def get_sourcefiles(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute(
+ 'SELECT sourcefile.filename AS filename, COUNT(machine.id) AS machines ' \
+ 'FROM sourcefile LEFT JOIN machine ON sourcefile.id = machine.sourcefile ' \
+ 'WHERE sourcefile.filename GLOB ?' \
+ 'GROUP BY sourcefile.id ',
+ (pattern, ))
+ else:
+ return self.dbcurs.execute(
+ 'SELECT sourcefile.filename AS filename, COUNT(machine.id) AS machines ' \
+ 'FROM sourcefile LEFT JOIN machine ON sourcefile.id = machine.sourcefile ' \
+ 'GROUP BY sourcefile.id')
+
+ def count_sourcefiles(self, pattern):
+ if pattern is not None:
+ return self.dbcurs.execute('SELECT COUNT(*) FROM sourcefile WHERE filename GLOB ?', (pattern, )).fetchone()[0]
+ else:
+ return self.dbcurs.execute('SELECT COUNT(*) FROM sourcefile').fetchone()[0]
+
+ def count_slots(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT COUNT(*) FROM slot WHERE machine = ?', (machine, )).fetchone()[0]
+
+ def get_feature_flags(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT featuretype.name AS featuretype, feature.status AS status, feature.overall AS overall ' \
+ 'FROM feature JOIN featuretype ON feature.featuretype = featuretype.id ' \
+ 'WHERE feature.machine = ?',
+ (machine, ))
+
+ def get_slot_defaults(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT slot.name AS name, slotoption.name AS option ' \
+ 'FROM slot JOIN slotdefault ON slot.id = slotdefault.id JOIN slotoption ON slotdefault.slotoption = slotoption.id ' \
+ 'WHERE slot.machine = ?',
+ (machine, ))
+
+ def get_slot_options(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT slot.name AS slot, slotoption.name AS option, machine.shortname AS shortname, machine.description AS description ' \
+ 'FROM slot JOIN slotoption ON slot.id = slotoption.slot JOIN machine ON slotoption.device = machine.id ' \
+ 'WHERE slot.machine = ?',
+ (machine, ))
+
+ def get_ram_options(self, machine):
+ return self.dbcurs.execute(
+ 'SELECT ramoption.name AS name, ramoption.size AS size, COUNT(ramdefault.machine) AS isdefault ' \
+ 'FROM ramoption LEFT JOIN ramdefault USING (machine, size) WHERE ramoption.machine = ? ' \
+ 'GROUP BY ramoption.machine, ramoption.size ' \
+ 'ORDER BY ramoption.size',
+ (machine, ))
+
+
+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_biosset(self, machine, name, description):
+ self.dbcurs.execute(UpdateQueries.ADD_BIOSSET, (machine, name, description))
+ return self.dbcurs.lastrowid
+
+ def add_biossetdefault(self, biosset):
+ self.dbcurs.execute(UpdateQueries.ADD_BIOSSETDEFAULT, (biosset, ))
+ return self.dbcurs.lastrowid
+
+ def add_devicereference(self, machine, device):
+ self.dbcurs.execute(UpdateQueries.ADD_TEMPORARY_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
+
+ def add_slot(self, machine, name):
+ self.dbcurs.execute(UpdateQueries.ADD_SLOT, (machine, name))
+ return self.dbcurs.lastrowid
+
+ def add_slotoption(self, slot, device, name):
+ self.dbcurs.execute(UpdateQueries.ADD_TEMPORARY_SLOTOPTION, (slot, device, name))
+ return self.dbcurs.lastrowid
+
+ def add_slotdefault(self, slot, slotoption):
+ self.dbcurs.execute(UpdateQueries.ADD_TEMPORARY_SLOTDEFAULT, (slot, slotoption))
+ return self.dbcurs.lastrowid
+
+ def add_ramoption(self, machine, name, size):
+ self.dbcurs.execute(UpdateQueries.ADD_RAMOPTION, (machine, size, name))
+ return self.dbcurs.lastrowid
+
+ def add_ramdefault(self, machine, size):
+ self.dbcurs.execute(UpdateQueries.ADD_RAMDEFAULT, (machine, size))
+ return self.dbcurs.lastrowid
+
+
+class QueryConnection(object):
+ def __init__(self, database, **kwargs):
+ super(QueryConnection, self).__init__(**kwargs)
+ if sys.version_info >= (3, 4):
+ self.dbconn = sqlite3.connect('file:' + urllib.request.pathname2url(database) + '?mode=ro', uri=True)
+ else:
+ 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 page_size = 4096')
+ 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 prepare_for_load(self):
+ # here be dragons - this is a poor man's DROP ALL TABLES etc.
+ self.dbconn.execute('PRAGMA foreign_keys = OFF')
+ for query in self.dbconn.execute('SELECT \'DROP INDEX \' || name FROM sqlite_master WHERE type = \'index\' AND NOT name GLOB \'sqlite_autoindex_*\'').fetchall():
+ self.dbconn.execute(query[0])
+ for query in self.dbconn.execute('SELECT \'DROP TABLE \' || name FROM sqlite_master WHERE type = \'table\'').fetchall():
+ self.dbconn.execute(query[0])
+ self.dbconn.execute('PRAGMA foreign_keys = ON')
+
+ # this is where the sanity starts
+ for query in SchemaQueries.DROP_INDEXES:
+ self.dbconn.execute(query)
+ for query in SchemaQueries.CREATE_TABLES:
+ self.dbconn.execute(query)
+ for query in SchemaQueries.CREATE_TEMPORARY_TABLES:
+ self.dbconn.execute(query)
+ self.dbconn.commit()
+
+ def finalise_load(self):
+ self.dbconn.execute(UpdateQueries.FINALISE_DEVICEREFERENCES)
+ self.dbconn.commit()
+ self.dbconn.execute(SchemaQueries.DROP_TEMPORARY_DEVICEREFERENCE)
+ self.dbconn.execute(UpdateQueries.FINALISE_SLOTOPTIONS)
+ self.dbconn.commit()
+ self.dbconn.execute(SchemaQueries.DROP_TEMPORARY_SLOTOPTION)
+ self.dbconn.execute(UpdateQueries.FINALISE_SLOTDEFAULTS)
+ self.dbconn.commit()
+ self.dbconn.execute(SchemaQueries.DROP_TEMPORARY_SLOTDEFAULT)
+ for query in SchemaQueries.CREATE_INDEXES:
+ self.dbconn.execute(query)
+ self.dbconn.commit()
diff --git a/docs/release/scripts/minimaws/lib/htmltmpl.py b/docs/release/scripts/minimaws/lib/htmltmpl.py
new file mode 100644
index 00000000000..1a030d04e93
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/htmltmpl.py
@@ -0,0 +1,165 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+import string
+
+
+ERROR_PAGE = string.Template(
+ '<!DOCTYPE html>\n' \
+ '<html>\n' \
+ '<head>\n' \
+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
+ ' <title>${code} ${message}</title>\n' \
+ '</head>\n' \
+ '<body>\n' \
+ '<h1>${message}</h1>\n' \
+ '</body>\n' \
+ '</html>\n')
+
+
+SORTABLE_TABLE_EPILOGUE = string.Template(
+ ' </tbody>\n'
+ '</table>\n'
+ '<script>make_table_sortable(document.getElementById("${id}"));</script>\n')
+
+MACHINE_PROLOGUE = string.Template(
+ '<!DOCTYPE html>\n' \
+ '<html>\n' \
+ '<head>\n' \
+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
+ ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \
+ ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \
+ ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \
+ ' <script type="text/javascript">\n' \
+ ' var appurl="${app}"\n' \
+ ' var assetsurl="${assets}"\n' \
+ ' </script>\n' \
+ ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \
+ ' <script type="text/javascript" src="${assets}/machine.js"></script>\n' \
+ ' <title>Machine: ${description} (${shortname})</title>\n' \
+ '</head>\n' \
+ '<body>\n' \
+ '<h1>${description}</h1>\n' \
+ '<table class="sysinfo">\n' \
+ ' <tr><th>Short name:</th><td>${shortname}</td></tr>\n' \
+ ' <tr><th>Is device:</th><td>${isdevice}</td></tr>\n' \
+ ' <tr><th>Runnable:</th><td>${runnable}</td></tr>\n' \
+ ' <tr><th>Source file:</th><td><a href="${sourcehref}">${sourcefile}</a></td></tr>\n')
+
+MACHINE_OPTIONS_HEADING = string.Template(
+ '<h2>Options</h2>\n' \
+ '<p>\n' \
+ ' Format: <select id="select-options-format" onchange="update_cmd_preview()"><option value="cmd">Command line</option><option value="ini">INI file</option></select>\n' \
+ ' <input type="checkbox" id="check-explicit-defaults" onchange="update_cmd_preview()"><label for="check-explicit-defaults">Explicit defaults</label>\n' \
+ '</p>\n' \
+ '<p id="para-cmd-preview"></p>\n')
+
+MACHINE_BIOS_PROLOGUE = string.Template(
+ '<h3>System BIOS</h3>' \
+ '<select id="select-system-bios" onchange="update_cmd_preview()">')
+
+MACHINE_BIOS_OPTION = string.Template(
+ ' <option value="${name}" data-isdefault="${isdefault}">${name} - ${description}</option>\n')
+
+MACHINE_RAM_PROLOGUE = string.Template(
+ '<h3>RAM Size</h3>' \
+ '<select id="select-ram-option" onchange="update_cmd_preview()">')
+
+MACHINE_RAM_OPTION = string.Template(
+ ' <option value="${name}" data-isdefault="${isdefault}">${name} (${size})</option>\n')
+
+MACHINE_SLOTS_PLACEHOLDER = string.Template(
+ '<h3>Slots</h3>\n' \
+ '<p id="para-slots-placeholder">Loading slot information&hellip;<p>\n' \
+ '<script>fetch_slots("${machine}");</script>\n')
+
+MACHINE_ROW = string.Template(
+ ' <tr>\n' \
+ ' <td><a href="${machinehref}">${shortname}</a></td>\n' \
+ ' <td><a href="${machinehref}">${description}</a></td>\n' \
+ ' <td><a href="${sourcehref}">${sourcefile}</a></td>\n' \
+ ' </tr>\n')
+
+EXCL_MACHINE_ROW = string.Template(
+ ' <tr>\n' \
+ ' <td><a href="${machinehref}">${shortname}</a></td>\n' \
+ ' <td></td>\n' \
+ ' <td></td>\n' \
+ ' </tr>\n')
+
+COMPATIBLE_SLOT_ROW = string.Template(
+ ' <tr>\n' \
+ ' <td><a href="${machinehref}">${shortname}</a></td>\n' \
+ ' <td><a href="${machinehref}">${description}</a></td>\n' \
+ ' <td>${slot}</td>\n' \
+ ' <td>${slotoption}</td>\n' \
+ ' <td><a href="${sourcehref}">${sourcefile}</a></td>\n' \
+ ' </tr>\n')
+
+
+SOURCEFILE_PROLOGUE = string.Template(
+ '<!DOCTYPE html>\n' \
+ '<html>\n' \
+ '<head>\n' \
+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
+ ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \
+ ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \
+ ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \
+ ' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \
+ ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \
+ ' <title>Source File: ${filename}</title>\n' \
+ '</head>\n' \
+ '<body>\n' \
+ '<h1>${title}</h1>\n')
+
+SOURCEFILE_ROW_PARENT = string.Template(
+ ' <tr>\n' \
+ ' <td><a href="${machinehref}">${shortname}</a></td>\n' \
+ ' <td><a href="${machinehref}">${description}</a></td>\n' \
+ ' <td>${year}</td>\n' \
+ ' <td>${manufacturer}</td>\n' \
+ ' <td>${runnable}</td>\n' \
+ ' <td></td>\n' \
+ ' </tr>\n')
+
+SOURCEFILE_ROW_CLONE = string.Template(
+ ' <tr>\n' \
+ ' <td><a href="${machinehref}">${shortname}</a></td>\n' \
+ ' <td><a href="${machinehref}">${description}</a></td>\n' \
+ ' <td>${year}</td>\n' \
+ ' <td>${manufacturer}</td>\n' \
+ ' <td>${runnable}</td>\n' \
+ ' <td><a href="${parenthref}">${parent}</a></td>\n' \
+ ' </tr>\n')
+
+
+SOURCEFILE_LIST_PROLOGUE = string.Template(
+ '<!DOCTYPE html>\n' \
+ '<html>\n' \
+ '<head>\n' \
+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \
+ ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \
+ ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \
+ ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \
+ ' <script type="text/javascript">var assetsurl="${assets}"</script>\n' \
+ ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \
+ ' <title>${title}</title>\n' \
+ '</head>\n' \
+ '<body>\n' \
+ '<h1>${heading}</h1>\n' \
+ '<table id="tbl-sourcefiles">\n' \
+ ' <thead>\n' \
+ ' <tr>\n' \
+ ' <th>Source file</th>\n' \
+ ' <th class="numeric">Machines</th>\n' \
+ ' </tr>\n' \
+ ' </thead>\n' \
+ ' <tbody>\n')
+
+SOURCEFILE_LIST_ROW = string.Template(
+ ' <tr>\n' \
+ ' <td>${sourcefile}</td>\n' \
+ ' <td style="text-align: right">${machines}</td>\n' \
+ ' </tr>\n')
diff --git a/docs/release/scripts/minimaws/lib/lxparse.py b/docs/release/scripts/minimaws/lib/lxparse.py
new file mode 100644
index 00000000000..dfd8cbae45e
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/lxparse.py
@@ -0,0 +1,280 @@
+#!/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.get('inverted', 'no') == 'yes')
+ self.mask >>= 1
+ self.bit += 1
+ elif (name == 'dipvalue') or (name == 'confsetting'):
+ self.dbcurs.add_dipvalue(self.id, attrs['name'], attrs['value'], attrs.get('default', 'no') == 'yes')
+ self.setChildHandler(name, attrs, self.IGNORE)
+
+
+class SlotHandler(ElementHandler):
+ def __init__(self, parent, **kwargs):
+ super(SlotHandler, self).__init__(parent=parent, **kwargs)
+ self.dbcurs = parent.dbcurs
+ self.machine = parent.id
+
+ def startMainElement(self, name, attrs):
+ self.id = self.dbcurs.add_slot(self.machine, attrs['name'])
+
+ def startChildElement(self, name, attrs):
+ if name == 'slotoption':
+ option = self.dbcurs.add_slotoption(self.id, attrs['devname'], attrs['name'])
+ if attrs.get('default') == 'yes':
+ self.dbcurs.add_slotdefault(self.id, option)
+ self.setChildHandler(name, attrs, self.IGNORE)
+
+
+class RamOptionHandler(TextAccumulator):
+ def __init__(self, parent, **kwargs):
+ super(RamOptionHandler, self).__init__(parent=parent, **kwargs)
+ self.dbcurs = parent.dbcurs
+ self.machine = parent.id
+
+ def startMainElement(self, name, attrs):
+ self.name = attrs['name']
+ self.default = attrs.get('default', 'no') == 'yes';
+
+ def endMainElement(self, name):
+ self.size = int(self.text)
+ self.dbcurs.add_ramoption(self.machine, self.name, self.size)
+ if self.default:
+ self.dbcurs.add_ramdefault(self.machine, self.size)
+
+
+class MachineHandler(ElementHandler):
+ CHILD_HANDLERS = {
+ 'description': TextAccumulator,
+ 'year': TextAccumulator,
+ 'manufacturer': TextAccumulator,
+ 'dipswitch': DipSwitchHandler,
+ 'configuration': DipSwitchHandler,
+ 'slot': SlotHandler,
+ 'ramoption': RamOptionHandler }
+
+ 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.get('isdevice', 'no') == 'yes'
+ self.runnable = attrs.get('runnable', 'yes') == 'yes'
+ self.cloneof = attrs.get('cloneof')
+ self.romof = attrs.get('romof')
+ self.dbcurs.add_sourcefile(self.sourcefile)
+
+ def startChildElement(self, name, attrs):
+ if name in self.CHILD_HANDLERS:
+ self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
+ else:
+ if name == 'biosset':
+ bios = self.dbcurs.add_biosset(self.id, attrs['name'], attrs['description'])
+ if attrs.get('default', 'no') == 'yes':
+ self.dbcurs.add_biossetdefault(bios)
+ elif name == 'device_ref':
+ self.dbcurs.add_devicereference(self.id, attrs['name'])
+ elif name == 'feature':
+ 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.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.prepare_for_load()
+ self.machines = 0
+
+ def endMainElement(self, name):
+ # TODO: build index by first letter or whatever
+ self.dbconn.finalise_load()
+
+ 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 endChildHandler(self, name, handler):
+ if name == 'machine':
+ if self.machines >= 1023:
+ self.dbconn.commit()
+ self.machines = 0
+ else:
+ self.machines += 1
+
+ def processingInstruction(self, target, data):
+ pass
+
+
+def load_info(options):
+ parser = xml.sax.make_parser()
+ parser.setContentHandler(ListXmlHandler(dbaccess.UpdateConnection(options.database)))
+ if options.executable is not None:
+ task = subprocess.Popen([options.executable, '-listxml'], stdout=subprocess.PIPE)
+ parser.parse(task.stdout)
+ else:
+ parser.parse(options.file)
diff --git a/docs/release/scripts/minimaws/lib/wsgiserve.py b/docs/release/scripts/minimaws/lib/wsgiserve.py
new file mode 100644
index 00000000000..b30828e01e0
--- /dev/null
+++ b/docs/release/scripts/minimaws/lib/wsgiserve.py
@@ -0,0 +1,526 @@
+#!/usr/bin/python
+##
+## license:BSD-3-Clause
+## copyright-holders:Vas Crabb
+
+from . import dbaccess
+from . import htmltmpl
+
+import cgi
+import inspect
+import json
+import mimetypes
+import os.path
+import re
+import sys
+import wsgiref.simple_server
+import wsgiref.util
+
+if sys.version_info >= (3, ):
+ import urllib.parse as urlparse
+else:
+ import urlparse
+
+
+class HandlerBase(object):
+ STATUS_MESSAGE = {
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported' }
+
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(HandlerBase, self).__init__(**kwargs)
+ self.app = app
+ self.js_escape = app.js_escape
+ self.application_uri = application_uri
+ self.environ = environ
+ self.start_response = start_response
+
+ def error_page(self, code):
+ yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % code), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8')
+
+
+class ErrorPageHandler(HandlerBase):
+ def __init__(self, code, app, application_uri, environ, start_response, **kwargs):
+ super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.code = code
+ self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+
+ def __iter__(self):
+ return self.error_page(self.code)
+
+
+class AssetHandler(HandlerBase):
+ def __init__(self, directory, app, application_uri, environ, start_response, **kwargs):
+ super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.directory = directory
+ self.asset = wsgiref.util.shift_path_info(environ)
+
+ def __iter__(self):
+ if not self.asset:
+ self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(403)
+ elif self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ else:
+ path = os.path.join(self.directory, self.asset)
+ if not os.path.isfile(path):
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ try:
+ f = open(path, 'rb')
+ type, encoding = mimetypes.guess_type(path)
+ self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')])
+ return wsgiref.util.FileWrapper(f)
+ except:
+ self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(500)
+
+
+class QueryPageHandler(HandlerBase):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.dbcurs = app.dbconn.cursor()
+
+ def machine_href(self, shortname):
+ return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)
+
+ def sourcefile_href(self, sourcefile):
+ return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (sourcefile, )), True)
+
+
+class MachineRpcHandlerBase(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.shortname = wsgiref.util.shift_path_info(environ)
+
+ def __iter__(self):
+ if not self.shortname:
+ self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(403)
+ elif self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ else:
+ machine = self.dbcurs.get_machine_id(self.shortname)
+ if machine is None:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.data_page(machine)
+
+
+class MachineHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.shortname = wsgiref.util.shift_path_info(environ)
+
+ def __iter__(self):
+ if not self.shortname:
+ # could probably list machines here or something
+ self.start_response('403 %s' % (self.STATUS_MESSAGE[403], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(403)
+ elif self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ else:
+ machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone()
+ if not machine_info:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.machine_page(machine_info)
+
+ def machine_page(self, machine_info):
+ id = machine_info['id']
+ description = machine_info['description']
+ yield htmltmpl.MACHINE_PROLOGUE.substitute(
+ app=self.js_escape(cgi.escape(self.application_uri, True)),
+ assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)),
+ sourcehref=self.sourcefile_href(machine_info['sourcefile']),
+ 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>Year:</th><td>%s</td></tr>\n' \
+ ' <tr><th>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>Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
+ (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8')
+ else:
+ yield (
+ ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
+ (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8')
+ if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']):
+ parent = self.dbcurs.listfull(machine_info['romof']).fetchone()
+ if parent:
+ yield (
+ ' <tr><th>Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
+ (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8')
+ else:
+ yield (
+ ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
+ (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8')
+ unemulated = []
+ imperfect = []
+ for feature, status, overall in self.dbcurs.get_feature_flags(id):
+ if overall == 1:
+ imperfect.append(feature)
+ elif overall > 1:
+ unemulated.append(feature)
+ if (unemulated):
+ unemulated.sort()
+ yield(
+ (' <tr><th>Unemulated Features:</th><td>%s' + (', %s' * (len(unemulated) - 1)) + '</td></tr>\n') %
+ tuple(unemulated)).encode('utf-8');
+ if (imperfect):
+ yield(
+ (' <tr><th>Imperfect Features:</th><td>%s' + (', %s' * (len(imperfect) - 1)) + '</td></tr>\n') %
+ tuple(imperfect)).encode('utf-8');
+ yield '</table>\n'.encode('utf-8')
+
+ # allow system BIOS selection
+ haveoptions = False
+ for name, desc, isdef in self.dbcurs.get_biossets(id):
+ if not haveoptions:
+ haveoptions = True;
+ yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
+ yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8')
+ yield htmltmpl.MACHINE_BIOS_OPTION.substitute(
+ name=cgi.escape(name, True),
+ description=cgi.escape(desc),
+ isdefault=('yes' if isdef else 'no')).encode('utf-8')
+ if haveoptions:
+ yield '</select>\n<script>set_default_system_bios();</script>\n'.encode('utf-8')
+
+ # allow RAM size selection
+ first = True
+ for name, size, isdef in self.dbcurs.get_ram_options(id):
+ if first:
+ if not haveoptions:
+ haveoptions = True;
+ yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
+ yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8')
+ first = False
+ yield htmltmpl.MACHINE_RAM_OPTION.substitute(
+ name=cgi.escape(name, True),
+ size=cgi.escape('{:,}'.format(size)),
+ isdefault=('yes' if isdef else 'no')).encode('utf-8')
+ if not first:
+ yield '</select>\n<script>set_default_ram_option();</script>\n'.encode('utf-8')
+
+ # placeholder for machine slots - populated by client-side JavaScript
+ if self.dbcurs.count_slots(id):
+ if not haveoptions:
+ haveoptions = True
+ yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
+ yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute(
+ machine=self.js_escape(self.shortname)).encode('utf=8')
+
+ # list devices referenced by this system/device
+ first = True
+ for name, desc, src in self.dbcurs.get_devices_referenced(id):
+ if first:
+ yield \
+ '<h2>Devices Referenced</h2>\n' \
+ '<table id="tbl-dev-refs">\n' \
+ ' <thead>\n' \
+ ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
+ ' </thead>\n' \
+ ' <tbody>\n'.encode('utf-8')
+ first = False
+ yield self.machine_row(name, desc, src)
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8')
+
+ # list slots where this device is an option
+ first = True
+ for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id):
+ if (first):
+ yield \
+ '<h2>Compatible Slots</h2>\n' \
+ '<table id="tbl-comp-slots">\n' \
+ ' <thead>\n' \
+ ' <tr><th>Short name</th><th>Description</th><th>Slot</th><th>Choice</th><th>Source file</th></tr>\n' \
+ ' </thead>\n' \
+ ' <tbody>\n'.encode('utf-8')
+ first = False
+ yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute(
+ machinehref=self.machine_href(name),
+ sourcehref=self.sourcefile_href(src),
+ shortname=cgi.escape(name),
+ description=cgi.escape(desc),
+ sourcefile=cgi.escape(src),
+ slot=cgi.escape(slot),
+ slotoption=cgi.escape(opt)).encode('utf-8')
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8')
+
+ # list systems/devices that reference this device
+ first = True
+ for name, desc, src in self.dbcurs.get_device_references(id):
+ if first:
+ yield \
+ '<h2>Referenced By</h2>\n' \
+ '<table id="tbl-ref-by">\n' \
+ ' <thead>\n' \
+ ' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
+ ' </thead>\n' \
+ ' <tbody>\n'.encode('utf-8')
+ first = False
+ yield self.machine_row(name, desc, src)
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8')
+
+ yield '</html>\n'.encode('utf-8')
+
+ def machine_row(self, shortname, description, sourcefile):
+ return (htmltmpl.MACHINE_ROW if description is not None else htmltmpl.EXCL_MACHINE_ROW).substitute(
+ machinehref=self.machine_href(shortname),
+ sourcehref=self.sourcefile_href(sourcefile),
+ shortname=cgi.escape(shortname),
+ description=cgi.escape(description or ''),
+ sourcefile=cgi.escape(sourcefile or '')).encode('utf-8')
+
+
+class SourceFileHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super(SourceFileHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+
+ def __iter__(self):
+ self.filename = self.environ['PATH_INFO']
+ if self.filename and (self.filename[0] == '/'):
+ self.filename = self.filename[1:]
+ if not self.filename:
+ if self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.sourcefile_listing_page(None)
+ else:
+ id = self.dbcurs.get_sourcefile_id(self.filename)
+ if id is None:
+ if ('*' not in self.filename) and ('?' not in self.filename) and ('?' not in self.filename):
+ self.filename += '*' if self.filename[-1] == '/' else '/*'
+ if not self.dbcurs.count_sourcefiles(self.filename):
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.sourcefile_listing_page(self.filename)
+ else:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.sourcefile_page(id)
+
+ def sourcefile_listing_page(self, pattern):
+ if not pattern:
+ title = heading = 'All Source Files'
+ else:
+ heading = self.linked_title(pattern)
+ title = 'Source Files: ' + cgi.escape(pattern)
+ yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute(
+ assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
+ title=title,
+ heading=heading).encode('utf-8')
+ for filename, machines in self.dbcurs.get_sourcefiles(pattern):
+ yield htmltmpl.SOURCEFILE_LIST_ROW.substitute(
+ sourcefile=self.linked_title(filename, True),
+ machines=cgi.escape('%d' % machines)).encode('utf-8')
+ yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-sourcefiles"));</script>\n</body>\n</html>\n'.encode('utf-8')
+
+ def sourcefile_page(self, id):
+ yield htmltmpl.SOURCEFILE_PROLOGUE.substitute(
+ assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
+ filename=cgi.escape(self.filename),
+ title=self.linked_title(self.filename)).encode('utf-8')
+
+ first = True
+ for machine_info in self.dbcurs.get_sourcefile_machines(id):
+ if first:
+ yield \
+ '<table id="tbl-machines">\n' \
+ ' <thead>\n' \
+ ' <tr>\n' \
+ ' <th>Short name</th>\n' \
+ ' <th>Description</th>\n' \
+ ' <th>Year</th>\n' \
+ ' <th>Manufacturer</th>\n' \
+ ' <th>Runnable</th>\n' \
+ ' <th>Parent</th>\n' \
+ ' </tr>\n' \
+ ' </thead>\n' \
+ ' <tbody>\n'.encode('utf-8')
+ first = False
+ yield self.machine_row(machine_info)
+ if first:
+ yield '<p>No machines found.</p>\n'.encode('utf-8')
+ else:
+ yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-machines"));</script>\n'.encode('utf-8')
+
+ yield '</body>\n</html>\n'.encode('utf-8')
+
+ def linked_title(self, filename, linkfinal=False):
+ parts = filename.split('/')
+ final = parts[-1]
+ del parts[-1]
+ uri = urlparse.urljoin(self.application_uri, 'sourcefile')
+ title = ''
+ for part in parts:
+ uri = urlparse.urljoin(uri + '/', part)
+ title += '<a href="{0}">{1}</a>/'.format(cgi.escape(uri, True), cgi.escape(part))
+ if linkfinal:
+ uri = urlparse.urljoin(uri + '/', final)
+ return title + '<a href="{0}">{1}</a>'.format(cgi.escape(uri, True), cgi.escape(final))
+ else:
+ return title + final
+
+ def machine_row(self, machine_info):
+ return (htmltmpl.SOURCEFILE_ROW_PARENT if machine_info['cloneof'] is None else htmltmpl.SOURCEFILE_ROW_CLONE).substitute(
+ machinehref=self.machine_href(machine_info['shortname']),
+ parenthref=self.machine_href(machine_info['cloneof'] or '__invalid'),
+ shortname=cgi.escape(machine_info['shortname']),
+ description=cgi.escape(machine_info['description']),
+ year=cgi.escape(machine_info['year'] or ''),
+ manufacturer=cgi.escape(machine_info['manufacturer'] or ''),
+ runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
+ parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8')
+
+
+class BiosRpcHandler(MachineRpcHandlerBase):
+ def data_page(self, machine):
+ result = { }
+ for name, description, isdefault in self.dbcurs.get_biossets(machine):
+ result[name] = { 'description': description, 'isdefault': True if isdefault else False }
+ yield json.dumps(result).encode('utf-8')
+
+
+class FlagsRpcHandler(MachineRpcHandlerBase):
+ def data_page(self, machine):
+ result = { 'features': { } }
+ for feature, status, overall in self.dbcurs.get_feature_flags(machine):
+ detail = { }
+ if status == 1:
+ detail['status'] = 'imperfect'
+ elif status > 1:
+ detail['status'] = 'unemulated'
+ if overall == 1:
+ detail['overall'] = 'imperfect'
+ elif overall > 1:
+ detail['overall'] = 'unemulated'
+ result['features'][feature] = detail
+ yield json.dumps(result).encode('utf-8')
+
+
+class SlotsRpcHandler(MachineRpcHandlerBase):
+ def data_page(self, machine):
+ result = { 'defaults': { }, 'slots': { } }
+
+ # get defaults and slot options
+ for slot, default in self.dbcurs.get_slot_defaults(machine):
+ result['defaults'][slot] = default
+ prev = None
+ for slot, option, shortname, description in self.dbcurs.get_slot_options(machine):
+ if slot != prev:
+ if slot in result['slots']:
+ options = result['slots'][slot]
+ else:
+ options = { }
+ result['slots'][slot] = options
+ prev = slot
+ options[option] = { 'device': shortname, 'description': description }
+
+ # remove slots that come from default cards in other slots
+ for slot in tuple(result['slots'].keys()):
+ slot += ':'
+ for candidate in tuple(result['slots'].keys()):
+ if candidate.startswith(slot):
+ del result['slots'][candidate]
+
+ yield json.dumps(result).encode('utf-8')
+
+
+class MiniMawsApp(object):
+ JS_ESCAPE = re.compile('([\"\'\\\\])')
+ RPC_SERVICES = {
+ 'bios': BiosRpcHandler,
+ 'flags': FlagsRpcHandler,
+ 'slots': SlotsRpcHandler }
+
+ def __init__(self, dbfile, **kwargs):
+ super(MiniMawsApp, self).__init__(**kwargs)
+ self.dbconn = dbaccess.QueryConnection(dbfile)
+ self.assetsdir = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), 'assets')
+ if not mimetypes.inited:
+ mimetypes.init()
+
+ def __call__(self, environ, start_response):
+ 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)
+ elif module == 'sourcefile':
+ return SourceFileHandler(self, application_uri, environ, start_response)
+ elif module == 'static':
+ return AssetHandler(self.assetsdir, self, application_uri, environ, start_response)
+ elif module == 'rpc':
+ service = wsgiref.util.shift_path_info(environ)
+ if not service:
+ return ErrorPageHandler(403, self, application_uri, environ, start_response)
+ elif service in self.RPC_SERVICES:
+ return self.RPC_SERVICES[service](self, application_uri, environ, start_response)
+ else:
+ return ErrorPageHandler(404, self, application_uri, environ, start_response)
+ elif not module:
+ return ErrorPageHandler(403, self, application_uri, environ, start_response)
+ else:
+ return ErrorPageHandler(404, self, application_uri, environ, start_response)
+
+ def js_escape(self, str):
+ return self.JS_ESCAPE.sub('\\\\\\1', str).replace('\0', '\\0')
+
+
+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