netdev-dpdk: fix mbuf leaks
[cascardo/ovs.git] / ovsdb / ovsdb-doc
index 6200915..5cf26ee 100755 (executable)
@@ -1,9 +1,22 @@
 #! /usr/bin/python
 
+# Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 from datetime import date
 import getopt
 import os
-import re
 import sys
 import xml.dom.minidom
 
@@ -11,148 +24,21 @@ import ovs.json
 from ovs.db import error
 import ovs.db.schema
 
-argv0 = sys.argv[0]
-
-def textToNroff(s, font=r'\fR'):
-    def escape(match):
-        c = match.group(0)
-        if c.startswith('-'):
-            if c != '-' or font == r'\fB':
-                return '\\' + c
-            else:
-                return '-'
-        if c == '\\':
-            return r'\e'
-        elif c == '"':
-            return r'\(dq'
-        elif c == "'":
-            return r'\(cq'
-        else:
-            raise error.Error("bad escape")
-
-    # Escape - \ " ' as needed by nroff.
-    s = re.sub('(-[0-9]|[-"\'\\\\])', escape, s)
-    if s.startswith('.'):
-        s = '\\' + s
-    return s
+from build.nroff import *
 
-def escapeNroffLiteral(s):
-    return r'\fB%s\fR' % textToNroff(s, r'\fB')
-
-def inlineXmlToNroff(node, font):
-    if node.nodeType == node.TEXT_NODE:
-        return textToNroff(node.data, font)
-    elif node.nodeType == node.ELEMENT_NODE:
-        if node.tagName in ['code', 'em', 'option']:
-            s = r'\fB'
-            for child in node.childNodes:
-                s += inlineXmlToNroff(child, r'\fB')
-            return s + font
-        elif node.tagName == 'ref':
-            s = r'\fB'
-            if node.hasAttribute('column'):
-                s += node.attributes['column'].nodeValue
-                if node.hasAttribute('key'):
-                    s += ':' + node.attributes['key'].nodeValue
-            elif node.hasAttribute('table'):
-                s += node.attributes['table'].nodeValue
-            elif node.hasAttribute('group'):
-                s += node.attributes['group'].nodeValue
-            else:
-                raise error.Error("'ref' lacks required attributes: %s" % node.attributes.keys())
-            return s + font
-        elif node.tagName == 'var':
-            s = r'\fI'
-            for child in node.childNodes:
-                s += inlineXmlToNroff(child, r'\fI')
-            return s + font
-        else:
-            raise error.Error("element <%s> unknown or invalid here" % node.tagName)
-    else:
-        raise error.Error("unknown node %s in inline xml" % node)
-
-def blockXmlToNroff(nodes, para='.PP'):
-    s = ''
-    for node in nodes:
-        if node.nodeType == node.TEXT_NODE:
-            s += textToNroff(node.data)
-            s = s.lstrip()
-        elif node.nodeType == node.ELEMENT_NODE:
-            if node.tagName in ['ul', 'ol']:
-                if s != "":
-                    s += "\n"
-                s += ".RS\n"
-                i = 0
-                for liNode in node.childNodes:
-                    if (liNode.nodeType == node.ELEMENT_NODE
-                        and liNode.tagName == 'li'):
-                        i += 1
-                        if node.tagName == 'ul':
-                            s += ".IP \\(bu\n"
-                        else:
-                            s += ".IP %d. .25in\n" % i
-                        s += blockXmlToNroff(liNode.childNodes, ".IP")
-                    elif (liNode.nodeType != node.TEXT_NODE
-                          or not liNode.data.isspace()):
-                        raise error.Error("<%s> element may only have <li> children" % node.tagName)
-                s += ".RE\n"
-            elif node.tagName == 'dl':
-                if s != "":
-                    s += "\n"
-                s += ".RS\n"
-                prev = "dd"
-                for liNode in node.childNodes:
-                    if (liNode.nodeType == node.ELEMENT_NODE
-                        and liNode.tagName == 'dt'):
-                        if prev == 'dd':
-                            s += '.TP\n'
-                        else:
-                            s += '.TQ\n'
-                        prev = 'dt'
-                    elif (liNode.nodeType == node.ELEMENT_NODE
-                          and liNode.tagName == 'dd'):
-                        if prev == 'dd':
-                            s += '.IP\n'
-                        prev = 'dd'
-                    elif (liNode.nodeType != node.TEXT_NODE
-                          or not liNode.data.isspace()):
-                        raise error.Error("<dl> element may only have <dt> and <dd> children")
-                    s += blockXmlToNroff(liNode.childNodes, ".IP")
-                s += ".RE\n"
-            elif node.tagName == 'p':
-                if s != "":
-                    if not s.endswith("\n"):
-                        s += "\n"
-                    s += para + "\n"
-                s += blockXmlToNroff(node.childNodes, para)
-            elif node.tagName in ('h1', 'h2', 'h3'):
-                if s != "":
-                    if not s.endswith("\n"):
-                        s += "\n"
-                nroffTag = {'h1': 'SH', 'h2': 'SS', 'h3': 'ST'}[node.tagName]
-                s += ".%s " % nroffTag
-                for child_node in node.childNodes:
-                    s += inlineXmlToNroff(child_node, r'\fR')
-                s += "\n"
-            else:
-                s += inlineXmlToNroff(node, r'\fR')
-        else:
-            raise error.Error("unknown node %s in block xml" % node)
-    if s != "" and not s.endswith('\n'):
-        s += '\n'
-    return s
+argv0 = sys.argv[0]
 
 def typeAndConstraintsToNroff(column):
-    type = column.type.toEnglish(escapeNroffLiteral)
-    constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
-                                                   textToNroff)
+    type = column.type.toEnglish(escape_nroff_literal)
+    constraints = column.type.constraintsToEnglish(escape_nroff_literal,
+                                                   text_to_nroff)
     if constraints:
         type += ", " + constraints
     if column.unique:
         type += " (must be unique within table)"
     return type
 
-def columnGroupToNroff(table, groupXml):
+def columnGroupToNroff(table, groupXml, documented_columns):
     introNodes = []
     columnNodes = []
     for node in groupXml.childNodes:
@@ -167,11 +53,12 @@ def columnGroupToNroff(table, groupXml):
             introNodes += [node]
 
     summary = []
-    intro = blockXmlToNroff(introNodes)
+    intro = block_xml_to_nroff(introNodes)
     body = ''
     for node in columnNodes:
         if node.tagName == 'column':
             name = node.attributes['name'].nodeValue
+            documented_columns.add(name)
             column = table.columns[name]
             if node.hasAttribute('key'):
                 key = node.attributes['key'].nodeValue
@@ -189,7 +76,7 @@ def columnGroupToNroff(table, groupXml):
 
                 if column.type.value:
                     typeNroff = "optional %s" % column.type.value.toEnglish(
-                        escapeNroffLiteral)
+                        escape_nroff_literal)
                     if (column.type.value.type == ovs.db.types.StringType and
                         type_.type == ovs.db.types.BooleanType):
                         # This is a little more explicit and helpful than
@@ -203,8 +90,8 @@ def columnGroupToNroff(table, groupXml):
                             else:
                                 typeNroff += ", containing a %s" % type_english
                         constraints = (
-                            type_.constraintsToEnglish(escapeNroffLiteral,
-                                                       textToNroff))
+                            type_.constraintsToEnglish(escape_nroff_literal,
+                                                       text_to_nroff))
                         if constraints:
                             typeNroff += ", %s" % constraints
                 else:
@@ -215,13 +102,14 @@ def columnGroupToNroff(table, groupXml):
             if not column.mutable:
                 typeNroff = "immutable %s" % typeNroff
             body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
-            body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
+            body += block_xml_to_nroff(node.childNodes, '.IP') + "\n"
             summary += [('column', nameNroff, typeNroff)]
         elif node.tagName == 'group':
             title = node.attributes["title"].nodeValue
-            subSummary, subIntro, subBody = columnGroupToNroff(table, node)
+            subSummary, subIntro, subBody = columnGroupToNroff(
+                table, node, documented_columns)
             summary += [('group', title, subSummary)]
-            body += '.ST "%s:"\n' % textToNroff(title)
+            body += '.ST "%s:"\n' % text_to_nroff(title)
             body += subIntro + subBody
         else:
             raise error.Error("unknown element %s in <table>" % node.tagName)
@@ -242,18 +130,27 @@ def tableToNroff(schema, tableXml):
     tableName = tableXml.attributes['name'].nodeValue
     table = schema.tables[tableName]
 
+    documented_columns = set()
     s = """.bp
 .SH "%s TABLE"
 """ % tableName
-    summary, intro, body = columnGroupToNroff(table, tableXml)
+    summary, intro, body = columnGroupToNroff(table, tableXml,
+                                              documented_columns)
     s += intro
     s += '.SS "Summary:\n'
     s += tableSummaryToNroff(summary)
     s += '.SS "Details:\n'
     s += body
+
+    schema_columns = set(table.columns.keys())
+    undocumented_columns = schema_columns - documented_columns
+    for column in undocumented_columns:
+        raise error.Error("table %s has undocumented column %s"
+                          % (tableName, column))
+
     return s
 
-def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
+def docsToNroff(schemaFile, xmlFile, erFile, version=None):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
     doc = xml.dom.minidom.parse(xmlFile).documentElement
 
@@ -261,8 +158,10 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
     xmlDate = os.stat(xmlFile).st_mtime
     d = date.fromtimestamp(max(schemaDate, xmlDate))
 
-    if title == None:
-        title = schema.name
+    if doc.hasAttribute('name'):
+        manpage = doc.attributes['name'].nodeValue
+    else:
+        manpage = schema.name
 
     if version == None:
         version = "UNKNOWN"
@@ -270,8 +169,9 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
     # Putting '\" p as the first line tells "man" that the manpage
     # needs to be preprocessed by "pic".
     s = r''''\" p
-.TH "%s" 5 " DB Schema %s" "Open vSwitch %s" "Open vSwitch Manual"
 .\" -*- nroff -*-
+.TH "%s" 5 " DB Schema %s" "Open vSwitch %s" "Open vSwitch Manual"
+.fp 5 L CR              \\" Make fixed-width font available as \\fL.
 .de TQ
 .  br
 .  ns
@@ -286,7 +186,7 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
 .SH NAME
 %s \- %s database schema
 .PP
-''' % (title, schema.version, version, textToNroff(schema.name), schema.name)
+''' % (manpage, schema.version, version, text_to_nroff(manpage), schema.name)
 
     tables = ""
     introNodes = []
@@ -306,7 +206,13 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None, version=None):
         else:
             introNodes += [dbNode]
 
-    s += blockXmlToNroff(introNodes) + "\n"
+    documented_tables = set((name for (name, title) in summary))
+    schema_tables = set(schema.tables.keys())
+    undocumented_tables = schema_tables - documented_tables
+    for table in undocumented_tables:
+        raise error.Error("undocumented table %s" % table)
+
+    s += block_xml_to_nroff(introNodes) + "\n"
 
     s += r"""
 .SH "TABLE SUMMARY"
@@ -322,7 +228,7 @@ Purpose
 .TQ 1in
 \fB%s\fR
 %s
-""" % (name, textToNroff(title))
+""" % (name, text_to_nroff(title))
 
     if erFile:
         s += """
@@ -361,7 +267,6 @@ where SCHEMA is an OVSDB schema in JSON format
 
 The following options are also available:
   --er-diagram=DIAGRAM.PIC    include E-R diagram from DIAGRAM.PIC
-  --title=TITLE               use TITLE as title instead of schema name
   --version=VERSION           use VERSION to display on document footer
   -h, --help                  display this help message\
 """ % {'argv0': argv0}
@@ -371,20 +276,17 @@ if __name__ == "__main__":
     try:
         try:
             options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
-                                              ['er-diagram=', 'title=',
+                                              ['er-diagram=',
                                                'version=', 'help'])
         except getopt.GetoptError, geo:
             sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
             sys.exit(1)
 
         er_diagram = None
-        title = None
         version = None
         for key, value in options:
             if key == '--er-diagram':
                 er_diagram = value
-            elif key == '--title':
-                title = value
             elif key == '--version':
                 version = value
             elif key in ['-h', '--help']:
@@ -398,7 +300,7 @@ if __name__ == "__main__":
             sys.exit(1)
 
         # XXX we should warn about undocumented tables or columns
-        s = docsToNroff(args[0], args[1], er_diagram, title, version)
+        s = docsToNroff(args[0], args[1], er_diagram, version)
         for line in s.split("\n"):
             line = line.strip()
             if len(line):