Skip to content

Commit 8936565

Browse files
committed
Add documentation for the vswitch database schema.
We can do better than this (I already have some comments) but this is still much better than what we had.
1 parent 28998b2 commit 8936565

8 files changed

Lines changed: 1579 additions & 392 deletions

File tree

ovsdb/OVSDB.py

Lines changed: 495 additions & 0 deletions
Large diffs are not rendered by default.

ovsdb/automake.mk

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,19 @@ EXTRA_DIST += \
8282
ovsdb/simplejson/tests/test_separators.py \
8383
ovsdb/simplejson/tests/test_unicode.py \
8484
ovsdb/simplejson/tool.py
85-
noinst_SCRIPTS += ovsdb/ovsdb-idlc
85+
noinst_SCRIPTS += ovsdb/ovsdb-idlc
8686
EXTRA_DIST += \
8787
ovsdb/ovsdb-idlc.in \
8888
ovsdb/ovsdb-idlc.1
8989
DISTCLEANFILES += ovsdb/ovsdb-idlc
90-
SUFFIXES += .ovsidl .txt
90+
SUFFIXES += .ovsidl
9191
OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in
9292
.ovsidl.c:
9393
$(OVSDB_IDLC) c-idl-source $< > $@.tmp
9494
mv $@.tmp $@
9595
.ovsidl.h:
9696
$(OVSDB_IDLC) c-idl-header $< > $@.tmp
9797
mv $@.tmp $@
98-
.ovsidl.txt:
99-
$(OVSDB_IDLC) doc $< | fmt -s > $@.tmp
100-
mv $@.tmp $@
10198

10299
EXTRA_DIST += $(OVSIDL_BUILT)
103100
BUILT_SOURCES += $(OVSIDL_BUILT)
@@ -111,3 +108,9 @@ BUILT_SOURCES += $(OVSIDL_BUILT)
111108
# assignments before any targets, so it doesn't seem to be a problem,
112109
# at least for now.
113110
$(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in
111+
112+
# ovsdb-doc
113+
EXTRA_DIST += ovsdb/ovsdb-doc.in
114+
noinst_SCRIPTS += ovsdb/ovsdb-doc
115+
DISTCLEANFILES += ovsdb/ovsdb-doc
116+
OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in

ovsdb/ovsdb-doc.in

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
#! @PYTHON@
2+
3+
from datetime import date
4+
import getopt
5+
import os
6+
import re
7+
import sys
8+
import xml.dom.minidom
9+
10+
sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
11+
import simplejson as json
12+
13+
from OVSDB import *
14+
15+
argv0 = sys.argv[0]
16+
17+
def textToNroff(s):
18+
def escape(match):
19+
c = match.group(0)
20+
if c == '\\':
21+
return r'\e'
22+
elif c == '"':
23+
return r'\(dq'
24+
elif c == "'":
25+
return r'\(cq'
26+
else:
27+
raise Error("bad escape")
28+
29+
s = re.sub('([\\\\"\'])', escape, s)
30+
if s.startswith('.'):
31+
s = '\\' + s
32+
return s
33+
34+
def escapeNroffLiteral(s):
35+
return r'\fB%s\fR' % textToNroff(s)
36+
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':
42+
s = r'\fB'
43+
for child in node.childNodes:
44+
s += inlineXmlToNroff(child, r'\fB')
45+
return s + font
46+
elif node.tagName == 'ref':
47+
s = r'\fB'
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
54+
else:
55+
raise Error("'ref' lacks column and table attributes")
56+
return s + font
57+
elif node.tagName == 'var':
58+
s = r'\fI'
59+
for child in node.childNodes:
60+
s += inlineXmlToNroff(child, r'\fI')
61+
return s + font
62+
else:
63+
raise Error("element <%s> unknown or invalid here" % node.tagName)
64+
else:
65+
raise Error("unknown node %s in inline xml" % node)
66+
67+
def blockXmlToNroff(nodes, para='.PP'):
68+
s = ''
69+
for node in nodes:
70+
if node.nodeType == node.TEXT_NODE:
71+
s += textToNroff(node.data)
72+
s = s.lstrip()
73+
elif node.nodeType == node.ELEMENT_NODE:
74+
if node.tagName == 'ul':
75+
if s != "":
76+
s += "\n"
77+
s += ".RS\n"
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")
85+
s += ".RE\n"
86+
elif node.tagName == 'dl':
87+
if s != "":
88+
s += "\n"
89+
s += ".RS\n"
90+
prev = "dd"
91+
for liNode in node.childNodes:
92+
if (liNode.nodeType == node.ELEMENT_NODE
93+
and liNode.tagName == 'dt'):
94+
if prev == 'dd':
95+
s += '.TP\n'
96+
else:
97+
s += '.TQ\n'
98+
prev = 'dt'
99+
elif (liNode.nodeType == node.ELEMENT_NODE
100+
and liNode.tagName == 'dd'):
101+
if prev == 'dd':
102+
s += '.IP\n'
103+
prev = '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")
108+
s += ".RE\n"
109+
elif node.tagName == 'p':
110+
if s != "":
111+
if not s.endswith("\n"):
112+
s += "\n"
113+
s += para + "\n"
114+
s += blockXmlToNroff(node.childNodes, para)
115+
else:
116+
s += inlineXmlToNroff(node, r'\fR')
117+
else:
118+
raise Error("unknown node %s in block xml" % node)
119+
if s != "" and not s.endswith('\n'):
120+
s += '\n'
121+
return s
122+
123+
def typeAndConstraintsToNroff(column):
124+
type = column.type.toEnglish(escapeNroffLiteral)
125+
constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
126+
if constraints:
127+
type += ", " + constraints
128+
return type
129+
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"
134+
return s
135+
136+
def columnGroupToNroff(table, groupXml):
137+
introNodes = []
138+
columnNodes = []
139+
for node in groupXml.childNodes:
140+
if (node.nodeType == node.ELEMENT_NODE
141+
and node.tagName in ('column', 'group')):
142+
columnNodes += [node]
143+
else:
144+
introNodes += [node]
145+
146+
summary = []
147+
intro = blockXmlToNroff(introNodes)
148+
body = ''
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
161+
else:
162+
raise Error("unknown element %s in <table>" % node.tagName)
163+
return summary, intro, body
164+
165+
def tableSummaryToNroff(summary, level=0):
166+
s = ""
167+
for type, name, arg in summary:
168+
if type == 'column':
169+
170+
s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
171+
r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
172+
else:
173+
if s != "":
174+
s += "_\n"
175+
s += """.T&
176+
li | s
177+
l | l.
178+
%s%s
179+
_
180+
""" % (r'\ \ ' * level, name)
181+
s += tableSummaryToNroff(arg, level + 1)
182+
return s
183+
184+
def tableToNroff(schema, tableXml):
185+
tableName = tableXml.attributes['name'].nodeValue
186+
table = schema.tables[tableName]
187+
188+
s = """.bp
189+
.SS "%s Table"
190+
""" % tableName
191+
summary, intro, body = columnGroupToNroff(table, tableXml)
192+
s += intro
193+
194+
s += r"""
195+
.sp
196+
.ce 1
197+
\fB%s\fR Table Columns:
198+
.TS
199+
center box;
200+
l | l.
201+
Column Type
202+
=
203+
""" % tableName
204+
s += tableSummaryToNroff(summary)
205+
s += ".TE\n"
206+
207+
s += body
208+
return s
209+
210+
def docsToNroff(schemaFile, xmlFile, title=None):
211+
schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
212+
doc = xml.dom.minidom.parse(xmlFile).documentElement
213+
214+
schemaDate = os.stat(schemaFile).st_mtime
215+
xmlDate = os.stat(xmlFile).st_mtime
216+
d = date.fromtimestamp(max(schemaDate, xmlDate))
217+
218+
if title == None:
219+
title = schema.name
220+
221+
s = r'''.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
222+
.\" -*- nroff -*-
223+
.de TQ
224+
. br
225+
. ns
226+
. TP "\\$1"
227+
..
228+
.de ST
229+
. PP
230+
. RS -0.15in
231+
. I "\\$1"
232+
. RE
233+
..
234+
''' % (title, d.strftime("%B %Y"))
235+
236+
s += '.SH "%s DATABASE"\n' % schema.name
237+
238+
tables = ""
239+
introNodes = []
240+
tableNodes = []
241+
summary = []
242+
for dbNode in doc.childNodes:
243+
if (dbNode.nodeType == dbNode.ELEMENT_NODE
244+
and dbNode.tagName == "table"):
245+
tableNodes += [dbNode]
246+
247+
name = dbNode.attributes['name'].nodeValue
248+
if dbNode.hasAttribute("title"):
249+
title = dbNode.attributes['title'].nodeValue
250+
else:
251+
title = name + " configuration."
252+
summary += [(name, title)]
253+
else:
254+
introNodes += [dbNode]
255+
256+
s += blockXmlToNroff(introNodes) + "\n"
257+
tableSummary = r"""
258+
.sp
259+
.ce 1
260+
\fB%s\fR Database Tables:
261+
.TS
262+
center box;
263+
l | l
264+
lb | l.
265+
Table Purpose
266+
=
267+
""" % schema.name
268+
for name, title in summary:
269+
tableSummary += "%s\t%s\n" % (name, textToNroff(title))
270+
tableSummary += '.TE\n'
271+
s += tableSummary
272+
for node in tableNodes:
273+
s += tableToNroff(schema, node) + "\n"
274+
return s
275+
276+
def usage():
277+
print """\
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.
283+
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}
289+
sys.exit(0)
290+
291+
if __name__ == "__main__":
292+
try:
293+
try:
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))
298+
sys.exit(1)
299+
300+
title = None
301+
for key, value in options:
302+
if key == '--title':
303+
title = value
304+
elif key in ['-h', '--help']:
305+
usage()
306+
elif key in ['-V', '--version']:
307+
print "ovsdb-doc (Open vSwitch) @VERSION@"
308+
else:
309+
sys.exit(0)
310+
311+
if len(args) != 2:
312+
sys.stderr.write("%s: exactly 2 non-option arguments required "
313+
"(use --help for help)\n" % argv0)
314+
sys.exit(1)
315+
316+
# XXX we should warn about undocumented tables or columns
317+
s = docsToNroff(args[0], args[1])
318+
for line in s.split("\n"):
319+
line = line.strip()
320+
if len(line):
321+
print line
322+
323+
except Error, e:
324+
sys.stderr.write("%s: %s\n" % (argv0, e.msg))
325+
sys.exit(1)
326+
327+
# Local variables:
328+
# mode: python
329+
# End:

0 commit comments

Comments
 (0)