Skip to content

Commit 7d7cc27

Browse files
committed
Provide --encoding-fallback. Only warn (no abort) for IO/encding errors
1 parent 4e2dc74 commit 7d7cc27

3 files changed

Lines changed: 83 additions & 16 deletions

File tree

‎ChangeLog‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# ChangeLog for replacer
22

3+
*replacer-1.1
4+
Martin Väth <martin at mvath.de>:
5+
- Provide --encoding-fallback
6+
- Warn (instead of aborting) for IO and file encoding errors
7+
- Fix indentation for PEP8 E128
8+
39
*replacer-1.0
410
Martin Väth <martin at mvath.de>:
511
- Provide --recursive and --skip options

‎bin/replacer‎

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def get_parser():
9090
r'“raw” strings.'
9191
p = argparse.ArgumentParser(description=d)
9292
a = p.add_argument
93-
a('-V', '--version', action='version', version='%(prog)s 1.0')
93+
a('-V', '--version', action='version', version='%(prog)s 1.1')
9494
a('-B', '--before', type=int, default=0,
9595
help=r'add lines of context before match')
9696
a('-A', '--after', type=int, default=0,
@@ -100,7 +100,10 @@ def get_parser():
100100
a('-P', '--plain-replace', action='store_true',
101101
help=r'treat replace expression as plain string')
102102
a('-e', '--encoding', default='utf8',
103-
help=r'file encoding, e.g. cp850')
103+
help=r'file encoding, e.g. utf8 (default), cp1252, or cp850')
104+
a('-E', '--encoding-fallback', default='cp1252',
105+
help=r'file encoding if the -e encoding fails, e.g. cp1252 (default) '
106+
r'or cp850. Use the empty string or the value of -e to avoid retrying.')
104107
a('-k', '--keep', action='store_true',
105108
help=r'try to keep timestamps when modifying files')
106109
a('-g', '--grep', action='store_true',
@@ -144,7 +147,6 @@ def write(*args, **kwargs):
144147
print(*args, end='', **kwargs)
145148

146149

147-
148150
def write_printable(text):
149151
write(write_printable.nonprintable.sub(
150152
(lambda match: repr(match.group(0)).replace("'", '')), text))
@@ -548,28 +550,82 @@ def ask_write(filename):
548550
return answer
549551

550552

551-
class Action:
552-
def _slurp(self, filename):
553-
file = codecs.open(filename, 'rb', encoding=self._encoding)
554-
text = file.read()
553+
def close_noexcept(file):
554+
try:
555555
file.close()
556+
except Exception:
557+
pass
558+
559+
560+
class Action:
561+
def _slurp(self, filename, encoding_override=None):
562+
self._encoding_override = encoding_override # Remember for writing
563+
if encoding_override:
564+
encoding = encoding_override
565+
else:
566+
encoding = self._encoding
567+
try:
568+
file = codecs.open(filename, 'rb', encoding=encoding)
569+
except Exception as e:
570+
warn('cannot open {0} for reading: {1}'.format(filename, e))
571+
return None
572+
try:
573+
text = file.read()
574+
except UnicodeDecodeError as e:
575+
if encoding_override:
576+
warn('fallback encoding fails, too: {0}'.format(e))
577+
return None
578+
close_noexcept(file)
579+
if self._encoding_fallback and self._encoding_fallback != encoding:
580+
warn('switching encoding {0}->{1} for {2}: {3}'.
581+
format(self._encoding, self._encoding_fallback,
582+
filename, e))
583+
return self._slurp(filename, self._encoding_fallback)
584+
warn('{0} encoding error for {1}: {2}'.
585+
format(self._encoding, filename, e))
586+
return None
587+
except Exception as e:
588+
close_noexcept(file)
589+
warn('error when reading {0}: {1}'.format(filename, e))
590+
return None
591+
try:
592+
file.close()
593+
except Exception as e:
594+
warn('error when closing {0} after reading: {1}'.
595+
format(filename, e))
596+
return None
556597
return text
557598

558599
def _write(self, filename, text):
559600
stat = None
560601
if self._keep:
561602
try:
562603
stat = os.stat(filename)
563-
except:
604+
except Exception as e:
564605
stat = None
565-
file = codecs.open(filename, 'wb', encoding=self._encoding)
566-
file.write(text)
567-
file.close()
606+
warn('cannot get timestamp of {0}: {1}'.format(filename, e))
607+
if self._write_encoding_override:
608+
encoding = self._encoding_override
609+
else:
610+
encoding = self._encoding
611+
try:
612+
file = codecs.open(filename, 'wb', encoding=encoding)
613+
except Exception as e:
614+
warn('cannot open {0} for writing: {1}'.format(filename, e))
615+
return
616+
try:
617+
file.write(text)
618+
except Exception as e:
619+
warn('write error to {0}: {1}'.format(filename, e))
620+
try:
621+
file.close()
622+
except Exception as e:
623+
warn('cannot close {0} after writing: {1}'.format(filename, e))
568624
if stat is not None:
569625
try:
570626
os.utime(filename, (stat.st_atime, stat.st_mtime))
571-
except:
572-
pass
627+
except Exception as e:
628+
warn('cannot set timestamp of {0}: {1}'.format(filename, e))
573629

574630
def _exit(self):
575631
if self._replace is None and not self._have:
@@ -578,6 +634,8 @@ class Action:
578634

579635
def _process(self, filename):
580636
text = self._slurp(filename)
637+
if text is None:
638+
return
581639
matcher = Matcher(
582640
self._search, self._replace,
583641
plain_search=self._plain_search,
@@ -651,8 +709,9 @@ class Action:
651709

652710
def _recurse(self, dir):
653711
for (dirpath, dirnames, filenames) in os.walk(dir):
654-
dirnames[:] = [name for name in sorted(dirnames)
655-
if not self._test_skip(os.path.join(dirpath, name))]
712+
dirnames[:] = \
713+
[name for name in sorted(dirnames)
714+
if not self._test_skip(os.path.join(dirpath, name))]
656715
for name in sorted(filenames):
657716
filename = os.path.join(dirpath, name)
658717
if not self._test_skip(filename) and os.path.isfile(filename):
@@ -703,6 +762,7 @@ class Action:
703762
if options.verbose:
704763
self._flags |= re.VERBOSE
705764
self._encoding = options.encoding
765+
self._encoding_fallback = options.encoding_fallback
706766
self._keep = options.keep
707767
self._quiet = options.quiet
708768
self._skip = options.skip

‎zsh/_replacer‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ _arguments -s -S : \
66
{'(--after)-A+','(-A)--after='}'[print specified lines after match]:number of lines after match:(2)' \
77
{'(--plain-search)-p','(-p)--plain-search'}'[use plain string for search]' \
88
{'(--plain-replace)-P','(-P)--plain-replace'}'[use plain string for replace]' \
9-
{'(--encoding)-e+','(-e)--encoding='}'[use specified file encoding]:file encoding:(cp850)' \
9+
{'(--encoding)-e+','(-e)--encoding='}'[use specified file encoding (utf8)]:file encoding:(cp850 cp1252)' \
10+
{'(--encoding-fallback)-E+','(-E)--encoding-fallback='}'[use specified fallback file encoding (cp1252)]:file encoding:(cp850)' \
1011
{'(--keep)-k','(-k)--keep'}'[try to keep timestamps]' \
1112
'(2 --count -c --list -l '{'--grep)-g','-g)--grep'}'[grep mode]' \
1213
'(2 --grep -g --list -l '{'--count)-c','-c)--count'}'[grep mode, count]' \

0 commit comments

Comments
 (0)