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"
253 ''' % (title, d.strftime("%B %Y"))
255 s += '.SH "%s DATABASE"\n' % schema.name
261 for dbNode in doc.childNodes:
262 if (dbNode.nodeType == dbNode.ELEMENT_NODE
263 and dbNode.tagName == "table"):
264 tableNodes += [dbNode]
266 name = dbNode.attributes['name'].nodeValue
267 if dbNode.hasAttribute("title"):
268 title = dbNode.attributes['title'].nodeValue
270 title = name + " configuration."
271 summary += [(name, title)]
273 introNodes += [dbNode]
275 s += blockXmlToNroff(introNodes) + "\n"
279 \fB%s\fR Database Tables:
287 for name, title in summary:
288 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
289 tableSummary += '.TE\n'
294 .if !'\*[.T]'ascii' \{
296 .SH "TABLE RELATIONSHIPS"
298 The following diagram shows the relationship among tables in the
299 database. Each node represents a table. Tables that are part of the
300 ``root set'' are shown with double borders. Each edge leads from the
301 table that contains it and points to the table that its value
302 represents. Edges are labeled with their column names, followed by a
303 constraint on the number of allowed values: \\fB?\\fR for zero or one,
304 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
305 represent strong references; thin lines represent weak references.
308 erStream = open(erFile, "r")
309 for line in erStream:
314 for node in tableNodes:
315 s += tableToNroff(schema, node) + "\n"
320 %(argv0)s: ovsdb schema documentation generator
321 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
322 usage: %(argv0)s [OPTIONS] SCHEMA XML
323 where SCHEMA is an OVSDB schema in JSON format
324 and XML is OVSDB documentation in XML format.
326 The following options are also available:
327 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
328 --title=TITLE use TITLE as title instead of schema name
329 -h, --help display this help message
330 -V, --version display version information\
331 """ % {'argv0': argv0}
334 if __name__ == "__main__":
337 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
338 ['er-diagram=', 'title=',
340 except getopt.GetoptError, geo:
341 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
346 for key, value in options:
347 if key == '--er-diagram':
349 elif key == '--title':
351 elif key in ['-h', '--help']:
353 elif key in ['-V', '--version']:
354 print "ovsdb-doc (Open vSwitch) @VERSION@"
359 sys.stderr.write("%s: exactly 2 non-option arguments required "
360 "(use --help for help)\n" % argv0)
363 # XXX we should warn about undocumented tables or columns
364 s = docsToNroff(args[0], args[1], er_diagram)
365 for line in s.split("\n"):
370 except error.Error, e:
371 sys.stderr.write("%s: %s\n" % (argv0, e.msg))