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