#! /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
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:
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
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
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:
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)
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
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"
# 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
.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 = []
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"
.TQ 1in
\fB%s\fR
%s
-""" % (name, textToNroff(title))
+""" % (name, text_to_nroff(title))
if erFile:
s += """
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}
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']:
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):