feat: code editor (#5)

* feat: code editor

* feat: code writer

* doc: fix comment
pull/7/head
ChungHwan Han 1 year ago committed by GitHub
parent cca84704ff
commit 65c0c6de80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,3 @@
from .patch import CodePatcher
from .read import CodeReader
from .write import CodeWriter

@ -0,0 +1,179 @@
"""
patch protocol:
<filepath>|<line>,<col>|<line>,<col>|<content>
---~~~+++===+++~~~---
<filepath>|<line>,<col>|<line>,<col>|<content>
---~~~+++===+++~~~---
...
---~~~+++===+++~~~---
let say original code is:
```
import requests
def crawl_news(keyword):
url = f"https://www.google.com/search?q={keyword}+news"
response = requests.get(url)
news = []
for result in response:
news.append(result.text)
return news
```
and we want to change it to:
```
import requests
from bs4 import BeautifulSoup
def crawl_news(keyword):
url = f"https://www.google.com/search?q={keyword}+news"
html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")
news_results = soup.find_all("div", class_="BNeawe vvjwJb AP7Wnd")
news_titles = []
for result in news_results:
news_titles.append(result.text)
return news_titles
```
then the command will be:
test.py|2,1|2,1|from bs4 import BeautifulSoup
---~~~+++===+++~~~---
test.py|5,5|5,33|html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")
news_results = soup.find_all("div", class_="BNeawe vvjwJb AP7Wnd")
---~~~+++===+++~~~---
test.py|7,5|9,13|news_titles = []
for result in news_results:
news_titles
---~~~+++===+++~~~---
test.py|11,16|11,16|_titles
"""
from typing import Tuple
class Position:
separator = ","
def __init__(self, line: int, col: int):
self.line: int = line
self.col: int = col
@staticmethod
def from_str(pos: str) -> "Position":
line, col = pos.split(Position.separator)
return Position(int(line) - 1, int(col) - 1)
class PatchCommand:
separator = "|"
def __init__(self, filepath: str, start: Position, end: Position, content: str):
self.filepath: str = filepath
self.start: Position = start
self.end: Position = end
self.content: str = content
def read_lines(self) -> list[str]:
with open(self.filepath, "r") as f:
lines = f.readlines()
return lines
def write_lines(self, lines: list[str]) -> int:
with open(self.filepath, "w") as f:
f.writelines(lines)
return sum([len(line) for line in lines])
def execute(self) -> Tuple[int, int]:
lines = self.read_lines()
before = sum([len(line) for line in lines])
lines[self.start.line] = (
lines[self.start.line][: self.start.col]
+ self.content
+ lines[self.end.line][self.end.col :]
)
lines = lines[: self.start.line + 1] + lines[self.end.line + 1 :]
after = self.write_lines(lines)
written = len(self.content)
deleted = before - after + written
return written, deleted
@staticmethod
def from_str(command: str) -> "PatchCommand":
filepath, start, end = command.split(PatchCommand.separator)[:3]
content = command[len(filepath + start + end) + 3 :]
return PatchCommand(
filepath, Position.from_str(start), Position.from_str(end), content
)
class CodePatcher:
separator = "\n---~~~+++===+++~~~---\n"
@staticmethod
def sort_commands(commands: list[PatchCommand]) -> list[PatchCommand]:
return sorted(commands, key=lambda c: c.start.line, reverse=True)
@staticmethod
def patch(bulk_command: str) -> Tuple[int, int]:
commands = [
PatchCommand.from_str(command)
for command in bulk_command.split(CodePatcher.separator)
if command != ""
]
commands = CodePatcher.sort_commands(commands)
written, deleted = 0, 0
for command in commands:
if command:
w, d = command.execute()
written += w
deleted += d
return written, deleted
if __name__ == "__main__":
commands = """test.py|2,1|2,1|from bs4 import BeautifulSoup
---~~~+++===+++~~~---
test.py|5,5|5,33|html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")
news_results = soup.find_all("div", class_="BNeawe vvjwJb AP7Wnd")
---~~~+++===+++~~~---
test.py|7,5|9,13|news_titles = []
for result in news_results:
news_titles
---~~~+++===+++~~~---
test.py|11,16|11,16|_titles
"""
example = """import requests
def crawl_news(keyword):
url = f"https://www.google.com/search?q={keyword}+news"
response = requests.get(url)
news = []
for result in response:
news.append(result.text)
return news
"""
testfile = "test.py"
with open(testfile, "w") as f:
f.write(example)
patcher = CodePatcher()
written, deleted = patcher.patch(commands)
print(f"written: {written}, deleted: {deleted}")

@ -0,0 +1,35 @@
"""
read protocol:
<filepath>|<start line>-<end line>
"""
class ReadCommand:
separator = "|"
def __init__(self, filepath: str, start: int, end: int):
self.filepath: str = filepath
self.start: int = start
self.end: int = end
def execute(self):
with open(self.filepath, "r") as f:
code = f.readlines()
if self.start == self.end:
code = code[self.start - 1]
else:
code = "".join(code[self.start - 1 : self.end])
return code
@staticmethod
def from_str(command: str) -> "ReadCommand":
filepath, line = command.split(ReadCommand.separator)
start, end = line.split("-")
return ReadCommand(filepath, int(start), int(end))
class CodeReader:
@staticmethod
def read(command: str) -> str:
return ReadCommand.from_str(command).execute()

@ -0,0 +1,39 @@
"""
write protocol:
<filepath>
<content>
"""
class WriteCommand:
separator = "\n"
def __init__(self, filepath: str, content: int):
self.filepath: str = filepath
self.content: str = content
self.mode: str = "w"
def with_mode(self, mode: str) -> "WriteCommand":
self.mode = mode
return self
def execute(self) -> str:
with open(self.filepath, self.mode) as f:
f.write(self.content)
return self.content
@staticmethod
def from_str(command: str) -> "WriteCommand":
filepath = command.split(WriteCommand.separator)[0]
return WriteCommand(filepath, command[len(filepath) + 1 :])
class CodeWriter:
@staticmethod
def write(command: str) -> str:
return WriteCommand.from_str(command).with_mode("w").execute()
@staticmethod
def append(command: str) -> str:
return WriteCommand.from_str(command).with_mode("a").execute()

@ -9,6 +9,7 @@ from bs4 import BeautifulSoup
import subprocess
from core.editor import CodePatcher, CodeReader, CodeWriter
from .base import tool, BaseToolSet, ToolScope, SessionGetter
from logger import logger
@ -48,21 +49,8 @@ class CodeEditor(BaseToolSet):
"and the output will be code. ",
)
def read(self, inputs: str) -> str:
filename, line = inputs.split(",")
line = line.split("-")
if len(line) == 1:
line = int(line[0])
else:
line = [int(i) for i in line]
try:
with open(filename, "r") as f:
code = f.readlines()
if isinstance(line, int):
code = code[line - 1]
else:
code = "".join(code[line[0] - 1 : line[1]])
output = code
output = CodeReader.read(inputs)
except Exception as e:
output = str(e)
@ -72,49 +60,65 @@ class CodeEditor(BaseToolSet):
)
return output
@tool(
name="CodeEditor.APPEND",
description="Append code to the existing file. "
"If the code is completed, use the Terminal tool to execute it, if not, append the code through the this tool. "
"Input should be filename and code to append. "
"Input code must be the code that should be appended, NOT whole code. "
"ex. test.py\nprint('hello world')\n "
"and the output will be last 3 line.",
)
def write(self, inputs: str) -> str:
try:
code = CodeWriter.append(inputs)
output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:])
except Exception as e:
output = str(e)
logger.debug(
f"\nProcessed CodeEditor, Input: {inputs} " f"Output Answer: {output}"
)
return output
@tool(
name="CodeEditor.WRITE",
description="Write code to create a new tool. "
"You must check the file's contents before writing. This tool only supports append code. "
"If the code is completed, use the Terminal tool to execute it, if not, append the code through the CodeEditor tool. "
"If the code is completed, use the Terminal tool to execute it, if not, append the code through the CodeEditor.APPEND tool. "
"Input should be filename and code. "
"ex. test.py\nprint('hello world')\n "
"and the output will be last 3 line.",
)
def write(self, inputs: str) -> str:
filename, code = inputs.split("\n", 1)
try:
with open(filename, "a") as f:
f.write(code)
code = CodeWriter.write(inputs)
output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:])
except Exception as e:
output = str(e)
logger.debug(
f"\nProcessed CodeEditor, Input Codes: {code} " f"Output Answer: {output}"
f"\nProcessed CodeEditor, Input: {inputs} " f"Output Answer: {output}"
)
return output
@tool(
name="CodeEditor.PATCH",
description="Correct the error throught the code patch if an error occurs. "
"Input is a list of patches. The patch is separated by \\n.The patch consists of a file name, line number, and new code. It is seperated by -||-. "
"ex. \"test.py-||-1-||-print('hello world')\ntest.py-||-2-||-print('hello world')\n\" "
"and the output will be success or error message. ",
description="Patch the code to correct the error if an error occurs or to improve it. "
"Input is a list of patches. The patch is separated by {seperator}. ".format(
seperator=CodePatcher.separator.replace("\n", "\\n")
)
+ "Each patch has to be formatted like below.\n"
"<filepath>|<start_line>,<start_col>|<end_line>,<end_col>|<new_code>"
"Code between start and end will be replaced with new_code. "
"The output will be written/deleted bytes or error message. ",
)
def patch(self, patches: str) -> str:
for patch in patches.split("\n"):
filename, line_number, new_line = patch.split("-||-") # TODO: fix this
try:
with open(filename, "r") as f:
lines = f.readlines()
lines[int(line_number) - 1] = new_line + "\n"
with open(filename, "w") as f:
f.writelines(lines)
output = "success"
except Exception as e:
output = str(e)
try:
w, d = CodePatcher.patch(patches)
output = f"successfully wrote {w}, deleted {d}"
except Exception as e:
output = str(e)
logger.debug(
f"\nProcessed CodeEditor, Input Patch: {patches} "
f"Output Answer: {output}"

Loading…
Cancel
Save