summaryrefslogtreecommitdiff
path: root/ovsdb/ovsdb-doc
diff options
context:
space:
mode:
authorGurucharan Shetty <gshetty@nicira.com>2013-07-31 09:24:46 -0700
committerGurucharan Shetty <gshetty@nicira.com>2013-07-31 10:33:44 -0700
commit54c18b66f669a243d43625ec39b2ddc55a28a056 (patch)
tree1a225bd4f0a918659afa07de90a38900678b0cd4 /ovsdb/ovsdb-doc
parent6ba7f5cccd68b2f014c5a6b71cbc39e045a3d14d (diff)
downloadopenvswitch-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-xovsdb/ovsdb-doc411
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: