def textToNroff(s, font=r'\fR'):
def escape(match):
c = match.group(0)
- if c == '-':
- if font == r'\fB':
- return r'\-'
+ if c.startswith('-'):
+ if c != '-' or font == r'\fB':
+ return '\\' + c
else:
return '-'
if c == '\\':
raise error.Error("bad escape")
# Escape - \ " ' as needed by nroff.
- s = re.sub('([-"\'\\\\])', escape, s)
+ s = re.sub('(-[0-9]|[-"\'\\\\])', escape, s)
if s.startswith('.'):
s = '\\' + s
return s
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':
+ if node.tagName in ['code', 'em', 'option']:
s = r'\fB'
for child in node.childNodes:
s += inlineXmlToNroff(child, r'\fB')
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 column and table attributes")
+ raise error.Error("'ref' lacks required attributes: %s" % node.attributes.keys())
return s + font
elif node.tagName == 'var':
s = r'\fI'
s += textToNroff(node.data)
s = s.lstrip()
elif node.nodeType == node.ELEMENT_NODE:
- if node.tagName == 'ul':
+ 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'):
- s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
+ 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("<ul> element may only have <li> children")
+ raise error.Error("<%s> element may only have <li> children" % node.tagName)
s += ".RE\n"
elif node.tagName == 'dl':
if s != "":
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:
def typeAndConstraintsToNroff(column):
type = column.type.toEnglish(escapeNroffLiteral)
- constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
+ constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
+ textToNroff)
if constraints:
type += ", " + constraints
+ if column.unique:
+ type += " (must be unique within table)"
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 = []
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 = []
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)]
+ 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)
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))
+ s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
else:
- if s != "":
- s += "_\n"
- s += """.T&
-li | s
-l | l.
-%s%s
-_
-""" % (r'\ \ ' * level, name)
+ 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):
table = schema.tables[tableName]
s = """.bp
-.SS "%s Table"
+.SH "%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 += '.SS "Summary:\n'
s += tableSummaryToNroff(summary)
- s += ".TE\n"
-
+ s += '.SS "Details:\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
+ # 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
. I "\\$1"
. RE
..
-''' % (title, d.strftime("%B %Y"))
-
- s += '.SH "%s DATABASE"\n' % schema.name
+.SH NAME
+%s \- %s database schema
+''' % (title, d.strftime("%B %Y"), textToNroff(title), schema.name)
tables = ""
introNodes = []
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
-=
+
+ 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:
- tableSummary += "%s\t%s\n" % (name, textToNroff(title))
- tableSummary += '.TE\n'
- s += tableSummary
+ s += r"""
+.TQ 1in
+\fB%s\fR
+%s
+""" % (name, textToNroff(title))
if erFile:
s += """
-.sp 1
+.\\" 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. Each edge leads from 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.
+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"
+ s += ".RE\\}\n"
for node in tableNodes:
s += tableToNroff(schema, node) + "\n"
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)