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 if node.hasAttribute('type'):
178 type_string = node.attributes['type'].nodeValue
179 type_json = ovs.json.from_string(str(type_string))
180 if type(type_json) in (str, unicode):
181 raise error.Error("%s %s:%s has invalid 'type': %s"
182 % (table.name, name, key, type_json))
183 type_ = ovs.db.types.BaseType.from_json(type_json)
185 type_ = column.type.value
187 nameNroff = "%s : %s" % (name, key)
189 if column.type.value:
190 typeNroff = "optional %s" % column.type.value.toEnglish()
191 if (column.type.value.type == ovs.db.types.StringType and
192 type_.type == ovs.db.types.BooleanType):
193 # This is a little more explicit and helpful than
194 # "containing a boolean"
195 typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
197 if type_.type != column.type.value.type:
198 type_english = type_.toEnglish()
199 if type_english[0] in 'aeiou':
200 typeNroff += ", containing an %s" % type_english
202 typeNroff += ", containing a %s" % type_english
204 type_.constraintsToEnglish(escapeNroffLiteral))
206 typeNroff += ", %s" % constraints
211 typeNroff = typeAndConstraintsToNroff(column)
212 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
213 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
214 summary += [('column', nameNroff, typeNroff)]
215 elif node.tagName == 'group':
216 title = node.attributes["title"].nodeValue
217 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
218 summary += [('group', title, subSummary)]
219 body += '.ST "%s:"\n' % textToNroff(title)
220 body += subIntro + subBody
222 raise error.Error("unknown element %s in <table>" % node.tagName)
223 return summary, intro, body
225 def tableSummaryToNroff(summary, level=0):
227 for type, name, arg in summary:
229 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
231 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
232 s += tableSummaryToNroff(arg, level + 1)
236 def tableToNroff(schema, tableXml):
237 tableName = tableXml.attributes['name'].nodeValue
238 table = schema.tables[tableName]
243 summary, intro, body = columnGroupToNroff(table, tableXml)
245 s += '.SS "Summary:\n'
246 s += tableSummaryToNroff(summary)
247 s += '.SS "Details:\n'
251 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
252 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
253 doc = xml.dom.minidom.parse(xmlFile).documentElement
255 schemaDate = os.stat(schemaFile).st_mtime
256 xmlDate = os.stat(xmlFile).st_mtime
257 d = date.fromtimestamp(max(schemaDate, xmlDate))
262 # Putting '\" p as the first line tells "man" that the manpage
263 # needs to be preprocessed by "pic".
265 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
278 ''' % (title, d.strftime("%B %Y"))
280 s += '.SH "%s DATABASE"\n' % schema.name
286 for dbNode in doc.childNodes:
287 if (dbNode.nodeType == dbNode.ELEMENT_NODE
288 and dbNode.tagName == "table"):
289 tableNodes += [dbNode]
291 name = dbNode.attributes['name'].nodeValue
292 if dbNode.hasAttribute("title"):
293 title = dbNode.attributes['title'].nodeValue
295 title = name + " configuration."
296 summary += [(name, title)]
298 introNodes += [dbNode]
300 s += blockXmlToNroff(introNodes) + "\n"
305 The following list summarizes the purpose of each of the tables in the
306 \fB%s\fR database. Each table is described in more detail on a later
311 for name, title in summary:
316 """ % (name, textToNroff(title))
320 .\\" check if in troff mode (TTY)
323 .SH "TABLE RELATIONSHIPS"
325 The following diagram shows the relationship among tables in the
326 database. Each node represents a table. Tables that are part of the
327 ``root set'' are shown with double borders. Each edge leads from the
328 table that contains it and points to the table that its value
329 represents. Edges are labeled with their column names, followed by a
330 constraint on the number of allowed values: \\fB?\\fR for zero or one,
331 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
332 represent strong references; thin lines represent weak references.
335 erStream = open(erFile, "r")
336 for line in erStream:
341 for node in tableNodes:
342 s += tableToNroff(schema, node) + "\n"
347 %(argv0)s: ovsdb schema documentation generator
348 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
349 usage: %(argv0)s [OPTIONS] SCHEMA XML
350 where SCHEMA is an OVSDB schema in JSON format
351 and XML is OVSDB documentation in XML format.
353 The following options are also available:
354 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
355 --title=TITLE use TITLE as title instead of schema name
356 -h, --help display this help message
357 -V, --version display version information\
358 """ % {'argv0': argv0}
361 if __name__ == "__main__":
364 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
365 ['er-diagram=', 'title=',
367 except getopt.GetoptError, geo:
368 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
373 for key, value in options:
374 if key == '--er-diagram':
376 elif key == '--title':
378 elif key in ['-h', '--help']:
380 elif key in ['-V', '--version']:
381 print "ovsdb-doc (Open vSwitch) @VERSION@"
386 sys.stderr.write("%s: exactly 2 non-option arguments required "
387 "(use --help for help)\n" % argv0)
390 # XXX we should warn about undocumented tables or columns
391 s = docsToNroff(args[0], args[1], er_diagram)
392 for line in s.split("\n"):
397 except error.Error, e:
398 sys.stderr.write("%s: %s\n" % (argv0, e.msg))