3 from datetime import date
10 sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
11 import simplejson as json
17 def textToNroff(s, font=r'\fR'):
32 raise Error("bad escape")
34 # Escape - \ " ' as needed by nroff.
35 s = re.sub('([-"\'\\\\])', escape, s)
40 def escapeNroffLiteral(s):
41 return r'\fB%s\fR' % textToNroff(s, r'\fB')
43 def inlineXmlToNroff(node, font):
44 if node.nodeType == node.TEXT_NODE:
45 return textToNroff(node.data, font)
46 elif node.nodeType == node.ELEMENT_NODE:
47 if node.tagName == 'code' or node.tagName == 'em':
49 for child in node.childNodes:
50 s += inlineXmlToNroff(child, r'\fB')
52 elif node.tagName == 'ref':
54 if node.hasAttribute('column'):
55 s += node.attributes['column'].nodeValue
56 elif node.hasAttribute('table'):
57 s += node.attributes['table'].nodeValue
58 elif node.hasAttribute('group'):
59 s += node.attributes['group'].nodeValue
61 raise Error("'ref' lacks column and table attributes")
63 elif node.tagName == 'var':
65 for child in node.childNodes:
66 s += inlineXmlToNroff(child, r'\fI')
69 raise Error("element <%s> unknown or invalid here" % node.tagName)
71 raise Error("unknown node %s in inline xml" % node)
73 def blockXmlToNroff(nodes, para='.PP'):
76 if node.nodeType == node.TEXT_NODE:
77 s += textToNroff(node.data)
79 elif node.nodeType == node.ELEMENT_NODE:
80 if node.tagName == 'ul':
84 for liNode in node.childNodes:
85 if (liNode.nodeType == node.ELEMENT_NODE
86 and liNode.tagName == 'li'):
87 s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
88 elif (liNode.nodeType != node.TEXT_NODE
89 or not liNode.data.isspace()):
90 raise Error("<ul> element may only have <li> children")
92 elif node.tagName == 'dl':
97 for liNode in node.childNodes:
98 if (liNode.nodeType == node.ELEMENT_NODE
99 and liNode.tagName == 'dt'):
105 elif (liNode.nodeType == node.ELEMENT_NODE
106 and liNode.tagName == 'dd'):
110 elif (liNode.nodeType != node.TEXT_NODE
111 or not liNode.data.isspace()):
112 raise Error("<dl> element may only have <dt> and <dd> children")
113 s += blockXmlToNroff(liNode.childNodes, ".IP")
115 elif node.tagName == 'p':
117 if not s.endswith("\n"):
120 s += blockXmlToNroff(node.childNodes, para)
122 s += inlineXmlToNroff(node, r'\fR')
124 raise Error("unknown node %s in block xml" % node)
125 if s != "" and not s.endswith('\n'):
129 def typeAndConstraintsToNroff(column):
130 type = column.type.toEnglish(escapeNroffLiteral)
131 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
133 type += ", " + constraints
136 def columnToNroff(columnName, column, node):
137 type = typeAndConstraintsToNroff(column)
138 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
139 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
142 def columnGroupToNroff(table, groupXml):
145 for node in groupXml.childNodes:
146 if (node.nodeType == node.ELEMENT_NODE
147 and node.tagName in ('column', 'group')):
148 columnNodes += [node]
153 intro = blockXmlToNroff(introNodes)
155 for node in columnNodes:
156 if node.tagName == 'column':
157 columnName = node.attributes['name'].nodeValue
158 column = table.columns[columnName]
159 body += columnToNroff(columnName, column, node)
160 summary += [('column', columnName, column)]
161 elif node.tagName == 'group':
162 title = node.attributes["title"].nodeValue
163 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
164 summary += [('group', title, subSummary)]
165 body += '.ST "%s:"\n' % textToNroff(title)
166 body += subIntro + subBody
168 raise Error("unknown element %s in <table>" % node.tagName)
169 return summary, intro, body
171 def tableSummaryToNroff(summary, level=0):
173 for type, name, arg in summary:
176 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
177 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
186 """ % (r'\ \ ' * level, name)
187 s += tableSummaryToNroff(arg, level + 1)
190 def tableToNroff(schema, tableXml):
191 tableName = tableXml.attributes['name'].nodeValue
192 table = schema.tables[tableName]
197 summary, intro, body = columnGroupToNroff(table, tableXml)
203 \fB%s\fR Table Columns:
210 s += tableSummaryToNroff(summary)
216 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
217 schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
218 doc = xml.dom.minidom.parse(xmlFile).documentElement
220 schemaDate = os.stat(schemaFile).st_mtime
221 xmlDate = os.stat(xmlFile).st_mtime
222 d = date.fromtimestamp(max(schemaDate, xmlDate))
227 # Putting '\" pt as the first line tells "man" that the manpage
228 # needs to be preprocessed by "pic" and "tbl".
230 .TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
243 ''' % (title, d.strftime("%B %Y"))
245 s += '.SH "%s DATABASE"\n' % schema.name
251 for dbNode in doc.childNodes:
252 if (dbNode.nodeType == dbNode.ELEMENT_NODE
253 and dbNode.tagName == "table"):
254 tableNodes += [dbNode]
256 name = dbNode.attributes['name'].nodeValue
257 if dbNode.hasAttribute("title"):
258 title = dbNode.attributes['title'].nodeValue
260 title = name + " configuration."
261 summary += [(name, title)]
263 introNodes += [dbNode]
265 s += blockXmlToNroff(introNodes) + "\n"
269 \fB%s\fR Database Tables:
277 for name, title in summary:
278 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
279 tableSummary += '.TE\n'
285 .SH "TABLE RELATIONSHIPS"
287 The following diagram shows the relationship among tables in the
288 database. Each node represents a table. Each edge leads from the
289 table that contains it and points to the table that its value
290 represents. Edges are labeled with their column names.
293 erStream = open(erFile, "r")
294 for line in erStream:
299 for node in tableNodes:
300 s += tableToNroff(schema, node) + "\n"
305 %(argv0)s: ovsdb schema documentation generator
306 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
307 usage: %(argv0)s [OPTIONS] SCHEMA XML
308 where SCHEMA is an OVSDB schema in JSON format
309 and XML is OVSDB documentation in XML format.
311 The following options are also available:
312 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
313 --title=TITLE use TITLE as title instead of schema name
314 -h, --help display this help message
315 -V, --version display version information\
316 """ % {'argv0': argv0}
319 if __name__ == "__main__":
322 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
323 ['er-diagram=', 'title=',
325 except getopt.GetoptError, geo:
326 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
331 for key, value in options:
332 if key == '--er-diagram':
334 elif key == '--title':
336 elif key in ['-h', '--help']:
338 elif key in ['-V', '--version']:
339 print "ovsdb-doc (Open vSwitch) @VERSION@"
344 sys.stderr.write("%s: exactly 2 non-option arguments required "
345 "(use --help for help)\n" % argv0)
348 # XXX we should warn about undocumented tables or columns
349 s = docsToNroff(args[0], args[1], er_diagram)
350 for line in s.split("\n"):
356 sys.stderr.write("%s: %s\n" % (argv0, e.msg))