Improve argument parsing and add delete line option
This commit is contained in:
parent
c5323ad2f1
commit
de278ae0ba
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = subprompt
|
||||
version = 0.0.4
|
||||
version = 0.0.5
|
||||
author = Augusto Lenz Gunsch
|
||||
author_email = augusto@augustogunsch.com
|
||||
description = Substitute Regex in files with prompt confirmation
|
||||
|
|
|
@ -25,23 +25,25 @@
|
|||
import re
|
||||
import hashlib
|
||||
import os
|
||||
import argparse
|
||||
from sys import argv, stderr
|
||||
from colorama import init, Fore, Back, Style
|
||||
|
||||
init(autoreset=True)
|
||||
|
||||
# Config
|
||||
line_spread = 3
|
||||
|
||||
class Filter:
|
||||
def __init__(self, regex, sub):
|
||||
def __init__(self, args):
|
||||
self.replace_all = False
|
||||
self.quit_loop = False
|
||||
self.sub_count = 0
|
||||
self.match_count = 0
|
||||
|
||||
self.regex = re.compile(regex)
|
||||
self.sub = sub
|
||||
self.regex = re.compile(args.regex)
|
||||
self.sub = args.r
|
||||
self.delete = args.d
|
||||
self.spread = args.n
|
||||
|
||||
self.lines_to_delete = []
|
||||
|
||||
def _number_lines(_, lines, start_n):
|
||||
return [Fore.YELLOW + Style.BRIGHT +
|
||||
|
@ -56,32 +58,40 @@ class Filter:
|
|||
if self.quit_loop:
|
||||
return curr_match
|
||||
|
||||
replaced_match = self.regex.sub(self.sub, curr_match)
|
||||
if not self.delete:
|
||||
replaced_match = self.regex.sub(self.sub, curr_match)
|
||||
|
||||
if replaced_match == curr_match:
|
||||
return replaced_match
|
||||
if replaced_match == curr_match:
|
||||
return replaced_match
|
||||
|
||||
self.match_count += 1
|
||||
|
||||
if self.replace_all:
|
||||
self.sub_count += 1
|
||||
if self.delete:
|
||||
self.lines_to_delete.append(line_n)
|
||||
return None
|
||||
return replaced_match
|
||||
|
||||
start_n = max(0, line_n - line_spread)
|
||||
end_n = min(len(lines), line_n + line_spread)
|
||||
start_n = max(0, line_n - self.spread)
|
||||
end_n = min(len(lines), line_n + self.spread)
|
||||
cut = lines[start_n:end_n]
|
||||
|
||||
highlighted = line.replace(curr_match,
|
||||
Back.RED + curr_match + Back.RESET)
|
||||
replaced = line.replace(curr_match,
|
||||
Back.YELLOW + Fore.BLACK + replaced_match + Back.RESET + Fore.RESET)
|
||||
|
||||
cut_highlighted = cut
|
||||
cut_highlighted[line_n] = highlighted
|
||||
cut_highlighted = self._number_lines(cut_highlighted, start_n)
|
||||
|
||||
cut_replaced = cut
|
||||
cut_replaced[line_n] = replaced
|
||||
|
||||
if not self.delete:
|
||||
cut_replaced[line_n] = line.replace(curr_match,
|
||||
Back.YELLOW + Fore.BLACK + replaced_match + Back.RESET + Fore.RESET)
|
||||
else:
|
||||
cut_replaced.pop(line_n)
|
||||
|
||||
cut_replaced = self._number_lines(cut_replaced, start_n)
|
||||
|
||||
print(Fore.GREEN + Style.BRIGHT + fname)
|
||||
|
@ -98,6 +108,9 @@ class Filter:
|
|||
if answer in ['y', 'yes', '']:
|
||||
self.sub_count += 1
|
||||
print()
|
||||
if self.delete:
|
||||
self.lines_to_delete.append(line_n)
|
||||
return None
|
||||
return replaced_match
|
||||
|
||||
elif answer in ['n', 'no']:
|
||||
|
@ -107,6 +120,9 @@ class Filter:
|
|||
elif answer in ['a', 'all']:
|
||||
self.sub_count += 1
|
||||
self.replace_all = True
|
||||
if self.delete:
|
||||
self.lines_to_delete.append(line_n)
|
||||
return None
|
||||
return replaced_match
|
||||
|
||||
elif answer in ['q', 'quit']:
|
||||
|
@ -129,6 +145,9 @@ class Filter:
|
|||
for (line_n, line) in enumerate(lines)
|
||||
]
|
||||
|
||||
for line in reversed(self.lines_to_delete):
|
||||
lines.pop(line)
|
||||
|
||||
new_contents = '\n'.join(lines)
|
||||
|
||||
hash_old = hashlib.md5(contents.encode())
|
||||
|
@ -140,23 +159,27 @@ class Filter:
|
|||
file.write('\n')
|
||||
|
||||
def run(args):
|
||||
if len(args) < 3:
|
||||
print('usage: subprompt [REGEX] [SUB] [FILES...]', file=stderr)
|
||||
exit(1)
|
||||
parser = argparse.ArgumentParser(description='Modifies lines matched by a regex interactively')
|
||||
parser.add_argument('regex', metavar='REGEX', type=str)
|
||||
action = parser.add_mutually_exclusive_group(required=True)
|
||||
action.add_argument('-d', help='delete line', action='store_true')
|
||||
action.add_argument('-r', help='replace match with expression', type=str)
|
||||
parser.add_argument('files', metavar='FILES', nargs='+', type=str)
|
||||
parser.add_argument('-n', help='size lines preview (default=3)', type=int, default=3)
|
||||
|
||||
regex = args[0]
|
||||
sub = args[1]
|
||||
files = args[2:]
|
||||
args = parser.parse_args(args)
|
||||
|
||||
filter_obj = Filter(regex, sub)
|
||||
filter_obj = Filter(args)
|
||||
|
||||
for file in files:
|
||||
for file in args.files:
|
||||
# Check if file is writable
|
||||
if os.access(file, os.W_OK):
|
||||
filter_obj.filter_file(file)
|
||||
|
||||
if filter_obj.quit_loop:
|
||||
break
|
||||
else:
|
||||
print('WARNING: File {0} is not writable. Skipping...'.format(file))
|
||||
|
||||
if filter_obj.match_count == 0:
|
||||
print(Fore.RED + Style.BRIGHT + 'No matches found.')
|
||||
|
@ -166,8 +189,7 @@ def run(args):
|
|||
Fore.WHITE + str(filter_obj.match_count) + Fore.YELLOW))
|
||||
|
||||
def main():
|
||||
argv.pop(0)
|
||||
run(argv)
|
||||
run(argv[1:])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
Loading…
Reference in New Issue