netdev-dpdk: fix mbuf leaks
[cascardo/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: