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)
188 typeNroff = "optional %s" % column.type.value.toEnglish()
189 if (column.type.value.type == ovs.db.types.StringType and
190 type_.type == ovs.db.types.BooleanType):
191 # This is a little more explicit and helpful than
192 # "containing a boolean"
193 typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
195 if type_.type != column.type.value.type:
196 type_english = type_.toEnglish()
197 if type_english[0] in 'aeiou':
198 typeNroff += ", containing an %s" % type_english
200 typeNroff += ", containing a %s" % type_english
201 constraints = type_.constraintsToEnglish(escapeNroffLiteral)
203 typeNroff += ", %s" % constraints
206 typeNroff = typeAndConstraintsToNroff(column)
207 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
208 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
209 summary += [('column', nameNroff, typeNroff)]
210 elif node.tagName == 'group':
211 title = node.attributes["title"].nodeValue
212 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
213 summary += [('group', title, subSummary)]
214 body += '.ST "%s:"\n' % textToNroff(title)
215 body += subIntro + subBody
217 raise error.Error("unknown element %s in <table>" % node.tagName)
218 return summary, intro, body
220 def tableSummaryToNroff(summary, level=0):
222 for type, name, arg in summary:
224 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
226 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
227 s += tableSummaryToNroff(arg, level + 1)
231 def tableToNroff(schema, tableXml):
232 tableName = tableXml.attributes['name'].nodeValue
233 table = schema.tables[tableName]
238 summary, intro, body = columnGroupToNroff(table, tableXml)
240 s += '.SS "Summary:\n'
241 s += tableSummaryToNroff(summary)
242 s += '.SS "Details:\n'
246 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
247 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
248 doc = xml.dom.minidom.parse(xmlFile).documentElement
250 schemaDate = os.stat(schemaFile).st_mtime
251 xmlDate = os.stat(xmlFile).st_mtime
252 d = date.fromtimestamp(max(schemaDate, xmlDate))
257 # Putting '\" p as the first line tells "man" that the manpage
258 # needs to be preprocessed by "pic".
260 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
273 ''' % (title, d.strftime("%B %Y"))
275 s += '.SH "%s DATABASE"\n' % schema.name
281 for dbNode in doc.childNodes:
282 if (dbNode.nodeType == dbNode.ELEMENT_NODE
283 and dbNode.tagName == "table"):
284 tableNodes += [dbNode]
286 name = dbNode.attributes['name'].nodeValue
287 if dbNode.hasAttribute("title"):
288 title = dbNode.attributes['title'].nodeValue
290 title = name + " configuration."
291 summary += [(name, title)]
293 introNodes += [dbNode]
295 s += blockXmlToNroff(introNodes) + "\n"
300 The following list summarizes the purpose of each of the tables in the
301 \fB%s\fR database. Each table is described in more detail on a later
306 for name, title in summary:
311 """ % (name, textToNroff(title))
315 .\\" check if in troff mode (TTY)
318 .SH "TABLE RELATIONSHIPS"
320 The following diagram shows the relationship among tables in the
321 database. Each node represents a table. Tables that are part of the
322 ``root set'' are shown with double borders. Each edge leads from the
323 table that contains it and points to the table that its value
324 represents. Edges are labeled with their column names, followed by a
325 constraint on the number of allowed values: \\fB?\\fR for zero or one,
326 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
327 represent strong references; thin lines represent weak references.
330 erStream = open(erFile, "r")
331 for line in erStream:
336 for node in tableNodes:
337 s += tableToNroff(schema, node) + "\n"
342 %(argv0)s: ovsdb schema documentation generator
343 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
344 usage: %(argv0)s [OPTIONS] SCHEMA XML
345 where SCHEMA is an OVSDB schema in JSON format
346 and XML is OVSDB documentation in XML format.
348 The following options are also available:
349 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
350 --title=TITLE use TITLE as title instead of schema name
351 -h, --help display this help message
352 -V, --version display version information\
353 """ % {'argv0': argv0}
356 if __name__ == "__main__":
359 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
360 ['er-diagram=', 'title=',
362 except getopt.GetoptError, geo:
363 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
368 for key, value in options:
369 if key == '--er-diagram':
371 elif key == '--title':
373 elif key in ['-h', '--help']:
375 elif key in ['-V', '--version']:
376 print "ovsdb-doc (Open vSwitch) @VERSION@"
381 sys.stderr.write("%s: exactly 2 non-option arguments required "
382 "(use --help for help)\n" % argv0)
385 # XXX we should warn about undocumented tables or columns
386 s = docsToNroff(args[0], args[1], er_diagram)
387 for line in s.split("\n"):
392 except error.Error, e:
393 sys.stderr.write("%s: %s\n" % (argv0, e.msg))