]> git.proxmox.com Git - ovs.git/blame - ovsdb/ovsdb-doc
ovsdb-doc: Add license and copyright notice.
[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):
32 type = column.type.toEnglish(escapeNroffLiteral)
746cb760
BP
33 constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
34 textToNroff)
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 = []
56 intro = blockXmlToNroff(introNodes)
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))
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
3fd8d445 75 nameNroff = "%s : %s" % (name, key)
3349f3bf
EJ
76
77 if column.type.value:
746cb760
BP
78 typeNroff = "optional %s" % column.type.value.toEnglish(
79 escapeNroffLiteral)
3349f3bf
EJ
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 = (
746cb760
BP
93 type_.constraintsToEnglish(escapeNroffLiteral,
94 textToNroff))
3349f3bf
EJ
95 if constraints:
96 typeNroff += ", %s" % constraints
f9e5e5b3 97 else:
3349f3bf 98 typeNroff = "none"
3fd8d445
BP
99 else:
100 nameNroff = name
101 typeNroff = typeAndConstraintsToNroff(column)
c4454e89
BP
102 if not column.mutable:
103 typeNroff = "immutable %s" % typeNroff
3fd8d445
BP
104 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
105 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
106 summary += [('column', nameNroff, typeNroff)]
89365653
BP
107 elif node.tagName == 'group':
108 title = node.attributes["title"].nodeValue
a826ac90
BP
109 subSummary, subIntro, subBody = columnGroupToNroff(
110 table, node, documented_columns)
89365653
BP
111 summary += [('group', title, subSummary)]
112 body += '.ST "%s:"\n' % textToNroff(title)
113 body += subIntro + subBody
114 else:
99155935 115 raise error.Error("unknown element %s in <table>" % node.tagName)
89365653
BP
116 return summary, intro, body
117
118def tableSummaryToNroff(summary, level=0):
119 s = ""
120 for type, name, arg in summary:
121 if type == 'column':
3fd8d445 122 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
89365653 123 else:
3fd8d445 124 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
89365653 125 s += tableSummaryToNroff(arg, level + 1)
3fd8d445 126 s += ".RE\n"
89365653
BP
127 return s
128
129def tableToNroff(schema, tableXml):
130 tableName = tableXml.attributes['name'].nodeValue
131 table = schema.tables[tableName]
132
a826ac90 133 documented_columns = set()
89365653 134 s = """.bp
3fd8d445 135.SH "%s TABLE"
89365653 136""" % tableName
a826ac90
BP
137 summary, intro, body = columnGroupToNroff(table, tableXml,
138 documented_columns)
89365653 139 s += intro
3fd8d445 140 s += '.SS "Summary:\n'
89365653 141 s += tableSummaryToNroff(summary)
3fd8d445 142 s += '.SS "Details:\n'
89365653 143 s += body
a826ac90
BP
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
89365653
BP
151 return s
152
57ba0a77 153def docsToNroff(schemaFile, xmlFile, erFile, version=None):
99155935 154 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
89365653 155 doc = xml.dom.minidom.parse(xmlFile).documentElement
c5c7c7c5 156
89365653
BP
157 schemaDate = os.stat(schemaFile).st_mtime
158 xmlDate = os.stat(xmlFile).st_mtime
159 d = date.fromtimestamp(max(schemaDate, xmlDate))
c5c7c7c5 160
57ba0a77
BP
161 if doc.hasAttribute('name'):
162 manpage = doc.attributes['name'].nodeValue
163 else:
164 manpage = schema.name
89365653 165
54c18b66
GS
166 if version == None:
167 version = "UNKNOWN"
168
3fd8d445
BP
169 # Putting '\" p as the first line tells "man" that the manpage
170 # needs to be preprocessed by "pic".
171 s = r''''\" p
89365653 172.\" -*- nroff -*-
7b8c46c8
BP
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.
89365653
BP
175.de TQ
176. br
177. ns
3fd8d445 178. TP "\\$1"
89365653
BP
179..
180.de ST
181. PP
182. RS -0.15in
183. I "\\$1"
184. RE
185..
9f88469c
BP
186.SH NAME
187%s \- %s database schema
e7c465fd 188.PP
57ba0a77 189''' % (manpage, schema.version, version, textToNroff(manpage), schema.name)
89365653
BP
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
a826ac90
BP
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
89365653 215 s += blockXmlToNroff(introNodes) + "\n"
3fd8d445
BP
216
217 s += r"""
218.SH "TABLE SUMMARY"
219.PP
220The 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
222page.
223.IP "Table" 1in
224Purpose
89365653
BP
225""" % schema.name
226 for name, title in summary:
3fd8d445
BP
227 s += r"""
228.TQ 1in
229\fB%s\fR
230%s
231""" % (name, textToNroff(title))
f8d739a9
BP
232
233 if erFile:
234 s += """
186ef5c1
CW
235.\\" check if in troff mode (TTY)
236.if t \{
3fd8d445 237.bp
f8d739a9
BP
238.SH "TABLE RELATIONSHIPS"
239.PP
240The following diagram shows the relationship among tables in the
c5f341ab
BP
241database. Each node represents a table. Tables that are part of the
242``root set'' are shown with double borders. Each edge leads from the
f8d739a9 243table that contains it and points to the table that its value
d5a59e7e
BP
244represents. Edges are labeled with their column names, followed by a
245constraint 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
c5f341ab 247represent strong references; thin lines represent weak references.
f8d739a9
BP
248.RS -1in
249"""
250 erStream = open(erFile, "r")
251 for line in erStream:
252 s += line + '\n'
253 erStream.close()
c182a514 254 s += ".RE\\}\n"
f8d739a9 255
89365653
BP
256 for node in tableNodes:
257 s += tableToNroff(schema, node) + "\n"
258 return s
259
260def usage():
261 print """\
262%(argv0)s: ovsdb schema documentation generator
263Prints documentation for an OVSDB schema as an nroff-formatted manpage.
264usage: %(argv0)s [OPTIONS] SCHEMA XML
265where SCHEMA is an OVSDB schema in JSON format
266 and XML is OVSDB documentation in XML format.
267
268The following options are also available:
f8d739a9 269 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
54c18b66
GS
270 --version=VERSION use VERSION to display on document footer
271 -h, --help display this help message\
89365653
BP
272""" % {'argv0': argv0}
273 sys.exit(0)
274
275if __name__ == "__main__":
276 try:
277 try:
278 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
57ba0a77 279 ['er-diagram=',
54c18b66 280 'version=', 'help'])
89365653
BP
281 except getopt.GetoptError, geo:
282 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
283 sys.exit(1)
284
f8d739a9 285 er_diagram = None
54c18b66 286 version = None
89365653 287 for key, value in options:
f8d739a9
BP
288 if key == '--er-diagram':
289 er_diagram = value
54c18b66
GS
290 elif key == '--version':
291 version = value
89365653
BP
292 elif key in ['-h', '--help']:
293 usage()
89365653
BP
294 else:
295 sys.exit(0)
c5c7c7c5 296
89365653
BP
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)
c5c7c7c5 301
89365653 302 # XXX we should warn about undocumented tables or columns
57ba0a77 303 s = docsToNroff(args[0], args[1], er_diagram, version)
89365653
BP
304 for line in s.split("\n"):
305 line = line.strip()
306 if len(line):
307 print line
c5c7c7c5 308
99155935 309 except error.Error, e:
89365653
BP
310 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
311 sys.exit(1)
312
313# Local variables:
314# mode: python
315# End: