Skip to content

Commit

Permalink
Uniform comments and refactor file_selector.py
Browse files Browse the repository at this point in the history
  • Loading branch information
similato87 committed Jan 5, 2024
1 parent 9ac23f9 commit 3131fc9
Showing 1 changed file with 27 additions and 148 deletions.
175 changes: 27 additions & 148 deletions gpt_engineer/applications/cli/file_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@
Classes:
- DisplayablePath: Represents a displayable path in a file explorer, allowing for a
tree structure display in the terminal.
- file_selector: Enables terminal-based file selection.
- FileSelector: Enables terminal-based file selection.
Functions:
- is_in_ignoring_extensions: Checks if a path should be ignored based on predefined rules.
- ask_for_files: Asks user to select files or uses a previous file list.
- editor_file_selector: Displays a GUI for file selection.
- open_with_default_editor: Opens a file in the default system editor or a common fallback.
- is_utf8: Checks if a file is UTF-8 encoded.
- get_files_from_toml: Retrieves selected files from a TOML configuration.
Dependencies:
- os
- re
- sys
- subprocess
- pathlib
- typing
- toml
Note:
This module is built on top of `gpt_engineer.core.db` and assumes existence and
Expand All @@ -52,30 +55,8 @@

class DisplayablePath(object):
"""
A class that represents a path in a file system and provides functionality
to display it in a tree-like structure similar to that of a file explorer.
Class Attributes:
- display_filename_prefix_middle (str): Prefix for filenames in the middle of a list.
- display_filename_prefix_last (str): Prefix for filenames at the end of a list.
- display_parent_prefix_middle (str): Prefix for parent directories in the middle of a list.
- display_parent_prefix_last (str): Prefix for parent directories at the end of a list.
Attributes:
- depth (int): Depth of the path in relation to the root.
- path (Path): The actual path object.
- parent (DisplayablePath): Parent path. None if it's the root.
- is_last (bool): Flag to check if the current path is the last child of its parent.
Methods:
- display_name: Return the display name for the path, with directories having a trailing '/'.
- make_tree: Class method to generate a tree of DisplayablePath objects for the given root.
- _default_criteria: Default criteria for filtering paths.
- displayable: Generate the displayable string representation of the file or directory.
Note:
It is assumed that the global constant IGNORE_FOLDERS is defined elsewhere,
which lists the folder names to ignore during the tree generation.
Represents a path in a file system and displays it in a tree-like structure.
Useful for displaying file and directory structures like in a file explorer.
"""

display_filename_prefix_middle = "├── "
Expand All @@ -88,11 +69,6 @@ def __init__(
):
"""
Initialize a DisplayablePath object.
Args:
path (Union[str, Path]): The path of the file or directory.
parent_path (DisplayablePath): The parent path of the file or directory.
is_last (bool): Whether the file or directory is the last child of its parent.
"""
self.depth: int = 0
self.path = Path(str(path))
Expand All @@ -106,8 +82,6 @@ def display_name(self) -> str:
"""
Get the display name of the file or directory.
Returns:
str: The display name.
"""
if self.path.is_dir():
return self.path.name + "/"
Expand All @@ -118,20 +92,10 @@ def make_tree(
cls, root: Union[str, Path], parent=None, is_last=False, criteria=None
):
"""
Generate a tree of DisplayablePath objects.
Args:
root: The root path of the tree.
parent: The parent path of the root path. Defaults to None.
is_last: Whether the root path is the last child of its parent.
criteria: The criteria function to filter the paths. Defaults to None.
Yields:
DisplayablePath: The DisplayablePath objects in the tree.
Generate a tree of DisplayablePath objects..
"""
root = Path(str(root))
criteria = criteria or cls._default_criteria

displayable_root = cls(root, parent, is_last)
yield displayable_root

Expand All @@ -154,21 +118,12 @@ def make_tree(
def _default_criteria(cls, path: Path) -> bool:
"""
The default criteria function to filter the paths.
Args:
path: The path to check.
Returns:
bool: True if the path should be included, False otherwise.
"""
return True

def displayable(self) -> str:
"""
Get the displayable string representation of the file or directory.
Returns:
str: The displayable string representation.
"""
if self.parent is None:
return self.display_name
Expand All @@ -193,74 +148,10 @@ def displayable(self) -> str:
return "".join(reversed(parts))


class FileSelector:
"""
A terminal-based file selector for navigating and selecting files from a specified root folder.
Attributes:
number_of_selectable_items (int): The number of items (files) that can be selected.
selectable_file_paths (dict[int, str]): A mapping from index number to the corresponding file path.
file_path_list (list): A list containing paths of the displayed files.
db_paths: A structured representation of all paths (both files and directories) within the root folder.
Args:
root_folder_path (Path): The root folder path from where files are to be listed and selected.
Methods:
display(): Prints the list of files and directories to the terminal, allowing files to be selectable by number.
ask_for_selection() -> List[str]: Prompts the user to select files by providing index numbers and returns the list of selected file paths.
"""

def __init__(self, root_folder_path: Path) -> None:
self.number_of_selectable_items = 0
self.selectable_file_paths: dict[int, str] = {}
self.file_path_list: list = []
self.db_paths = DisplayablePath.make_tree(
root_folder_path, parent=None, criteria=is_in_ignoring_extensions
)
self.root_folder_path = root_folder_path

def display(self):
"""
Displays a list of files from the root folder in the terminal. Files are enumerated for selection,
while directories are simply listed (currently non-selectable).
"""
count = 0
file_path_enumeration = {}
file_path_list = []
for path in self.db_paths:
n_digits = len(str(count))
n_spaces = 3 - n_digits
if n_spaces < 0:
# We can only print 1000 aligned files. I think it is decent enough
n_spaces = 0
spaces_str = " " * n_spaces
if not path.path.is_dir():
print(f"{count}. {spaces_str}{path.displayable()}")
file_path_enumeration[count] = path.path
file_path_list.append(path.path)
count += 1
else:
# By now we do not accept selecting entire dirs.
# But could add that in the future. Just need to add more functions
# and remove this else block...
number_space = " " * n_digits
print(f"{number_space} {spaces_str}{path.displayable()}")

self.number_of_selectable_items = count
self.file_path_list = file_path_list
self.selectable_file_paths = file_path_enumeration


def is_in_ignoring_extensions(path: Path) -> bool:
"""
Check if a path is not hidden or in the __pycache__ directory.
Args:
path: The path to check.
Returns:
bool: True if the path is not in ignored rules. False otherwise.
Check if a path is not hidden or in the '__pycache__' directory.
Helps in filtering out unnecessary files during file selection.
"""
is_hidden = not path.name.startswith(".")
is_pycache = "__pycache__" not in path.name
Expand All @@ -269,11 +160,8 @@ def is_in_ignoring_extensions(path: Path) -> bool:

def ask_for_files(project_path: Union[str, Path]) -> FilesDict:
"""
Ask user to select files to improve.
It can be done by terminal, gui, or using the old selection.
Returns:
dict[str, str]: Dictionary where key = file name and value = file path
Asks the user to select files for the purpose of context improvement.
It supports selection from the terminal or using a previously saved list.
"""

metadata_db = DiskMemory(metadata_path(project_path))
Expand All @@ -282,10 +170,6 @@ def ask_for_files(project_path: Union[str, Path]) -> FilesDict:
selected_files = get_files_from_toml(
project_path, metadata_db.path / FILE_LIST_NAME
)
# print("Test mode: Simulating file selection")
# resolved_path = Path(project_path).resolve()
# all_files = list(resolved_path.glob("**/*"))
# selected_files = [file for file in all_files if file.is_file()]
else:
if FILE_LIST_NAME in metadata_db:
print(
Expand All @@ -295,10 +179,12 @@ def ask_for_files(project_path: Union[str, Path]) -> FilesDict:
selected_files = editor_file_selector(project_path, False)
else:
selected_files = editor_file_selector(project_path, True)

content_dict = {}
for file_path in selected_files:
# selected files contains paths that are relative to the project path
file_path = Path(file_path)
file_path = Path(
file_path
) # selected files contains paths that are relative to the project path
try:
# to open the file we need the path from the cwd
with open(Path(project_path) / file_path, "r") as content:
Expand All @@ -310,13 +196,8 @@ def ask_for_files(project_path: Union[str, Path]) -> FilesDict:

def open_with_default_editor(file_path):
"""
Opens the specified file using the system's default text editor or a common fallback.
Parameters:
- file_path (str): The path to the file to be opened.
Attempts to open the specified file using the system's default text editor or a common fallback editor.
"""

editors = ["vim", "nano", "notepad", "gedit"] # A list of common editors
chosen_editor = os.environ.get("EDITOR") # Get the preferred editor if set

Expand All @@ -338,13 +219,8 @@ def open_with_default_editor(file_path):

def is_utf8(file_path):
"""
Determines if a file is UTF-8 encoded by attempting to decode it.
Parameters:
- file_path (str): Path to the file.
Returns:
- bool: True if file is UTF-8 encoded, False otherwise.
Determines if the file is UTF-8 encoded by trying to read and decode it.
Useful for ensuring that files are in a readable and compatible format.
"""
try:
with open(file_path, "rb") as file:
Expand All @@ -357,13 +233,13 @@ def is_utf8(file_path):

def editor_file_selector(input_path: str, init: bool = True) -> List[str]:
"""
Display an editor file selection to select context files.
Generates a tree representation in a .toml file and allows users to edit it.
Users can comment out files to ignore them.
Provides an interactive file selection interface by generating a tree representation in a .toml file.
Allows users to select or deselect files for the context improvement process.
"""
root_path = Path(input_path)
tree_dict = {"files": {}}
toml_file = DiskMemory(metadata_path(input_path)).path / "file_selection.toml"

if init:
for path in DisplayablePath.make_tree(root_path):
if path.path.is_dir() or not is_utf8(path.path):
Expand All @@ -386,11 +262,14 @@ def editor_file_selector(input_path: str, init: bool = True) -> List[str]:
"Please select(true) and deselect(false) files, save it, and close it to continue..."
)
open_with_default_editor(toml_file)

return get_files_from_toml(input_path, toml_file)


def get_files_from_toml(input_path, toml_file):
"""
Retrieves the list of files selected by the user from a .toml configuration file.
This function parses the .toml file and returns the list of selected files.
"""
selected_files = []
edited_tree = toml.load(toml_file)
for file, properties in edited_tree["files"].items():
Expand Down

0 comments on commit 3131fc9

Please sign in to comment.