]> git.proxmox.com Git - ovs.git/blame - ovsdb/ovsdb-doc.in
Implement initial Python bindings for Open vSwitch database.
[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
BP
45 elif node.nodeType == node.ELEMENT_NODE:
46 if node.tagName == 'code' or node.tagName == 'em':
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
55 elif node.hasAttribute('table'):
56 s += node.attributes['table'].nodeValue
57 elif node.hasAttribute('group'):
58 s += node.attributes['group'].nodeValue
59 else:
99155935 60 raise error.Error("'ref' lacks column and table attributes")
89365653
BP
61 return s + font
62 elif node.tagName == 'var':
63 s = r'\fI'
64 for child in node.childNodes:
65 s += inlineXmlToNroff(child, r'\fI')
66 return s + font
67 else:
99155935 68 raise error.Error("element <%s> unknown or invalid here" % node.tagName)
89365653 69 else:
99155935 70 raise error.Error("unknown node %s in inline xml" % node)
89365653
BP
71
72def blockXmlToNroff(nodes, para='.PP'):
73 s = ''
74 for node in nodes:
75 if node.nodeType == node.TEXT_NODE:
76 s += textToNroff(node.data)
77 s = s.lstrip()
78 elif node.nodeType == node.ELEMENT_NODE:
79 if node.tagName == 'ul':
80 if s != "":
81 s += "\n"
82 s += ".RS\n"
83 for liNode in node.childNodes:
84 if (liNode.nodeType == node.ELEMENT_NODE
85 and liNode.tagName == 'li'):
86 s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
87 elif (liNode.nodeType != node.TEXT_NODE
88 or not liNode.data.isspace()):
99155935 89 raise error.Error("<ul> element may only have <li> children")
89365653
BP
90 s += ".RE\n"
91 elif node.tagName == 'dl':
92 if s != "":
93 s += "\n"
94 s += ".RS\n"
95 prev = "dd"
96 for liNode in node.childNodes:
97 if (liNode.nodeType == node.ELEMENT_NODE
98 and liNode.tagName == 'dt'):
99 if prev == 'dd':
100 s += '.TP\n'
101 else:
102 s += '.TQ\n'
103 prev = 'dt'
104 elif (liNode.nodeType == node.ELEMENT_NODE
105 and liNode.tagName == 'dd'):
106 if prev == 'dd':
107 s += '.IP\n'
108 prev = 'dd'
109 elif (liNode.nodeType != node.TEXT_NODE
110 or not liNode.data.isspace()):
99155935 111 raise error.Error("<dl> element may only have <dt> and <dd> children")
89365653
BP
112 s += blockXmlToNroff(liNode.childNodes, ".IP")
113 s += ".RE\n"
114 elif node.tagName == 'p':
115 if s != "":
116 if not s.endswith("\n"):
117 s += "\n"
118 s += para + "\n"
119 s += blockXmlToNroff(node.childNodes, para)
120 else:
121 s += inlineXmlToNroff(node, r'\fR')
122 else:
99155935 123 raise error.Error("unknown node %s in block xml" % node)
89365653
BP
124 if s != "" and not s.endswith('\n'):
125 s += '\n'
126 return s
127
128def typeAndConstraintsToNroff(column):
129 type = column.type.toEnglish(escapeNroffLiteral)
130 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
131 if constraints:
132 type += ", " + constraints
133 return type
134
135def columnToNroff(columnName, column, node):
136 type = typeAndConstraintsToNroff(column)
137 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
138 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
139 return s
140
141def columnGroupToNroff(table, groupXml):
142 introNodes = []
143 columnNodes = []
144 for node in groupXml.childNodes:
145 if (node.nodeType == node.ELEMENT_NODE
146 and node.tagName in ('column', 'group')):
147 columnNodes += [node]
148 else:
149 introNodes += [node]
150
151 summary = []
152 intro = blockXmlToNroff(introNodes)
153 body = ''
154 for node in columnNodes:
155 if node.tagName == 'column':
156 columnName = node.attributes['name'].nodeValue
157 column = table.columns[columnName]
158 body += columnToNroff(columnName, column, node)
159 summary += [('column', columnName, column)]
160 elif node.tagName == 'group':
161 title = node.attributes["title"].nodeValue
162 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
163 summary += [('group', title, subSummary)]
164 body += '.ST "%s:"\n' % textToNroff(title)
165 body += subIntro + subBody
166 else:
99155935 167 raise error.Error("unknown element %s in <table>" % node.tagName)
89365653
BP
168 return summary, intro, body
169
170def tableSummaryToNroff(summary, level=0):
171 s = ""
172 for type, name, arg in summary:
173 if type == 'column':
174
175 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
176 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
177 else:
178 if s != "":
179 s += "_\n"
180 s += """.T&
181li | s
182l | l.
183%s%s
184_
185""" % (r'\ \ ' * level, name)
186 s += tableSummaryToNroff(arg, level + 1)
187 return s
188
189def tableToNroff(schema, tableXml):
190 tableName = tableXml.attributes['name'].nodeValue
191 table = schema.tables[tableName]
192
193 s = """.bp
194.SS "%s Table"
195""" % tableName
196 summary, intro, body = columnGroupToNroff(table, tableXml)
197 s += intro
198
199 s += r"""
200.sp
201.ce 1
202\fB%s\fR Table Columns:
203.TS
204center box;
205l | l.
206Column Type
207=
208""" % tableName
209 s += tableSummaryToNroff(summary)
210 s += ".TE\n"
211
212 s += body
213 return s
214
f8d739a9 215def docsToNroff(schemaFile, xmlFile, erFile, title=None):
99155935 216 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
89365653 217 doc = xml.dom.minidom.parse(xmlFile).documentElement
f8d739a9 218
89365653
BP
219 schemaDate = os.stat(schemaFile).st_mtime
220 xmlDate = os.stat(xmlFile).st_mtime
221 d = date.fromtimestamp(max(schemaDate, xmlDate))
222
223 if title == None:
224 title = schema.name
225
f8d739a9
BP
226 # Putting '\" pt as the first line tells "man" that the manpage
227 # needs to be preprocessed by "pic" and "tbl".
228 s = r''''\" pt
229.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
89365653
BP
230.\" -*- nroff -*-
231.de TQ
232. br
233. ns
234. TP "\\$1"
235..
236.de ST
237. PP
238. RS -0.15in
239. I "\\$1"
240. RE
241..
242''' % (title, d.strftime("%B %Y"))
243
244 s += '.SH "%s DATABASE"\n' % schema.name
245
246 tables = ""
247 introNodes = []
248 tableNodes = []
249 summary = []
250 for dbNode in doc.childNodes:
251 if (dbNode.nodeType == dbNode.ELEMENT_NODE
252 and dbNode.tagName == "table"):
253 tableNodes += [dbNode]
254
255 name = dbNode.attributes['name'].nodeValue
256 if dbNode.hasAttribute("title"):
257 title = dbNode.attributes['title'].nodeValue
258 else:
259 title = name + " configuration."
260 summary += [(name, title)]
261 else:
262 introNodes += [dbNode]
263
264 s += blockXmlToNroff(introNodes) + "\n"
265 tableSummary = r"""
266.sp
267.ce 1
268\fB%s\fR Database Tables:
269.TS
270center box;
271l | l
272lb | l.
273Table Purpose
274=
275""" % schema.name
276 for name, title in summary:
277 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
278 tableSummary += '.TE\n'
279 s += tableSummary
f8d739a9
BP
280
281 if erFile:
282 s += """
283.sp 1
284.SH "TABLE RELATIONSHIPS"
285.PP
286The following diagram shows the relationship among tables in the
287database. Each node represents a table. Each edge leads from the
288table that contains it and points to the table that its value
289represents. Edges are labeled with their column names.
290.RS -1in
291"""
292 erStream = open(erFile, "r")
293 for line in erStream:
294 s += line + '\n'
295 erStream.close()
296 s += ".RE\n"
297
89365653
BP
298 for node in tableNodes:
299 s += tableToNroff(schema, node) + "\n"
300 return s
301
302def usage():
303 print """\
304%(argv0)s: ovsdb schema documentation generator
305Prints documentation for an OVSDB schema as an nroff-formatted manpage.
306usage: %(argv0)s [OPTIONS] SCHEMA XML
307where SCHEMA is an OVSDB schema in JSON format
308 and XML is OVSDB documentation in XML format.
309
310The following options are also available:
f8d739a9 311 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
89365653
BP
312 --title=TITLE use TITLE as title instead of schema name
313 -h, --help display this help message
314 -V, --version display version information\
315""" % {'argv0': argv0}
316 sys.exit(0)
317
318if __name__ == "__main__":
319 try:
320 try:
321 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
f8d739a9
BP
322 ['er-diagram=', 'title=',
323 'help', 'version'])
89365653
BP
324 except getopt.GetoptError, geo:
325 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
326 sys.exit(1)
327
f8d739a9 328 er_diagram = None
89365653
BP
329 title = None
330 for key, value in options:
f8d739a9
BP
331 if key == '--er-diagram':
332 er_diagram = value
333 elif key == '--title':
89365653
BP
334 title = value
335 elif key in ['-h', '--help']:
336 usage()
337 elif key in ['-V', '--version']:
338 print "ovsdb-doc (Open vSwitch) @VERSION@"
339 else:
340 sys.exit(0)
341
342 if len(args) != 2:
343 sys.stderr.write("%s: exactly 2 non-option arguments required "
344 "(use --help for help)\n" % argv0)
345 sys.exit(1)
346
347 # XXX we should warn about undocumented tables or columns
f8d739a9 348 s = docsToNroff(args[0], args[1], er_diagram)
89365653
BP
349 for line in s.split("\n"):
350 line = line.strip()
351 if len(line):
352 print line
353
99155935 354 except error.Error, e:
89365653
BP
355 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
356 sys.exit(1)
357
358# Local variables:
359# mode: python
360# End: