Skip to content

Commit

Permalink
implement diffing approach
Browse files Browse the repository at this point in the history
  • Loading branch information
vysakh0 committed Jul 18, 2024
1 parent 5f83d1d commit 543aec2
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 103 deletions.
1 change: 0 additions & 1 deletion src/drd/cli/query/dynamic_command_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def execute_commands(commands, executor, metadata_manager, is_fix=False, debug=F
all_outputs.append(
f"Step {i}/{total_steps}: File operation - {cmd['operation']} - {cmd['filename']} - {output}")
elif cmd['type'] == 'metadata':
print(cmd, "---")
output = handle_metadata_operation(cmd, metadata_manager)
all_outputs.append(
f"Step {i}/{total_steps}: Metadata operation - {cmd['operation']} - {output}")
Expand Down
1 change: 0 additions & 1 deletion src/drd/cli/query/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def execute_dravid_command(query, image_path, debug, instruction_prompt):
print_warning("Please make sure you are in a fresh directory.")
print_warning(
"If it is an existing project, please ensure you're in a git branch")
print_warning("Use Ctrl+C to exit if you're not")

executor = Executor()
metadata_manager = ProjectMetadataManager(executor.current_dir)
Expand Down
36 changes: 36 additions & 0 deletions src/drd/utils/apply_file_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import re


def apply_changes(original_content, diff_content):
# Split the content into lines
original_lines = original_content.splitlines()
diff_lines = diff_content.splitlines()

result_lines = original_lines.copy()
line_offset = 0

for diff_line in diff_lines:
# Parse the diff line
match = re.match(r'([+\-r]) (\d+):(.*)', diff_line.strip())
if match:
operation, line_number, content = match.groups()
line_number = int(line_number) - 1 # Convert to 0-based index

# Adjust line number for previous additions/deletions
adjusted_line_number = line_number + line_offset

if operation == '+':
# Add a new line
result_lines.insert(adjusted_line_number, content.strip())
line_offset += 1
elif operation == '-':
# Remove a line
if 0 <= adjusted_line_number < len(result_lines):
del result_lines[adjusted_line_number]
line_offset -= 1
elif operation == 'r':
# Replace a line
if 0 <= adjusted_line_number < len(result_lines):
result_lines[adjusted_line_number] = content.strip()

return '\n'.join(result_lines)
55 changes: 55 additions & 0 deletions src/drd/utils/diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import difflib
from colorama import Fore, Style, init

# Initialize colorama
init(autoreset=True)


def generate_colored_diff(original_content, new_content, context_lines=3):
original_lines = original_content.splitlines()
new_lines = new_content.splitlines()

differ = difflib.unified_diff(
original_lines, new_lines, lineterm='', n=context_lines)

colored_diff = []
for line in differ:
if line.startswith('+'):
colored_diff.append(f"{Fore.GREEN}{line}{Style.RESET_ALL}")
elif line.startswith('-'):
colored_diff.append(f"{Fore.RED}{line}{Style.RESET_ALL}")
elif line.startswith('^'):
colored_diff.append(f"{Fore.BLUE}{line}{Style.RESET_ALL}")
else:
colored_diff.append(line)

return '\n'.join(colored_diff)


def preview_file_changes(operation, filename, new_content=None, original_content=None):
preview = [f"{Fore.CYAN}{Style.BRIGHT}File: {filename}{Style.RESET_ALL}"]

if operation == 'CREATE':
preview.append(
f"{Fore.GREEN}{Style.BRIGHT}Operation: CREATE{Style.RESET_ALL}")
preview.append(f"{Fore.GREEN}New content:{Style.RESET_ALL}")
preview.append(f"{Fore.GREEN}{new_content}{Style.RESET_ALL}")
elif operation == 'UPDATE':
preview.append(
f"{Fore.YELLOW}{Style.BRIGHT}Operation: UPDATE{Style.RESET_ALL}")
if original_content:
preview.append(generate_colored_diff(
original_content, new_content))
else:
preview.append(
f"{Fore.RED}Error: Missing original content or changes for UPDATE operation{Style.RESET_ALL}")
elif operation == 'DELETE':
preview.append(
f"{Fore.RED}{Style.BRIGHT}Operation: DELETE{Style.RESET_ALL}")
preview.append(
f"{Fore.RED}The file '{filename}' will be deleted.{Style.RESET_ALL}")
else:
preview.append(
f"{Fore.RED}Unknown operation: {operation}{Style.RESET_ALL}")

return '\n'.join(preview)
79 changes: 21 additions & 58 deletions src/drd/utils/step_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import time
import re
from .utils import print_error, print_success, print_info, print_warning, create_confirmation_box
from .diff import preview_file_changes
from .apply_file_changes import apply_changes
from ..metadata.common_utils import get_ignore_patterns, get_folder_structure


Expand Down Expand Up @@ -59,15 +61,13 @@ def perform_file_operation(self, operation, filename, content=None, force=False)
return False

print_info(f"File: {filename}")
if content:
print_info(f"Content preview: {content[:100]}...")

if operation in ['DELETE', 'UPDATE']:
if operation in ['DELETE']:
confirmation_box = create_confirmation_box(
filename, f"{operation.lower()} this file")
print(confirmation_box)

if not click.confirm(f"{Fore.YELLOW}Confirm {operation.lower()} [y/N]:{Style.RESET_ALL}", default=False):
if not click.confirm(f"{Fore.YELLOW}Confirm {operation.lower()} :{Style.RESET_ALL}", default=False):
print_info(f"File {operation.lower()} cancelled by user.")
return False

Expand All @@ -78,6 +78,9 @@ def perform_file_operation(self, operation, filename, content=None, force=False)
try:
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w') as f:
preview = preview_file_changes(
operation, filename, new_content=content)
print(preview)
f.write(content)
print_success(f"File created successfully: {filename}")
return True
Expand All @@ -94,8 +97,21 @@ def perform_file_operation(self, operation, filename, content=None, force=False)
original_content = f.read()

if content:
updated_content = self.apply_changes(
print(content, "content is")
updated_content = apply_changes(
original_content, content)
preview = preview_file_changes(
operation, filename, new_content=updated_content, original_content=original_content)
print(preview)
confirmation_box = create_confirmation_box(
filename, f"{operation.lower()} this file")
print(confirmation_box)

if not click.confirm(f"{Fore.YELLOW}Confirm {operation.lower()} :{Style.RESET_ALL}", default=False):
print_info(
f"File {operation.lower()} cancelled by user.")
return False

else:
print_error(
"No content or changes provided for update operation")
Expand Down Expand Up @@ -126,59 +142,6 @@ def perform_file_operation(self, operation, filename, content=None, force=False)
print_error(f"Unknown file operation: {operation}")
return False

def apply_changes(self, original_content, changes):
if not changes:
return original_content

lines = original_content.splitlines()
change_instructions = self.parse_change_instructions(changes)

# Sort changes by line number in descending order to avoid index shifts
change_instructions.sort(key=lambda x: x['line'], reverse=True)

for change in change_instructions:
if change['action'] == 'add':
lines.insert(change['line'] - 1, change['content'])
elif change['action'] == 'remove':
if 0 <= change['line'] - 1 < len(lines):
del lines[change['line'] - 1]
else:
print_warning(
f"Line {change['line']} does not exist in the file")
elif change['action'] == 'replace':
if 0 <= change['line'] - 1 < len(lines):
lines[change['line'] - 1] = change['content']
else:
print_warning(
f"Line {change['line']} does not exist in the file")

return '\n'.join(lines)

def parse_change_instructions(self, changes):
instructions = []
for line in changes.strip().splitlines():
parts = line.strip().split(':', 1)
if len(parts) != 2:
print_warning(f"Invalid change instruction: {line}")
continue

action_line, content = parts
action, line_num = action_line.split()
line_num = int(line_num)

if action in ('+', 'add'):
instructions.append(
{'action': 'add', 'line': line_num, 'content': content.strip()})
elif action in ('-', 'remove'):
instructions.append({'action': 'remove', 'line': line_num})
elif action in ('r', 'replace'):
instructions.append(
{'action': 'replace', 'line': line_num, 'content': content.strip()})
else:
print_warning(f"Unknown action in change instruction: {line}")

return instructions

def parse_json(self, json_string):
try:
return json.loads(json_string)
Expand Down
85 changes: 85 additions & 0 deletions tests/utils/test_apply_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import unittest
from drd.utils.step_executor import apply_changes # Update this import as needed


class TestApplyChangesSpecific(unittest.TestCase):
def test_add_email_and_move_paragraph(self):
original_content = """<body>
<h1>Welcome to Our Company</h1>
<p>We are a leading provider of services.</p>
</body>
<p>Contact us for more information.</p>
"""
changes = """
+ 4: <p>Email: [email protected]</p>
- 5:
+ 5: <p>Contact us for more information.</p>
"""
expected_content = """<body>
<h1>Welcome to Our Company</h1>
<p>We are a leading provider of services.</p>
<p>Email: [email protected]</p>
<p>Contact us for more information.</p>
</body>"""
result = apply_changes(original_content, changes)
self.assertEqual(result.strip(), expected_content.strip())

def test_html_structure_changes(self):
original_content = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Welcome to Our Company</h1>
<h3>hello</h3>
<h2>About Us</h2>
<p>Address: 123 Main Street, Anytown, ST 12345</p>
</body>
<p>Phone: (555) 123-4567</p>
</html>"""
changes = """
r 5: <title>About Us - TestApp</title>
r 11: <h2>About Us</h2>
r 12: <p>Address: 123 Main Street, Anytown, ST 12345</p>
r 13: <p>Phone: (555) 123-4567</p>
+ 14: <p>Email: [email protected]</p>
- 15:
- 16:
"""
expected_content = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>About Us - TestApp</title>
<title>Document</title>
</head>
<body>
<h1>Welcome to Our Company</h1>
<h3>hello</h3>
<h2>About Us</h2>
<p>Address: 123 Main Street, Anytown, ST 12345</p>
<p>Phone: (555) 123-4567</p>
<p>Email: [email protected]</p>
</body>
</html>"""
result = apply_changes(original_content, changes)
self.assertEqual(result.strip(), expected_content.strip())

def test_multiple_changes_same_line(self):
original_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n"
changes = """
r 2: Modified Line 2
+ 2: New Line between 1 and 2
- 4:
r 5: Modified Line 5
"""
expected_content = "Line 1\nNew Line between 1 and 2\nModified Line 2\nLine 3\nModified Line 5\n"
result = apply_changes(original_content, changes)
self.assertEqual(result.strip(), expected_content.strip())


if __name__ == '__main__':
unittest.main()
62 changes: 62 additions & 0 deletions tests/utils/test_diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import unittest
from drd.utils.diff import generate_colored_diff, preview_file_changes
from colorama import Fore, Style, init

# Initialize colorama
init(autoreset=True)


class TestDiffUtils(unittest.TestCase):

def test_generate_colored_diff(self):
original_content = "Line 1\nLine 2\nLine 3\n"
new_content = "Line 1\nModified Line 2\nLine 3\nNew Line 4\n"

expected_diff = (
f"{Fore.RED}--- {Style.RESET_ALL}\n"
f"{Fore.GREEN}+++ {Style.RESET_ALL}\n"
f"@@ -1,3 +1,4 @@\n"
f" Line 1\n"
f"{Fore.RED}-Line 2{Style.RESET_ALL}\n"
f"{Fore.GREEN}+Modified Line 2{Style.RESET_ALL}\n"
f" Line 3\n"
f"{Fore.GREEN}+New Line 4{Style.RESET_ALL}"
)

result = generate_colored_diff(original_content, new_content)
self.assertEqual(result, expected_diff)

def test_preview_file_changes_create(self):
result = preview_file_changes(
'CREATE', 'new_file.txt', new_content='New file content')
expected_output = (
f"{Fore.CYAN}{Style.BRIGHT}File: new_file.txt{Style.RESET_ALL}\n"
f"{Fore.GREEN}{Style.BRIGHT}Operation: CREATE{Style.RESET_ALL}\n"
f"{Fore.GREEN}New content:{Style.RESET_ALL}\n"
f"{Fore.GREEN}New file content{Style.RESET_ALL}"
)
self.assertEqual(result, expected_output)

def test_preview_file_changes_update(self):
original_content = "Line 1\nLine 2\nLine 3\n"
new_content = "Line 1\nModified Line 2\nLine 3\nNew Line 4\n"
result = preview_file_changes(
'UPDATE', 'existing_file.txt', new_content=new_content, original_content=original_content)
self.assertIn(
f"{Fore.YELLOW}{Style.BRIGHT}Operation: UPDATE{Style.RESET_ALL}", result)
self.assertIn(f"{Fore.RED}-Line 2{Style.RESET_ALL}", result)
self.assertIn(f"{Fore.GREEN}+Modified Line 2{Style.RESET_ALL}", result)
self.assertIn(f"{Fore.GREEN}+New Line 4{Style.RESET_ALL}", result)

def test_preview_file_changes_delete(self):
result = preview_file_changes('DELETE', 'file_to_delete.txt')
expected_output = (
f"{Fore.CYAN}{Style.BRIGHT}File: file_to_delete.txt{Style.RESET_ALL}\n"
f"{Fore.RED}{Style.BRIGHT}Operation: DELETE{Style.RESET_ALL}\n"
f"{Fore.RED}The file 'file_to_delete.txt' will be deleted.{Style.RESET_ALL}"
)
self.assertEqual(result, expected_output)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 543aec2

Please sign in to comment.