cmd/contentreplace.py

123 lines
3.7 KiB
Python

'''
contentreplace - find-and-replace en masse
==========================================
> contentreplace filename_glob replace_from replace_to <flags>
filename_glob:
A glob pattern that targets the files of interest.
replace_from:
String to be replaced.
replace_to:
String with which to replace.
flags:
--recurse:
If provided, we will recurse into subdirectories and look for glob matches
there too. If not provided, only files in the cwd are affected.
--regex:
If provided, the given replace_from, replace_to will be treated as regex
strings. If not provided, we use regular str.replace
--clip_prompt:
If you want to do contentreplace with unicode that is difficult to enter
into your terminal, or multi-line strings that don't work as command line
arguments, this option might help you. The program will wait for you to put
the text of interest into your clipboard and press Enter.
--yes:
If provided, replacements will occur automatically without prompting.
'''
import argparse
import codecs
import pyperclip
import re
import sys
from voussoirkit import betterhelp
from voussoirkit import interactive
from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import spinal
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'contentreplace')
def contentreplace(file, replace_from, replace_to, autoyes=False, do_regex=False):
file = pathclass.Path(file)
content = file.read('r', encoding='utf-8')
if do_regex:
occurances = len(re.findall(replace_from, content, flags=re.MULTILINE))
else:
occurances = content.count(replace_from)
print(f'{file.absolute_path}: Found {occurances} occurences.')
if occurances == 0:
return
if not (autoyes or interactive.getpermission('Replace?')):
return
if do_regex:
content = re.sub(replace_from, replace_to, content, flags=re.MULTILINE)
else:
content = content.replace(replace_from, replace_to)
file.write('w', content, encoding='utf-8')
@pipeable.ctrlc_return1
def contentreplace_argparse(args):
files = spinal.walk(
glob_filenames=args.filename_glob,
recurse=args.recurse,
)
if args.clip_prompt:
replace_from = input('Ready from')
if not replace_from:
replace_from = pyperclip.paste()
replace_to = input('Ready to')
if not replace_to:
replace_to = pyperclip.paste()
else:
replace_from = codecs.decode(args.replace_from, 'unicode_escape')
if args.do_regex:
replace_to = args.replace_to
else:
replace_to = codecs.decode(args.replace_to, 'unicode_escape')
for file in files:
try:
contentreplace(
file,
replace_from,
replace_to,
autoyes=args.autoyes,
do_regex=args.do_regex,
)
except UnicodeDecodeError:
log.error('%s encountered unicode decode error.', file.absolute_path)
return 0
@vlogging.main_decorator
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filename_glob')
parser.add_argument('replace_from')
parser.add_argument('replace_to')
parser.add_argument('--yes', dest='autoyes', action='store_true')
parser.add_argument('--recurse', action='store_true')
parser.add_argument('--regex', dest='do_regex', action='store_true')
parser.add_argument('--clip_prompt', '--clip-prompt', action='store_true')
parser.set_defaults(func=contentreplace_argparse)
return betterhelp.single_main(argv, parser, __doc__)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))