diff options
author | Gurucharan Shetty <gshetty@nicira.com> | 2013-07-31 09:24:46 -0700 |
---|---|---|
committer | Gurucharan Shetty <gshetty@nicira.com> | 2013-07-31 10:33:44 -0700 |
commit | 54c18b66f669a243d43625ec39b2ddc55a28a056 (patch) | |
tree | 1a225bd4f0a918659afa07de90a38900678b0cd4 /ovsdb/ovsdb-doc | |
parent | 6ba7f5cccd68b2f014c5a6b71cbc39e045a3d14d (diff) | |
download | openvswitch-54c18b66f669a243d43625ec39b2ddc55a28a056.tar.gz |
ovsdb-doc: Add ovsdb-doc to distribution tar ball.
Certain platforms like xenserver do not have the latest
python libraries that are needed by ovsdb-doc (which in-turn
creates ovs-vswitchd.conf.db.5). When we run 'make dist' and
copy over the tar ball to xenserver ddk environemt, we
already include ovs-vswitchd.conf.db.5. But the absence of
ovsdb-doc results in an attempt to regenerate ovs-vswitchd.conf.db.5
and that fails because of the missing python libraries.
Instead of producing ovsdb-doc from ovsdb-doc.in dynamically, we
statically provide ovsdb-doc and pass on the version information
to it through the command line option --version.
Signed-off-by: Gurucharan Shetty <gshetty@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
Diffstat (limited to 'ovsdb/ovsdb-doc')
-rwxr-xr-x | ovsdb/ovsdb-doc | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/ovsdb/ovsdb-doc b/ovsdb/ovsdb-doc new file mode 100755 index 000000000..662ed974a --- /dev/null +++ b/ovsdb/ovsdb-doc @@ -0,0 +1,411 @@ +#! /usr/bin/python + +from datetime import date +import getopt +import os +import re +import sys +import xml.dom.minidom + +import ovs.json +from ovs.db import error +import ovs.db.schema + +argv0 = sys.argv[0] + +def textToNroff(s, font=r'\fR'): + def escape(match): + c = match.group(0) + if c.startswith('-'): + if c != '-' or font == r'\fB': + return '\\' + c + else: + return '-' + if c == '\\': + return r'\e' + elif c == '"': + return r'\(dq' + elif c == "'": + return r'\(cq' + else: + raise error.Error("bad escape") + + # Escape - \ " ' as needed by nroff. + s = re.sub('(-[0-9]|[-"\'\\\\])', escape, s) + if s.startswith('.'): + s = '\\' + s + return s + +def escapeNroffLiteral(s): + return r'\fB%s\fR' % textToNroff(s, r'\fB') + +def inlineXmlToNroff(node, font): + if node.nodeType == node.TEXT_NODE: + return textToNroff(node.data, font) + elif node.nodeType == node.ELEMENT_NODE: + if node.tagName in ['code', 'em', 'option']: + s = r'\fB' + for child in node.childNodes: + s += inlineXmlToNroff(child, r'\fB') + return s + font + elif node.tagName == 'ref': + s = r'\fB' + if node.hasAttribute('column'): + s += node.attributes['column'].nodeValue + if node.hasAttribute('key'): + s += ':' + node.attributes['key'].nodeValue + elif node.hasAttribute('table'): + s += node.attributes['table'].nodeValue + elif node.hasAttribute('group'): + s += node.attributes['group'].nodeValue + else: + raise error.Error("'ref' lacks required attributes: %s" % node.attributes.keys()) + return s + font + elif node.tagName == 'var': + s = r'\fI' + for child in node.childNodes: + s += inlineXmlToNroff(child, r'\fI') + return s + font + else: + raise error.Error("element <%s> unknown or invalid here" % node.tagName) + else: + raise error.Error("unknown node %s in inline xml" % node) + +def blockXmlToNroff(nodes, para='.PP'): + s = '' + for node in nodes: + if node.nodeType == node.TEXT_NODE: + s += textToNroff(node.data) + s = s.lstrip() + elif node.nodeType == node.ELEMENT_NODE: + if node.tagName in ['ul', 'ol']: + if s != "": + s += "\n" + s += ".RS\n" + i = 0 + for liNode in node.childNodes: + if (liNode.nodeType == node.ELEMENT_NODE + and liNode.tagName == 'li'): + i += 1 + if node.tagName == 'ul': + s += ".IP \\(bu\n" + else: + s += ".IP %d. .25in\n" % i + s += blockXmlToNroff(liNode.childNodes, ".IP") + elif (liNode.nodeType != node.TEXT_NODE + or not liNode.data.isspace()): + raise error.Error("<%s> element may only have <li> children" % node.tagName) + s += ".RE\n" + elif node.tagName == 'dl': + if s != "": + s += "\n" + s += ".RS\n" + prev = "dd" + for liNode in node.childNodes: + if (liNode.nodeType == node.ELEMENT_NODE + and liNode.tagName == 'dt'): + if prev == 'dd': + s += '.TP\n' + else: + s += '.TQ\n' + prev = 'dt' + elif (liNode.nodeType == node.ELEMENT_NODE + and liNode.tagName == 'dd'): + if prev == 'dd': + s += '.IP\n' + prev = 'dd' + elif (liNode.nodeType != node.TEXT_NODE + or not liNode.data.isspace()): + raise error.Error("<dl> element may only have <dt> and <dd> children") + s += blockXmlToNroff(liNode.childNodes, ".IP") + s += ".RE\n" + elif node.tagName == 'p': + if s != "": + if not s.endswith("\n"): + s += "\n" + s += para + "\n" + s += blockXmlToNroff(node.childNodes, para) + elif node.tagName in ('h1', 'h2', 'h3'): + if s != "": + if not s.endswith("\n"): + s += "\n" + nroffTag = {'h1': 'SH', 'h2': 'SS', 'h3': 'ST'}[node.tagName] + s += ".%s " % nroffTag + for child_node in node.childNodes: + s += inlineXmlToNroff(child_node, r'\fR') + s += "\n" + else: + s += inlineXmlToNroff(node, r'\fR') + else: + raise error.Error("unknown node %s in block xml" % node) + if s != "" and not s.endswith('\n'): + s += '\n' + return s + +def typeAndConstraintsToNroff(column): + type = column.type.toEnglish(escapeNroffLiteral) + constraints = column.type.constraintsToEnglish(escapeNroffLiteral, + textToNroff) + if constraints: + type += ", " + constraints + if column.unique: + type += " (must be unique within table)" + return type + +def columnGroupToNroff(table, groupXml): + introNodes = [] + columnNodes = [] + for node in groupXml.childNodes: + if (node.nodeType == node.ELEMENT_NODE + and node.tagName in ('column', 'group')): + columnNodes += [node] + else: + if (columnNodes + and not (node.nodeType == node.TEXT_NODE + and node.data.isspace())): + raise error.Error("text follows <column> or <group> inside <group>: %s" % node) + introNodes += [node] + + summary = [] + intro = blockXmlToNroff(introNodes) + body = '' + for node in columnNodes: + if node.tagName == 'column': + name = node.attributes['name'].nodeValue + column = table.columns[name] + if node.hasAttribute('key'): + key = node.attributes['key'].nodeValue + if node.hasAttribute('type'): + type_string = node.attributes['type'].nodeValue + type_json = ovs.json.from_string(str(type_string)) + if type(type_json) in (str, unicode): + raise error.Error("%s %s:%s has invalid 'type': %s" + % (table.name, name, key, type_json)) + type_ = ovs.db.types.BaseType.from_json(type_json) + else: + type_ = column.type.value + + nameNroff = "%s : %s" % (name, key) + + if column.type.value: + typeNroff = "optional %s" % column.type.value.toEnglish( + escapeNroffLiteral) + if (column.type.value.type == ovs.db.types.StringType and + type_.type == ovs.db.types.BooleanType): + # This is a little more explicit and helpful than + # "containing a boolean" + typeNroff += r", either \fBtrue\fR or \fBfalse\fR" + else: + if type_.type != column.type.value.type: + type_english = type_.toEnglish() + if type_english[0] in 'aeiou': + typeNroff += ", containing an %s" % type_english + else: + typeNroff += ", containing a %s" % type_english + constraints = ( + type_.constraintsToEnglish(escapeNroffLiteral, + textToNroff)) + if constraints: + typeNroff += ", %s" % constraints + else: + typeNroff = "none" + else: + nameNroff = name + typeNroff = typeAndConstraintsToNroff(column) + body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff) + body += blockXmlToNroff(node.childNodes, '.IP') + "\n" + summary += [('column', nameNroff, typeNroff)] + elif node.tagName == 'group': + title = node.attributes["title"].nodeValue + subSummary, subIntro, subBody = columnGroupToNroff(table, node) + summary += [('group', title, subSummary)] + body += '.ST "%s:"\n' % textToNroff(title) + body += subIntro + subBody + else: + raise error.Error("unknown element %s in <table>" % node.tagName) + return summary, intro, body + +def tableSummaryToNroff(summary, level=0): + s = "" + for type, name, arg in summary: + if type == 'column': + s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg) + else: + s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name + s += tableSummaryToNroff(arg, level + 1) + s += ".RE\n" + return s + +def tableToNroff(schema, tableXml): + tableName = tableXml.attributes['name'].nodeValue + table = schema.tables[tableName] + + s = """.bp +.SH "%s TABLE" +""" % tableName + summary, intro, body = columnGroupToNroff(table, tableXml) + s += intro + s += '.SS "Summary:\n' + s += tableSummaryToNroff(summary) + s += '.SS "Details:\n' + s += body + return s + +def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None): + schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile)) + doc = xml.dom.minidom.parse(xmlFile).documentElement + + schemaDate = os.stat(schemaFile).st_mtime + xmlDate = os.stat(xmlFile).st_mtime + d = date.fromtimestamp(max(schemaDate, xmlDate)) + + if title == None: + title = schema.name + + if version == None: + version = "UNKNOWN" + + # Putting '\" p as the first line tells "man" that the manpage + # needs to be preprocessed by "pic". + s = r''''\" p +.TH "%s" 5 "%s" "Open vSwitch" "Open vSwitch Manual" +.\" -*- nroff -*- +.de TQ +. br +. ns +. TP "\\$1" +.. +.de ST +. PP +. RS -0.15in +. I "\\$1" +. RE +.. +.SH NAME +%s \- %s database schema +.PP +''' % (title, version, textToNroff(schema.name), schema.name) + + tables = "" + introNodes = [] + tableNodes = [] + summary = [] + for dbNode in doc.childNodes: + if (dbNode.nodeType == dbNode.ELEMENT_NODE + and dbNode.tagName == "table"): + tableNodes += [dbNode] + + name = dbNode.attributes['name'].nodeValue + if dbNode.hasAttribute("title"): + title = dbNode.attributes['title'].nodeValue + else: + title = name + " configuration." + summary += [(name, title)] + else: + introNodes += [dbNode] + + s += blockXmlToNroff(introNodes) + "\n" + + s += r""" +.SH "TABLE SUMMARY" +.PP +The following list summarizes the purpose of each of the tables in the +\fB%s\fR database. Each table is described in more detail on a later +page. +.IP "Table" 1in +Purpose +""" % schema.name + for name, title in summary: + s += r""" +.TQ 1in +\fB%s\fR +%s +""" % (name, textToNroff(title)) + + if erFile: + s += """ +.\\" check if in troff mode (TTY) +.if t \{ +.bp +.SH "TABLE RELATIONSHIPS" +.PP +The following diagram shows the relationship among tables in the +database. Each node represents a table. Tables that are part of the +``root set'' are shown with double borders. Each edge leads from the +table that contains it and points to the table that its value +represents. Edges are labeled with their column names, followed by a +constraint on the number of allowed values: \\fB?\\fR for zero or one, +\\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines +represent strong references; thin lines represent weak references. +.RS -1in +""" + erStream = open(erFile, "r") + for line in erStream: + s += line + '\n' + erStream.close() + s += ".RE\\}\n" + + for node in tableNodes: + s += tableToNroff(schema, node) + "\n" + return s + +def usage(): + print """\ +%(argv0)s: ovsdb schema documentation generator +Prints documentation for an OVSDB schema as an nroff-formatted manpage. +usage: %(argv0)s [OPTIONS] SCHEMA XML +where SCHEMA is an OVSDB schema in JSON format + and XML is OVSDB documentation in XML format. + +The following options are also available: + --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC + --title=TITLE use TITLE as title instead of schema name + --version=VERSION use VERSION to display on document footer + -h, --help display this help message\ +""" % {'argv0': argv0} + sys.exit(0) + +if __name__ == "__main__": + try: + try: + options, args = getopt.gnu_getopt(sys.argv[1:], 'hV', + ['er-diagram=', 'title=', + 'version=', 'help']) + except getopt.GetoptError, geo: + sys.stderr.write("%s: %s\n" % (argv0, geo.msg)) + sys.exit(1) + + er_diagram = None + title = None + version = None + for key, value in options: + if key == '--er-diagram': + er_diagram = value + elif key == '--title': + title = value + elif key == '--version': + version = value + elif key in ['-h', '--help']: + usage() + else: + sys.exit(0) + + if len(args) != 2: + sys.stderr.write("%s: exactly 2 non-option arguments required " + "(use --help for help)\n" % argv0) + sys.exit(1) + + # XXX we should warn about undocumented tables or columns + s = docsToNroff(args[0], args[1], er_diagram, title, version) + for line in s.split("\n"): + line = line.strip() + if len(line): + print line + + except error.Error, e: + sys.stderr.write("%s: %s\n" % (argv0, e.msg)) + sys.exit(1) + +# Local variables: +# mode: python +# End: |