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