3 from datetime import date
11 from ovs.db import error
16 def textToNroff(s, font=r'\fR'):
20 if c != '-' or font == r'\fB':
31 raise error.Error("bad escape")
33 # Escape - \ " ' as needed by nroff.
34 s = re.sub('(-[0-9]|[-"\'\\\\])', 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,
150 type += ", " + constraints
152 type += " (must be unique within table)"
155 def columnGroupToNroff(table, groupXml):
158 for node in groupXml.childNodes:
159 if (node.nodeType == node.ELEMENT_NODE
160 and node.tagName in ('column', 'group')):
161 columnNodes += [node]
164 and not (node.nodeType == node.TEXT_NODE
165 and node.data.isspace())):
166 raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
170 intro = blockXmlToNroff(introNodes)
172 for node in columnNodes:
173 if node.tagName == 'column':
174 name = node.attributes['name'].nodeValue
175 column = table.columns[name]
176 if node.hasAttribute('key'):
177 key = node.attributes['key'].nodeValue
178 if node.hasAttribute('type'):
179 type_string = node.attributes['type'].nodeValue
180 type_json = ovs.json.from_string(str(type_string))
181 if type(type_json) in (str, unicode):
182 raise error.Error("%s %s:%s has invalid 'type': %s"
183 % (table.name, name, key, type_json))
184 type_ = ovs.db.types.BaseType.from_json(type_json)
186 type_ = column.type.value
188 nameNroff = "%s : %s" % (name, key)
189 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(escapeNroffLiteral)
199 if type_english[0] in 'aeiou':
200 typeNroff += ", containing an %s" % type_english
202 typeNroff += ", containing a %s" % type_english
203 constraints = type_.constraintsToEnglish(escapeNroffLiteral)
205 typeNroff += ", %s" % constraints
208 typeNroff = typeAndConstraintsToNroff(column)
209 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
210 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
211 summary += [('column', nameNroff, typeNroff)]
212 elif node.tagName == 'group':
213 title = node.attributes["title"].nodeValue
214 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
215 summary += [('group', title, subSummary)]
216 body += '.ST "%s:"\n' % textToNroff(title)
217 body += subIntro + subBody
219 raise error.Error("unknown element %s in <table>" % node.tagName)
220 return summary, intro, body
222 def tableSummaryToNroff(summary, level=0):
224 for type, name, arg in summary:
226 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
228 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
229 s += tableSummaryToNroff(arg, level + 1)
233 def tableToNroff(schema, tableXml):
234 tableName = tableXml.attributes['name'].nodeValue
235 table = schema.tables[tableName]
240 summary, intro, body = columnGroupToNroff(table, tableXml)
242 s += '.SS "Summary:\n'
243 s += tableSummaryToNroff(summary)
244 s += '.SS "Details:\n'
248 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
249 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
250 doc = xml.dom.minidom.parse(xmlFile).documentElement
252 schemaDate = os.stat(schemaFile).st_mtime
253 xmlDate = os.stat(xmlFile).st_mtime
254 d = date.fromtimestamp(max(schemaDate, xmlDate))
259 # Putting '\" p as the first line tells "man" that the manpage
260 # needs to be preprocessed by "pic".
262 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
276 %s \- %s database schema
277 ''' % (title, d.strftime("%B %Y"), textToNroff(title), schema.name)
283 for dbNode in doc.childNodes:
284 if (dbNode.nodeType == dbNode.ELEMENT_NODE
285 and dbNode.tagName == "table"):
286 tableNodes += [dbNode]
288 name = dbNode.attributes['name'].nodeValue
289 if dbNode.hasAttribute("title"):
290 title = dbNode.attributes['title'].nodeValue
292 title = name + " configuration."
293 summary += [(name, title)]
295 introNodes += [dbNode]
297 s += blockXmlToNroff(introNodes) + "\n"
302 The following list summarizes the purpose of each of the tables in the
303 \fB%s\fR database. Each table is described in more detail on a later
308 for name, title in summary:
313 """ % (name, textToNroff(title))
317 .\\" check if in troff mode (TTY)
320 .SH "TABLE RELATIONSHIPS"
322 The following diagram shows the relationship among tables in the
323 database. Each node represents a table. Tables that are part of the
324 ``root set'' are shown with double borders. Each edge leads from the
325 table that contains it and points to the table that its value
326 represents. Edges are labeled with their column names, followed by a
327 constraint on the number of allowed values: \\fB?\\fR for zero or one,
328 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
329 represent strong references; thin lines represent weak references.
332 erStream = open(erFile, "r")
333 for line in erStream:
338 for node in tableNodes:
339 s += tableToNroff(schema, node) + "\n"
344 %(argv0)s: ovsdb schema documentation generator
345 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
346 usage: %(argv0)s [OPTIONS] SCHEMA XML
347 where SCHEMA is an OVSDB schema in JSON format
348 and XML is OVSDB documentation in XML format.
350 The following options are also available:
351 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
352 --title=TITLE use TITLE as title instead of schema name
353 -h, --help display this help message
354 -V, --version display version information\
355 """ % {'argv0': argv0}
358 if __name__ == "__main__":
361 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
362 ['er-diagram=', 'title=',
364 except getopt.GetoptError, geo:
365 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
370 for key, value in options:
371 if key == '--er-diagram':
373 elif key == '--title':
375 elif key in ['-h', '--help']:
377 elif key in ['-V', '--version']:
378 print "ovsdb-doc (Open vSwitch) @VERSION@"
383 sys.stderr.write("%s: exactly 2 non-option arguments required "
384 "(use --help for help)\n" % argv0)
387 # XXX we should warn about undocumented tables or columns
388 s = docsToNroff(args[0], args[1], er_diagram)
389 for line in s.split("\n"):
394 except error.Error, e:
395 sys.stderr.write("%s: %s\n" % (argv0, e.msg))