source4/scripting python: convert 'except X, e' to 'except X as e'
[abartlet/samba.git/.git] / source4 / scripting / bin / w32err_code.py
1 #!/usr/bin/env python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 2009
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 """Import generete werror.h/doserr.c files from WSPP HTML"""
21
22 import re
23 import os
24 import sys
25 import urllib
26 import pprint
27 from xml.dom import minidom
28 from optparse import OptionParser, OptionGroup
29
30 _wspp_werror_url = 'http://msdn.microsoft.com/en-us/library/cc231199%28PROT.10%29.aspx'
31
32 class WerrorHtmlParser(object):
33     """
34     Parses HTML from WSPP documentation generating dictionary of
35     dictionaries with following keys:
36       - "err_hex" - hex number (as string)
37       - "err_name" - error name
38       - "err_desc" - error long description
39     For the key of returned dictionary err_hex is used,
40     i.e. "hex-error-code-str" => {error dictionary object}
41     """
42
43     ERROR_PREFIX = ['ERROR_', 'NERR_', 'FRS_', 'RPC_', 'EPT_', 'OR_', 'WAIT_TIMEOUT']
44     ERROR_REPLACE = ['ERROR_']
45
46     def __init__(self, opt):
47         self.opt = opt
48         self._errors_skipped = []
49         pass
50
51     def _is_error_code_name(self, err_name):
52         for pref in self.ERROR_PREFIX:
53             if err_name.startswith(pref):
54                 return True
55         return False
56
57     def _make_werr_name(self, err_name):
58         err_name = err_name.upper()
59         for pref in self.ERROR_REPLACE:
60             if err_name.startswith(pref):
61                 return err_name.replace(pref, 'WERR_', 1)
62         return 'WERR_' + err_name
63
64     def parse_url(self, url):
65         errors = {}
66         html = self._load_url(url)
67
68         # let minidom to parse the tree, should be:
69         # table -> tr -> td
70         #     p -> [hex code, br, error code]
71         #     p -> [description]
72         table_node = minidom.parseString(html)
73         for row_node in table_node.getElementsByTagName("tr"):
74             # verify we got right number of td elements
75             td_nodes = row_node.getElementsByTagName('td')
76             if len(td_nodes) != 2:
77                 continue
78             # now get the real data
79             p_nodes = row_node.getElementsByTagName('p')
80             if len(p_nodes) != 3:   continue
81             if len(p_nodes[0].childNodes) != 1: continue
82             if len(p_nodes[1].childNodes) != 1: continue
83             if len(p_nodes[2].childNodes) != 1: continue
84             err_hex = str(p_nodes[0].childNodes[0].nodeValue)
85             err_name = str(p_nodes[1].childNodes[0].nodeValue)
86             err_desc = p_nodes[2].childNodes[0].nodeValue.encode('utf-8')
87             err_desc = err_desc.replace('"', '\\"').replace("\'", "\\'")
88             # do some checking
89             if not err_hex.startswith('0x'):    continue
90             if not self._is_error_code_name(err_name):
91                 self._errors_skipped.append("%s - %s - %d" % (err_name, err_hex, int(err_hex, 16)))
92                 continue
93             # create entry
94             err_name = self._make_werr_name(err_name)
95             err_def = {'err_hex': err_hex,
96                        'err_name': err_name,
97                        'err_desc': err_desc,
98                        'code': int(err_hex, 16)}
99             errors[err_def['code']] = err_def
100
101         # print skipped errors
102         if self.opt.print_skipped and len(self._errors_skipped):
103             print "\nErrors skipped during HTML parsing:"
104             pprint.pprint(self._errors_skipped)
105             print "\n"
106
107         return errors
108
109     def _load_url(self, url):
110         html_str = ""
111         try:
112             fp = urllib.urlopen(url)
113             for line in fp:
114                 html_str += line.strip()
115             fp.close()
116         except IOError as e:
117             print "error loading url: " + e.strerror
118             pass
119
120         # currently ERROR codes are rendered as table
121         # locate table chunk with ERROR_SUCCESS
122         html = [x for x in html_str.split('<table ') if "ERROR_SUCCESS" in x]
123         html = '<table ' + html[0]
124         pos = html.find('</table>')
125         if pos == -1:
126             return '';
127         html = html[:pos] + '</table>'
128
129         # html clean up
130         html = re.sub(r'<a[^>]*>(.*?)</a>', r'\1', html)
131
132         return html
133
134
135 class WerrorGenerator(object):
136     """
137     provides methods to generate parts of werror.h and doserr.c files
138     """
139
140     FNAME_WERRORS   = 'w32errors.lst'
141     FNAME_WERROR_DEFS  = 'werror_defs.h'
142     FNAME_DOSERR_DEFS = 'doserr_defs.c'
143     FNAME_DOSERR_DESC = 'doserr_desc.c'
144
145     def __init__(self, opt):
146         self.opt = opt
147         self._out_dir = opt.out_dir
148         pass
149
150     def _open_out_file(self, fname):
151         fname = os.path.join(self._out_dir, fname)
152         return open(fname, "w")
153
154     def _gen_werrors_list(self, errors):
155         """uses 'errors' dictionary to display list of Win32 Errors"""
156
157         fp = self._open_out_file(self.FNAME_WERRORS)
158         for err_code in sorted(errors.keys()):
159             err_name = errors[err_code]['err_name']
160             fp.write(err_name)
161             fp.write("\n")
162         fp.close()
163
164     def _gen_werror_defs(self, errors):
165         """uses 'errors' dictionary to generate werror.h file"""
166
167         fp = self._open_out_file(self.FNAME_WERROR_DEFS)
168         for err_code in sorted(errors.keys()):
169             err_name = errors[err_code]['err_name']
170             err_hex = errors[err_code]['err_hex']
171             fp.write('#define %s\tW_ERROR(%s)' % (err_name, err_hex))
172             fp.write("\n")
173         fp.close()
174
175     def _gen_doserr_defs(self, errors):
176         """uses 'errors' dictionary to generate defines in doserr.c file"""
177
178         fp = self._open_out_file(self.FNAME_DOSERR_DEFS)
179         for err_code in sorted(errors.keys()):
180             err_name = errors[err_code]['err_name']
181             fp.write('\t{ "%s", %s },' % (err_name, err_name))
182             fp.write("\n")
183         fp.close()
184
185     def _gen_doserr_descriptions(self, errors):
186         """uses 'errors' dictionary to generate descriptions in doserr.c file"""
187
188         fp = self._open_out_file(self.FNAME_DOSERR_DESC)
189         for err_code in sorted(errors.keys()):
190             err_name = errors[err_code]['err_name']
191             fp.write('\t{ %s, "%s" },' % (err_name, errors[err_code]['err_desc']))
192             fp.write("\n")
193         fp.close()
194
195     def _lookup_error_by_name(self, err_name, defined_errors):
196         for err in defined_errors.itervalues():
197             if err['err_name'] == err_name:
198                 return err
199         return None
200
201     def _filter_errors(self, errors, defined_errors):
202         """
203         returns tuple (new_erros, diff_code_errors, diff_name_errors)
204         new_errors - dictionary of errors not in defined_errors
205         diff_code_errors - list of errors found in defined_errors
206                            but with different value
207         diff_name_errors - list of errors found with same code in
208                             defined_errors, but with different name
209         Most critical is diff_code_errors list to be empty!
210         """
211         new_errors = {}
212         diff_code_errors = []
213         diff_name_errors = []
214         for err_def in errors.itervalues():
215             add_error = True
216             # try get defined error by code
217             if defined_errors.has_key(err_def['code']):
218                 old_err = defined_errors[err_def['code']]
219                 if err_def['err_name'] != old_err['err_name']:
220                     warning = {'msg': 'New and Old errors has different error names',
221                                'err_new': err_def,
222                                'err_old': old_err}
223                     diff_name_errors.append(warning)
224
225             # sanity check for errors with same name but different values
226             old_err = self._lookup_error_by_name(err_def['err_name'], defined_errors)
227             if old_err:
228                 if err_def['code'] != old_err['code']:
229                     warning = {'msg': 'New and Old error defs has different error value',
230                                'err_new': err_def,
231                                'err_old': old_err}
232                     diff_code_errors.append(warning)
233                 # exclude error already defined with same name
234                 add_error = False
235             # do add the error in new_errors if everything is fine
236             if add_error:
237                 new_errors[err_def['code']] = err_def
238             pass
239         return (new_errors, diff_code_errors, diff_name_errors)
240
241     def generate(self, errors):
242         # load already defined error codes
243         werr_parser = WerrorParser(self.opt)
244         (defined_errors,
245          no_value_errors) = werr_parser.load_err_codes(self.opt.werror_file)
246         if not defined_errors:
247             print "\nUnable to load existing errors file: %s" % self.opt.werror_file
248             sys.exit(1)
249         if self.opt.verbose and len(no_value_errors):
250             print "\nWarning: there are errors defines using macro value:"
251             pprint.pprint(no_value_errors)
252             print ""
253         # filter generated error codes
254         (new_errors,
255          diff_code_errors,
256          diff_name_errors) = self._filter_errors(errors, defined_errors)
257         if diff_code_errors:
258             print("\nFound %d errors with same names but different error values! Aborting."
259                   % len(diff_code_errors))
260             pprint.pprint(diff_code_errors)
261             sys.exit(2)
262
263         if diff_name_errors:
264             print("\nFound %d errors with same values but different names (should be normal)"
265                   % len(diff_name_errors))
266             pprint.pprint(diff_name_errors)
267
268         # finally generate output files
269         self._gen_werror_defs(new_errors)
270         self._gen_doserr_defs(new_errors)
271         self._gen_werrors_list(errors)
272         self._gen_doserr_descriptions(errors)
273         pass
274
275 class WerrorParser(object):
276     """
277     Parses errors defined in werror.h file
278     """
279
280     def __init__(self, opt):
281         self.opt = opt
282         pass
283
284     def _parse_werror_line(self, line):
285         m = re.match('#define[ \t]*(.*?)[ \t]*W_ERROR\((.*?)\)', line)
286         if not m or (len(m.groups()) != 2):
287             return None
288         if len(m.group(1)) == 0:
289             return None
290         if str(m.group(2)).startswith('0x'):
291             err_code = int(m.group(2), 16)
292         elif m.group(2).isdigit():
293             err_code = int(m.group(2))
294         else:
295             self.err_no_values.append(line)
296             return None
297         return {'err_name': str(m.group(1)),
298                 'err_hex': "0x%08X" % err_code,
299                 'code': err_code}
300         pass
301
302     def load_err_codes(self, fname):
303         """
304         Returns tuple of:
305             dictionary of "hex_err_code" => {code, name}
306                 "hex_err_code" is string
307                 "code" is int value for the error
308             list of errors that was ignored for some reason
309         """
310         # reset internal variables
311         self.err_no_values = []
312         err_codes = {}
313         fp = open(fname)
314         for line in fp.readlines():
315             err_def = self._parse_werror_line(line)
316             if err_def:
317                 err_codes[err_def['code']] = err_def
318         fp.close();
319         return (err_codes, self.err_no_values)
320
321
322
323 def _generate_files(opt):
324     parser = WerrorHtmlParser(opt)
325     errors = parser.parse_url(opt.url)
326
327     out = WerrorGenerator(opt)
328     out.generate(errors)
329     pass
330
331
332 if __name__ == '__main__':
333     _cur_dir = os.path.abspath(os.path.dirname(__file__))
334     opt_parser = OptionParser(usage="usage: %prog [options]", version="%prog 0.3")
335     opt_group = OptionGroup(opt_parser, "Main options")
336     opt_group.add_option("--url", dest="url",
337                          default=_wspp_werror_url,
338                          help="url for w32 error codes html - may be local file")
339     opt_group.add_option("--out", dest="out_dir",
340                          default=_cur_dir,
341                          help="output dir for generated files")
342     opt_group.add_option("--werror", dest="werror_file",
343                          default=os.path.join(_cur_dir, 'werror.h'),
344                          help="path to werror.h file")
345     opt_group.add_option("--print_skipped",
346                         action="store_true", dest="print_skipped", default=False,
347                         help="print errors skipped during HTML parsing")
348     opt_group.add_option("-q", "--quiet",
349                         action="store_false", dest="verbose", default=False,
350                         help="don't print warnings to stdout")
351
352     opt_parser.add_option_group(opt_group)
353
354     (options, args) = opt_parser.parse_args()
355
356     # add some options to be used internally
357     options.err_defs_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_WERROR_DEFS)
358     options.dos_defs_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_DOSERR_DEFS)
359     options.dos_desc_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_DOSERR_DESC)
360
361     # check options
362     _generate_files(options)