cmd/contentreplace.py

124 lines
3.7 KiB
Python
Raw Normal View History

2021-11-08 19:39:17 +00:00
'''
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.
'''
2019-06-12 05:41:31 +00:00
import argparse
import codecs
import pyperclip
2020-01-15 07:47:11 +00:00
import re
import sys
2019-06-12 05:41:31 +00:00
2021-11-08 19:39:17 +00:00
from voussoirkit import betterhelp
from voussoirkit import interactive
2021-05-17 03:56:31 +00:00
from voussoirkit import pathclass
2020-12-01 06:05:54 +00:00
from voussoirkit import pipeable
from voussoirkit import spinal
2021-05-17 03:56:31 +00:00
from voussoirkit import vlogging
2021-05-17 03:56:31 +00:00
log = vlogging.getLogger(__name__, 'contentreplace')
2019-06-12 05:41:31 +00:00
2021-05-17 03:56:31 +00:00
def contentreplace(file, replace_from, replace_to, autoyes=False, do_regex=False):
file = pathclass.Path(file)
2021-10-05 00:21:14 +00:00
content = file.read('r', encoding='utf-8')
2019-06-12 05:41:31 +00:00
2020-01-15 07:47:11 +00:00
if do_regex:
occurances = len(re.findall(replace_from, content, flags=re.MULTILINE))
2020-01-15 07:47:11 +00:00
else:
occurances = content.count(replace_from)
2019-06-12 05:41:31 +00:00
2021-05-17 03:56:31 +00:00
print(f'{file.absolute_path}: Found {occurances} occurences.')
2019-06-12 05:41:31 +00:00
if occurances == 0:
return
if not (autoyes or interactive.getpermission('Replace?')):
2019-06-12 05:41:31 +00:00
return
2020-01-15 07:47:11 +00:00
if do_regex:
content = re.sub(replace_from, replace_to, content, flags=re.MULTILINE)
2020-01-15 07:47:11 +00:00
else:
content = content.replace(replace_from, replace_to)
2019-06-12 05:41:31 +00:00
2021-10-05 00:21:14 +00:00
file.write('w', content, encoding='utf-8')
2019-06-12 05:41:31 +00:00
2020-12-01 06:05:54 +00:00
@pipeable.ctrlc_return1
2019-06-12 05:41:31 +00:00
def contentreplace_argparse(args):
files = spinal.walk(
glob_filenames=args.filename_glob,
recurse=args.recurse,
)
2019-06-12 05:41:31 +00:00
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')
2019-06-12 05:41:31 +00:00
2021-05-17 03:56:31 +00:00
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)
2019-06-12 05:41:31 +00:00
return 0
@vlogging.main_decorator
2019-06-12 05:41:31 +00:00
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filename_glob')
parser.add_argument('replace_from')
parser.add_argument('replace_to')
2021-11-08 19:39:17 +00:00
parser.add_argument('--yes', dest='autoyes', action='store_true')
parser.add_argument('--recurse', action='store_true')
2020-01-15 07:47:11 +00:00
parser.add_argument('--regex', dest='do_regex', action='store_true')
parser.add_argument('--clip_prompt', '--clip-prompt', action='store_true')
2019-06-12 05:41:31 +00:00
parser.set_defaults(func=contentreplace_argparse)
2021-11-08 19:39:17 +00:00
return betterhelp.single_main(argv, parser, __doc__)
2019-06-12 05:41:31 +00:00
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))