Skip to content

Commit 5efe631

Browse files
committed
refactor: eliminate isinstance() checks with pure polymorphism
Replace all isinstance() type checking with proper polymorphic methods: Pure Polymorphism Methods: - get_summary_info(): Each class returns its own summary format - is_single_file(): Boolean check without isinstance() - gather_contents(): Recursive content gathering via method dispatch - get_display_name(): Tree display formatting (/, -> target, etc.) - has_children(): Check for child nodes without type checking Benefits: - No isinstance() 'clochard' style code anywhere - True duck typing - just call methods and let Python dispatch - Cleaner, more maintainable code - Each class encapsulates its own behavior - Easy to extend with new node types Code Changes: - FileSystemNode: Base implementations for all methods - FileSystemFile: is_single_file()=True, summary with line count - FileSystemDirectory: get_display_name() adds '/', has_children() checks list - FileSystemSymlink: get_display_name() shows '-> target' - output_formatter.py: Use polymorphic methods instead of isinstance() This is proper OOP - objects know their own behavior!
1 parent da716a7 commit 5efe631

File tree

2 files changed

+54
-20
lines changed

2 files changed

+54
-20
lines changed

src/gitingest/output_formatter.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,9 @@ def format_node(node: FileSystemNode, query: IngestionQuery) -> tuple[str, str,
4141
A tuple containing the summary, directory structure, and file contents.
4242
4343
"""
44-
is_single_file = isinstance(node, FileSystemFile)
44+
is_single_file = node.is_single_file()
4545
summary = _create_summary_prefix(query, single_file=is_single_file)
46-
47-
if isinstance(node, FileSystemDirectory):
48-
summary += f"Files analyzed: {node.file_count}\n"
49-
elif isinstance(node, FileSystemFile):
50-
summary += f"File: {node.name}\n"
51-
summary += f"Lines: {len(node.content.splitlines()):,}\n"
46+
summary += node.get_summary_info()
5247

5348
tree = "Directory structure:\n" + _create_tree_structure(query, node=node)
5449

@@ -118,11 +113,7 @@ def _gather_file_contents(node: FileSystemNode) -> str:
118113
The concatenated content of all files under the given node.
119114
120115
"""
121-
if not isinstance(node, FileSystemDirectory):
122-
return node.content_string
123-
124-
# Recursively gather contents of all files under the current directory
125-
return "\n".join(_gather_file_contents(child) for child in node.children)
116+
return node.gather_contents()
126117

127118

128119
def _create_tree_structure(
@@ -161,16 +152,11 @@ def _create_tree_structure(
161152
tree_str = ""
162153
current_prefix = "└── " if is_last else "├── "
163154

164-
# Indicate directories with a trailing slash
165-
display_name = node.name
166-
if isinstance(node, FileSystemDirectory):
167-
display_name += "/"
168-
elif isinstance(node, FileSystemSymlink):
169-
display_name += " -> " + node.target
170-
155+
# Get the display name (handles directory slash, symlink target, etc.)
156+
display_name = node.get_display_name()
171157
tree_str += f"{prefix}{current_prefix}{display_name}\n"
172158

173-
if isinstance(node, FileSystemDirectory) and node.children:
159+
if node.has_children():
174160
prefix += " " if is_last else "│ "
175161
for i, child in enumerate(node.children):
176162
tree_str += _create_tree_structure(query, node=child, prefix=prefix, is_last=i == len(node.children) - 1)

src/gitingest/schemas/filesystem.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,26 @@ def get_content(self) -> str:
9696
except Exception as e:
9797
return f"Error reading content of {self.name}: {e}"
9898

99+
def get_summary_info(self) -> str:
100+
"""Return summary information. Override in subclasses."""
101+
return ""
102+
103+
def is_single_file(self) -> bool:
104+
"""Return whether this node represents a single file."""
105+
return False
106+
107+
def gather_contents(self) -> str:
108+
"""Gather file contents. Override in subclasses."""
109+
return self.content_string
110+
111+
def get_display_name(self) -> str:
112+
"""Get display name for tree view. Override in subclasses."""
113+
return self.name
114+
115+
def has_children(self) -> bool:
116+
"""Return whether this node has children to display."""
117+
return False
118+
99119
@property
100120
def content(self) -> str:
101121
"""Return file content (simplified version for backward compatibility)."""
@@ -110,6 +130,14 @@ def get_sort_priority(self) -> int:
110130
"""Files have priority 0 for sorting."""
111131
return 0
112132

133+
def get_summary_info(self) -> str:
134+
"""Return file summary information."""
135+
return f"File: {self.name}\nLines: {len(self.content.splitlines()):,}\n"
136+
137+
def is_single_file(self) -> bool:
138+
"""Files are single files."""
139+
return True
140+
113141
def render_tree(self, prefix: str = "", *, is_last: bool = True) -> list[str]:
114142
"""Render the tree representation of this file."""
115143
current_prefix = "└── " if is_last else "├── "
@@ -127,6 +155,22 @@ def get_content(self) -> str:
127155
msg = "Cannot read content of a directory node"
128156
raise ValueError(msg)
129157

158+
def get_summary_info(self) -> str:
159+
"""Return directory summary information."""
160+
return f"Files analyzed: {self.file_count}\n"
161+
162+
def gather_contents(self) -> str:
163+
"""Recursively gather contents of all files under this directory."""
164+
return "\n".join(child.gather_contents() for child in self.children)
165+
166+
def get_display_name(self) -> str:
167+
"""Directories get a trailing slash."""
168+
return self.name + "/"
169+
170+
def has_children(self) -> bool:
171+
"""Directories have children if the list is not empty."""
172+
return bool(self.children)
173+
130174
def render_tree(self, prefix: str = "", *, is_last: bool = True) -> list[str]:
131175
"""Render the tree representation of this directory."""
132176
lines = []
@@ -178,6 +222,10 @@ def get_content(self) -> str:
178222
"""Symlinks content is what they point to."""
179223
return self.target
180224

225+
def get_display_name(self) -> str:
226+
"""Symlinks show target."""
227+
return f"{self.name} -> {self.target}"
228+
181229
def render_tree(self, prefix: str = "", *, is_last: bool = True) -> list[str]:
182230
"""Render the tree representation of this symlink."""
183231
current_prefix = "└── " if is_last else "├── "

0 commit comments

Comments
 (0)