mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-09 21:27:53 -05:00
154 lines
5.0 KiB
Python
154 lines
5.0 KiB
Python
from pathlib import Path
|
|
import os
|
|
from typing import Optional, Union
|
|
|
|
from utils.style import color_green
|
|
from utils.ignore import IgnoreMatcher
|
|
|
|
def update_file(path: str, new_content: Union[str, bytes], project=None):
|
|
"""
|
|
Update file with the new content.
|
|
|
|
:param path: Full path to the file
|
|
:param new_content: New content to write to the file
|
|
:param project: Optional; a Project object related to the file update. Default is None.
|
|
|
|
Any intermediate directories will be created if they don't exist.
|
|
If file is text, it will be written using UTF-8 encoding.
|
|
"""
|
|
# TODO: we should know where project root is and ensure no
|
|
# files are written outside of it.
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
|
|
if isinstance(new_content, str):
|
|
file_mode = "w"
|
|
encoding = "utf-8"
|
|
else:
|
|
file_mode = "wb"
|
|
encoding = None
|
|
|
|
with open(path, file_mode, encoding=encoding) as file:
|
|
file.write(new_content)
|
|
if project is not None: # project can be None only in tests
|
|
if not project.skip_steps:
|
|
print({"path": path, "line": None}, type='openFile')
|
|
if not project.check_ipc():
|
|
print(color_green(f"Updated file {path}"))
|
|
|
|
|
|
def get_file_contents(
|
|
path: str, project_root_path: str
|
|
) -> dict[str, Union[str, bytes]]:
|
|
"""
|
|
Get file content and metadata.
|
|
|
|
:param path: Full path to the file
|
|
:param project_root_path: Full path to the project root directory
|
|
:return: Object with the following keys:
|
|
- name: File name
|
|
- path: Relative path to the file
|
|
- content: File content (str or bytes)
|
|
- full_path: Full path to the file
|
|
|
|
If file is text, it will be read using UTF-8 encoding and `content`
|
|
will be a Python string. If that fails, it will be treated as a
|
|
binary file and `content` will be a Python bytes object.
|
|
"""
|
|
# Normalize the path to avoid issues with different path separators
|
|
full_path = os.path.normpath(path)
|
|
|
|
try:
|
|
# Assume it's a text file using UTF-8 encoding
|
|
with open(full_path, "r", encoding="utf-8") as file:
|
|
file_content = file.read()
|
|
except UnicodeDecodeError:
|
|
# If that fails, we'll treat it as a binary file
|
|
with open(full_path, "rb") as file:
|
|
file_content = file.read()
|
|
except NotADirectoryError:
|
|
raise ValueError(f"Path is not a directory: {path}")
|
|
except FileNotFoundError:
|
|
raise ValueError(f"File not found: {full_path}")
|
|
except Exception as e:
|
|
raise ValueError(f"Exception in get_file_contents: {e}")
|
|
|
|
file_name = os.path.basename(path)
|
|
relative_path = str(Path(path).parent.relative_to(project_root_path))
|
|
|
|
if relative_path == '.':
|
|
relative_path = ''
|
|
|
|
return {
|
|
"name": file_name,
|
|
"path": relative_path,
|
|
"content": file_content,
|
|
"full_path": full_path,
|
|
}
|
|
|
|
|
|
def get_directory_contents(
|
|
directory: str,
|
|
ignore: Optional[list[str]] = None,
|
|
) -> list[dict[str, Union[str, bytes]]]:
|
|
"""
|
|
Get the content of all files in the given directory.
|
|
|
|
:param directory: Full path to the directory to search
|
|
:param ignore: List of files or folders to ignore (optional)
|
|
:return: List of file objects as returned by `get_file_contents`
|
|
|
|
See `get_file_contents()` for the details on the output structure
|
|
and how files are read.
|
|
"""
|
|
return_array = []
|
|
|
|
matcher = IgnoreMatcher(ignore, root_path=directory)
|
|
|
|
# TODO: Convert to use pathlib.Path.walk()
|
|
for dpath, dirs, files in os.walk(directory):
|
|
# In-place update of dirs so that os.walk() doesn't traverse them
|
|
dirs[:] = [
|
|
d for d in dirs
|
|
if not matcher.ignore(os.path.join(dpath, d))
|
|
]
|
|
|
|
for file in files:
|
|
full_path = os.path.join(dpath, file)
|
|
if matcher.ignore(full_path):
|
|
continue
|
|
|
|
return_array.append(get_file_contents(full_path, directory))
|
|
|
|
return return_array
|
|
|
|
|
|
def clear_directory(directory: str, ignore: Optional[list[str]] = None):
|
|
"""
|
|
Delete all files and directories (except ignored ones) in the given directory.
|
|
|
|
:param dir_path: Full path to the directory to clear
|
|
:param ignore: List of files or folders to ignore (optional)
|
|
"""
|
|
matcher = IgnoreMatcher(ignore, root_path=directory)
|
|
|
|
# TODO: Convert to use pathlib.Path.walk()
|
|
for dpath, dirs, files in os.walk(directory, topdown=True):
|
|
# In-place update of dirs so that os.walk() doesn't traverse them
|
|
dirs[:] = [
|
|
d for d in dirs
|
|
if not matcher.ignore(os.path.join(dpath, d))
|
|
]
|
|
|
|
for file in files:
|
|
full_path = os.path.join(dpath, file)
|
|
if matcher.ignore(full_path):
|
|
continue
|
|
|
|
os.remove(full_path)
|
|
|
|
# Delete empty subdirectories not in ignore list
|
|
for d in dirs:
|
|
subdir_path = os.path.join(dpath, d)
|
|
if not os.listdir(subdir_path):
|
|
os.rmdir(subdir_path)
|