comparison tools/pygettext.py @ 465:70b0809cd15c

Updated to version 1.4 (python 2.2) version of pygettext
author Jürgen Hermann <jhermann@users.sourceforge.net>
date Wed, 19 Dec 2001 00:32:13 +0000
parents a787b2413997
children b2d6657cd2a6
comparison
equal deleted inserted replaced
464:29f5ac8a0d2b 465:70b0809cd15c
1 #! /usr/bin/env python 1 #! /usr/bin/env python
2 # Originally written by Barry Warsaw <bwarsaw@python.org> 2 # Originally written by Barry Warsaw <barry@zope.com>
3 # 3 #
4 # minimally patched to make it even more xgettext compatible 4 # Minimally patched to make it even more xgettext compatible
5 # by Peter Funk <pf@artcom-gmbh.de> 5 # by Peter Funk <pf@artcom-gmbh.de>
6 # 6 #
7 # 2001-11-21 Jürgen Hermann <jh@web.de> 7 # 2001-12-18 Jürgen Hermann <jh@web.de>
8 # Checks that _() only contains string literals added, and 8 # Added checks that _() only contains string literals, and
9 # command line args are resolved to module lists, i.e. you 9 # command line args are resolved to module lists, i.e. you
10 # can now pass a filename, a module or package name, or a 10 # can now pass a filename, a module or package name, or a
11 # directory (including globbing chars, important for Win32). 11 # directory (including globbing chars, important for Win32).
12 # Made docstring fit in 80 chars wide displays using pydoc.
12 # 13 #
13 14
14 # for selftesting 15 # for selftesting
15 try: 16 try:
16 import fintl 17 import fintl
17 _ = fintl.gettext 18 _ = fintl.gettext
18 except ImportError: 19 except ImportError:
19 _ = lambda s: s 20 _ = lambda s: s
20 21
21
22 __doc__ = _("""pygettext -- Python equivalent of xgettext(1) 22 __doc__ = _("""pygettext -- Python equivalent of xgettext(1)
23 23
24 Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the 24 Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the
25 internationalization of C programs. Most of these tools are independent of 25 internationalization of C programs. Most of these tools are independent of
26 the programming language and can be used from within Python programs. Martin 26 the programming language and can be used from within Python programs.
27 von Loewis' work[1] helps considerably in this regard. 27 Martin von Loewis' work[1] helps considerably in this regard.
28 28
29 There's one problem though; xgettext is the program that scans source code 29 There's one problem though; xgettext is the program that scans source code
30 looking for message strings, but it groks only C (or C++). Python introduces 30 looking for message strings, but it groks only C (or C++). Python
31 a few wrinkles, such as dual quoting characters, triple quoted strings, and 31 introduces a few wrinkles, such as dual quoting characters, triple quoted
32 raw strings. xgettext understands none of this. 32 strings, and raw strings. xgettext understands none of this.
33 33
34 Enter pygettext, which uses Python's standard tokenize module to scan Python 34 Enter pygettext, which uses Python's standard tokenize module to scan
35 source code, generating .pot files identical to what GNU xgettext[2] generates 35 Python source code, generating .pot files identical to what GNU xgettext[2]
36 for C and C++ code. From there, the standard GNU tools can be used. 36 generates for C and C++ code. From there, the standard GNU tools can be
37 37 used.
38 A word about marking Python strings as candidates for translation. GNU 38
39 xgettext recognizes the following keywords: gettext, dgettext, dcgettext, and 39 A word about marking Python strings as candidates for translation. GNU
40 gettext_noop. But those can be a lot of text to include all over your code. 40 xgettext recognizes the following keywords: gettext, dgettext, dcgettext,
41 C and C++ have a trick: they use the C preprocessor. Most internationalized C 41 and gettext_noop. But those can be a lot of text to include all over your
42 source includes a #define for gettext() to _() so that what has to be written 42 code. C and C++ have a trick: they use the C preprocessor. Most
43 in the source is much less. Thus these are both translatable strings: 43 internationalized C source includes a #define for gettext() to _() so that
44 what has to be written in the source is much less. Thus these are both
45 translatable strings:
44 46
45 gettext("Translatable String") 47 gettext("Translatable String")
46 _("Translatable String") 48 _("Translatable String")
47 49
48 Python of course has no preprocessor so this doesn't work so well. Thus, 50 Python of course has no preprocessor so this doesn't work so well. Thus,
50 below for how to augment this. 52 below for how to augment this.
51 53
52 [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html 54 [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html
53 [2] http://www.gnu.org/software/gettext/gettext.html 55 [2] http://www.gnu.org/software/gettext/gettext.html
54 56
55 NOTE: pygettext attempts to be option and feature compatible with GNU xgettext 57 NOTE: pygettext attempts to be option and feature compatible with GNU
56 where ever possible. However some options are still missing or are not fully 58 xgettext where ever possible. However some options are still missing or are
57 implemented. Also, xgettext's use of command line switches with option 59 not fully implemented. Also, xgettext's use of command line switches with
58 arguments is broken, and in these cases, pygettext just defines additional 60 option arguments is broken, and in these cases, pygettext just defines
59 switches. 61 additional switches.
60 62
61 Usage: pygettext [options] inputfile ... 63 Usage: pygettext [options] inputfile ...
62 64
63 Options: 65 Options:
64 66
65 -a 67 -a
66 --extract-all 68 --extract-all
67 Extract all strings 69 Extract all strings.
68 70
69 -d name 71 -d name
70 --default-domain=name 72 --default-domain=name
71 Rename the default output file from messages.pot to name.pot 73 Rename the default output file from messages.pot to name.pot.
72 74
73 -E 75 -E
74 --escape 76 --escape
75 replace non-ASCII characters with octal escape sequences. 77 Replace non-ASCII characters with octal escape sequences.
78
79 -D
80 --docstrings
81 Extract module, class, method, and function docstrings. These do
82 not need to be wrapped in _() markers, and in fact cannot be for
83 Python to consider them docstrings. (See also the -X option).
76 84
77 -h 85 -h
78 --help 86 --help
79 print this help message and exit 87 Print this help message and exit.
80 88
81 -k word 89 -k word
82 --keyword=word 90 --keyword=word
83 Keywords to look for in addition to the default set, which are: 91 Keywords to look for in addition to the default set, which are:
84 %(DEFAULTKEYWORDS)s 92 %(DEFAULTKEYWORDS)s
98 Write filename/lineno location comments indicating where each 106 Write filename/lineno location comments indicating where each
99 extracted string is found in the source. These lines appear before 107 extracted string is found in the source. These lines appear before
100 each msgid. The style of comments is controlled by the -S/--style 108 each msgid. The style of comments is controlled by the -S/--style
101 option. This is the default. 109 option. This is the default.
102 110
111 -o filename
112 --output=filename
113 Rename the default output file from messages.pot to filename. If
114 filename is `-' then the output is sent to standard out.
115
116 -p dir
117 --output-dir=dir
118 Output files will be placed in directory dir.
119
103 -S stylename 120 -S stylename
104 --style stylename 121 --style stylename
105 Specify which style to use for location comments. Two styles are 122 Specify which style to use for location comments. Two styles are
106 supported: 123 supported:
107 124
108 Solaris # File: filename, line: line-number 125 Solaris # File: filename, line: line-number
109 GNU #: filename:line 126 GNU #: filename:line
110 127
111 The style name is case insensitive. GNU style is the default. 128 The style name is case insensitive. GNU style is the default.
112
113 -o filename
114 --output=filename
115 Rename the default output file from messages.pot to filename. If
116 filename is `-' then the output is sent to standard out.
117
118 -p dir
119 --output-dir=dir
120 Output files will be placed in directory dir.
121 129
122 -v 130 -v
123 --verbose 131 --verbose
124 Print the names of the files being processed. 132 Print the names of the files being processed.
125 133
135 --exclude-file=filename 143 --exclude-file=filename
136 Specify a file that contains a list of strings that are not be 144 Specify a file that contains a list of strings that are not be
137 extracted from the input files. Each string to be excluded must 145 extracted from the input files. Each string to be excluded must
138 appear on a line by itself in the file. 146 appear on a line by itself in the file.
139 147
148 -X filename
149 --no-docstrings=filename
150 Specify a file that contains a list of files (one per line) that
151 should not have their docstrings extracted. This is only useful in
152 conjunction with the -D option above.
153
140 If `inputfile' is -, standard input is read. 154 If `inputfile' is -, standard input is read.
141
142 """) 155 """)
143 156
144 import os 157 import os
145 import sys 158 import sys
146 import time 159 import time
147 import getopt 160 import getopt
148 import token 161 import token
149 import tokenize 162 import tokenize
150 163 import operator
151 __version__ = '1.1' 164
165 __version__ = '1.5'
152 166
153 default_keywords = ['_'] 167 default_keywords = ['_']
154 DEFAULTKEYWORDS = ', '.join(default_keywords) 168 DEFAULTKEYWORDS = ', '.join(default_keywords)
155 169
156 EMPTYSTRING = '' 170 EMPTYSTRING = ''
157 171
158 172
159 173
160 # The normal pot-file header. msgmerge and EMACS' po-mode work better if 174 # The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
161 # it's there. 175 # there.
162 pot_header = _('''\ 176 pot_header = _('''\
163 # SOME DESCRIPTIVE TITLE. 177 # SOME DESCRIPTIVE TITLE.
164 # Copyright (C) YEAR ORGANIZATION 178 # Copyright (C) YEAR ORGANIZATION
165 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. 179 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
166 # 180 #
167 msgid "" 181 msgid ""
168 msgstr "" 182 msgstr ""
169 "Project-Id-Version: PACKAGE VERSION\\n" 183 "Project-Id-Version: PACKAGE VERSION\\n"
170 "PO-Revision-Date: %(time)s\\n" 184 "POT-Creation-Date: %(time)s\\n"
185 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
171 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" 186 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
172 "Language-Team: LANGUAGE <LL@li.org>\\n" 187 "Language-Team: LANGUAGE <LL@li.org>\\n"
173 "MIME-Version: 1.0\\n" 188 "MIME-Version: 1.0\\n"
174 "Content-Type: text/plain; charset=CHARSET\\n" 189 "Content-Type: text/plain; charset=CHARSET\\n"
175 "Content-Transfer-Encoding: ENCODING\\n" 190 "Content-Transfer-Encoding: ENCODING\\n"
177 192
178 ''') 193 ''')
179 194
180 195
181 def usage(code, msg=''): 196 def usage(code, msg=''):
182 print __doc__ % globals() 197 print >> sys.stderr, __doc__ % globals()
183 if msg: 198 if msg:
184 print msg 199 print >> sys.stderr, msg
185 sys.exit(code) 200 sys.exit(code)
186 201
187 202
188 203
189 escapes = [] 204 escapes = []
237 lineterm = '\\n"\n"' 252 lineterm = '\\n"\n"'
238 s = '""\n"' + lineterm.join(lines) + '"' 253 s = '""\n"' + lineterm.join(lines) + '"'
239 return s 254 return s
240 255
241 256
242
243 def containsAny(str, set): 257 def containsAny(str, set):
244 """ Check whether 'str' contains ANY of the chars in 'set' 258 """ Check whether 'str' contains ANY of the chars in 'set'
245 """ 259 """
246 return 1 in [c in str for c in set] 260 return 1 in [c in str for c in set]
247 261
343 self.__options = options 357 self.__options = options
344 self.__messages = {} 358 self.__messages = {}
345 self.__state = self.__waiting 359 self.__state = self.__waiting
346 self.__data = [] 360 self.__data = []
347 self.__lineno = -1 361 self.__lineno = -1
362 self.__freshmodule = 1
363 self.__curfile = None
348 364
349 def __call__(self, ttype, tstring, stup, etup, line): 365 def __call__(self, ttype, tstring, stup, etup, line):
350 # dispatch 366 # dispatch
367 ## import token
368 ## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \
369 ## 'tstring:', tstring
351 self.__state(ttype, tstring, stup[0]) 370 self.__state(ttype, tstring, stup[0])
352 371
353 def __waiting(self, ttype, tstring, lineno): 372 def __waiting(self, ttype, tstring, lineno):
354 if ttype == tokenize.NAME and tstring in self.__options.keywords: 373 opts = self.__options
374 # Do docstring extractions, if enabled
375 if opts.docstrings and not opts.nodocstrings.get(self.__curfile):
376 # module docstring?
377 if self.__freshmodule:
378 if ttype == tokenize.STRING:
379 self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
380 self.__freshmodule = 0
381 elif ttype not in (tokenize.COMMENT, tokenize.NL):
382 self.__freshmodule = 0
383 return
384 # class docstring?
385 if ttype == tokenize.NAME and tstring in ('class', 'def'):
386 self.__state = self.__suiteseen
387 return
388 if ttype == tokenize.NAME and tstring in opts.keywords:
355 self.__state = self.__keywordseen 389 self.__state = self.__keywordseen
390
391 def __suiteseen(self, ttype, tstring, lineno):
392 # ignore anything until we see the colon
393 if ttype == tokenize.OP and tstring == ':':
394 self.__state = self.__suitedocstring
395
396 def __suitedocstring(self, ttype, tstring, lineno):
397 # ignore any intervening noise
398 if ttype == tokenize.STRING:
399 self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
400 self.__state = self.__waiting
401 elif ttype not in (tokenize.NEWLINE, tokenize.INDENT,
402 tokenize.COMMENT):
403 # there was no class docstring
404 self.__state = self.__waiting
356 405
357 def __keywordseen(self, ttype, tstring, lineno): 406 def __keywordseen(self, ttype, tstring, lineno):
358 if ttype == tokenize.OP and tstring == '(': 407 if ttype == tokenize.OP and tstring == '(':
359 self.__data = [] 408 self.__data = []
360 self.__lineno = lineno 409 self.__lineno = lineno
367 # We've seen the last of the translatable strings. Record the 416 # We've seen the last of the translatable strings. Record the
368 # line number of the first line of the strings and update the list 417 # line number of the first line of the strings and update the list
369 # of messages seen. Reset state for the next batch. If there 418 # of messages seen. Reset state for the next batch. If there
370 # were no strings inside _(), then just ignore this entry. 419 # were no strings inside _(), then just ignore this entry.
371 if self.__data: 420 if self.__data:
372 msg = EMPTYSTRING.join(self.__data) 421 self.__addentry(EMPTYSTRING.join(self.__data))
373 if not msg in self.__options.toexclude:
374 entry = (self.__curfile, self.__lineno)
375 linenos = self.__messages.get(msg)
376 if linenos is None:
377 self.__messages[msg] = [entry]
378 else:
379 linenos.append(entry)
380 self.__state = self.__waiting 422 self.__state = self.__waiting
381 elif ttype == tokenize.STRING: 423 elif ttype == tokenize.STRING:
382 self.__data.append(safe_eval(tstring)) 424 self.__data.append(safe_eval(tstring))
383 elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT, 425 elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT,
384 token.NEWLINE, tokenize.NL]: 426 token.NEWLINE, tokenize.NL]:
385 # warn if we see anything else than STRING or whitespace 427 # warn if we see anything else than STRING or whitespace
386 print >>sys.stderr, _('*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"') % { 428 print >>sys.stderr, _('*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"') % {
387 'token': tstring, 'file': self.__curfile, 'lineno': self.__lineno} 429 'token': tstring, 'file': self.__curfile, 'lineno': self.__lineno}
388 self.__state = self.__waiting 430 self.__state = self.__waiting
389 431
432 def __addentry(self, msg, lineno=None, isdocstring=0):
433 if lineno is None:
434 lineno = self.__lineno
435 if not msg in self.__options.toexclude:
436 entry = (self.__curfile, lineno)
437 self.__messages.setdefault(msg, {})[entry] = isdocstring
438
390 def set_filename(self, filename): 439 def set_filename(self, filename):
391 self.__curfile = filename 440 self.__curfile = filename
441 self.__freshmodule = 1
392 442
393 def write(self, fp): 443 def write(self, fp):
394 options = self.__options 444 options = self.__options
395 timestamp = time.ctime(time.time()) 445 timestamp = time.ctime(time.time())
396 # common header 446 # The time stamp in the header doesn't have the same format as that
397 try: 447 # generated by xgettext...
398 sys.stdout = fp 448 print >> fp, pot_header % {'time': timestamp, 'version': __version__}
399 # The time stamp in the header doesn't have the same format 449 # Sort the entries. First sort each particular entry's keys, then
400 # as that generated by xgettext... 450 # sort all the entries by their first item.
401 print pot_header % {'time': timestamp, 'version': __version__} 451 reverse = {}
402 for k, v in self.__messages.items(): 452 for k, v in self.__messages.items():
453 keys = v.keys()
454 keys.sort()
455 reverse.setdefault(tuple(keys), []).append((k, v))
456 rkeys = reverse.keys()
457 rkeys.sort()
458 for rkey in rkeys:
459 rentries = reverse[rkey]
460 rentries.sort()
461 for k, v in rentries:
462 isdocstring = 0
463 # If the entry was gleaned out of a docstring, then add a
464 # comment stating so. This is to aid translators who may wish
465 # to skip translating some unimportant docstrings.
466 if reduce(operator.__add__, v.values()):
467 isdocstring = 1
468 # k is the message string, v is a dictionary-set of (filename,
469 # lineno) tuples. We want to sort the entries in v first by
470 # file name and then by line number.
471 v = v.keys()
472 v.sort()
403 if not options.writelocations: 473 if not options.writelocations:
404 pass 474 pass
405 # location comments are different b/w Solaris and GNU: 475 # location comments are different b/w Solaris and GNU:
406 elif options.locationstyle == options.SOLARIS: 476 elif options.locationstyle == options.SOLARIS:
407 for filename, lineno in v: 477 for filename, lineno in v:
408 d = {'filename': filename, 'lineno': lineno} 478 d = {'filename': filename, 'lineno': lineno}
409 print _('# File: %(filename)s, line: %(lineno)d') % d 479 print >>fp, _(
480 '# File: %(filename)s, line: %(lineno)d') % d
410 elif options.locationstyle == options.GNU: 481 elif options.locationstyle == options.GNU:
411 # fit as many locations on one line, as long as the 482 # fit as many locations on one line, as long as the
412 # resulting line length doesn't exceeds 'options.width' 483 # resulting line length doesn't exceeds 'options.width'
413 locline = '#:' 484 locline = '#:'
414 for filename, lineno in v: 485 for filename, lineno in v:
415 d = {'filename': filename, 'lineno': lineno} 486 d = {'filename': filename, 'lineno': lineno}
416 s = _(' %(filename)s:%(lineno)d') % d 487 s = _(' %(filename)s:%(lineno)d') % d
417 if len(locline) + len(s) <= options.width: 488 if len(locline) + len(s) <= options.width:
418 locline = locline + s 489 locline = locline + s
419 else: 490 else:
420 print locline 491 print >> fp, locline
421 locline = "#:" + s 492 locline = "#:" + s
422 if len(locline) > 2: 493 if len(locline) > 2:
423 print locline 494 print >> fp, locline
424 # TBD: sorting, normalizing 495 if isdocstring:
425 print 'msgid', normalize(k) 496 print >> fp, '#, docstring'
426 print 'msgstr ""\n' 497 print >> fp, 'msgid', normalize(k)
427 finally: 498 print >> fp, 'msgstr ""\n'
428 sys.stdout = sys.__stdout__ 499
429 500
430 501
431 def main(): 502 def main():
432 global default_keywords 503 global default_keywords
433 try: 504 try:
434 opts, args = getopt.getopt( 505 opts, args = getopt.getopt(
435 sys.argv[1:], 506 sys.argv[1:],
436 'ad:Ehk:Kno:p:S:Vvw:x:', 507 'ad:DEhk:Kno:p:S:Vvw:x:X:',
437 ['extract-all', 'default-domain', 'escape', 'help', 508 ['extract-all', 'default-domain=', 'escape', 'help',
438 'keyword=', 'no-default-keywords', 509 'keyword=', 'no-default-keywords',
439 'add-location', 'no-location', 'output=', 'output-dir=', 510 'add-location', 'no-location', 'output=', 'output-dir=',
440 'style=', 'verbose', 'version', 'width=', 'exclude-file=', 511 'style=', 'verbose', 'version', 'width=', 'exclude-file=',
512 'docstrings', 'no-docstrings',
441 ]) 513 ])
442 except getopt.error, msg: 514 except getopt.error, msg:
443 usage(1, msg) 515 usage(1, msg)
444 516
445 # for holding option values 517 # for holding option values
456 writelocations = 1 528 writelocations = 1
457 locationstyle = GNU 529 locationstyle = GNU
458 verbose = 0 530 verbose = 0
459 width = 78 531 width = 78
460 excludefilename = '' 532 excludefilename = ''
533 docstrings = 0
534 nodocstrings = {}
461 535
462 options = Options() 536 options = Options()
463 locations = {'gnu' : options.GNU, 537 locations = {'gnu' : options.GNU,
464 'solaris' : options.SOLARIS, 538 'solaris' : options.SOLARIS,
465 } 539 }
472 options.extractall = 1 546 options.extractall = 1
473 elif opt in ('-d', '--default-domain'): 547 elif opt in ('-d', '--default-domain'):
474 options.outfile = arg + '.pot' 548 options.outfile = arg + '.pot'
475 elif opt in ('-E', '--escape'): 549 elif opt in ('-E', '--escape'):
476 options.escape = 1 550 options.escape = 1
551 elif opt in ('-D', '--docstrings'):
552 options.docstrings = 1
477 elif opt in ('-k', '--keyword'): 553 elif opt in ('-k', '--keyword'):
478 options.keywords.append(arg) 554 options.keywords.append(arg)
479 elif opt in ('-K', '--no-default-keywords'): 555 elif opt in ('-K', '--no-default-keywords'):
480 default_keywords = [] 556 default_keywords = []
481 elif opt in ('-n', '--add-location'): 557 elif opt in ('-n', '--add-location'):
500 options.width = int(arg) 576 options.width = int(arg)
501 except ValueError: 577 except ValueError:
502 usage(1, _('--width argument must be an integer: %s') % arg) 578 usage(1, _('--width argument must be an integer: %s') % arg)
503 elif opt in ('-x', '--exclude-file'): 579 elif opt in ('-x', '--exclude-file'):
504 options.excludefilename = arg 580 options.excludefilename = arg
581 elif opt in ('-X', '--no-docstrings'):
582 fp = open(arg)
583 try:
584 while 1:
585 line = fp.readline()
586 if not line:
587 break
588 options.nodocstrings[line[:-1]] = 1
589 finally:
590 fp.close()
505 591
506 # calculate escapes 592 # calculate escapes
507 make_escapes(options.escape) 593 make_escapes(options.escape)
508 594
509 # calculate all keywords 595 # calculate all keywords
514 try: 600 try:
515 fp = open(options.excludefilename) 601 fp = open(options.excludefilename)
516 options.toexclude = fp.readlines() 602 options.toexclude = fp.readlines()
517 fp.close() 603 fp.close()
518 except IOError: 604 except IOError:
519 sys.stderr.write(_("Can't read --exclude-file: %s") % 605 print >> sys.stderr, _(
520 options.excludefilename) 606 "Can't read --exclude-file: %s") % options.excludefilename
521 sys.exit(1) 607 sys.exit(1)
522 else: 608 else:
523 options.toexclude = [] 609 options.toexclude = []
524 610
525 # resolve args to module lists 611 # resolve args to module lists
526 expanded = [] 612 expanded = []
527 for arg in args: 613 for arg in args:
528 expanded.extend(getFilesForName(arg)) 614 if arg == '-':
615 expanded.append(arg)
616 else:
617 expanded.extend(getFilesForName(arg))
529 args = expanded 618 args = expanded
530 619
531 # slurp through all the files 620 # slurp through all the files
532 eater = TokenEater(options) 621 eater = TokenEater(options)
533 for filename in args: 622 for filename in args:
541 print _('Working on %s') % filename 630 print _('Working on %s') % filename
542 fp = open(filename) 631 fp = open(filename)
543 closep = 1 632 closep = 1
544 try: 633 try:
545 eater.set_filename(filename) 634 eater.set_filename(filename)
546 tokenize.tokenize(fp.readline, eater) 635 try:
636 tokenize.tokenize(fp.readline, eater)
637 except tokenize.TokenError, e:
638 print >> sys.stderr, '%s: %s, line %d, column %d' % (
639 e[0], filename, e[1][0], e[1][1])
547 finally: 640 finally:
548 if closep: 641 if closep:
549 fp.close() 642 fp.close()
550 643
551 # write the output 644 # write the output

Roundup Issue Tracker: http://roundup-tracker.org/