]> git.proxmox.com Git - mirror_ovs.git/blame - ovsdb/ovsdb-doc.in
idl: Generalize special case boolean exception.
[mirror_ovs.git] / ovsdb / ovsdb-doc.in
CommitLineData
89365653
BP
1#! @PYTHON@
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)
fcbaf28c
BP
19 if c == '-':
20 if font == r'\fB':
21 return r'\-'
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
BP
33 # Escape - \ " ' as needed by nroff.
34 s = re.sub('([-"\'\\\\])', 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)
147 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
148 if constraints:
149 type += ", " + constraints
6910a6e6
BP
150 if column.unique:
151 type += " (must be unique within table)"
89365653
BP
152 return type
153
89365653
BP
154def columnGroupToNroff(table, groupXml):
155 introNodes = []
156 columnNodes = []
157 for node in groupXml.childNodes:
158 if (node.nodeType == node.ELEMENT_NODE
159 and node.tagName in ('column', 'group')):
160 columnNodes += [node]
161 else:
3fd8d445
BP
162 if (columnNodes
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)
89365653
BP
166 introNodes += [node]
167
168 summary = []
169 intro = blockXmlToNroff(introNodes)
170 body = ''
171 for node in columnNodes:
172 if node.tagName == 'column':
3fd8d445
BP
173 name = node.attributes['name'].nodeValue
174 column = table.columns[name]
175 if node.hasAttribute('key'):
176 key = node.attributes['key'].nodeValue
f9e5e5b3
BP
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)
184 else:
185 type_ = column.type.value
186
3fd8d445
BP
187 nameNroff = "%s : %s" % (name, key)
188 typeNroff = "optional %s" % column.type.value.toEnglish()
f9e5e5b3
BP
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"
194 else:
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
199 else:
200 typeNroff += ", containing a %s" % type_english
201 constraints = type_.constraintsToEnglish(escapeNroffLiteral)
202 if constraints:
203 typeNroff += ", %s" % constraints
3fd8d445
BP
204 else:
205 nameNroff = name
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)]
89365653
BP
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
216 else:
99155935 217 raise error.Error("unknown element %s in <table>" % node.tagName)
89365653
BP
218 return summary, intro, body
219
220def tableSummaryToNroff(summary, level=0):
221 s = ""
222 for type, name, arg in summary:
223 if type == 'column':
3fd8d445 224 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
89365653 225 else:
3fd8d445 226 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
89365653 227 s += tableSummaryToNroff(arg, level + 1)
3fd8d445 228 s += ".RE\n"
89365653
BP
229 return s
230
231def tableToNroff(schema, tableXml):
232 tableName = tableXml.attributes['name'].nodeValue
233 table = schema.tables[tableName]
234
235 s = """.bp
3fd8d445 236.SH "%s TABLE"
89365653
BP
237""" % tableName
238 summary, intro, body = columnGroupToNroff(table, tableXml)
239 s += intro
3fd8d445 240 s += '.SS "Summary:\n'
89365653 241 s += tableSummaryToNroff(summary)
3fd8d445 242 s += '.SS "Details:\n'
89365653
BP
243 s += body
244 return s
245
f8d739a9 246def docsToNroff(schemaFile, xmlFile, erFile, title=None):
99155935 247 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
89365653 248 doc = xml.dom.minidom.parse(xmlFile).documentElement
c5c7c7c5 249
89365653
BP
250 schemaDate = os.stat(schemaFile).st_mtime
251 xmlDate = os.stat(xmlFile).st_mtime
252 d = date.fromtimestamp(max(schemaDate, xmlDate))
c5c7c7c5 253
89365653
BP
254 if title == None:
255 title = schema.name
256
3fd8d445
BP
257 # Putting '\" p as the first line tells "man" that the manpage
258 # needs to be preprocessed by "pic".
259 s = r''''\" p
f8d739a9 260.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
89365653
BP
261.\" -*- nroff -*-
262.de TQ
263. br
264. ns
3fd8d445 265. TP "\\$1"
89365653
BP
266..
267.de ST
268. PP
269. RS -0.15in
270. I "\\$1"
271. RE
272..
273''' % (title, d.strftime("%B %Y"))
274
275 s += '.SH "%s DATABASE"\n' % schema.name
276
277 tables = ""
278 introNodes = []
279 tableNodes = []
280 summary = []
281 for dbNode in doc.childNodes:
282 if (dbNode.nodeType == dbNode.ELEMENT_NODE
283 and dbNode.tagName == "table"):
284 tableNodes += [dbNode]
285
286 name = dbNode.attributes['name'].nodeValue
287 if dbNode.hasAttribute("title"):
288 title = dbNode.attributes['title'].nodeValue
289 else:
290 title = name + " configuration."
291 summary += [(name, title)]
292 else:
293 introNodes += [dbNode]
294
295 s += blockXmlToNroff(introNodes) + "\n"
3fd8d445
BP
296
297 s += r"""
298.SH "TABLE SUMMARY"
299.PP
300The 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
302page.
303.IP "Table" 1in
304Purpose
89365653
BP
305""" % schema.name
306 for name, title in summary:
3fd8d445
BP
307 s += r"""
308.TQ 1in
309\fB%s\fR
310%s
311""" % (name, textToNroff(title))
f8d739a9
BP
312
313 if erFile:
314 s += """
186ef5c1
CW
315.\\" check if in troff mode (TTY)
316.if t \{
3fd8d445 317.bp
f8d739a9
BP
318.SH "TABLE RELATIONSHIPS"
319.PP
320The following diagram shows the relationship among tables in the
c5f341ab
BP
321database. Each node represents a table. Tables that are part of the
322``root set'' are shown with double borders. Each edge leads from the
f8d739a9 323table that contains it and points to the table that its value
d5a59e7e
BP
324represents. Edges are labeled with their column names, followed by a
325constraint on the number of allowed values: \\fB?\\fR for zero or one,
326\\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
c5f341ab 327represent strong references; thin lines represent weak references.
f8d739a9
BP
328.RS -1in
329"""
330 erStream = open(erFile, "r")
331 for line in erStream:
332 s += line + '\n'
333 erStream.close()
c182a514 334 s += ".RE\\}\n"
f8d739a9 335
89365653
BP
336 for node in tableNodes:
337 s += tableToNroff(schema, node) + "\n"
338 return s
339
340def usage():
341 print """\
342%(argv0)s: ovsdb schema documentation generator
343Prints documentation for an OVSDB schema as an nroff-formatted manpage.
344usage: %(argv0)s [OPTIONS] SCHEMA XML
345where SCHEMA is an OVSDB schema in JSON format
346 and XML is OVSDB documentation in XML format.
347
348The following options are also available:
f8d739a9 349 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
89365653
BP
350 --title=TITLE use TITLE as title instead of schema name
351 -h, --help display this help message
352 -V, --version display version information\
353""" % {'argv0': argv0}
354 sys.exit(0)
355
356if __name__ == "__main__":
357 try:
358 try:
359 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
f8d739a9
BP
360 ['er-diagram=', 'title=',
361 'help', 'version'])
89365653
BP
362 except getopt.GetoptError, geo:
363 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
364 sys.exit(1)
365
f8d739a9 366 er_diagram = None
89365653
BP
367 title = None
368 for key, value in options:
f8d739a9
BP
369 if key == '--er-diagram':
370 er_diagram = value
371 elif key == '--title':
89365653
BP
372 title = value
373 elif key in ['-h', '--help']:
374 usage()
375 elif key in ['-V', '--version']:
376 print "ovsdb-doc (Open vSwitch) @VERSION@"
377 else:
378 sys.exit(0)
c5c7c7c5 379
89365653
BP
380 if len(args) != 2:
381 sys.stderr.write("%s: exactly 2 non-option arguments required "
382 "(use --help for help)\n" % argv0)
383 sys.exit(1)
c5c7c7c5 384
89365653 385 # XXX we should warn about undocumented tables or columns
f8d739a9 386 s = docsToNroff(args[0], args[1], er_diagram)
89365653
BP
387 for line in s.split("\n"):
388 line = line.strip()
389 if len(line):
390 print line
c5c7c7c5 391
99155935 392 except error.Error, e:
89365653
BP
393 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
394 sys.exit(1)
395
396# Local variables:
397# mode: python
398# End: