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 required attributes: %s" % node.attributes.keys())
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)
128 elif node.tagName in ('h1', 'h2', 'h3'):
130 if not s.endswith("\n"):
132 nroffTag = {'h1': 'SH', 'h2': 'SS', 'h3': 'ST'}[node.tagName]
133 s += ".%s " % nroffTag
134 for child_node in node.childNodes:
135 s += inlineXmlToNroff(child_node, r'\fR')
138 s += inlineXmlToNroff(node, r'\fR')
140 raise error.Error("unknown node %s in block xml" % node)
141 if s != "" and not s.endswith('\n'):
145 def typeAndConstraintsToNroff(column):
146 type = column.type.toEnglish(escapeNroffLiteral)
147 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
149 type += ", " + constraints
151 type += " (must be unique within table)"
154 def columnGroupToNroff(table, groupXml):
157 for node in groupXml.childNodes:
158 if (node.nodeType == node.ELEMENT_NODE
159 and node.tagName in ('column', 'group')):
160 columnNodes += [node]
163 and not (node.nodeType == node.TEXT_NODE
164 and node.data.isspace())):
165 raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
169 intro = blockXmlToNroff(introNodes)
171 for node in columnNodes:
172 if node.tagName == 'column':
173 name = node.attributes['name'].nodeValue
174 column = table.columns[name]
175 if node.hasAttribute('key'):
176 key = node.attributes['key'].nodeValue
177 nameNroff = "%s : %s" % (name, key)
178 typeNroff = "optional %s" % column.type.value.toEnglish()
181 typeNroff = typeAndConstraintsToNroff(column)
182 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
183 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
184 summary += [('column', nameNroff, typeNroff)]
185 elif node.tagName == 'group':
186 title = node.attributes["title"].nodeValue
187 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
188 summary += [('group', title, subSummary)]
189 body += '.ST "%s:"\n' % textToNroff(title)
190 body += subIntro + subBody
192 raise error.Error("unknown element %s in <table>" % node.tagName)
193 return summary, intro, body
195 def tableSummaryToNroff(summary, level=0):
197 for type, name, arg in summary:
199 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
201 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
202 s += tableSummaryToNroff(arg, level + 1)
206 def tableToNroff(schema, tableXml):
207 tableName = tableXml.attributes['name'].nodeValue
208 table = schema.tables[tableName]
213 summary, intro, body = columnGroupToNroff(table, tableXml)
215 s += '.SS "Summary:\n'
216 s += tableSummaryToNroff(summary)
217 s += '.SS "Details:\n'
221 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
222 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
223 doc = xml.dom.minidom.parse(xmlFile).documentElement
225 schemaDate = os.stat(schemaFile).st_mtime
226 xmlDate = os.stat(xmlFile).st_mtime
227 d = date.fromtimestamp(max(schemaDate, xmlDate))
232 # Putting '\" p as the first line tells "man" that the manpage
233 # needs to be preprocessed by "pic".
235 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
248 ''' % (title, d.strftime("%B %Y"))
250 s += '.SH "%s DATABASE"\n' % schema.name
256 for dbNode in doc.childNodes:
257 if (dbNode.nodeType == dbNode.ELEMENT_NODE
258 and dbNode.tagName == "table"):
259 tableNodes += [dbNode]
261 name = dbNode.attributes['name'].nodeValue
262 if dbNode.hasAttribute("title"):
263 title = dbNode.attributes['title'].nodeValue
265 title = name + " configuration."
266 summary += [(name, title)]
268 introNodes += [dbNode]
270 s += blockXmlToNroff(introNodes) + "\n"
275 The following list summarizes the purpose of each of the tables in the
276 \fB%s\fR database. Each table is described in more detail on a later
281 for name, title in summary:
286 """ % (name, textToNroff(title))
290 .if !'\*[.T]'ascii' \{
292 .SH "TABLE RELATIONSHIPS"
294 The following diagram shows the relationship among tables in the
295 database. Each node represents a table. Tables that are part of the
296 ``root set'' are shown with double borders. Each edge leads from the
297 table that contains it and points to the table that its value
298 represents. Edges are labeled with their column names, followed by a
299 constraint on the number of allowed values: \\fB?\\fR for zero or one,
300 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
301 represent strong references; thin lines represent weak references.
304 erStream = open(erFile, "r")
305 for line in erStream:
310 for node in tableNodes:
311 s += tableToNroff(schema, node) + "\n"
316 %(argv0)s: ovsdb schema documentation generator
317 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
318 usage: %(argv0)s [OPTIONS] SCHEMA XML
319 where SCHEMA is an OVSDB schema in JSON format
320 and XML is OVSDB documentation in XML format.
322 The following options are also available:
323 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
324 --title=TITLE use TITLE as title instead of schema name
325 -h, --help display this help message
326 -V, --version display version information\
327 """ % {'argv0': argv0}
330 if __name__ == "__main__":
333 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
334 ['er-diagram=', 'title=',
336 except getopt.GetoptError, geo:
337 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
342 for key, value in options:
343 if key == '--er-diagram':
345 elif key == '--title':
347 elif key in ['-h', '--help']:
349 elif key in ['-V', '--version']:
350 print "ovsdb-doc (Open vSwitch) @VERSION@"
355 sys.stderr.write("%s: exactly 2 non-option arguments required "
356 "(use --help for help)\n" % argv0)
359 # XXX we should warn about undocumented tables or columns
360 s = docsToNroff(args[0], args[1], er_diagram)
361 for line in s.split("\n"):
366 except error.Error, e:
367 sys.stderr.write("%s: %s\n" % (argv0, e.msg))