diff options
Diffstat (limited to 'docs/release/scripts/minimaws/lib')
-rw-r--r-- | docs/release/scripts/minimaws/lib/__init__.py | 4 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/common.js | 78 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/machine.js | 525 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/sortasc.png | bin | 0 -> 1157 bytes | |||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/sortdesc.png | bin | 0 -> 1195 bytes | |||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/sortind.png | bin | 0 -> 1247 bytes | |||
-rw-r--r-- | docs/release/scripts/minimaws/lib/assets/style.css | 11 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/auxverbs.py | 83 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/dbaccess.py | 606 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/htmltmpl.py | 165 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/lxparse.py | 280 | ||||
-rw-r--r-- | docs/release/scripts/minimaws/lib/wsgiserve.py | 526 |
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 Binary files differnew file mode 100644 index 00000000000..29b893cdd02 --- /dev/null +++ b/docs/release/scripts/minimaws/lib/assets/sortasc.png diff --git a/docs/release/scripts/minimaws/lib/assets/sortdesc.png b/docs/release/scripts/minimaws/lib/assets/sortdesc.png Binary files differnew file mode 100644 index 00000000000..63f48fc7213 --- /dev/null +++ b/docs/release/scripts/minimaws/lib/assets/sortdesc.png diff --git a/docs/release/scripts/minimaws/lib/assets/sortind.png b/docs/release/scripts/minimaws/lib/assets/sortind.png Binary files differnew file mode 100644 index 00000000000..773dd6aec02 --- /dev/null +++ b/docs/release/scripts/minimaws/lib/assets/sortind.png 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…<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 |