3 # Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
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:
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 from datetime import date
21 import xml.dom.minidom
24 from ovs.db import error
27 from build.nroff import *
31 def typeAndConstraintsToNroff(column):
32 type = column.type.toEnglish(escape_nroff_literal)
33 constraints = column.type.constraintsToEnglish(escape_nroff_literal,
36 type += ", " + constraints
38 type += " (must be unique within table)"
41 def columnGroupToNroff(table, groupXml, documented_columns):
44 for node in groupXml.childNodes:
45 if (node.nodeType == node.ELEMENT_NODE
46 and node.tagName in ('column', 'group')):
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)
56 intro = block_xml_to_nroff(introNodes)
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)
73 type_ = column.type.value
75 nameNroff = "%s : %s" % (name, key)
78 typeNroff = "optional %s" % column.type.value.toEnglish(
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"
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
91 typeNroff += ", containing a %s" % type_english
93 type_.constraintsToEnglish(escape_nroff_literal,
96 typeNroff += ", %s" % constraints
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
115 raise error.Error("unknown element %s in <table>" % node.tagName)
116 return summary, intro, body
118 def tableSummaryToNroff(summary, level=0):
120 for type, name, arg in summary:
122 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
124 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
125 s += tableSummaryToNroff(arg, level + 1)
129 def tableToNroff(schema, tableXml):
130 tableName = tableXml.attributes['name'].nodeValue
131 table = schema.tables[tableName]
133 documented_columns = set()
137 summary, intro, body = columnGroupToNroff(table, tableXml,
140 s += '.SS "Summary:\n'
141 s += tableSummaryToNroff(summary)
142 s += '.SS "Details:\n'
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))
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
157 schemaDate = os.stat(schemaFile).st_mtime
158 xmlDate = os.stat(xmlFile).st_mtime
159 d = date.fromtimestamp(max(schemaDate, xmlDate))
161 if doc.hasAttribute('name'):
162 manpage = doc.attributes['name'].nodeValue
164 manpage = schema.name
169 # Putting '\" p as the first line tells "man" that the manpage
170 # needs to be preprocessed by "pic".
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.
187 %s \- %s database schema
189 ''' % (manpage, schema.version, version, text_to_nroff(manpage), schema.name)
195 for dbNode in doc.childNodes:
196 if (dbNode.nodeType == dbNode.ELEMENT_NODE
197 and dbNode.tagName == "table"):
198 tableNodes += [dbNode]
200 name = dbNode.attributes['name'].nodeValue
201 if dbNode.hasAttribute("title"):
202 title = dbNode.attributes['title'].nodeValue
204 title = name + " configuration."
205 summary += [(name, title)]
207 introNodes += [dbNode]
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)
215 s += block_xml_to_nroff(introNodes) + "\n"
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
226 for name, title in summary:
231 """ % (name, text_to_nroff(title))
235 .\\" check if in troff mode (TTY)
238 .SH "TABLE RELATIONSHIPS"
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.
250 erStream = open(erFile, "r")
251 for line in erStream:
256 for node in tableNodes:
257 s += tableToNroff(schema, node) + "\n"
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.
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}
275 if __name__ == "__main__":
278 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
281 except getopt.GetoptError, geo:
282 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
287 for key, value in options:
288 if key == '--er-diagram':
290 elif key == '--version':
292 elif key in ['-h', '--help']:
298 sys.stderr.write("%s: exactly 2 non-option arguments required "
299 "(use --help for help)\n" % argv0)
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"):
309 except error.Error, e:
310 sys.stderr.write("%s: %s\n" % (argv0, e.msg))