2017-03-13 21:51:03 +01:00
|
|
|
#!/usr/bin/env python3
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
|
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
|
|
#
|
|
|
|
# Copyright (C) 2014-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
|
|
# Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2015-01-19 18:03:23 +01:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import textwrap
|
|
|
|
|
|
|
|
import lxml.etree
|
|
|
|
|
2018-07-15 23:08:23 +02:00
|
|
|
class Element:
|
2015-01-12 13:22:04 +01:00
|
|
|
def __init__(self, schema, xml):
|
|
|
|
self.schema = schema
|
|
|
|
self.xml = xml
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
def nsmap(self):
|
|
|
|
return self.schema.nsmap
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self.xml.get('name')
|
|
|
|
|
|
|
|
|
|
|
|
def get_description(self, xml=None, wrap=True):
|
2015-01-19 17:06:30 +01:00
|
|
|
if xml is None:
|
|
|
|
xml = self.xml
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
xml = xml.xpath('./doc:description', namespaces=self.nsmap)
|
2015-01-19 17:06:30 +01:00
|
|
|
if not xml:
|
|
|
|
return ''
|
2015-01-12 13:22:04 +01:00
|
|
|
xml = xml[0]
|
|
|
|
|
|
|
|
if wrap:
|
|
|
|
return ''.join(self.schema.wrapper.fill(p) + '\n\n'
|
|
|
|
for p in textwrap.dedent(xml.text.strip('\n')).split('\n\n'))
|
2017-04-15 20:04:38 +02:00
|
|
|
|
|
|
|
return ' '.join(xml.text.strip().split())
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_data_type(self, xml=None):
|
2015-01-19 17:06:30 +01:00
|
|
|
if xml is None:
|
|
|
|
xml = self.xml
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
value = xml.xpath('./rng:value', namespaces=self.nsmap)
|
|
|
|
if value:
|
|
|
|
value = '``{}``'.format(value[0].text.strip())
|
|
|
|
else:
|
|
|
|
metavar = xml.xpath('./doc:metavar', namespaces=self.nsmap)
|
|
|
|
if metavar:
|
|
|
|
value = '``{}``'.format(metavar[0].text.strip())
|
|
|
|
else:
|
|
|
|
value = ''
|
|
|
|
|
|
|
|
xml = xml.xpath('./rng:data', namespaces=self.nsmap)
|
|
|
|
if not xml:
|
|
|
|
return ('', value)
|
|
|
|
|
|
|
|
xml = xml[0]
|
|
|
|
type_ = xml.get('type', '')
|
|
|
|
|
|
|
|
if not value:
|
|
|
|
pattern = xml.xpath('./rng:param[@name="pattern"]',
|
|
|
|
namespaces=self.nsmap)
|
|
|
|
if pattern:
|
|
|
|
value = '``{}``'.format(pattern[0].text.strip())
|
|
|
|
|
|
|
|
return type_, value
|
|
|
|
|
|
|
|
|
|
|
|
def get_attributes(self):
|
|
|
|
for xml in self.xml.xpath('''./rng:attribute |
|
|
|
|
./rng:optional/rng:attribute |
|
|
|
|
./rng:choice/rng:attribute''', namespaces=self.nsmap):
|
2017-04-15 23:48:02 +02:00
|
|
|
required = 'yes' if xml.getparent() == self.xml else 'no'
|
2015-01-12 13:22:04 +01:00
|
|
|
yield (xml, required)
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_ref(self, ref):
|
2015-01-19 17:06:30 +01:00
|
|
|
refs = self.xml.xpath(
|
|
|
|
'//rng:define[name="{}"]/rng:element'.format(ref['name']))
|
2015-01-12 13:22:04 +01:00
|
|
|
return refs[0] if refs else None
|
|
|
|
|
|
|
|
|
|
|
|
def get_child_elements(self):
|
|
|
|
for xml in self.xml.xpath('''./rng:element | ./rng:ref |
|
|
|
|
./rng:optional/rng:element | ./rng:optional/rng:ref |
|
|
|
|
./rng:zeroOrMore/rng:element | ./rng:zeroOrMore/rng:ref |
|
2015-01-19 17:06:30 +01:00
|
|
|
./rng:oneOrMore/rng:element | ./rng:oneOrMore/rng:ref''',
|
|
|
|
namespaces=self.nsmap):
|
2015-01-12 13:22:04 +01:00
|
|
|
parent = xml.getparent()
|
|
|
|
qname = lxml.etree.QName(parent)
|
|
|
|
if parent == self.xml:
|
2015-01-20 14:41:19 +01:00
|
|
|
number = '1'
|
2015-01-12 13:22:04 +01:00
|
|
|
elif qname.localname == 'optional':
|
2015-01-20 14:41:19 +01:00
|
|
|
number = '?'
|
2015-01-12 13:22:04 +01:00
|
|
|
elif qname.localname == 'zeroOrMore':
|
2015-01-20 14:41:19 +01:00
|
|
|
number = '\\*'
|
2015-01-12 13:22:04 +01:00
|
|
|
elif qname.localname == 'oneOrMore':
|
2015-01-20 14:41:19 +01:00
|
|
|
number = '\\+'
|
2015-01-12 13:22:04 +01:00
|
|
|
else:
|
|
|
|
print(parent.tag)
|
|
|
|
|
|
|
|
if xml.tag == 'ref':
|
|
|
|
xml = self.resolve_ref(xml)
|
2015-01-19 17:06:30 +01:00
|
|
|
if xml is None:
|
|
|
|
continue
|
2015-01-12 13:22:04 +01:00
|
|
|
|
2015-01-20 14:41:19 +01:00
|
|
|
yield (self.schema.elements[xml.get('name')], number)
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
|
|
|
|
def write_rst(self, stream):
|
|
|
|
stream.write('.. _qubesxml-element-{}:\n\n'.format(self.name))
|
|
|
|
stream.write(make_rst_section('Element: **{}**'.format(self.name), '-'))
|
|
|
|
stream.write(self.get_description())
|
|
|
|
|
|
|
|
attrtable = []
|
|
|
|
for attr, required in self.get_attributes():
|
|
|
|
type_, value = self.get_data_type(attr)
|
|
|
|
attrtable.append((
|
|
|
|
attr.get('name'),
|
|
|
|
required,
|
|
|
|
type_,
|
|
|
|
value,
|
|
|
|
self.get_description(attr, wrap=False)))
|
|
|
|
|
|
|
|
if attrtable:
|
|
|
|
stream.write(make_rst_section('Attributes', '^'))
|
|
|
|
write_rst_table(stream, attrtable,
|
|
|
|
('attribute', 'req.', 'type', 'value', 'description'))
|
|
|
|
|
2015-01-19 17:06:30 +01:00
|
|
|
childtable = [(':ref:`{0} <qubesxml-element-{0}>`'.format(
|
|
|
|
child.xml.get('name')), n)
|
2015-01-12 13:22:04 +01:00
|
|
|
for child, n in self.get_child_elements()]
|
|
|
|
if childtable:
|
|
|
|
stream.write(make_rst_section('Child elements', '^'))
|
2015-01-20 14:41:19 +01:00
|
|
|
write_rst_table(stream, childtable, ('element', 'number'))
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
|
2018-07-15 23:08:23 +02:00
|
|
|
class Schema:
|
2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=too-few-public-methods
|
2015-01-12 13:22:04 +01:00
|
|
|
nsmap = {
|
|
|
|
'rng': 'http://relaxng.org/ns/structure/1.0',
|
|
|
|
'q': 'http://qubes-os.org/qubes/3',
|
|
|
|
'doc': 'http://qubes-os.org/qubes-doc/1'}
|
|
|
|
|
|
|
|
def __init__(self, xml):
|
|
|
|
self.xml = xml
|
|
|
|
|
|
|
|
self.wrapper = textwrap.TextWrapper(width=80,
|
|
|
|
break_long_words=False, break_on_hyphens=False)
|
|
|
|
|
|
|
|
self.elements = {}
|
2015-01-20 14:41:19 +01:00
|
|
|
for node in self.xml.xpath('//rng:element', namespaces=self.nsmap):
|
|
|
|
element = Element(self, node)
|
2015-01-12 13:22:04 +01:00
|
|
|
self.elements[element.name] = element
|
|
|
|
|
|
|
|
|
2015-01-20 14:41:19 +01:00
|
|
|
def make_rst_section(heading, char):
|
|
|
|
return '{}\n{}\n\n'.format(heading, char[0] * len(heading))
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
|
2015-01-20 14:41:19 +01:00
|
|
|
def write_rst_table(stream, itr, heads):
|
2015-01-12 13:22:04 +01:00
|
|
|
stream.write('.. csv-table::\n')
|
2015-01-19 17:06:30 +01:00
|
|
|
stream.write(' :header: {}\n'.format(', '.join('"{}"'.format(c)
|
|
|
|
for c in heads)))
|
|
|
|
stream.write(' :widths: {}\n\n'.format(', '.join('1'
|
|
|
|
for c in heads)))
|
2015-01-12 13:22:04 +01:00
|
|
|
|
2015-01-20 14:41:19 +01:00
|
|
|
for row in itr:
|
2015-01-12 13:22:04 +01:00
|
|
|
stream.write(' {}\n'.format(', '.join('"{}"'.format(i) for i in row)))
|
|
|
|
|
|
|
|
stream.write('\n')
|
|
|
|
|
|
|
|
|
|
|
|
def main(filename, example):
|
|
|
|
schema = Schema(lxml.etree.parse(open(filename, 'rb')))
|
|
|
|
|
|
|
|
sys.stdout.write(make_rst_section('Qubes XML specification', '='))
|
2015-01-19 17:06:30 +01:00
|
|
|
sys.stdout.write('''
|
|
|
|
This is the documentation of qubes.xml autogenerated from RelaxNG source.
|
|
|
|
|
|
|
|
Quick example, worth thousands lines of specification:
|
|
|
|
.. literalinclude:: {}
|
|
|
|
:language: xml
|
|
|
|
|
|
|
|
'''[1:].format(example))
|
2015-01-12 13:22:04 +01:00
|
|
|
|
|
|
|
for name in sorted(schema.elements):
|
|
|
|
schema.elements[name].write_rst(sys.stdout)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2017-04-15 23:48:02 +02:00
|
|
|
# pylint: disable=no-value-for-parameter
|
2015-01-12 13:22:04 +01:00
|
|
|
main(*sys.argv[1:])
|
|
|
|
|
|
|
|
# vim: ts=4 sw=4 et
|