mirror of
https://github.com/FAUSheppy/config
synced 2026-06-18 22:32:36 +02:00
131 lines
3.1 KiB
Python
Executable File
131 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
def find_files(directory: Path, recursive: bool):
|
|
if recursive:
|
|
yield from (p for p in directory.rglob("*") if p.is_file())
|
|
else:
|
|
yield from (p for p in directory.iterdir() if p.is_file())
|
|
|
|
def process_file(file_path: Path, search: str, replace: str):
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
except (UnicodeDecodeError, OSError):
|
|
return None
|
|
|
|
if search not in content:
|
|
return None
|
|
|
|
changed_lines = []
|
|
|
|
for lineno, line in enumerate(content.splitlines(), start=1):
|
|
if search in line:
|
|
changed_lines.append({
|
|
"line": lineno,
|
|
"old": line,
|
|
"new": line.replace(search, replace),
|
|
})
|
|
|
|
return {
|
|
"path": file_path,
|
|
"count": content.count(search),
|
|
"new_content": content.replace(search, replace),
|
|
"changed_lines": changed_lines,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Search and replace text in files with confirmation."
|
|
)
|
|
parser.add_argument(
|
|
"directory",
|
|
help="Directory to search"
|
|
)
|
|
parser.add_argument(
|
|
"search",
|
|
help="String to search for"
|
|
)
|
|
parser.add_argument(
|
|
"replace",
|
|
help="Replacement string"
|
|
)
|
|
parser.add_argument(
|
|
"-r",
|
|
"--recursive",
|
|
action="store_true",
|
|
help="Search recursively"
|
|
)
|
|
parser.add_argument(
|
|
"-y",
|
|
"--yes",
|
|
action="store_true",
|
|
help="Skip confirmation"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
directory = Path(args.directory)
|
|
|
|
if not directory.is_dir():
|
|
print(f"Error: '{directory}' is not a directory.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
changes = []
|
|
|
|
for file_path in find_files(directory, args.recursive):
|
|
result = process_file(file_path, args.search, args.replace)
|
|
if result:
|
|
changes.append(result)
|
|
|
|
if not changes:
|
|
print("No matches found.")
|
|
return
|
|
|
|
print("\nPlanned changes:\n")
|
|
total_replacements = 0
|
|
|
|
for change in changes:
|
|
print("=" * 80)
|
|
print(f"File: {change['path']}")
|
|
print(f"Replacements: {change['count']}")
|
|
print()
|
|
|
|
for line_change in change["changed_lines"]:
|
|
print(f"Line {line_change['line']}:")
|
|
print(f" - {line_change['old']}")
|
|
print(f" + {line_change['new']}")
|
|
print()
|
|
|
|
total_replacements += change["count"]
|
|
|
|
if not args.yes:
|
|
try:
|
|
response = input("\nApply these changes? [y/N]: ").strip().lower()
|
|
except KeyboardInterrupt:
|
|
print("Aborted")
|
|
sys.exit(1)
|
|
|
|
if response.lower() not in ("y", "yes"):
|
|
print("Aborted.")
|
|
return
|
|
|
|
for change in changes:
|
|
change["path"].write_text(
|
|
change["new_content"],
|
|
encoding="utf-8"
|
|
)
|
|
|
|
print(
|
|
f"Completed. Modified {len(changes)} file(s), "
|
|
f"{total_replacements} replacement(s)."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|