]> git.proxmox.com Git - mirror_ovs.git/blob - ovsdb/ovsdb-doc
ovn: fix ovn-northd leak in build_acls
[mirror_ovs.git] / ovsdb / ovsdb-doc
1 #! /usr/bin/python
2
3 # Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 from datetime import date
18 import getopt
19 import os
20 import sys
21 import xml.dom.minidom
22
23 import ovs.json
24 from ovs.db import error
25 import ovs.db.schema
26
27 from build.nroff import *
28
29 argv0 = sys.argv[0]
30
31 def typeAndConstraintsToNroff(column):
32 type = column.type.toEnglish(escape_nroff_literal)
33 constraints = column.type.constraintsToEnglish(escape_nroff_literal,
34 text_to_nroff)
35 if constraints:
36 type += ", " + constraints
37 if column.unique:
38 type += " (must be unique within table)"
39 return type
40
41 def columnGroupToNroff(table, groupXml, documented_columns):
42 introNodes = []
43 columnNodes = []
44 for node in groupXml.childNodes:
45 if (node.nodeType == node.ELEMENT_NODE
46 and node.tagName in ('column', 'group')):
47 columnNodes += [node]
48 else:
49 if (columnNodes
50 and not (node.nodeType == node.TEXT_NODE
51 and node.data.isspace())):
52 raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
53 introNodes += [node]
54
55 summary = []
56 intro = block_xml_to_nroff(introNodes)
57 body = ''
58 for node in columnNodes:
59 if node.tagName == 'column':
60 name = node.attributes['name'].nodeValue
61 documented_columns.add(name)
62 column = table.columns[name]
63 if node.hasAttribute('key'):
64 key = node.attributes['key'].nodeValue
65 if node.hasAttribute('type'):
66 type_string = node.attributes['type'].nodeValue
67 type_json = ovs.json.from_string(str(type_string))
68 if type(type_json) in (str, unicode):
69 raise error.Error("%s %s:%s has invalid 'type': %s"
70 % (table.name, name, key, type_json))
71 type_ = ovs.db.types.BaseType.from_json(type_json)
72 else:
73 type_ = column.type.value
74
75 nameNroff = "%s : %s" % (name, key)
76
77 if column.type.value:
78 typeNroff = "optional %s" % column.type.value.toEnglish(
79 escape_nroff_literal)
80 if (column.type.value.type == ovs.db.types.StringType and
81 type_.type == ovs.db.types.BooleanType):
82 # This is a little more explicit and helpful than
83 # "containing a boolean"
84 typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
85 else:
86 if type_.type != column.type.value.type:
87 type_english = type_.toEnglish()
88 if type_english[0] in 'aeiou':
89 typeNroff += ", containing an %s" % type_english
90 else:
91 typeNroff += ", containing a %s" % type_english
92 constraints = (
93 type_.constraintsToEnglish(escape_nroff_literal,
94 text_to_nroff))
95 if constraints:
96 typeNroff += ", %s" % constraints
97 else:
98 typeNroff = "none"
99 else:
100 nameNroff = name
101 typeNroff = typeAndConstraintsToNroff(column)
102 if not column.mutable:
103 typeNroff = "immutable %s" % typeNroff
104 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
105 body += block_xml_to_nroff(node.childNodes, '.IP') + "\n"
106 summary += [('column', nameNroff, typeNroff)]
107 elif node.tagName == 'group':
108 title = node.attributes["title"].nodeValue
109 subSummary, subIntro, subBody = columnGroupToNroff(
110 table, node, documented_columns)
111 summary += [('group', title, subSummary)]
112 body += '.ST "%s:"\n' % text_to_nroff(title)
113 body += subIntro + subBody
114 else:
115 raise error.Error("unknown element %s in <table>" % node.tagName)
116 return summary, intro, body
117
118 def tableSummaryToNroff(summary, level=0):
119 s = ""
120 for type, name, arg in summary:
121 if type == 'column':
122 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
123 else:
124 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
125 s += tableSummaryToNroff(arg, level + 1)
126 s += ".RE\n"
127 return s
128
129 def tableToNroff(schema, tableXml):
130 tableName = tableXml.attributes['name'].nodeValue
131 table = schema.tables[tableName]
132
133 documented_columns = set()
134 s = """.bp
135 .SH "%s TABLE"
136 """ % tableName
137 summary, intro, body = columnGroupToNroff(table, tableXml,
138 documented_columns)
139 s += intro
140 s += '.SS "Summary:\n'
141 s += tableSummaryToNroff(summary)
142 s += '.SS "Details:\n'
143 s += body
144
145 schema_columns = set(table.columns.keys())
146 undocumented_columns = schema_columns - documented_columns
147 for column in undocumented_columns:
148 raise error.Error("table %s has undocumented column %s"
149 % (tableName, column))
150
151 return s
152
153 def docsToNroff(schemaFile, xmlFile, erFile, version=None):
154 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
155 doc = xml.dom.minidom.parse(xmlFile).documentElement
156
157 schemaDate = os.stat(schemaFile).st_mtime
158 xmlDate = os.stat(xmlFile).st_mtime
159 d = date.fromtimestamp(max(schemaDate, xmlDate))
160
161 if doc.hasAttribute('name'):
162 manpage = doc.attributes['name'].nodeValue
163 else:
164 manpage = schema.name
165
166 if version == None:
167 version = "UNKNOWN"
168
169 # Putting '\" p as the first line tells "man" that the manpage
170 # needs to be preprocessed by "pic".
171 s = r''''\" p
172 .\" -*- nroff -*-
173 .TH "%s" 5 " DB Schema %s" "Open vSwitch %s" "Open vSwitch Manual"
174 .fp 5 L CR \\" Make fixed-width font available as \\fL.
175 .de TQ
176 . br
177 . ns
178 . TP "\\$1"
179 ..
180 .de ST
181 . PP
182 . RS -0.15in
183 . I "\\$1"
184 . RE
185 ..
186 .SH NAME
187 %s \- %s database schema
188 .PP
189 ''' % (manpage, schema.version, version, text_to_nroff(manpage), schema.name)
190
191 tables = ""
192 introNodes = []
193 tableNodes = []
194 summary = []
195 for dbNode in doc.childNodes:
196 if (dbNode.nodeType == dbNode.ELEMENT_NODE
197 and dbNode.tagName == "table"):
198 tableNodes += [dbNode]
199
200 name = dbNode.attributes['name'].nodeValue
201 if dbNode.hasAttribute("title"):
202 title = dbNode.attributes['title'].nodeValue
203 else:
204 title = name + " configuration."
205 summary += [(name, title)]
206 else:
207 introNodes += [dbNode]
208
209 documented_tables = set((name for (name, title) in summary))
210 schema_tables = set(schema.tables.keys())
211 undocumented_tables = schema_tables - documented_tables
212 for table in undocumented_tables:
213 raise error.Error("undocumented table %s" % table)
214
215 s += block_xml_to_nroff(introNodes) + "\n"
216
217 s += r"""
218 .SH "TABLE SUMMARY"
219 .PP
220 The following list summarizes the purpose of each of the tables in the
221 \fB%s\fR database. Each table is described in more detail on a later
222 page.
223 .IP "Table" 1in
224 Purpose
225 """ % schema.name
226 for name, title in summary:
227 s += r"""
228 .TQ 1in
229 \fB%s\fR
230 %s
231 """ % (name, text_to_nroff(title))
232
233 if erFile:
234 s += """
235 .\\" check if in troff mode (TTY)
236 .if t \{
237 .bp
238 .SH "TABLE RELATIONSHIPS"
239 .PP
240 The following diagram shows the relationship among tables in the
241 database. Each node represents a table. Tables that are part of the
242 ``root set'' are shown with double borders. Each edge leads from the
243 table that contains it and points to the table that its value
244 represents. Edges are labeled with their column names, followed by a
245 constraint on the number of allowed values: \\fB?\\fR for zero or one,
246 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
247 represent strong references; thin lines represent weak references.
248 .RS -1in
249 """
250 erStream = open(erFile, "r")
251 for line in erStream:
252 s += line + '\n'
253 erStream.close()
254 s += ".RE\\}\n"
255
256 for node in tableNodes:
257 s += tableToNroff(schema, node) + "\n"
258 return s
259
260 def usage():
261 print """\
262 %(argv0)s: ovsdb schema documentation generator
263 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
264 usage: %(argv0)s [OPTIONS] SCHEMA XML
265 where SCHEMA is an OVSDB schema in JSON format
266 and XML is OVSDB documentation in XML format.
267
268 The following options are also available:
269 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
270 --version=VERSION use VERSION to display on document footer
271 -h, --help display this help message\
272 """ % {'argv0': argv0}
273 sys.exit(0)
274
275 if __name__ == "__main__":
276 try:
277 try:
278 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
279 ['er-diagram=',
280 'version=', 'help'])
281 except getopt.GetoptError, geo:
282 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
283 sys.exit(1)
284
285 er_diagram = None
286 version = None
287 for key, value in options:
288 if key == '--er-diagram':
289 er_diagram = value
290 elif key == '--version':
291 version = value
292 elif key in ['-h', '--help']:
293 usage()
294 else:
295 sys.exit(0)
296
297 if len(args) != 2:
298 sys.stderr.write("%s: exactly 2 non-option arguments required "
299 "(use --help for help)\n" % argv0)
300 sys.exit(1)
301
302 # XXX we should warn about undocumented tables or columns
303 s = docsToNroff(args[0], args[1], er_diagram, version)
304 for line in s.split("\n"):
305 line = line.strip()
306 if len(line):
307 print line
308
309 except error.Error, e:
310 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
311 sys.exit(1)
312
313 # Local variables:
314 # mode: python
315 # End: