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 .if !'\*[.T]'ascii' \{
317 .SH "TABLE RELATIONSHIPS"
319 The following diagram shows the relationship among tables in the
320 database. Each node represents a table. Tables that are part of the
321 ``root set'' are shown with double borders. Each edge leads from the
322 table that contains it and points to the table that its value
323 represents. Edges are labeled with their column names, followed by a
324 constraint on the number of allowed values: \\fB?\\fR for zero or one,
325 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
326 represent strong references; thin lines represent weak references.
329 erStream = open(erFile, "r")
330 for line in erStream:
335 for node in tableNodes:
336 s += tableToNroff(schema, node) + "\n"
341 %(argv0)s: ovsdb schema documentation generator
342 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
343 usage: %(argv0)s [OPTIONS] SCHEMA XML
344 where SCHEMA is an OVSDB schema in JSON format
345 and XML is OVSDB documentation in XML format.
347 The following options are also available:
348 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
349 --title=TITLE use TITLE as title instead of schema name
350 -h, --help display this help message
351 -V, --version display version information\
352 """ % {'argv0': argv0}
355 if __name__ == "__main__":
358 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
359 ['er-diagram=', 'title=',
361 except getopt.GetoptError, geo:
362 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
367 for key, value in options:
368 if key == '--er-diagram':
370 elif key == '--title':
372 elif key in ['-h', '--help']:
374 elif key in ['-V', '--version']:
375 print "ovsdb-doc (Open vSwitch) @VERSION@"
380 sys.stderr.write("%s: exactly 2 non-option arguments required "
381 "(use --help for help)\n" % argv0)
384 # XXX we should warn about undocumented tables or columns
385 s = docsToNroff(args[0], args[1], er_diagram)
386 for line in s.split("\n"):
391 except error.Error, e:
392 sys.stderr.write("%s: %s\n" % (argv0, e.msg))