3 from datetime import date
10 from ovs.db import error
13 from build.nroff import *
17 def typeAndConstraintsToNroff(column):
18 type = column.type.toEnglish(escapeNroffLiteral)
19 constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
22 type += ", " + constraints
24 type += " (must be unique within table)"
27 def columnGroupToNroff(table, groupXml, documented_columns):
30 for node in groupXml.childNodes:
31 if (node.nodeType == node.ELEMENT_NODE
32 and node.tagName in ('column', 'group')):
36 and not (node.nodeType == node.TEXT_NODE
37 and node.data.isspace())):
38 raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
42 intro = blockXmlToNroff(introNodes)
44 for node in columnNodes:
45 if node.tagName == 'column':
46 name = node.attributes['name'].nodeValue
47 documented_columns.add(name)
48 column = table.columns[name]
49 if node.hasAttribute('key'):
50 key = node.attributes['key'].nodeValue
51 if node.hasAttribute('type'):
52 type_string = node.attributes['type'].nodeValue
53 type_json = ovs.json.from_string(str(type_string))
54 if type(type_json) in (str, unicode):
55 raise error.Error("%s %s:%s has invalid 'type': %s"
56 % (table.name, name, key, type_json))
57 type_ = ovs.db.types.BaseType.from_json(type_json)
59 type_ = column.type.value
61 nameNroff = "%s : %s" % (name, key)
64 typeNroff = "optional %s" % column.type.value.toEnglish(
66 if (column.type.value.type == ovs.db.types.StringType and
67 type_.type == ovs.db.types.BooleanType):
68 # This is a little more explicit and helpful than
69 # "containing a boolean"
70 typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
72 if type_.type != column.type.value.type:
73 type_english = type_.toEnglish()
74 if type_english[0] in 'aeiou':
75 typeNroff += ", containing an %s" % type_english
77 typeNroff += ", containing a %s" % type_english
79 type_.constraintsToEnglish(escapeNroffLiteral,
82 typeNroff += ", %s" % constraints
87 typeNroff = typeAndConstraintsToNroff(column)
88 if not column.mutable:
89 typeNroff = "immutable %s" % typeNroff
90 body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
91 body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
92 summary += [('column', nameNroff, typeNroff)]
93 elif node.tagName == 'group':
94 title = node.attributes["title"].nodeValue
95 subSummary, subIntro, subBody = columnGroupToNroff(
96 table, node, documented_columns)
97 summary += [('group', title, subSummary)]
98 body += '.ST "%s:"\n' % textToNroff(title)
99 body += subIntro + subBody
101 raise error.Error("unknown element %s in <table>" % node.tagName)
102 return summary, intro, body
104 def tableSummaryToNroff(summary, level=0):
106 for type, name, arg in summary:
108 s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
110 s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
111 s += tableSummaryToNroff(arg, level + 1)
115 def tableToNroff(schema, tableXml):
116 tableName = tableXml.attributes['name'].nodeValue
117 table = schema.tables[tableName]
119 documented_columns = set()
123 summary, intro, body = columnGroupToNroff(table, tableXml,
126 s += '.SS "Summary:\n'
127 s += tableSummaryToNroff(summary)
128 s += '.SS "Details:\n'
131 schema_columns = set(table.columns.keys())
132 undocumented_columns = schema_columns - documented_columns
133 for column in undocumented_columns:
134 raise error.Error("table %s has undocumented column %s"
135 % (tableName, column))
139 def docsToNroff(schemaFile, xmlFile, erFile, version=None):
140 schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
141 doc = xml.dom.minidom.parse(xmlFile).documentElement
143 schemaDate = os.stat(schemaFile).st_mtime
144 xmlDate = os.stat(xmlFile).st_mtime
145 d = date.fromtimestamp(max(schemaDate, xmlDate))
147 if doc.hasAttribute('name'):
148 manpage = doc.attributes['name'].nodeValue
150 manpage = schema.name
155 # Putting '\" p as the first line tells "man" that the manpage
156 # needs to be preprocessed by "pic".
159 .TH "%s" 5 " DB Schema %s" "Open vSwitch %s" "Open vSwitch Manual"
160 .fp 5 L CR \\" Make fixed-width font available as \\fL.
173 %s \- %s database schema
175 ''' % (manpage, schema.version, version, textToNroff(manpage), schema.name)
181 for dbNode in doc.childNodes:
182 if (dbNode.nodeType == dbNode.ELEMENT_NODE
183 and dbNode.tagName == "table"):
184 tableNodes += [dbNode]
186 name = dbNode.attributes['name'].nodeValue
187 if dbNode.hasAttribute("title"):
188 title = dbNode.attributes['title'].nodeValue
190 title = name + " configuration."
191 summary += [(name, title)]
193 introNodes += [dbNode]
195 documented_tables = set((name for (name, title) in summary))
196 schema_tables = set(schema.tables.keys())
197 undocumented_tables = schema_tables - documented_tables
198 for table in undocumented_tables:
199 raise error.Error("undocumented table %s" % table)
201 s += blockXmlToNroff(introNodes) + "\n"
206 The following list summarizes the purpose of each of the tables in the
207 \fB%s\fR database. Each table is described in more detail on a later
212 for name, title in summary:
217 """ % (name, textToNroff(title))
221 .\\" check if in troff mode (TTY)
224 .SH "TABLE RELATIONSHIPS"
226 The following diagram shows the relationship among tables in the
227 database. Each node represents a table. Tables that are part of the
228 ``root set'' are shown with double borders. Each edge leads from the
229 table that contains it and points to the table that its value
230 represents. Edges are labeled with their column names, followed by a
231 constraint on the number of allowed values: \\fB?\\fR for zero or one,
232 \\fB*\\fR for zero or more, \\fB+\\fR for one or more. Thick lines
233 represent strong references; thin lines represent weak references.
236 erStream = open(erFile, "r")
237 for line in erStream:
242 for node in tableNodes:
243 s += tableToNroff(schema, node) + "\n"
248 %(argv0)s: ovsdb schema documentation generator
249 Prints documentation for an OVSDB schema as an nroff-formatted manpage.
250 usage: %(argv0)s [OPTIONS] SCHEMA XML
251 where SCHEMA is an OVSDB schema in JSON format
252 and XML is OVSDB documentation in XML format.
254 The following options are also available:
255 --er-diagram=DIAGRAM.PIC include E-R diagram from DIAGRAM.PIC
256 --version=VERSION use VERSION to display on document footer
257 -h, --help display this help message\
258 """ % {'argv0': argv0}
261 if __name__ == "__main__":
264 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
267 except getopt.GetoptError, geo:
268 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
273 for key, value in options:
274 if key == '--er-diagram':
276 elif key == '--version':
278 elif key in ['-h', '--help']:
284 sys.stderr.write("%s: exactly 2 non-option arguments required "
285 "(use --help for help)\n" % argv0)
288 # XXX we should warn about undocumented tables or columns
289 s = docsToNroff(args[0], args[1], er_diagram, version)
290 for line in s.split("\n"):
295 except error.Error, e:
296 sys.stderr.write("%s: %s\n" % (argv0, e.msg))