Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 33 additions & 54 deletions llama_patch/llama_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,25 @@ class LlamaPatchException(Exception):
def apply_patch(llmpatch, out, cwd, verbose):
if verbose:
logger.setLevel(logging.DEBUG)

os.chdir(cwd)

patch_content = llmpatch.read()
patches = parse_patches(patch_content)

try:
diff_output = []
for patch in patches:
file_path, element_type, element_name, changes = patch
logger.debug(f"Processing patch for {element_type} {element_name} in {file_path}")
apply_patch_to_file(file_path, element_type, element_name, changes)

diff_output.append(generate_diff_for_patch(file_path, element_type, element_name, changes))
if out.name != '<stdout>':
generate_patch_file(out.name)
with open(out.name, 'w') as file:
file.write('\n'.join(diff_output))
else:
sys.stdout.write(generate_patch_diff())

sys.stdout.write('\n'.join(diff_output))
except LlamaPatchException as e:
logger.error(f"Error applying patch: {e}")
sys.exit(1)
Expand All @@ -46,49 +48,45 @@ def apply_patch(llmpatch, out, cwd, verbose):
def parse_patches(patch_content):
patch_pattern = re.compile(r'^--- (.+?)\n\?\? (\w+)\s*(\w+)?\n((?:[+\-].*?\n)+)', re.DOTALL | re.MULTILINE)
matches = patch_pattern.findall(patch_content)

if not matches:
raise LlamaPatchException("No valid patches found in the provided patch content.")

patches = []
for match in matches:
path, element_type, element_name, changes = match
changes = changes.strip().split('\n')
patches.append((path, element_type, element_name, changes))

return patches

def apply_patch_to_file(file_path, element_type, element_name, changes):
def generate_diff_for_patch(file_path, element_type, element_name, changes):
if not os.path.isfile(file_path):
raise LlamaPatchException(f"File not found: {file_path}")

with open(file_path, 'r') as file:
original_code = file.read()

try:
if element_type in ["function", "def"]:
updated_code = apply_function_patch(original_code, element_name, changes)
elif element_type == "class":
updated_code = apply_class_patch(original_code, element_name, changes)
elif element_type == "struct":
updated_code = apply_struct_patch(original_code, element_name, changes)
elif element_type == "<<":
updated_code = prepend_code(original_code, changes)
elif element_type == ">>":
updated_code = append_code(original_code, changes)
elif element_type == "call":
updated_code = apply_call_patch(original_code, element_name, changes)
else:
raise LlamaPatchException(f"Unsupported element type: {element_type}")

with open(file_path, 'w') as file:
file.write(updated_code)
except LlamaPatchException as e:
logger.error(f"Error applying patch for {element_type} {element_name} in {file_path}: {str(e)}")
logger.debug(f"Context code:\n{original_code}\nChanges:\n{changes}")
raise
if element_type in ["function", "def"]:
updated_code = apply_function_patch(original_code, element_name, changes)
elif element_type == "class":
updated_code = apply_class_patch(original_code, element_name, changes)
elif element_type == "struct":
updated_code = apply_struct_patch(original_code, element_name, changes)
elif element_type == "<<":
updated_code = prepend_code(original_code, changes)
elif element_type == ">>":
updated_code = append_code(original_code, changes)
elif element_type == "call":
updated_code = apply_call_patch(original_code, element_name, changes)
else:
raise LlamaPatchException(f"Unsupported element type: {element_type}")

original_lines = original_code.splitlines(keepends=True)
updated_lines = updated_code.splitlines(keepends=True)

diff = difflib.unified_diff(original_lines, updated_lines, fromfile=file_path, tofile=file_path)
return ''.join(diff)

def apply_function_patch(original_code, function_name, changes):
function_pattern = re.compile(rf'def {function_name}\(.*?\):\n((?:\s+.*?\n)*)', re.DOTALL)
Expand Down Expand Up @@ -159,7 +157,7 @@ def append_code(original_code, changes):
return updated_code

def generate_new_code(context_code, changes, call=False):
context_lines = context_code.strip().split('\n')
context_lines = context_code.split('\n')
new_code = []

try:
Expand All @@ -179,25 +177,6 @@ def generate_new_code(context_code, changes, call=False):
else:
return "\n".join(new_code)


def generate_patch_file(output_file):
diff = generate_patch_diff()
with open(output_file, 'w') as file:
file.write(diff)

def generate_patch_diff():
file_list = [f for f in os.listdir('.') if os.path.isfile(f)]
diff = []

for file_name in file_list:
with open(file_name, 'r') as file:
new_code = file.readlines()
with open(f'{file_name}.orig', 'r') as file:
old_code = file.readlines()
file_diff = difflib.unified_diff(old_code, new_code, fromfile=f'{file_name}.orig', tofile=file_name)
diff.extend(file_diff)

return ''.join(diff)

if __name__ == "__main__":
apply_patch()

32 changes: 32 additions & 0 deletions tests/python/useless-exercise.llmpatch
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--- useless-exercise.py
?? def add_numbers
- def add_numbers(a, b):
- return a + b

?? def subtract_numbers
- def subtract_numbers(a, b):
- return a - b

?? def multiply_numbers
- def multiply_numbers(a, b):
- return a * b

?? def divide_numbers
- def divide_numbers(a, b):
- return a / b if b != 0 else None

?? call add_numbers
- numbers[i] = add_numbers(numbers[i], CONST_ONE)
+ numbers[i] = numbers[i] + CONST_ONE

?? call subtract_numbers
- numbers[i] = subtract_numbers(numbers[i], CONST_TWO)
+ numbers[i] = numbers[i] - CONST_TWO

?? call multiply_numbers
- numbers[i] = multiply_numbers(numbers[i], CONST_THREE)
+ numbers[i] = numbers[i] * CONST_THREE

?? call divide_numbers
- numbers[i] = divide_numbers(numbers[i], CONST_ONE)
+ numbers[i] = numbers[i] / CONST_ONE
142 changes: 142 additions & 0 deletions tests/python/useless-exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# This is a very long Python program that essentially does nothing of importance.
# It includes a lot of repetitive and unnecessary code to increase its length.

import sys
import time
import random

# Define some constants
CONST_ONE = 1
CONST_TWO = 2
CONST_THREE = 3

# This function does nothing
def do_nothing():
pass

# This function adds two numbers and returns the result
def add_numbers(a, b):
return a + b

# This function subtracts two numbers and returns the result
def subtract_numbers(a, b):
return a - b

# This function multiplies two numbers and returns the result
def multiply_numbers(a, b):
return a * b

# This function divides two numbers and returns the result
def divide_numbers(a, b):
return a / b if b != 0 else None

# This function does nothing useful
def useless_function():
for i in range(100):
do_nothing()

# This class represents a useless object
class UselessClass:
def __init__(self):
self.value = 0

def increment_value(self):
self.value += 1

def decrement_value(self):
self.value -= 1

def get_value(self):
return self.value

# Create a list of numbers
numbers = list(range(1000))

# Perform some meaningless operations on the list
for i in range(len(numbers)):
numbers[i] = add_numbers(numbers[i], CONST_ONE)
numbers[i] = subtract_numbers(numbers[i], CONST_TWO)
numbers[i] = multiply_numbers(numbers[i], CONST_THREE)
numbers[i] = divide_numbers(numbers[i], CONST_ONE)

# Print the list of numbers
for number in numbers:
print(number)

# Create an instance of the useless class
useless_instance = UselessClass()

# Perform some operations on the useless instance
for i in range(100):
useless_instance.increment_value()
useless_instance.decrement_value()
print(useless_instance.get_value())

# Another useless function
def another_useless_function():
for i in range(50):
do_nothing()

# Yet another useless function
def yet_another_useless_function():
for i in range(200):
do_nothing()

# More useless code
for i in range(100):
another_useless_function()
yet_another_useless_function()

# A function that simulates doing something important
def pretend_to_do_something_important():
print("Pretending to do something important...")
time.sleep(1)

# Execute the pretend function
for i in range(10):
pretend_to_do_something_important()

# Another class that does nothing
class AnotherUselessClass:
def __init__(self):
self.data = []

def add_data(self, item):
self.data.append(item)

def remove_data(self, item):
if item in self.data:
self.data.remove(item)

def get_data(self):
return self.data

# Create an instance of another useless class
another_useless_instance = AnotherUselessClass()

# Perform some operations on the useless instance
for i in range(10):
another_useless_instance.add_data(i)
print(another_useless_instance.get_data())

for i in range(10):
another_useless_instance.remove_data(i)
print(another_useless_instance.get_data())

# Simulate a random meaningless task
def random_task():
tasks = ["task1", "task2", "task3"]
selected_task = random.choice(tasks)
print(f"Performing {selected_task}...")

# Execute the random task function multiple times
for i in range(10):
random_task()

# A final useless function to end the program
def final_useless_function():
print("This is the end of the useless program.")

# Execute the final useless function
final_useless_function()

11 changes: 11 additions & 0 deletions tests/rust/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// example.rs

struct MyStruct {
x: i32,
y: i32,
}

fn my_function(a: i32) -> i32 {
a + 1
}

11 changes: 11 additions & 0 deletions tests/rust/example_added.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// example.rs

struct MyStruct {
x: i32,
y: i32,
}

fn my_function(a: i32) -> i32 {
a + 1
}

11 changes: 11 additions & 0 deletions tests/rust/example_removed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// example.rs

struct MyStruct {
x: i32,
y: i32,
}

fn my_function(a: i32) -> i32 {
a + 1
}

11 changes: 11 additions & 0 deletions tests/rust/example_replaced.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// example.rs

struct MyStruct {
x: i32,
y: i32,
}

fn my_function(a: i32) -> i32 {
a + 1
}

7 changes: 7 additions & 0 deletions tests/rust/patch_add_function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"file": "example.rs",
"type": "fn",
"name": "new_function",
"code": "fn new_function(b: i32) -> i32 {\n b * b\n}"
}

7 changes: 7 additions & 0 deletions tests/rust/patch_remove_struct.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"file": "example.rs",
"type": "struct",
"name": "MyStruct",
"code": null
}

7 changes: 7 additions & 0 deletions tests/rust/patch_replace_function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"file": "example.rs",
"type": "fn",
"name": "my_function",
"code": "fn my_function(a: i32) -> i32 {\n a * 2\n}"
}

Loading