3 from datetime import date
11 from ovs.db import error
16 def textToNroff(s, font=r'\fR'):
31 raise error.Error("bad escape")
33 # Escape - \ " ' as needed by nroff.
34 s = re.sub('([-"\'\\\\])', escape, s)
39 def escapeNroffLiteral(s):
40 return r'\fB%s\fR' % textToNroff(s, r'\fB')
42 def inlineXmlToNroff(node, font):
43 if node.nodeType == node.TEXT_NODE:
44 return textToNroff(node.data, font)
45 elif node.nodeType == node.ELEMENT_NODE:
46 if node.tagName in ['code', 'em', 'option']:
48 for child in node.childNodes:
49 s += inlineXmlToNroff(child, r'\fB')
51 elif node.tagName == 'ref':
53 if node.hasAttribute('column'):
54 s += node.attributes['column'].nodeValue
55 if node.hasAttribute('key'):
56 s += ':' + node.attributes['key'].nodeValue
57 elif node.hasAttribute('table'):
58 s += node.attributes['table'].nodeValue
59 elif node.hasAttribute('group'):
60 s += node.attributes['group'].nodeValue
62 raise error.Error("'ref' lacks column and table attributes")
64 elif node.tagName == 'var':
66 for child in node.childNodes:
67 s += inlineXmlToNroff(child, r'\fI')
70 raise error.Error("element <%s> unknown or invalid here" % node.tagName)
72 raise error.Error("unknown node %s in inline xml" % node)
74 def blockXmlToNroff(nodes, para='.PP'):
77 if node.nodeType == node.TEXT_NODE:
78 s += textToNroff(node.data)
80 elif node.nodeType == node.ELEMENT_NODE:
81 if node.tagName in ['ul', 'ol']:
86 for liNode in node.childNodes:
87 if (liNode.nodeType == node.ELEMENT_NODE
88 and liNode.tagName == 'li'):
90 if node.tagName == 'ul':
93 s += ".IP %d. .25in\n" % i
94 s += blockXmlToNroff(liNode.childNodes, ".IP")
95 elif (liNode.nodeType != node.TEXT_NODE
96 or not liNode.data.isspace()):
97 raise error.Error("<%s> element may only have <li> children" % node.tagName)
99 elif node.tagName == 'dl':
104 for liNode in node.childNodes:
105 if (liNode.nodeType == node.ELEMENT_NODE
106 and liNode.tagName == 'dt'):
112 elif (liNode.nodeType == node.ELEMENT_NODE
113 and liNode.tagName == 'dd'):
117 elif (liNode.nodeType != node.TEXT_NODE
118 or not liNode.data.isspace()):
119 raise error.Error("<dl> element may only have <dt> and <dd> children")
120 s += blockXmlToNroff(liNode.childNodes, ".IP")
122 elif node.tagName == 'p':
124 if not s.endswith("\n"):
127 s += blockXmlToNroff(node.childNodes, para)
129 s += inlineXmlToNroff(node, r'\fR')
131 raise error.Error("unknown node %s in block xml" % node)
132 if s != "" and not s.endswith('\n'):
136 def typeAndConstraintsToNroff(column):
137 type = column.type.toEnglish(escapeNroffLiteral)
138 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
140 type += ", " + constraints
142 type += " (must be unique within table)"
145 def columnToNroff(columnName, column, node):
146 type = typeAndConstraintsToNroff(column)
147 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
148 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
151 def columnGroupToNroff(table, groupXml):
154 for node in groupXml.childNodes:
155 if (node.nodeType == node.ELEMENT_NODE
156 and node.tagName in ('column', 'group')):
157 columnNodes += [node]
162 intro = blockXmlToNroff(introNodes)
164 for node in columnNodes:
165 if node.tagName == 'column':
166 columnName = node.attributes['name'].nodeValue
167 column = table.columns[columnName]
168 body += columnToNroff(columnName, column, node)
169 summary += [('column', columnName, column)]
170 elif node.tagName == 'group':
171 title = node.attributes["title"].nodeValue
172 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
173 summary += [('group', title, subSummary)]
174 body += '.ST "%s:"\n' % textToNroff(title)
175 body += subIntro + subBody
177 raise error.Error("unknown element %s in <table>" % node.tagName)
178 return summary, intro, body
180 def tableSummaryToNroff(summary, level=0):
182 for type, name, arg in summary:
185 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
186 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
195 """ % (r'\ \ ' * level, name)
196 s += tableSummaryToNroff(arg, level + 1)
199 def tableToNroff(schema, tableXml):
200 tableName = tableXml.attributes['name'].nodeValue
201 table = schema.tables[tableName]
206 summary, intro, body = columnGroupToNroff(table, tableXml)
212 \fB%s\fR Table Columns:
219 s += tableSummaryToNroff(summary)
225 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
226 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
227 doc = xml.dom.minidom.parse(xmlFile).documentElement
229 schemaDate = os.stat(schemaFile).st_mtime
230 xmlDate = os.stat(xmlFile).st_mtime
231 d = date.fromtimestamp(max(schemaDate, xmlDate))
236 # Putting '\" pt as the first line tells "man" that the manpage
237 # needs to be preprocessed by "pic" and "tbl".
239 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
252 ''' % (title, d.strftime("%B %Y"))
254 s += '.SH "%s DATABASE"\n' % schema.name
260 for dbNode in doc.childNodes:
261 if (dbNode.nodeType == dbNode.ELEMENT_NODE
262 and dbNode.tagName == "table"):
263 tableNodes += [dbNode]
265 name = dbNode.attributes['name'].nodeValue
266 if dbNode.hasAttribute("title"):
267 title = dbNode.attributes['title'].nodeValue
269 title = name + " configuration."
270 summary += [(name, title)]
272 introNodes += [dbNode]
274 s += blockXmlToNroff(introNodes) + "\n"
278 \fB%s\fR Database Tables:
286 for name, title in summary:
287 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
288 tableSummary += '.TE\n'
293 .if !'\*[.T]'ascii' \{
295 .SH "TABLE RELATIONSHIPS"
297 The following diagram shows the relationship among tables in the
298 database. Each node represents a table. Tables that are part of the
299 ``root set'' are shown with double borders. Each edge leads from the
300 table that contains it and points to the table that its value
301 represents. Edges are labeled with their column names, followed by a
302 constraint on the number of allowed values: \\fB?\\fR for zero or one,
303 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
304 represent strong references; thin lines represent weak references.
307 erStream = open(erFile, "r")
308 for line in erStream:
313 for node in tableNodes:
314 s += tableToNroff(schema, node) + "\n"
319 %(argv0)s: ovsdb schema documentation generator
320 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
321 usage: %(argv0)s [OPTIONS] SCHEMA XML
322 where SCHEMA is an OVSDB schema in JSON format
323 and XML is OVSDB documentation in XML format.
325 The following options are also available:
326 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
327 --title=TITLE use TITLE as title instead of schema name
328 -h, --help display this help message
329 -V, --version display version information\
330 """ % {'argv0': argv0}
333 if __name__ == "__main__":
336 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
337 ['er-diagram=', 'title=',
339 except getopt.GetoptError, geo:
340 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
345 for key, value in options:
346 if key == '--er-diagram':
348 elif key == '--title':
350 elif key in ['-h', '--help']:
352 elif key in ['-V', '--version']:
353 print "ovsdb-doc (Open vSwitch) @VERSION@"
358 sys.stderr.write("%s: exactly 2 non-option arguments required "
359 "(use --help for help)\n" % argv0)
362 # XXX we should warn about undocumented tables or columns
363 s = docsToNroff(args[0], args[1], er_diagram)
364 for line in s.split("\n"):
369 except error.Error, e:
370 sys.stderr.write("%s: %s\n" % (argv0, e.msg))