99
1010import requests .exceptions
1111import tiktoken
12- from jinja2 import Environment , FileSystemLoader , TemplateNotFound
12+ from jinja2 import Environment , FileSystemLoader , Template , TemplateNotFound
1313
14- from gitingest .schemas import FileSystemDirectory , FileSystemFile , FileSystemNode , FileSystemSymlink , Source
15- from gitingest .schemas .filesystem import SEPARATOR , ContextV1 , FileSystemNodeType , GitRepository
14+ from gitingest .schemas import ContextV1 , FileSystemNode , Source
15+ from gitingest .schemas .filesystem import SEPARATOR , FileSystemNodeType
1616from gitingest .utils .compat_func import readlink
1717from gitingest .utils .logging_config import get_logger
1818
2828]
2929
3030
31- # Backward compatibility
32-
33-
34- def _create_summary_prefix (query : IngestionQuery , * , single_file : bool = False ) -> str :
35- """Create a prefix string for summarizing a repository or local directory.
36-
37- Includes repository name (if provided), commit/branch details, and subpath if relevant.
38-
39- Parameters
40- ----------
41- query : IngestionQuery
42- The parsed query object containing information about the repository and query parameters.
43- single_file : bool
44- A flag indicating whether the summary is for a single file (default: ``False``).
45-
46- Returns
47- -------
48- str
49- A summary prefix string containing repository, commit, branch, and subpath details.
50-
51- """
52- parts = []
53-
54- if query .user_name :
55- parts .append (f"Repository: { query .user_name } /{ query .repo_name } " )
56- else :
57- # Local scenario
58- parts .append (f"Directory: { query .slug } " )
59-
60- if query .tag :
61- parts .append (f"Tag: { query .tag } " )
62- elif query .branch and query .branch not in ("main" , "master" ):
63- parts .append (f"Branch: { query .branch } " )
64-
65- if query .commit :
66- parts .append (f"Commit: { query .commit } " )
67-
68- if query .subpath != "/" and not single_file :
69- parts .append (f"Subpath: { query .subpath } " )
70-
71- return "\n " .join (parts ) + "\n "
72-
73-
7431def _gather_file_contents (node : FileSystemNode ) -> str :
7532 """Recursively gather contents of all files under the given node.
7633
@@ -181,71 +138,76 @@ def _format_token_count(text: str) -> str | None:
181138
182139def generate_digest (context : ContextV1 ) -> str :
183140 """Generate a digest string from a ContextV1 object.
184-
141+
185142 This is a convenience function that uses the DefaultFormatter to format a ContextV1.
186-
143+
187144 Parameters
188145 ----------
189146 context : ContextV1
190147 The ContextV1 object containing sources and query information.
191-
148+
192149 Returns
193150 -------
194151 str
195152 The formatted digest string.
153+
196154 """
197155 formatter = DefaultFormatter ()
198156 return formatter .format (context , context .query )
199157
200158
201159class DefaultFormatter :
202- def __init__ (self ):
160+ """Default formatter for rendering filesystem nodes using Jinja2 templates."""
161+
162+ def __init__ (self ) -> None :
203163 self .separator = SEPARATOR
204164 template_dir = Path (__file__ ).parent / "format" / "DefaultFormatter"
205- self .env = Environment (loader = FileSystemLoader (template_dir ))
206-
207- def _get_template_for_node (self , node ) :
165+ self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
166+
167+ def _get_template_for_node (self , node : Source ) -> Template :
208168 """Get template based on node class name."""
209169 template_name = f"{ node .__class__ .__name__ } .j2"
210170 return self .env .get_template (template_name )
211171
212172 @singledispatchmethod
213- def format (self , node : Source , query ) :
173+ def format (self , node : Source , query : IngestionQuery ) -> str :
214174 """Dynamically format any node type based on available templates."""
215175 try :
216176 template = self ._get_template_for_node (node )
217177 # Provide common template variables
218178 context_vars = {
219- ' node' : node ,
220- ' query' : query ,
221- ' formatter' : self ,
222- ' SEPARATOR' : SEPARATOR
179+ " node" : node ,
180+ " query" : query ,
181+ " formatter" : self ,
182+ " SEPARATOR" : SEPARATOR ,
223183 }
224184 # Special handling for ContextV1 objects
225185 if isinstance (node , ContextV1 ):
226- context_vars [' context' ] = node
186+ context_vars [" context" ] = node
227187 # Use ContextV1 for backward compatibility
228188 template = self .env .get_template ("ContextV1.j2" )
229-
189+
230190 return template .render (** context_vars )
231191 except TemplateNotFound :
232192 # Fallback: return content if available, otherwise empty string
233193 return f"{ getattr (node , 'content' , '' )} "
234194
235195
236196class DebugFormatter :
237- def __init__ (self ):
197+ """Debug formatter that shows detailed information about filesystem nodes."""
198+
199+ def __init__ (self ) -> None :
238200 self .separator = SEPARATOR
239201 template_dir = Path (__file__ ).parent / "format" / "DebugFormatter"
240- self .env = Environment (loader = FileSystemLoader (template_dir ))
241-
242- def _get_template_for_node (self , node ) :
202+ self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
203+
204+ def _get_template_for_node (self , node : Source ) -> Template :
243205 """Get template based on node class name."""
244206 template_name = f"{ node .__class__ .__name__ } .j2"
245207 return self .env .get_template (template_name )
246208
247209 @singledispatchmethod
248- def format (self , node : Source , query ) :
210+ def format (self , node : Source , query : IngestionQuery ) -> str :
249211 """Dynamically format any node type with debug information."""
250212 try :
251213 # Get the actual class name
@@ -255,11 +217,15 @@ def format(self, node: Source, query):
255217 field_names = []
256218
257219 # Try to get dataclass fields first
220+ def _raise_no_dataclass_fields () -> None :
221+ msg = "No dataclass fields found"
222+ raise AttributeError (msg )
223+
258224 try :
259225 if hasattr (node , "__dataclass_fields__" ) and hasattr (node .__dataclass_fields__ , "keys" ):
260226 field_names .extend (node .__dataclass_fields__ .keys ())
261227 else :
262- raise AttributeError # Fall through to backup method
228+ _raise_no_dataclass_fields () # Fall through to backup method
263229 except (AttributeError , TypeError ):
264230 # Fall back to getting all non-private attributes
265231 field_names = [
@@ -268,20 +234,20 @@ def format(self, node: Source, query):
268234
269235 # Format the debug output
270236 fields_str = ", " .join (field_names )
271-
237+
272238 # Try to get specific template, fallback to Source.j2
273239 try :
274240 template = self ._get_template_for_node (node )
275241 except TemplateNotFound :
276242 template = self .env .get_template ("Source.j2" )
277-
243+
278244 return template .render (
279245 SEPARATOR = SEPARATOR ,
280246 class_name = class_name ,
281247 fields_str = fields_str ,
282248 node = node ,
283249 query = query ,
284- formatter = self
250+ formatter = self ,
285251 )
286252 except TemplateNotFound :
287253 # Ultimate fallback
@@ -291,34 +257,34 @@ def format(self, node: Source, query):
291257class SummaryFormatter :
292258 """Dedicated formatter for generating summaries of filesystem nodes."""
293259
294- def __init__ (self ):
260+ def __init__ (self ) -> None :
295261 template_dir = Path (__file__ ).parent / "format" / "SummaryFormatter"
296- self .env = Environment (loader = FileSystemLoader (template_dir ))
297-
298- def _get_template_for_node (self , node ) :
262+ self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
263+
264+ def _get_template_for_node (self , node : Source ) -> Template :
299265 """Get template based on node class name."""
300266 template_name = f"{ node .__class__ .__name__ } .j2"
301267 return self .env .get_template (template_name )
302268
303269 @singledispatchmethod
304- def summary (self , node : Source , query ) :
270+ def summary (self , node : Source , query : IngestionQuery ) -> str :
305271 """Dynamically generate summary for any node type based on available templates."""
306272 try :
307273 # Provide common template variables
308274 context_vars = {
309- ' node' : node ,
310- ' query' : query ,
311- ' formatter' : self
275+ " node" : node ,
276+ " query" : query ,
277+ " formatter" : self ,
312278 }
313-
279+
314280 # Special handling for ContextV1 objects
315281 if isinstance (node , ContextV1 ):
316- context_vars [' context' ] = node
282+ context_vars [" context" ] = node
317283 # Use ContextV1 for backward compatibility
318284 template = self .env .get_template ("ContextV1.j2" )
319285 else :
320286 template = self ._get_template_for_node (node )
321-
287+
322288 return template .render (** context_vars )
323289 except TemplateNotFound :
324290 # Fallback: return name if available
0 commit comments