better dependencies for external links
[sliver-openvswitch.git] / ovsdb / ovsdb-doc.in
index 9e0a318..aa4fae2 100755 (executable)
@@ -16,9 +16,9 @@ argv0 = sys.argv[0]
 def textToNroff(s, font=r'\fR'):
     def escape(match):
         c = match.group(0)
 def textToNroff(s, font=r'\fR'):
     def escape(match):
         c = match.group(0)
-        if c == '-':
-            if font == r'\fB':
-                return r'\-'
+        if c.startswith('-'):
+            if c != '-' or font == r'\fB':
+                return '\\' + c
             else:
                 return '-'
         if c == '\\':
             else:
                 return '-'
         if c == '\\':
@@ -31,7 +31,7 @@ def textToNroff(s, font=r'\fR'):
             raise error.Error("bad escape")
 
     # Escape - \ " ' as needed by nroff.
             raise error.Error("bad escape")
 
     # Escape - \ " ' as needed by nroff.
-    s = re.sub('([-"\'\\\\])', escape, s)
+    s = re.sub('(-[0-9]|[-"\'\\\\])', escape, s)
     if s.startswith('.'):
         s = '\\' + s
     return s
     if s.startswith('.'):
         s = '\\' + s
     return s
@@ -52,12 +52,14 @@ def inlineXmlToNroff(node, font):
             s = r'\fB'
             if node.hasAttribute('column'):
                 s += node.attributes['column'].nodeValue
             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:
             elif node.hasAttribute('table'):
                 s += node.attributes['table'].nodeValue
             elif node.hasAttribute('group'):
                 s += node.attributes['group'].nodeValue
             else:
-                raise error.Error("'ref' lacks column and table attributes")
+                raise error.Error("'ref' lacks required attributes: %s" % node.attributes.keys())
             return s + font
         elif node.tagName == 'var':
             s = r'\fI'
             return s + font
         elif node.tagName == 'var':
             s = r'\fI'
@@ -86,7 +88,7 @@ def blockXmlToNroff(nodes, para='.PP'):
                         and liNode.tagName == 'li'):
                         i += 1
                         if node.tagName == 'ul':
                         and liNode.tagName == 'li'):
                         i += 1
                         if node.tagName == 'ul':
-                            s += ".IP \\bu\n"
+                            s += ".IP \\(bu\n"
                         else:
                             s += ".IP %d. .25in\n" % i
                         s += blockXmlToNroff(liNode.childNodes, ".IP")
                         else:
                             s += ".IP %d. .25in\n" % i
                         s += blockXmlToNroff(liNode.childNodes, ".IP")
@@ -123,6 +125,15 @@ def blockXmlToNroff(nodes, para='.PP'):
                         s += "\n"
                     s += para + "\n"
                 s += blockXmlToNroff(node.childNodes, para)
                         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:
             else:
                 s += inlineXmlToNroff(node, r'\fR')
         else:
@@ -133,17 +144,14 @@ def blockXmlToNroff(nodes, para='.PP'):
 
 def typeAndConstraintsToNroff(column):
     type = column.type.toEnglish(escapeNroffLiteral)
 
 def typeAndConstraintsToNroff(column):
     type = column.type.toEnglish(escapeNroffLiteral)
-    constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
+    constraints = column.type.constraintsToEnglish(escapeNroffLiteral,
+                                                   textToNroff)
     if constraints:
         type += ", " + constraints
     if constraints:
         type += ", " + constraints
+    if column.unique:
+        type += " (must be unique within table)"
     return type
 
     return type
 
-def columnToNroff(columnName, column, node):
-    type = typeAndConstraintsToNroff(column)
-    s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
-    s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
-    return s
-
 def columnGroupToNroff(table, groupXml):
     introNodes = []
     columnNodes = []
 def columnGroupToNroff(table, groupXml):
     introNodes = []
     columnNodes = []
@@ -152,6 +160,10 @@ def columnGroupToNroff(table, groupXml):
             and node.tagName in ('column', 'group')):
             columnNodes += [node]
         else:
             and node.tagName in ('column', 'group')):
             columnNodes += [node]
         else:
+            if (columnNodes
+                and not (node.nodeType == node.TEXT_NODE
+                         and node.data.isspace())):
+                raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
             introNodes += [node]
 
     summary = []
             introNodes += [node]
 
     summary = []
@@ -159,10 +171,50 @@ def columnGroupToNroff(table, groupXml):
     body = ''
     for node in columnNodes:
         if node.tagName == 'column':
     body = ''
     for node in columnNodes:
         if node.tagName == 'column':
-            columnName = node.attributes['name'].nodeValue
-            column = table.columns[columnName]
-            body += columnToNroff(columnName, column, node)
-            summary += [('column', columnName, column)]
+            name = node.attributes['name'].nodeValue
+            column = table.columns[name]
+            if node.hasAttribute('key'):
+                key = node.attributes['key'].nodeValue
+                if node.hasAttribute('type'):
+                    type_string = node.attributes['type'].nodeValue
+                    type_json = ovs.json.from_string(str(type_string))
+                    if type(type_json) in (str, unicode):
+                        raise error.Error("%s %s:%s has invalid 'type': %s" 
+                                          % (table.name, name, key, type_json))
+                    type_ = ovs.db.types.BaseType.from_json(type_json)
+                else:
+                    type_ = column.type.value
+
+                nameNroff = "%s : %s" % (name, key)
+
+                if column.type.value:
+                    typeNroff = "optional %s" % column.type.value.toEnglish(
+                        escapeNroffLiteral)
+                    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
+                        # "containing a boolean"
+                        typeNroff += r", either \fBtrue\fR or \fBfalse\fR"
+                    else:
+                        if type_.type != column.type.value.type:
+                            type_english = type_.toEnglish()
+                            if type_english[0] in 'aeiou':
+                                typeNroff += ", containing an %s" % type_english
+                            else:
+                                typeNroff += ", containing a %s" % type_english
+                        constraints = (
+                            type_.constraintsToEnglish(escapeNroffLiteral,
+                                                       textToNroff))
+                        if constraints:
+                            typeNroff += ", %s" % constraints
+                else:
+                    typeNroff = "none"
+            else:
+                nameNroff = name
+                typeNroff = typeAndConstraintsToNroff(column)
+            body += '.IP "\\fB%s\\fR: %s"\n' % (nameNroff, typeNroff)
+            body += blockXmlToNroff(node.childNodes, '.IP') + "\n"
+            summary += [('column', nameNroff, typeNroff)]
         elif node.tagName == 'group':
             title = node.attributes["title"].nodeValue
             subSummary, subIntro, subBody = columnGroupToNroff(table, node)
         elif node.tagName == 'group':
             title = node.attributes["title"].nodeValue
             subSummary, subIntro, subBody = columnGroupToNroff(table, node)
@@ -177,19 +229,11 @@ def tableSummaryToNroff(summary, level=0):
     s = ""
     for type, name, arg in summary:
         if type == 'column':
     s = ""
     for type, name, arg in summary:
         if type == 'column':
-            
-            s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
-                r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
+            s += ".TQ %.2fin\n\\fB%s\\fR\n%s\n" % (3 - level * .25, name, arg)
         else:
         else:
-            if s != "":
-                s += "_\n"
-            s += """.T&
-li | s
-l | l.
-%s%s
-_
-""" % (r'\ \ ' * level, name)
+            s += ".TQ .25in\n\\fI%s:\\fR\n.RS .25in\n" % name
             s += tableSummaryToNroff(arg, level + 1)
             s += tableSummaryToNroff(arg, level + 1)
+            s += ".RE\n"
     return s
 
 def tableToNroff(schema, tableXml):
     return s
 
 def tableToNroff(schema, tableXml):
@@ -197,42 +241,31 @@ def tableToNroff(schema, tableXml):
     table = schema.tables[tableName]
 
     s = """.bp
     table = schema.tables[tableName]
 
     s = """.bp
-.SS "%s Table"
+.SH "%s TABLE"
 """ % tableName
     summary, intro, body = columnGroupToNroff(table, tableXml)
     s += intro
 """ % tableName
     summary, intro, body = columnGroupToNroff(table, tableXml)
     s += intro
-
-    s += r"""
-.sp
-.ce 1
-\fB%s\fR Table Columns:
-.TS
-center box;
-l | l.
-Column Type
-=
-""" % tableName
+    s += '.SS "Summary:\n'
     s += tableSummaryToNroff(summary)
     s += tableSummaryToNroff(summary)
-    s += ".TE\n"
-
+    s += '.SS "Details:\n'
     s += body
     return s
 
 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
     doc = xml.dom.minidom.parse(xmlFile).documentElement
     s += body
     return s
 
 def docsToNroff(schemaFile, xmlFile, erFile, title=None):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
     doc = xml.dom.minidom.parse(xmlFile).documentElement
-    
+
     schemaDate = os.stat(schemaFile).st_mtime
     xmlDate = os.stat(xmlFile).st_mtime
     d = date.fromtimestamp(max(schemaDate, xmlDate))
     schemaDate = os.stat(schemaFile).st_mtime
     xmlDate = os.stat(xmlFile).st_mtime
     d = date.fromtimestamp(max(schemaDate, xmlDate))
-    
+
     if title == None:
         title = schema.name
 
     if title == None:
         title = schema.name
 
-    # Putting '\" pt as the first line tells "man" that the manpage
-    # needs to be preprocessed by "pic" and "tbl".
-    s = r''''\" pt
-.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
+    # Putting '\" p as the first line tells "man" that the manpage
+    # needs to be preprocessed by "pic".
+    s = r''''\" p
+.TH @VERSION@ 5 "%s" "Open vSwitch" "Open vSwitch Manual"
 .\" -*- nroff -*-
 .de TQ
 .  br
 .\" -*- nroff -*-
 .de TQ
 .  br
@@ -245,9 +278,10 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None):
 .  I "\\$1"
 .  RE
 ..
 .  I "\\$1"
 .  RE
 ..
-''' % (title, d.strftime("%B %Y"))
-
-    s += '.SH "%s DATABASE"\n' % schema.name
+.SH NAME
+%s \- %s database schema
+.PP
+''' % (title, textToNroff(title), schema.name)
 
     tables = ""
     introNodes = []
 
     tables = ""
     introNodes = []
@@ -268,38 +302,45 @@ def docsToNroff(schemaFile, xmlFile, erFile, title=None):
             introNodes += [dbNode]
 
     s += blockXmlToNroff(introNodes) + "\n"
             introNodes += [dbNode]
 
     s += blockXmlToNroff(introNodes) + "\n"
-    tableSummary = r"""
-.sp
-.ce 1
-\fB%s\fR Database Tables:
-.TS
-center box;
-l | l
-lb | l.
-Table  Purpose
-=
+
+    s += r"""
+.SH "TABLE SUMMARY"
+.PP
+The following list summarizes the purpose of each of the tables in the
+\fB%s\fR database.  Each table is described in more detail on a later
+page.
+.IP "Table" 1in
+Purpose
 """ % schema.name
     for name, title in summary:
 """ % schema.name
     for name, title in summary:
-        tableSummary += "%s\t%s\n" % (name, textToNroff(title))
-    tableSummary += '.TE\n'
-    s += tableSummary
+        s += r"""
+.TQ 1in
+\fB%s\fR
+%s
+""" % (name, textToNroff(title))
 
     if erFile:
         s += """
 
     if erFile:
         s += """
-.sp 1
+.\\" check if in troff mode (TTY)
+.if t \{
+.bp
 .SH "TABLE RELATIONSHIPS"
 .PP
 The following diagram shows the relationship among tables in the
 .SH "TABLE RELATIONSHIPS"
 .PP
 The following diagram shows the relationship among tables in the
-database.  Each node represents a table.  Each edge leads from the
+database.  Each node represents a table.  Tables that are part of the
+``root set'' are shown with double borders.  Each edge leads from the
 table that contains it and points to the table that its value
 table that contains it and points to the table that its value
-represents.  Edges are labeled with their column names.
+represents.  Edges are labeled with their column names, followed by a
+constraint on the number of allowed values: \\fB?\\fR for zero or one,
+\\fB*\\fR for zero or more, \\fB+\\fR for one or more.  Thick lines
+represent strong references; thin lines represent weak references.
 .RS -1in
 """
         erStream = open(erFile, "r")
         for line in erStream:
             s += line + '\n'
         erStream.close()
 .RS -1in
 """
         erStream = open(erFile, "r")
         for line in erStream:
             s += line + '\n'
         erStream.close()
-        s += ".RE\n"
+        s += ".RE\\}\n"
 
     for node in tableNodes:
         s += tableToNroff(schema, node) + "\n"
 
     for node in tableNodes:
         s += tableToNroff(schema, node) + "\n"
@@ -344,19 +385,19 @@ if __name__ == "__main__":
                 print "ovsdb-doc (Open vSwitch) @VERSION@"
             else:
                 sys.exit(0)
                 print "ovsdb-doc (Open vSwitch) @VERSION@"
             else:
                 sys.exit(0)
-            
+
         if len(args) != 2:
             sys.stderr.write("%s: exactly 2 non-option arguments required "
                              "(use --help for help)\n" % argv0)
             sys.exit(1)
         if len(args) != 2:
             sys.stderr.write("%s: exactly 2 non-option arguments required "
                              "(use --help for help)\n" % argv0)
             sys.exit(1)
-        
+
         # XXX we should warn about undocumented tables or columns
         s = docsToNroff(args[0], args[1], er_diagram)
         for line in s.split("\n"):
             line = line.strip()
             if len(line):
                 print line
         # XXX we should warn about undocumented tables or columns
         s = docsToNroff(args[0], args[1], er_diagram)
         for line in s.split("\n"):
             line = line.strip()
             if len(line):
                 print line
-            
+
     except error.Error, e:
         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
         sys.exit(1)
     except error.Error, e:
         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
         sys.exit(1)