#! @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 == '-':
if font == r'\fB':
return r'\-'
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('([-"\'\\\\])', 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 == 'code' or node.tagName == 'em':
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
elif node.hasAttribute('table'):
s += node.attributes['table'].nodeValue
elif node.hasAttribute('group'):
s += node.attributes['group'].nodeValue
else:
raise error.Error("'ref' lacks column and table attributes")
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 == 'ul':
if s != "":
s += "\n"
s += ".RS\n"
for liNode in node.childNodes:
if (liNode.nodeType == node.ELEMENT_NODE
and liNode.tagName == 'li'):
s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
elif (liNode.nodeType != node.TEXT_NODE
or not liNode.data.isspace()):
raise error.Error("
element may only have - children")
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("
element may only have - and
- 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)
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)
if constraints:
type += ", " + constraints
return type
def columnToNroff(columnName, column, node):
type = typeAndConstraintsToNroff(column)
s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
return s
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:
introNodes += [node]
summary = []
intro = blockXmlToNroff(introNodes)
body = ''
for node in columnNodes:
if node.tagName == 'column':
columnName = node.attributes['name'].nodeValue
column = table.columns[columnName]
body += columnToNroff(columnName, column, node)
summary += [('column', columnName, column)]
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
" % node.tagName)
return summary, intro, body
def tableSummaryToNroff(summary, level=0):
s = ""
for type, name, arg in summary:
if type == 'column':
s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
else:
if s != "":
s += "_\n"
s += """.T&
li | s
l | l.
%s%s
_
""" % (r'\ \ ' * level, name)
s += tableSummaryToNroff(arg, level + 1)
return s
def tableToNroff(schema, tableXml):
tableName = tableXml.attributes['name'].nodeValue
table = schema.tables[tableName]
s = """.bp
.SS "%s Table"
""" % tableName
summary, intro, body = columnGroupToNroff(table, tableXml)
s += intro
s += r"""
.sp
.ce 1
\fB%s\fR Table Columns:
.TS
center box;
l | l.
Column Type
=
""" % tableName
s += tableSummaryToNroff(summary)
s += ".TE\n"
s += body
return s
def docsToNroff(schemaFile, xmlFile, erFile, title=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
# Putting '\" pt as the first line tells "man" that the manpage
# needs to be preprocessed by "pic" and "tbl".
s = r''''\" pt
.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
..
''' % (title, d.strftime("%B %Y"))
s += '.SH "%s DATABASE"\n' % 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"
tableSummary = r"""
.sp
.ce 1
\fB%s\fR Database Tables:
.TS
center box;
l | l
lb | l.
Table Purpose
=
""" % schema.name
for name, title in summary:
tableSummary += "%s\t%s\n" % (name, textToNroff(title))
tableSummary += '.TE\n'
s += tableSummary
if erFile:
s += """
.sp 1
.SH "TABLE RELATIONSHIPS"
.PP
The following diagram shows the relationship among tables in the
database. Each node represents a table. 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.
.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
-h, --help display this help message
-V, --version display version information\
""" % {'argv0': argv0}
sys.exit(0)
if __name__ == "__main__":
try:
try:
options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
['er-diagram=', 'title=',
'help', 'version'])
except getopt.GetoptError, geo:
sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
sys.exit(1)
er_diagram = None
title = None
for key, value in options:
if key == '--er-diagram':
er_diagram = value
elif key == '--title':
title = value
elif key in ['-h', '--help']:
usage()
elif key in ['-V', '--version']:
print "ovsdb-doc (Open vSwitch) @VERSION@"
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)
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: