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=''' find-and-replace en masse ''', ) parser.add_argument( 'filename_glob', help=''' A glob pattern that targets the files of interest. ''', ) parser.add_argument( 'replace_from', help=''' String to be replaced. You can use backslash-escaped symbols like \\n for newline. ''', ) parser.add_argument( 'replace_to', help=''' String with which to replace. Can use backslash-escaped symbols. ''', ) parser.add_argument( '--yes', dest='autoyes', action='store_true', help=''' If provided, replacements will occur automatically without prompting. ''', ) parser.add_argument( '--recurse', action='store_true', help=''' If provided, we will recurse into subdirectories and look for glob matches there too. If not provided, only files in the cwd are affected. ''', ) parser.add_argument( '--regex', dest='do_regex', action='store_true', help=''' If provided, the given replace_from, replace_to will be treated as regex strings. If not provided, we use regular str.replace. ''', ) parser.add_argument( '--clip_prompt', '--clip-prompt', action='store_true', help=''' 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. ''', ) parser.set_defaults(func=contentreplace_argparse) return betterhelp.go(parser, argv) if __name__ == '__main__': raise SystemExit(main(sys.argv[1:]))