3 from datetime import date
10 sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
11 import simplejson as json
27 raise Error("bad escape")
29 s = re.sub('([\\\\"\'])', escape, s)
34 def escapeNroffLiteral(s):
35 return r'\fB%s\fR' % textToNroff(s)
37 def inlineXmlToNroff(node, font):
38 if node.nodeType == node.TEXT_NODE:
39 return textToNroff(node.data)
40 elif node.nodeType == node.ELEMENT_NODE:
41 if node.tagName == 'code' or node.tagName == 'em':
43 for child in node.childNodes:
44 s += inlineXmlToNroff(child, r'\fB')
46 elif node.tagName == 'ref':
48 if node.hasAttribute('column'):
49 s += node.attributes['column'].nodeValue
50 elif node.hasAttribute('table'):
51 s += node.attributes['table'].nodeValue
52 elif node.hasAttribute('group'):
53 s += node.attributes['group'].nodeValue
55 raise Error("'ref' lacks column and table attributes")
57 elif node.tagName == 'var':
59 for child in node.childNodes:
60 s += inlineXmlToNroff(child, r'\fI')
63 raise Error("element <%s> unknown or invalid here" % node.tagName)
65 raise Error("unknown node %s in inline xml" % node)
67 def blockXmlToNroff(nodes, para='.PP'):
70 if node.nodeType == node.TEXT_NODE:
71 s += textToNroff(node.data)
73 elif node.nodeType == node.ELEMENT_NODE:
74 if node.tagName == 'ul':
78 for liNode in node.childNodes:
79 if (liNode.nodeType == node.ELEMENT_NODE
80 and liNode.tagName == 'li'):
81 s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
82 elif (liNode.nodeType != node.TEXT_NODE
83 or not liNode.data.isspace()):
84 raise Error("<ul> element may only have <li> children")
86 elif node.tagName == 'dl':
91 for liNode in node.childNodes:
92 if (liNode.nodeType == node.ELEMENT_NODE
93 and liNode.tagName == 'dt'):
99 elif (liNode.nodeType == node.ELEMENT_NODE
100 and liNode.tagName == 'dd'):
104 elif (liNode.nodeType != node.TEXT_NODE
105 or not liNode.data.isspace()):
106 raise Error("<dl> element may only have <dt> and <dd> children")
107 s += blockXmlToNroff(liNode.childNodes, ".IP")
109 elif node.tagName == 'p':
111 if not s.endswith("\n"):
114 s += blockXmlToNroff(node.childNodes, para)
116 s += inlineXmlToNroff(node, r'\fR')
118 raise Error("unknown node %s in block xml" % node)
119 if s != "" and not s.endswith('\n'):
123 def typeAndConstraintsToNroff(column):
124 type = column.type.toEnglish(escapeNroffLiteral)
125 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
127 type += ", " + constraints
130 def columnToNroff(columnName, column, node):
131 type = typeAndConstraintsToNroff(column)
132 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
133 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
136 def columnGroupToNroff(table, groupXml):
139 for node in groupXml.childNodes:
140 if (node.nodeType == node.ELEMENT_NODE
141 and node.tagName in ('column', 'group')):
142 columnNodes += [node]
147 intro = blockXmlToNroff(introNodes)
149 for node in columnNodes:
150 if node.tagName == 'column':
151 columnName = node.attributes['name'].nodeValue
152 column = table.columns[columnName]
153 body += columnToNroff(columnName, column, node)
154 summary += [('column', columnName, column)]
155 elif node.tagName == 'group':
156 title = node.attributes["title"].nodeValue
157 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
158 summary += [('group', title, subSummary)]
159 body += '.ST "%s:"\n' % textToNroff(title)
160 body += subIntro + subBody
162 raise Error("unknown element %s in <table>" % node.tagName)
163 return summary, intro, body
165 def tableSummaryToNroff(summary, level=0):
167 for type, name, arg in summary:
170 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
171 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
180 """ % (r'\ \ ' * level, name)
181 s += tableSummaryToNroff(arg, level + 1)
184 def tableToNroff(schema, tableXml):
185 tableName = tableXml.attributes['name'].nodeValue
186 table = schema.tables[tableName]
191 summary, intro, body = columnGroupToNroff(table, tableXml)
197 \fB%s\fR Table Columns:
204 s += tableSummaryToNroff(summary)
210 def docsToNroff(schemaFile, xmlFile, title=None):
211 schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
212 doc = xml.dom.minidom.parse(xmlFile).documentElement
214 schemaDate = os.stat(schemaFile).st_mtime
215 xmlDate = os.stat(xmlFile).st_mtime
216 d = date.fromtimestamp(max(schemaDate, xmlDate))
221 s = r'''.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
234 ''' % (title, d.strftime("%B %Y"))
236 s += '.SH "%s DATABASE"\n' % schema.name
242 for dbNode in doc.childNodes:
243 if (dbNode.nodeType == dbNode.ELEMENT_NODE
244 and dbNode.tagName == "table"):
245 tableNodes += [dbNode]
247 name = dbNode.attributes['name'].nodeValue
248 if dbNode.hasAttribute("title"):
249 title = dbNode.attributes['title'].nodeValue
251 title = name + " configuration."
252 summary += [(name, title)]
254 introNodes += [dbNode]
256 s += blockXmlToNroff(introNodes) + "\n"
260 \fB%s\fR Database Tables:
268 for name, title in summary:
269 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
270 tableSummary += '.TE\n'
272 for node in tableNodes:
273 s += tableToNroff(schema, node) + "\n"
278 %(argv0)s: ovsdb schema documentation generator
279 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
280 usage: %(argv0)s [OPTIONS] SCHEMA XML
281 where SCHEMA is an OVSDB schema in JSON format
282 and XML is OVSDB documentation in XML format.
284 The following options are also available:
285 --title=TITLE use TITLE as title instead of schema name
286 -h, --help display this help message
287 -V, --version display version information\
288 """ % {'argv0': argv0}
291 if __name__ == "__main__":
294 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
295 ['title=', 'help', 'version'])
296 except getopt.GetoptError, geo:
297 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
301 for key, value in options:
304 elif key in ['-h', '--help']:
306 elif key in ['-V', '--version']:
307 print "ovsdb-doc (Open vSwitch) @VERSION@"
312 sys.stderr.write("%s: exactly 2 non-option arguments required "
313 "(use --help for help)\n" % argv0)
316 # XXX we should warn about undocumented tables or columns
317 s = docsToNroff(args[0], args[1])
318 for line in s.split("\n"):
324 sys.stderr.write("%s: %s\n" % (argv0, e.msg))