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 import subprocess
from core.editor import CodePatcher, CodeReader, CodeWriter
from .base import tool, BaseToolSet, ToolScope, SessionGetter from .base import tool, BaseToolSet, ToolScope, SessionGetter
from logger import logger from logger import logger
@ -48,21 +49,8 @@ class CodeEditor(BaseToolSet):
"and the output will be code. ", "and the output will be code. ",
) )
def read(self, inputs: str) -> str: 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: try:
with open(filename, "r") as f: output = CodeReader.read(inputs)
code = f.readlines()
if isinstance(line, int):
code = code[line - 1]
else:
code = "".join(code[line[0] - 1 : line[1]])
output = code
except Exception as e: except Exception as e:
output = str(e) output = str(e)
@ -72,49 +60,65 @@ class CodeEditor(BaseToolSet):
) )
return output 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( @tool(
name="CodeEditor.WRITE", name="CodeEditor.WRITE",
description="Write code to create a new tool. " 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.APPEND tool. "
"If the code is completed, use the Terminal tool to execute it, if not, append the code through the CodeEditor tool. "
"Input should be filename and code. " "Input should be filename and code. "
"ex. test.py\nprint('hello world')\n " "ex. test.py\nprint('hello world')\n "
"and the output will be last 3 line.", "and the output will be last 3 line.",
) )
def write(self, inputs: str) -> str: def write(self, inputs: str) -> str:
filename, code = inputs.split("\n", 1)
try: try:
with open(filename, "a") as f: code = CodeWriter.write(inputs)
f.write(code)
output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:]) output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:])
except Exception as e: except Exception as e:
output = str(e) output = str(e)
logger.debug( logger.debug(
f"\nProcessed CodeEditor, Input Codes: {code} " f"Output Answer: {output}" f"\nProcessed CodeEditor, Input: {inputs} " f"Output Answer: {output}"
) )
return output return output
@tool( @tool(
name="CodeEditor.PATCH", name="CodeEditor.PATCH",
description="Correct the error throught the code patch if an error occurs. " 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 \\n.The patch consists of a file name, line number, and new code. It is seperated by -||-. " "Input is a list of patches. The patch is separated by {seperator}. ".format(
"ex. \"test.py-||-1-||-print('hello world')\ntest.py-||-2-||-print('hello world')\n\" " seperator=CodePatcher.separator.replace("\n", "\\n")
"and the output will be success or error message. ", )
+ "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: def patch(self, patches: str) -> str:
for patch in patches.split("\n"): try:
filename, line_number, new_line = patch.split("-||-") # TODO: fix this w, d = CodePatcher.patch(patches)
try: output = f"successfully wrote {w}, deleted {d}"
with open(filename, "r") as f: except Exception as e:
lines = f.readlines() output = str(e)
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)
logger.debug( logger.debug(
f"\nProcessed CodeEditor, Input Patch: {patches} " f"\nProcessed CodeEditor, Input Patch: {patches} "
f"Output Answer: {output}" f"Output Answer: {output}"

Loading…
Cancel
Save