]> git.proxmox.com Git - mirror_ovs.git/blame - ovsdb/ovsdb-doc.in
ovsdb-doc: Distinguish hyphens and minus signs in nroff output.
[mirror_ovs.git] / ovsdb / ovsdb-doc.in
CommitLineData
89365653
BP
1#! @PYTHON@
2
3from datetime import date
4import getopt
5import os
6import re
7import sys
8import xml.dom.minidom
9
10sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
11import simplejson as json
12
13from OVSDB import *
14
15argv0 = sys.argv[0]
16
fcbaf28c 17def textToNroff(s, font=r'\fR'):
89365653
BP
18 def escape(match):
19 c = match.group(0)
fcbaf28c
BP
20 if c == '-':
21 if font == r'\fB':
22 return r'\-'
23 else:
24 return '-'
89365653
BP
25 if c == '\\':
26 return r'\e'
27 elif c == '"':
28 return r'\(dq'
29 elif c == "'":
30 return r'\(cq'
31 else:
32 raise Error("bad escape")
33
fcbaf28c
BP
34 # Escape - \ " ' as needed by nroff.
35 s = re.sub('([-"\'\\\\])', escape, s)
89365653
BP
36 if s.startswith('.'):
37 s = '\\' + s
38 return s
39
40def escapeNroffLiteral(s):
fcbaf28c 41 return r'\fB%s\fR' % textToNroff(s, r'\fB')
89365653
BP
42
43def inlineXmlToNroff(node, font):
44 if node.nodeType == node.TEXT_NODE:
fcbaf28c 45 return textToNroff(node.data, font)
89365653
BP
46 elif node.nodeType == node.ELEMENT_NODE:
47 if node.tagName == 'code' or node.tagName == 'em':
48 s = r'\fB'
49 for child in node.childNodes:
50 s += inlineXmlToNroff(child, r'\fB')
51 return s + font
52 elif node.tagName == 'ref':
53 s = r'\fB'
54 if node.hasAttribute('column'):
55 s += node.attributes['column'].nodeValue
56 elif node.hasAttribute('table'):
57 s += node.attributes['table'].nodeValue
58 elif node.hasAttribute('group'):
59 s += node.attributes['group'].nodeValue
60 else:
61 raise Error("'ref' lacks column and table attributes")
62 return s + font
63 elif node.tagName == 'var':
64 s = r'\fI'
65 for child in node.childNodes:
66 s += inlineXmlToNroff(child, r'\fI')
67 return s + font
68 else:
69 raise Error("element <%s> unknown or invalid here" % node.tagName)
70 else:
71 raise Error("unknown node %s in inline xml" % node)
72
73def blockXmlToNroff(nodes, para='.PP'):
74 s = ''
75 for node in nodes:
76 if node.nodeType == node.TEXT_NODE:
77 s += textToNroff(node.data)
78 s = s.lstrip()
79 elif node.nodeType == node.ELEMENT_NODE:
80 if node.tagName == 'ul':
81 if s != "":
82 s += "\n"
83 s += ".RS\n"
84 for liNode in node.childNodes:
85 if (liNode.nodeType == node.ELEMENT_NODE
86 and liNode.tagName == 'li'):
87 s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP")
88 elif (liNode.nodeType != node.TEXT_NODE
89 or not liNode.data.isspace()):
90 raise Error("<ul> element may only have <li> children")
91 s += ".RE\n"
92 elif node.tagName == 'dl':
93 if s != "":
94 s += "\n"
95 s += ".RS\n"
96 prev = "dd"
97 for liNode in node.childNodes:
98 if (liNode.nodeType == node.ELEMENT_NODE
99 and liNode.tagName == 'dt'):
100 if prev == 'dd':
101 s += '.TP\n'
102 else:
103 s += '.TQ\n'
104 prev = 'dt'
105 elif (liNode.nodeType == node.ELEMENT_NODE
106 and liNode.tagName == 'dd'):
107 if prev == 'dd':
108 s += '.IP\n'
109 prev = 'dd'
110 elif (liNode.nodeType != node.TEXT_NODE
111 or not liNode.data.isspace()):
112 raise Error("<dl> element may only have <dt> and <dd> children")
113 s += blockXmlToNroff(liNode.childNodes, ".IP")
114 s += ".RE\n"
115 elif node.tagName == 'p':
116 if s != "":
117 if not s.endswith("\n"):
118 s += "\n"
119 s += para + "\n"
120 s += blockXmlToNroff(node.childNodes, para)
121 else:
122 s += inlineXmlToNroff(node, r'\fR')
123 else:
124 raise Error("unknown node %s in block xml" % node)
125 if s != "" and not s.endswith('\n'):
126 s += '\n'
127 return s
128
129def typeAndConstraintsToNroff(column):
130 type = column.type.toEnglish(escapeNroffLiteral)
131 constraints = column.type.constraintsToEnglish(escapeNroffLiteral)
132 if constraints:
133 type += ", " + constraints
134 return type
135
136def columnToNroff(columnName, column, node):
137 type = typeAndConstraintsToNroff(column)
138 s = '.IP "\\fB%s\\fR: %s"\n' % (columnName, type)
139 s += blockXmlToNroff(node.childNodes, '.IP') + "\n"
140 return s
141
142def columnGroupToNroff(table, groupXml):
143 introNodes = []
144 columnNodes = []
145 for node in groupXml.childNodes:
146 if (node.nodeType == node.ELEMENT_NODE
147 and node.tagName in ('column', 'group')):
148 columnNodes += [node]
149 else:
150 introNodes += [node]
151
152 summary = []
153 intro = blockXmlToNroff(introNodes)
154 body = ''
155 for node in columnNodes:
156 if node.tagName == 'column':
157 columnName = node.attributes['name'].nodeValue
158 column = table.columns[columnName]
159 body += columnToNroff(columnName, column, node)
160 summary += [('column', columnName, column)]
161 elif node.tagName == 'group':
162 title = node.attributes["title"].nodeValue
163 subSummary, subIntro, subBody = columnGroupToNroff(table, node)
164 summary += [('group', title, subSummary)]
165 body += '.ST "%s:"\n' % textToNroff(title)
166 body += subIntro + subBody
167 else:
168 raise Error("unknown element %s in <table>" % node.tagName)
169 return summary, intro, body
170
171def tableSummaryToNroff(summary, level=0):
172 s = ""
173 for type, name, arg in summary:
174 if type == 'column':
175
176 s += "%s\\fB%s\\fR\tT{\n%s\nT}\n" % (
177 r'\ \ ' * level, name, typeAndConstraintsToNroff(arg))
178 else:
179 if s != "":
180 s += "_\n"
181 s += """.T&
182li | s
183l | l.
184%s%s
185_
186""" % (r'\ \ ' * level, name)
187 s += tableSummaryToNroff(arg, level + 1)
188 return s
189
190def tableToNroff(schema, tableXml):
191 tableName = tableXml.attributes['name'].nodeValue
192 table = schema.tables[tableName]
193
194 s = """.bp
195.SS "%s Table"
196""" % tableName
197 summary, intro, body = columnGroupToNroff(table, tableXml)
198 s += intro
199
200 s += r"""
201.sp
202.ce 1
203\fB%s\fR Table Columns:
204.TS
205center box;
206l | l.
207Column Type
208=
209""" % tableName
210 s += tableSummaryToNroff(summary)
211 s += ".TE\n"
212
213 s += body
214 return s
215
216def docsToNroff(schemaFile, xmlFile, title=None):
217 schema = DbSchema.fromJson(json.load(open(schemaFile, "r")))
218 doc = xml.dom.minidom.parse(xmlFile).documentElement
219
220 schemaDate = os.stat(schemaFile).st_mtime
221 xmlDate = os.stat(xmlFile).st_mtime
222 d = date.fromtimestamp(max(schemaDate, xmlDate))
223
224 if title == None:
225 title = schema.name
226
227 s = r'''.TH %s 5 "%s" "Open vSwitch" "Open vSwitch Manual"
228.\" -*- nroff -*-
229.de TQ
230. br
231. ns
232. TP "\\$1"
233..
234.de ST
235. PP
236. RS -0.15in
237. I "\\$1"
238. RE
239..
240''' % (title, d.strftime("%B %Y"))
241
242 s += '.SH "%s DATABASE"\n' % schema.name
243
244 tables = ""
245 introNodes = []
246 tableNodes = []
247 summary = []
248 for dbNode in doc.childNodes:
249 if (dbNode.nodeType == dbNode.ELEMENT_NODE
250 and dbNode.tagName == "table"):
251 tableNodes += [dbNode]
252
253 name = dbNode.attributes['name'].nodeValue
254 if dbNode.hasAttribute("title"):
255 title = dbNode.attributes['title'].nodeValue
256 else:
257 title = name + " configuration."
258 summary += [(name, title)]
259 else:
260 introNodes += [dbNode]
261
262 s += blockXmlToNroff(introNodes) + "\n"
263 tableSummary = r"""
264.sp
265.ce 1
266\fB%s\fR Database Tables:
267.TS
268center box;
269l | l
270lb | l.
271Table Purpose
272=
273""" % schema.name
274 for name, title in summary:
275 tableSummary += "%s\t%s\n" % (name, textToNroff(title))
276 tableSummary += '.TE\n'
277 s += tableSummary
278 for node in tableNodes:
279 s += tableToNroff(schema, node) + "\n"
280 return s
281
282def usage():
283 print """\
284%(argv0)s: ovsdb schema documentation generator
285Prints documentation for an OVSDB schema as an nroff-formatted manpage.
286usage: %(argv0)s [OPTIONS] SCHEMA XML
287where SCHEMA is an OVSDB schema in JSON format
288 and XML is OVSDB documentation in XML format.
289
290The following options are also available:
291 --title=TITLE use TITLE as title instead of schema name
292 -h, --help display this help message
293 -V, --version display version information\
294""" % {'argv0': argv0}
295 sys.exit(0)
296
297if __name__ == "__main__":
298 try:
299 try:
300 options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
301 ['title=', 'help', 'version'])
302 except getopt.GetoptError, geo:
303 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
304 sys.exit(1)
305
306 title = None
307 for key, value in options:
308 if key == '--title':
309 title = value
310 elif key in ['-h', '--help']:
311 usage()
312 elif key in ['-V', '--version']:
313 print "ovsdb-doc (Open vSwitch) @VERSION@"
314 else:
315 sys.exit(0)
316
317 if len(args) != 2:
318 sys.stderr.write("%s: exactly 2 non-option arguments required "
319 "(use --help for help)\n" % argv0)
320 sys.exit(1)
321
322 # XXX we should warn about undocumented tables or columns
323 s = docsToNroff(args[0], args[1])
324 for line in s.split("\n"):
325 line = line.strip()
326 if len(line):
327 print line
328
329 except Error, e:
330 sys.stderr.write("%s: %s\n" % (argv0, e.msg))
331 sys.exit(1)
332
333# Local variables:
334# mode: python
335# End: