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 elif node.hasAttribute('table'):
56 s += node.attributes['table'].nodeValue
57 elif node.hasAttribute('group'):
58 s += node.attributes['group'].nodeValue
60 raise error.Error("'ref' lacks column and table attributes")
62 elif node.tagName == 'var':
64 for child in node.childNodes:
65 s += inlineXmlToNroff(child, r'\fI')
68 raise error.Error("element <%s> unknown or invalid here" % node.tagName)
70 raise error.Error("unknown node %s in inline xml" % node)
72 def blockXmlToNroff(nodes, para='.PP'):
75 if node.nodeType == node.TEXT_NODE:
76 s += textToNroff(node.data)
78 elif node.nodeType == node.ELEMENT_NODE:
79 if node.tagName in ['ul', 'ol']:
84 for liNode in node.childNodes:
85 if (liNode.nodeType == node.ELEMENT_NODE
86 and liNode.tagName == 'li'):
88 if node.tagName == 'ul':
91 s += ".IP %d. .25in\n" % i
92 s += blockXmlToNroff(liNode.childNodes, ".IP")
93 elif (liNode.nodeType != node.TEXT_NODE
94 or not liNode.data.isspace()):
95 raise error.Error("<%s> element may only have <li> children" % node.tagName)
97 elif node.tagName == 'dl':
102 for liNode in node.childNodes:
103 if (liNode.nodeType == node.ELEMENT_NODE
104 and liNode.tagName == 'dt'):
110 elif (liNode.nodeType == node.ELEMENT_NODE
111 and liNode.tagName == 'dd'):
115 elif (liNode.nodeType != node.TEXT_NODE
116 or not liNode.data.isspace()):
117 raise error.Error("<dl> element may only have <dt> and <dd> children")
118 s += blockXmlToNroff(liNode.childNodes, ".IP")
120 elif node.tagName == 'p':
122 if not s.endswith("\n"):
125 s += blockXmlToNroff(node.childNodes, para)
127 s += inlineXmlToNroff(node, r'\fR')
129 raise error.Error("unknown node %s in block xml" % node)
130 if s != "" and not s.endswith('\n'):
134 def typeAndConstraintsToNroff(column):
135 type = column.type.toEnglish(escapeNroffLiteral)
136 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
138 type += ", " + constraints
140 type += " (must be unique within table)"
143 def columnToNroff(columnName, column, node):
144 type = typeAndConstraintsToNroff(column)
145 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
146 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
149 def columnGroupToNroff(table, groupXml):
152 for node in groupXml.childNodes:
153 if (node.nodeType == node.ELEMENT_NODE
154 and node.tagName in ('column', 'group')):
155 columnNodes += [node]
160 intro = blockXmlToNroff(introNodes)
162 for node in columnNodes:
163 if node.tagName == 'column':
164 columnName = node.attributes['name'].nodeValue
165 column = table.columns[columnName]
166 body += columnToNroff(columnName, column, node)
167 summary += [('column', columnName, column)]
168 elif node.tagName == 'group':
169 title = node.attributes["title"].nodeValue
170 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
171 summary += [('group', title, subSummary)]
172 body += '.ST "%s:"\n' % textToNroff(title)
173 body += subIntro + subBody
175 raise error.Error("unknown element %s in <table>" % node.tagName)
176 return summary, intro, body
178 def tableSummaryToNroff(summary, level=0):
180 for type, name, arg in summary:
183 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
184 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
193 """ % (r'\ \ ' * level, name)
194 s += tableSummaryToNroff(arg, level + 1)
197 def tableToNroff(schema, tableXml):
198 tableName = tableXml.attributes['name'].nodeValue
199 table = schema.tables[tableName]
204 summary, intro, body = columnGroupToNroff(table, tableXml)
210 \fB%s\fR Table Columns:
217 s += tableSummaryToNroff(summary)
223 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
224 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
225 doc = xml.dom.minidom.parse(xmlFile).documentElement
227 schemaDate = os.stat(schemaFile).st_mtime
228 xmlDate = os.stat(xmlFile).st_mtime
229 d = date.fromtimestamp(max(schemaDate, xmlDate))
234 # Putting '\" pt as the first line tells "man" that the manpage
235 # needs to be preprocessed by "pic" and "tbl".
237 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
250 ''' % (title, d.strftime("%B %Y"))
252 s += '.SH "%s DATABASE"\n' % schema.name
258 for dbNode in doc.childNodes:
259 if (dbNode.nodeType == dbNode.ELEMENT_NODE
260 and dbNode.tagName == "table"):
261 tableNodes += [dbNode]
263 name = dbNode.attributes['name'].nodeValue
264 if dbNode.hasAttribute("title"):
265 title = dbNode.attributes['title'].nodeValue
267 title = name + " configuration."
268 summary += [(name, title)]
270 introNodes += [dbNode]
272 s += blockXmlToNroff(introNodes) + "\n"
276 \fB%s\fR Database Tables:
284 for name, title in summary:
285 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
286 tableSummary += '.TE\n'
291 .if !'\*[.T]'ascii' \{
293 .SH "TABLE RELATIONSHIPS"
295 The following diagram shows the relationship among tables in the
296 database. Each node represents a table. Tables that are part of the
297 ``root set'' are shown with double borders. Each edge leads from the
298 table that contains it and points to the table that its value
299 represents. Edges are labeled with their column names, followed by a
300 constraint on the number of allowed values: \\fB?\\fR for zero or one,
301 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
302 represent strong references; thin lines represent weak references.
305 erStream = open(erFile, "r")
306 for line in erStream:
311 for node in tableNodes:
312 s += tableToNroff(schema, node) + "\n"
317 %(argv0)s: ovsdb schema documentation generator
318 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
319 usage: %(argv0)s [OPTIONS] SCHEMA XML
320 where SCHEMA is an OVSDB schema in JSON format
321 and XML is OVSDB documentation in XML format.
323 The following options are also available:
324 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
325 --title=TITLE use TITLE as title instead of schema name
326 -h, --help display this help message
327 -V, --version display version information\
328 """ % {'argv0': argv0}
331 if __name__ == "__main__":
334 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
335 ['er-diagram=', 'title=',
337 except getopt.GetoptError, geo:
338 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
343 for key, value in options:
344 if key == '--er-diagram':
346 elif key == '--title':
348 elif key in ['-h', '--help']:
350 elif key in ['-V', '--version']:
351 print "ovsdb-doc (Open vSwitch) @VERSION@"
356 sys.stderr.write("%s: exactly 2 non-option arguments required "
357 "(use --help for help)\n" % argv0)
360 # XXX we should warn about undocumented tables or columns
361 s = docsToNroff(args[0], args[1], er_diagram)
362 for line in s.split("\n"):
367 except error.Error, e:
368 sys.stderr.write("%s: %s\n" % (argv0, e.msg))