11
11
import tiktoken
12
12
from jinja2 import Environment , FileSystemLoader , Template , TemplateNotFound
13
13
14
- from gitingest .schemas import ContextV1 , FileSystemNode , Source
14
+ from gitingest .schemas import FileSystemNode , Source
15
15
from gitingest .schemas .filesystem import SEPARATOR , FileSystemNodeType
16
16
from gitingest .utils .compat_func import readlink
17
17
from gitingest .utils .logging_config import get_logger
@@ -136,15 +136,15 @@ def _format_token_count(text: str) -> str | None:
136
136
return str (total_tokens )
137
137
138
138
139
- def generate_digest (context : ContextV1 ) -> str :
140
- """Generate a digest string from a ContextV1 object.
139
+ def generate_digest (context : Source ) -> str :
140
+ """Generate a digest string from a Source object.
141
141
142
- This is a convenience function that uses the DefaultFormatter to format a ContextV1 .
142
+ This is a convenience function that uses the DefaultFormatter to format a Source .
143
143
144
144
Parameters
145
145
----------
146
- context : ContextV1
147
- The ContextV1 object containing sources and query information.
146
+ context : Source
147
+ The Source object containing sources and query information.
148
148
149
149
Returns
150
150
-------
@@ -156,57 +156,103 @@ def generate_digest(context: ContextV1) -> str:
156
156
return formatter .format (context , context .query )
157
157
158
158
159
- class DefaultFormatter :
160
- """Default formatter for rendering filesystem nodes using Jinja2 templates ."""
159
+ class Formatter :
160
+ """Base formatter class ."""
161
161
162
- def __init__ (self ) -> None :
162
+ def __init__ (self , template_subdir : str ) -> None :
163
163
self .separator = SEPARATOR
164
- template_dir = Path (__file__ ).parent / "format" / "DefaultFormatter"
164
+ template_dir = Path (__file__ ).parent / "format" / template_subdir
165
165
self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
166
166
167
167
def _get_template_for_node (self , node : Source ) -> Template :
168
168
"""Get template based on node class name."""
169
169
template_name = f"{ node .__class__ .__name__ } .j2"
170
170
return self .env .get_template (template_name )
171
171
172
+
173
+ class DefaultFormatter (Formatter ):
174
+ """Default formatter for rendering filesystem nodes using Jinja2 templates."""
175
+
176
+ def __init__ (self ) -> None :
177
+ super ().__init__ ("DefaultFormatter" )
178
+
179
+ def format (self , source : Source , query : IngestionQuery ) -> str :
180
+ """Format a source with the given query."""
181
+ if query is None :
182
+ # Handle case where query is None (shouldn't happen in normal usage)
183
+ raise ValueError ("ContextV1 must have a valid query object" )
184
+
185
+ # Calculate and set token count for ContextV1
186
+ if hasattr (source , '_token_count' ):
187
+ token_count = self ._calculate_token_count (source )
188
+ source ._token_count = token_count
189
+ # Also set token count in the extra dict
190
+ source .extra ["token_count" ] = token_count
191
+
192
+ try :
193
+ return self ._format_node (source , query )
194
+ except Exception as e :
195
+ # Log the error for debugging
196
+ import logging
197
+ logging .error (f"Error in DefaultFormatter: { e } " )
198
+ raise
199
+
200
+ def _calculate_token_count (self , source : Source ) -> str :
201
+ """Calculate token count for the entire source."""
202
+ # Gather all content from the source
203
+ content = self ._gather_all_content (source )
204
+ return _format_token_count (content ) or "Unknown"
205
+
206
+ def _gather_all_content (self , node : Source ) -> str :
207
+ """Recursively gather all content from the source tree."""
208
+ content_parts = []
209
+
210
+ # Add content from the current node
211
+ if hasattr (node , 'content' ):
212
+ content_parts .append (node .content )
213
+
214
+ # Add content from all sources if it's a ContextV1
215
+ if hasattr (node , 'sources' ):
216
+ for source in node .sources :
217
+ content_parts .append (self ._gather_all_content (source ))
218
+
219
+ # Add content from children if it's a directory
220
+ if hasattr (node , 'children' ):
221
+ for child in node .children :
222
+ content_parts .append (self ._gather_all_content (child ))
223
+
224
+ return "\n " .join (filter (None , content_parts ))
225
+
172
226
@singledispatchmethod
173
- def format (self , node : Source , query : IngestionQuery ) -> str :
227
+ def _format_node (self , node : Source , query : IngestionQuery ) -> str :
174
228
"""Dynamically format any node type based on available templates."""
175
229
try :
176
230
template = self ._get_template_for_node (node )
177
231
# Provide common template variables
178
232
context_vars = {
179
- "node " : node ,
233
+ "source " : node ,
180
234
"query" : query ,
181
235
"formatter" : self ,
182
236
"SEPARATOR" : SEPARATOR ,
183
237
}
184
- # Special handling for ContextV1 objects
185
- if isinstance (node , ContextV1 ):
186
- context_vars ["context" ] = node
187
- # Use ContextV1 for backward compatibility
188
- template = self .env .get_template ("ContextV1.j2" )
189
238
190
239
return template .render (** context_vars )
191
240
except TemplateNotFound :
192
241
# Fallback: return content if available, otherwise empty string
193
242
return f"{ getattr (node , 'content' , '' )} "
194
243
195
244
196
- class DebugFormatter :
245
+ class DebugFormatter ( Formatter ) :
197
246
"""Debug formatter that shows detailed information about filesystem nodes."""
198
247
199
248
def __init__ (self ) -> None :
200
- self .separator = SEPARATOR
201
- template_dir = Path (__file__ ).parent / "format" / "DebugFormatter"
202
- self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
249
+ super ().__init__ ("DebugFormatter" )
203
250
204
251
def _get_template_for_node (self , node : Source ) -> Template :
205
252
"""Get template based on node class name."""
206
253
template_name = f"{ node .__class__ .__name__ } .j2"
207
254
return self .env .get_template (template_name )
208
255
209
- @singledispatchmethod
210
256
def format (self , node : Source , query : IngestionQuery ) -> str :
211
257
"""Dynamically format any node type with debug information."""
212
258
try :
@@ -254,37 +300,31 @@ def _raise_no_dataclass_fields() -> None:
254
300
return f"DEBUG: { node .__class__ .__name__ } "
255
301
256
302
257
- class SummaryFormatter :
303
+ class SummaryFormatter ( Formatter ) :
258
304
"""Dedicated formatter for generating summaries of filesystem nodes."""
259
305
260
306
def __init__ (self ) -> None :
261
- template_dir = Path (__file__ ).parent / "format" / "SummaryFormatter"
262
- self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
307
+ super ().__init__ ("SummaryFormatter" )
263
308
264
- def _get_template_for_node (self , node : Source ) -> Template :
265
- """Get template based on node class name."""
266
- template_name = f"{ node .__class__ .__name__ } .j2"
267
- return self .env .get_template (template_name )
309
+ def format (self , source : Source , query : IngestionQuery ) -> str :
310
+ """Generate the summary output."""
311
+ if query is None :
312
+ # Handle case where query is None (shouldn't happen in normal usage)
313
+ raise ValueError ("ContextV1 must have a valid query object" )
314
+ return self .summary (source , query )
268
315
269
316
@singledispatchmethod
270
317
def summary (self , node : Source , query : IngestionQuery ) -> str :
271
318
"""Dynamically generate summary for any node type based on available templates."""
272
319
try :
273
320
# Provide common template variables
274
321
context_vars = {
275
- "node " : node ,
322
+ "source " : node ,
276
323
"query" : query ,
277
324
"formatter" : self ,
278
325
}
279
326
280
- # Special handling for ContextV1 objects
281
- if isinstance (node , ContextV1 ):
282
- context_vars ["context" ] = node
283
- # Use ContextV1 for backward compatibility
284
- template = self .env .get_template ("ContextV1.j2" )
285
- else :
286
- template = self ._get_template_for_node (node )
287
-
327
+ template = self ._get_template_for_node (node )
288
328
return template .render (** context_vars )
289
329
except TemplateNotFound :
290
330
# Fallback: return name if available
0 commit comments