python: Convert dict iterators.
[cascardo/ovs.git] / python / build / nroff.py
1 # Copyright (c) 2010, 2011, 2012, 2015 Nicira, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import re
16 import sys
17
18 from ovs.db import error
19
20
21 def text_to_nroff(s, font=r'\fR'):
22     def escape(match):
23         c = match.group(0)
24
25         # In Roman type, let -- in XML be \- in nroff.  That gives us a way to
26         # write minus signs, which is important in some places in manpages.
27         #
28         # Bold in nroff usually represents literal text, where there's no
29         # distinction between hyphens and minus sign.  The convention in nroff
30         # appears to be to use a minus sign in such cases, so we follow that
31         # convention.
32         #
33         # Finally, we always output - as a minus sign when it is followed by a
34         # digit.
35         if c.startswith('-'):
36             if c == '--' and font == r'\fR':
37                 return r'\-'
38             if c != '-' or font in (r'\fB', r'\fL'):
39                 return c.replace('-', r'\-')
40             else:
41                 return '-'
42
43         if c == '\\':
44             return r'\e'
45         elif c == '"':
46             return r'\(dq'
47         elif c == "'":
48             return r'\(cq'
49         elif c == ".":
50             # groff(7) says that . can be escaped by \. but in practice groff
51             # still gives an error with \. at the beginning of a line.
52             return r'\[char46]'
53         else:
54             raise error.Error("bad escape")
55
56     # Escape - \ " ' . as needed by nroff.
57     s = re.sub('(-[0-9]|--|[-"\'\\\\.])', escape, s)
58     return s
59
60
61 def escape_nroff_literal(s, font=r'\fB'):
62     return font + r'%s\fR' % text_to_nroff(s, font)
63
64
65 def inline_xml_to_nroff(node, font, to_upper=False, newline='\n'):
66     if node.nodeType == node.TEXT_NODE:
67         if to_upper:
68             s = text_to_nroff(node.data.upper(), font)
69         else:
70             s = text_to_nroff(node.data, font)
71         return s.replace('\n', newline)
72     elif node.nodeType == node.ELEMENT_NODE:
73         if node.tagName in ['code', 'em', 'option', 'env', 'b']:
74             s = r'\fB'
75             for child in node.childNodes:
76                 s += inline_xml_to_nroff(child, r'\fB', to_upper, newline)
77             return s + font
78         elif node.tagName == 'ref':
79             s = r'\fB'
80             if node.hasAttribute('column'):
81                 s += node.attributes['column'].nodeValue
82                 if node.hasAttribute('key'):
83                     s += ':' + node.attributes['key'].nodeValue
84             elif node.hasAttribute('table'):
85                 s += node.attributes['table'].nodeValue
86             elif node.hasAttribute('group'):
87                 s += node.attributes['group'].nodeValue
88             elif node.hasAttribute('db'):
89                 s += node.attributes['db'].nodeValue
90             else:
91                 raise error.Error("'ref' lacks required attributes: %s"
92                                   % list(node.attributes.keys()))
93             return s + font
94         elif node.tagName in ['var', 'dfn', 'i']:
95             s = r'\fI'
96             for child in node.childNodes:
97                 s += inline_xml_to_nroff(child, r'\fI', to_upper, newline)
98             return s + font
99         else:
100             raise error.Error("element <%s> unknown or invalid here"
101                               % node.tagName)
102     elif node.nodeType == node.COMMENT_NODE:
103         return ''
104     else:
105         raise error.Error("unknown node %s in inline xml" % node)
106
107
108 def pre_to_nroff(nodes, para, font):
109     # This puts 'font' at the beginning of each line so that leading and
110     # trailing whitespace stripping later doesn't removed leading spaces
111     # from preformatted text.
112     s = para + '\n.nf\n' + font
113     for node in nodes:
114         s += inline_xml_to_nroff(node, font, False, '\n.br\n' + font)
115     s += '\n.fi\n'
116     return s
117
118
119 def fatal(msg):
120     sys.stderr.write('%s\n' % msg)
121     sys.exit(1)
122
123
124 def diagram_header_to_nroff(header_node):
125     header_fields = []
126     i = 0
127     for node in header_node.childNodes:
128         if node.nodeType == node.ELEMENT_NODE and node.tagName == 'bits':
129             name = node.attributes['name'].nodeValue
130             width = node.attributes['width'].nodeValue
131             above = node.getAttribute('above')
132             below = node.getAttribute('below')
133             fill = node.getAttribute('fill')
134             header_fields += [{"name": name,
135                               "tag": "B%d" % i,
136                               "width": width,
137                               "above": above,
138                               "below": below,
139                               "fill": fill}]
140             i += 1
141         elif node.nodeType == node.COMMENT_NODE:
142             pass
143         elif node.nodeType == node.TEXT_NODE and node.data.isspace():
144             pass
145         else:
146             fatal("unknown node %s in diagram <header> element" % node)
147
148     pic_s = ""
149     for f in header_fields:
150         pic_s += "  %s: box \"%s\" width %s" % (f['tag'], f['name'],
151                                                 f['width'])
152         if f['fill'] == 'yes':
153             pic_s += " fill"
154         pic_s += '\n'
155     for f in header_fields:
156         pic_s += "  \"%s\" at %s.n above\n" % (f['above'], f['tag'])
157         pic_s += "  \"%s\" at %s.s below\n" % (f['below'], f['tag'])
158     name = header_node.getAttribute('name')
159     if name == "":
160         visible = " invis"
161     else:
162         visible = ""
163     pic_s += "line <->%s \"%s\" above " % (visible, name)
164     pic_s += "from %s.nw + (0,textht) " % header_fields[0]['tag']
165     pic_s += "to %s.ne + (0,textht)\n" % header_fields[-1]['tag']
166
167     text_s = ""
168     for f in header_fields:
169         text_s += """.IP \\(bu
170 %s bits""" % (f['above'])
171         if f['name']:
172             text_s += ": %s" % f['name']
173         if f['below']:
174             text_s += " (%s)" % f['below']
175         text_s += "\n"
176     return pic_s, text_s
177
178
179 def diagram_to_nroff(nodes, para):
180     pic_s = ''
181     text_s = ''
182     move = False
183     for node in nodes:
184         if node.nodeType == node.ELEMENT_NODE and node.tagName == 'header':
185             if move:
186                 pic_s += "move .1\n"
187                 text_s += ".sp\n"
188             pic_header, text_header = diagram_header_to_nroff(node)
189             pic_s += "[\n" + pic_header + "]\n"
190             text_s += text_header
191             move = True
192         elif node.nodeType == node.ELEMENT_NODE and node.tagName == 'nospace':
193             move = False
194         elif node.nodeType == node.ELEMENT_NODE and node.tagName == 'dots':
195             pic_s += "move .1\n"
196             pic_s += '". . ." ljust\n'
197             text_s += ".sp\n"
198         elif node.nodeType == node.COMMENT_NODE:
199             pass
200         elif node.nodeType == node.TEXT_NODE and node.data.isspace():
201             pass
202         else:
203             fatal("unknown node %s in diagram <header> element" % node)
204     return para + """
205 .\\" check if in troff mode (TTY)
206 .if t \{
207 .PS
208 boxht = .2
209 textht = 1/6
210 fillval = .2
211 """ + pic_s + """\
212 .PE
213 \\}
214 .\\" check if in nroff mode:
215 .if n \{
216 .RS
217 """ + text_s + """\
218 .RE
219 \\}"""
220
221
222 def block_xml_to_nroff(nodes, para='.PP'):
223     s = ''
224     for node in nodes:
225         if node.nodeType == node.TEXT_NODE:
226             s += text_to_nroff(node.data)
227             s = s.lstrip()
228         elif node.nodeType == node.ELEMENT_NODE:
229             if node.tagName in ['ul', 'ol']:
230                 if s != "":
231                     s += "\n"
232                 s += ".RS\n"
233                 i = 0
234                 for li_node in node.childNodes:
235                     if (li_node.nodeType == node.ELEMENT_NODE
236                         and li_node.tagName == 'li'):
237                         i += 1
238                         if node.tagName == 'ul':
239                             s += ".IP \\(bu\n"
240                         else:
241                             s += ".IP %d. .25in\n" % i
242                         s += block_xml_to_nroff(li_node.childNodes, ".IP")
243                     elif li_node.nodeType == node.COMMENT_NODE:
244                         pass
245                     elif (li_node.nodeType != node.TEXT_NODE
246                           or not li_node.data.isspace()):
247                         raise error.Error("<%s> element may only have "
248                                           "<li> children" % node.tagName)
249                 s += ".RE\n"
250             elif node.tagName == 'dl':
251                 if s != "":
252                     s += "\n"
253                 s += ".RS\n"
254                 prev = "dd"
255                 for li_node in node.childNodes:
256                     if (li_node.nodeType == node.ELEMENT_NODE
257                         and li_node.tagName == 'dt'):
258                         if prev == 'dd':
259                             s += '.TP\n'
260                         else:
261                             s += '.TQ .5in\n'
262                         prev = 'dt'
263                     elif (li_node.nodeType == node.ELEMENT_NODE
264                           and li_node.tagName == 'dd'):
265                         if prev == 'dd':
266                             s += '.IP\n'
267                         prev = 'dd'
268                     elif li_node.nodeType == node.COMMENT_NODE:
269                         continue
270                     elif (li_node.nodeType != node.TEXT_NODE
271                           or not li_node.data.isspace()):
272                         raise error.Error("<dl> element may only have "
273                                           "<dt> and <dd> children")
274                     s += block_xml_to_nroff(li_node.childNodes, ".IP")
275                 s += ".RE\n"
276             elif node.tagName == 'p':
277                 if s != "":
278                     if not s.endswith("\n"):
279                         s += "\n"
280                     s += para + "\n"
281                 s += block_xml_to_nroff(node.childNodes, para)
282             elif node.tagName in ('h1', 'h2', 'h3'):
283                 if s != "":
284                     if not s.endswith("\n"):
285                         s += "\n"
286                 nroffTag = {'h1': 'SH', 'h2': 'SS', 'h3': 'ST'}[node.tagName]
287                 s += '.%s "' % nroffTag
288                 for child_node in node.childNodes:
289                     s += inline_xml_to_nroff(child_node, r'\fR',
290                                           to_upper=(nroffTag == 'SH'))
291                 s += '"\n'
292             elif node.tagName == 'pre':
293                 fixed = node.getAttribute('fixed')
294                 if fixed == 'yes':
295                     font = r'\fL'
296                 else:
297                     font = r'\fB'
298                 s += pre_to_nroff(node.childNodes, para, font)
299             elif node.tagName == 'diagram':
300                 s += diagram_to_nroff(node.childNodes, para)
301             else:
302                 s += inline_xml_to_nroff(node, r'\fR')
303         elif node.nodeType == node.COMMENT_NODE:
304             pass
305         else:
306             raise error.Error("unknown node %s in block xml" % node)
307     if s != "" and not s.endswith('\n'):
308         s += '\n'
309     return s