]> git.proxmox.com Git - ovs.git/blame - ovsdb/ovsdb-doc
vtep: Document the ipaddr column in the Mcast_Macs_Local table.
[ovs.git] / ovsdb / ovsdb-doc
CommitLineData
54c18b66 1#! /usr/bin/python
89365653
BP
2
3from datetime import date
4import getopt
5import os
6import re
7import sys
8import xml.dom.minidom
9
99155935
BP
10import ovs.json
11from ovs.db import error
12import ovs.db.schema
89365653
BP
13
14argv0 = sys.argv[0]
15
fcbaf28c 16def textToNroff(s, font=r'\fR'):
89365653
BP
17 def escape(match):
18 c = match.group(0)
a03bd69a
BP
19 if c.startswith('-'):
20 if c != '-' or font == r'\fB':
21 return '\\' + c
fcbaf28c
BP
22 else:
23 return '-'
89365653
BP
24 if c == '\\':
25 return r'\e'
26 elif c == '"':
27 return r'\(dq'
28 elif c == "'":
29 return r'\(cq'
30 else:
99155935 31 raise error.Error("bad escape")
89365653 32
fcbaf28c 33 # Escape - \ " ' as needed by nroff.
a03bd69a 34 s = re.sub('(-[0-9]|[-"\'\\\\])', escape, s)
89365653
BP
35 if s.startswith('.'):
36 s = '\\' + s
37 return s
38
39def escapeNroffLiteral(s):
fcbaf28c 40 return r'\fB%s\fR' % textToNroff(s, r'\fB')
89365653
BP
41
42def inlineXmlToNroff(node, font):
43 if node.nodeType == node.TEXT_NODE:
fcbaf28c 44 return textToNroff(node.data, font)
89365653 45 elif node.nodeType == node.ELEMENT_NODE:
c9423856 46 if node.tagName in ['code', 'em', 'option']:
89365653
BP
47 s = r'\fB'
48 for child in node.childNodes:
49 s += inlineXmlToNroff(child, r'\fB')
50 return s + font
51 elif node.tagName == 'ref':
52 s = r'\fB'
53 if node.hasAttribute('column'):
54 s += node.attributes['column'].nodeValue
8de67146
BP
55 if node.hasAttribute('key'):
56 s += ':' + node.attributes['key'].nodeValue
89365653
BP
57 elif node.hasAttribute('table'):
58 s += node.attributes['table'].nodeValue
59 elif node.hasAttribute('group'):
60 s += node.attributes['group'].nodeValue
61 else:
3fd8d445 62 raise error.Error("'ref' lacks required attributes: %s" % node.attributes.keys())
89365653
BP
63 return s + font
64 elif node.tagName == 'var':
65 s = r'\fI'
66 for child in node.childNodes:
67 s += inlineXmlToNroff(child, r'\fI')
68 return s + font
69 else:
99155935 70 raise error.Error("element <%s> unknown or invalid here" % node.tagName)
89365653 71 else:
99155935 72 raise error.Error("unknown node %s in inline xml" % node)
89365653
BP
73
74def blockXmlToNroff(nodes, para='.PP'):
75 s = ''
76 for node in nodes:
77 if node.nodeType == node.TEXT_NODE:
78 s += textToNroff(node.data)
79 s = s.lstrip()
80 elif node.nodeType == node.ELEMENT_NODE:
c9423856 81 if node.tagName in ['ul', 'ol']:
89365653
BP
82 if s != "":
83 s += "\n"
84 s += ".RS\n"
c9423856 85 i = 0
89365653
BP
86 for liNode in node.childNodes:
87 if (liNode.nodeType == node.ELEMENT_NODE
88 and liNode.tagName == 'li'):
c9423856
BP
89 i += 1
90 if node.tagName == 'ul':
5d943800 91 s += ".IP \\(bu\n"
c9423856
BP
92 else:
93 s += ".IP %d. .25in\n" % i
94 s += blockXmlToNroff(liNode.childNodes, ".IP")
89365653
BP
95 elif (liNode.nodeType != node.TEXT_NODE
96 or not liNode.data.isspace()):
c9423856 97 raise error.Error("<%s> element may only have <li> children" % node.tagName)
89365653
BP
98 s += ".RE\n"
99 elif node.tagName == 'dl':
100 if s != "":
101 s += "\n"
102 s += ".RS\n"
103 prev = "dd"
104 for liNode in node.childNodes:
105 if (liNode.nodeType == node.ELEMENT_NODE
106 and liNode.tagName == 'dt'):
107 if prev == 'dd':
108 s += '.TP\n'
109 else:
110 s += '.TQ\n'
111 prev = 'dt'
112 elif (liNode.nodeType == node.ELEMENT_NODE
113 and liNode.tagName == 'dd'):
114 if prev == 'dd':
115 s += '.IP\n'
116 prev = 'dd'
117 elif (liNode.nodeType != node.TEXT_NODE
118 or not liNode.data.isspace()):
99155935 119 raise error.Error("<dl> element may only have <dt> and <dd> children")
89365653
BP
120 s += blockXmlToNroff(liNode.childNodes, ".IP")
121 s += ".RE\n"
122 elif node.tagName == 'p':
123 if s != "":
124 if not s.endswith("\n"):
125 s += "\n"
126 s += para + "\n"
127 s += blockXmlToNroff(node.childNodes, para)
3fd8d445
BP
128 elif node.tagName in ('h1', 'h2', 'h3'):
129 if s != "":
130 if not s.endswith("\n"):
131 s += "\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')
136 s += "\n"
89365653
BP
137 else:
138 s += inlineXmlToNroff(node, r'\fR')
139 else:
99155935 140 raise error.Error("unknown node %s in block xml" % node)
89365653
BP
141 if s != "" and not s.endswith('\n'):
142 s += '\n'
143 return s
144
145def typeAndConstraintsToNroff(column):
146 type = column.type.toEnglish(escapeNroffLiteral)
746cb760
BP
147 constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
148 textToNroff)
89365653
BP
149 if constraints:
150 type += ", " + constraints
6910a6e6
BP
151 if column.unique:
152 type += " (must be unique within table)"
89365653
BP
153 return type
154
89365653
BP
155def columnGroupToNroff(table, groupXml):
156 introNodes = []
157 columnNodes = []
158 for node in groupXml.childNodes:
159 if (node.nodeType == node.ELEMENT_NODE
160 and node.tagName in ('column', 'group')):
161 columnNodes += [node]
162 else:
3fd8d445
BP
163 if (columnNodes
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)
89365653
BP
167 introNodes += [node]
168
169 summary = []
170 intro = blockXmlToNroff(introNodes)
171 body = ''
172 for node in columnNodes:
173 if node.tagName == 'column':
3fd8d445
BP
174 name = node.attributes['name'].nodeValue
175 column = table.columns[name]
176 if node.hasAttribute('key'):
177 key = node.attributes['key'].nodeValue
f9e5e5b3
BP
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)
185 else:
186 type_ = column.type.value
187
3fd8d445 188 nameNroff = "%s : %s" % (name, key)
3349f3bf
EJ
189
190 if column.type.value:
746cb760
BP
191 typeNroff = "optional %s" % column.type.value.toEnglish(
192 escapeNroffLiteral)
3349f3bf
EJ
193 if (column.type.value.type == ovs.db.types.StringType and
194 type_.type == ovs.db.types.BooleanType):
195 # This is a little more explicit and helpful than
196 # "containing a boolean"
197 typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
198 else:
199 if type_.type != column.type.value.type:
200 type_english = type_.toEnglish()
201 if type_english[0] in 'aeiou':
202 typeNroff += ", containing an %s" % type_english
203 else:
204 typeNroff += ", containing a %s" % type_english
205 constraints = (
746cb760
BP
206 type_.constraintsToEnglish(escapeNroffLiteral,
207 textToNroff))
3349f3bf
EJ
208 if constraints:
209 typeNroff += ", %s" % constraints
f9e5e5b3 210 else:
3349f3bf 211 typeNroff = "none"
3fd8d445
BP
212 else:
213 nameNroff = name
214 typeNroff = typeAndConstraintsToNroff(column)
c4454e89
BP
215 if not column.mutable:
216 typeNroff = "immutable %s" % typeNroff
3fd8d445
BP
217 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
218 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
219 summary += [('column', nameNroff, typeNroff)]
89365653
BP
220 elif node.tagName == 'group':
221 title = node.attributes["title"].nodeValue
222 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
223 summary += [('group', title, subSummary)]
224 body += '.ST "%s:"\n' % textToNroff(title)
225 body += subIntro + subBody
226 else:
99155935 227 raise error.Error("unknown element %s in <table>" % node.tagName)
89365653
BP
228 return summary, intro, body
229
230def tableSummaryToNroff(summary, level=0):
231 s = ""
232 for type, name, arg in summary:
233 if type == 'column':
3fd8d445 234 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
89365653 235 else:
3fd8d445 236 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
89365653 237 s += tableSummaryToNroff(arg, level + 1)
3fd8d445 238 s += ".RE\n"
89365653
BP
239 return s
240
241def tableToNroff(schema, tableXml):
242 tableName = tableXml.attributes['name'].nodeValue
243 table = schema.tables[tableName]
244
245 s = """.bp
3fd8d445 246.SH "%s TABLE"
89365653
BP
247""" % tableName
248 summary, intro, body = columnGroupToNroff(table, tableXml)
249 s += intro
3fd8d445 250 s += '.SS "Summary:\n'
89365653 251 s += tableSummaryToNroff(summary)
3fd8d445 252 s += '.SS "Details:\n'
89365653
BP
253 s += body
254 return s
255
54c18b66 256def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
99155935 257 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
89365653 258 doc = xml.dom.minidom.parse(xmlFile).documentElement
c5c7c7c5 259
89365653
BP
260 schemaDate = os.stat(schemaFile).st_mtime
261 xmlDate = os.stat(xmlFile).st_mtime
262 d = date.fromtimestamp(max(schemaDate, xmlDate))
c5c7c7c5 263
89365653
BP
264 if title == None:
265 title = schema.name
266
54c18b66
GS
267 if version == None:
268 version = "UNKNOWN"
269
3fd8d445
BP
270 # Putting '\" p as the first line tells "man" that the manpage
271 # needs to be preprocessed by "pic".
272 s = r''''\" p
a4f22c24 273.TH "%s" 5 " DB Schema %s" "Open vSwitch %s" "Open vSwitch Manual"
89365653
BP
274.\" -*- nroff -*-
275.de TQ
276. br
277. ns
3fd8d445 278. TP "\\$1"
89365653
BP
279..
280.de ST
281. PP
282. RS -0.15in
283. I "\\$1"
284. RE
285..
9f88469c
BP
286.SH NAME
287%s \- %s database schema
e7c465fd 288.PP
a4f22c24 289''' % (title, schema.version, version, textToNroff(schema.name), schema.name)
89365653
BP
290
291 tables = ""
292 introNodes = []
293 tableNodes = []
294 summary = []
295 for dbNode in doc.childNodes:
296 if (dbNode.nodeType == dbNode.ELEMENT_NODE
297 and dbNode.tagName == "table"):
298 tableNodes += [dbNode]
299
300 name = dbNode.attributes['name'].nodeValue
301 if dbNode.hasAttribute("title"):
302 title = dbNode.attributes['title'].nodeValue
303 else:
304 title = name + " configuration."
305 summary += [(name, title)]
306 else:
307 introNodes += [dbNode]
308
309 s += blockXmlToNroff(introNodes) + "\n"
3fd8d445
BP
310
311 s += r"""
312.SH "TABLE SUMMARY"
313.PP
314The following list summarizes the purpose of each of the tables in the
315\fB%s\fR database. Each table is described in more detail on a later
316page.
317.IP "Table" 1in
318Purpose
89365653
BP
319""" % schema.name
320 for name, title in summary:
3fd8d445
BP
321 s += r"""
322.TQ 1in
323\fB%s\fR
324%s
325""" % (name, textToNroff(title))
f8d739a9
BP
326
327 if erFile:
328 s += """
186ef5c1
CW
329.\\" check if in troff mode (TTY)
330.if t \{
3fd8d445 331.bp
f8d739a9
BP
332.SH "TABLE RELATIONSHIPS"
333.PP
334The following diagram shows the relationship among tables in the
c5f341ab
BP
335database. Each node represents a table. Tables that are part of the
336``root set'' are shown with double borders. Each edge leads from the
f8d739a9 337table that contains it and points to the table that its value
d5a59e7e
BP
338represents. Edges are labeled with their column names, followed by a
339constraint on the number of allowed values: \\fB?\\fR for zero or one,
340\\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
c5f341ab 341represent strong references; thin lines represent weak references.
f8d739a9
BP
342.RS -1in
343"""
344 erStream = open(erFile, "r")
345 for line in erStream:
346 s += line + '\n'
347 erStream.close()
c182a514 348 s += ".RE\\}\n"
f8d739a9 349
89365653
BP
350 for node in tableNodes:
351 s += tableToNroff(schema, node) + "\n"
352 return s
353
354def usage():
355 print """\
356%(argv0)s: ovsdb schema documentation generator
357Prints documentation for an OVSDB schema as an nroff-formatted manpage.
358usage: %(argv0)s [OPTIONS] SCHEMA XML
359where SCHEMA is an OVSDB schema in JSON format
360 and XML is OVSDB documentation in XML format.
361
362The following options are also available:
f8d739a9 363 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
89365653 364 --title=TITLE use TITLE as title instead of schema name
54c18b66
GS
365 --version=VERSION use VERSION to display on document footer
366 -h, --help display this help message\
89365653
BP
367""" % {'argv0': argv0}
368 sys.exit(0)
369
370if __name__ == "__main__":
371 try:
372 try:
373 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
f8d739a9 374 ['er-diagram=', 'title=',
54c18b66 375 'version=', 'help'])
89365653
BP
376 except getopt.GetoptError, geo:
377 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
378 sys.exit(1)
379
f8d739a9 380 er_diagram = None
89365653 381 title = None
54c18b66 382 version = None
89365653 383 for key, value in options:
f8d739a9
BP
384 if key == '--er-diagram':
385 er_diagram = value
386 elif key == '--title':
89365653 387 title = value
54c18b66
GS
388 elif key == '--version':
389 version = value
89365653
BP
390 elif key in ['-h', '--help']:
391 usage()
89365653
BP
392 else:
393 sys.exit(0)
c5c7c7c5 394
89365653
BP
395 if len(args) != 2:
396 sys.stderr.write("%s: exactly 2 non-option arguments required "
397 "(use --help for help)\n" % argv0)
398 sys.exit(1)
c5c7c7c5 399
89365653 400 # XXX we should warn about undocumented tables or columns
54c18b66 401 s = docsToNroff(args[0], args[1], er_diagram, title, version)
89365653
BP
402 for line in s.split("\n"):
403 line = line.strip()
404 if len(line):
405 print line
c5c7c7c5 406
99155935 407 except error.Error, e:
89365653
BP
408 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
409 sys.exit(1)
410
411# Local variables:
412# mode: python
413# End: