1111from gitingest .schemas import FileSystemNode
1212from gitingest .utils .compat_func import readlink
1313from functools import singledispatchmethod
14- from gitingest .schemas import Source , FileSystemFile , FileSystemDirectory , FileSystemSymlink , FileSystemTextFile
15- from gitingest .schemas .filesystem import SEPARATOR , FileSystemNodeType , CONTEXT_HEADER , CONTEXT_FOOTER
14+ from gitingest .schemas import Source , FileSystemFile , FileSystemDirectory , FileSystemSymlink
15+ from gitingest .schemas .filesystem import SEPARATOR , Context , FileSystemNodeType
1616from gitingest .utils .logging_config import get_logger
1717from jinja2 import Environment , BaseLoader
1818
19-
20- class OverridableDispatcher :
21- """Custom dispatcher that allows later registrations to override earlier ones, even for parent types."""
22-
23- def __init__ (self , default_func ):
24- self .default_func = default_func
25- self .registry = [] # List of (type, func) in registration order
26-
27- def register (self , type_ ):
28- def decorator (func ):
29- # Remove any existing registration for this exact type
30- self .registry = [(t , f ) for t , f in self .registry if t != type_ ]
31- # Add new registration at the end (highest priority)
32- self .registry .append ((type_ , func ))
33- return func
34- return decorator
35-
36- def __call__ (self , instance , * args , ** kwargs ):
37- # Check registrations in reverse order (most recent first)
38- for type_ , func in reversed (self .registry ):
39- if isinstance (instance , type_ ):
40- return func (instance , * args , ** kwargs )
41- # Fall back to default
42- return self .default_func (instance , * args , ** kwargs )
43-
4419if TYPE_CHECKING :
4520 from gitingest .schemas import IngestionQuery
4621
@@ -202,116 +177,164 @@ def _format_token_count(text: str) -> str | None:
202177
203178 return str (total_tokens )
204179
180+
181+ def generate_digest (context : Context ) -> str :
182+ """Generate a digest string from a Context object.
183+
184+ This is a convenience function that uses the DefaultFormatter to format a Context.
185+
186+ Parameters
187+ ----------
188+ context : Context
189+ The Context object containing sources and query information.
190+
191+ Returns
192+ -------
193+ str
194+ The formatted digest string.
195+ """
196+ formatter = DefaultFormatter ()
197+ return formatter .format (context , context .query )
198+
199+
205200class DefaultFormatter :
206201 def __init__ (self ):
207202 self .separator = SEPARATOR
208203 self .env = Environment (loader = BaseLoader ())
209204
210- # Set up custom dispatchers
211- def _default_format (node : Source , query ):
212- return f"{ getattr (node , 'content' , '' )} "
213-
214- def _default_summary (node : Source , query ):
215- return f"{ getattr (node , 'name' , '' )} "
205+ @singledispatchmethod
206+ def format (self , node : Source , query ):
207+ return f"{ getattr (node , 'content' , '' )} "
216208
217- self .format = OverridableDispatcher (_default_format )
218- self .summary = OverridableDispatcher (_default_summary )
219-
220- # Register the default implementations
221- self ._register_defaults ()
222-
223- def _register_defaults (self ):
224- @self .format .register (FileSystemFile )
225- def _ (node : FileSystemFile , query ):
226- template = \
209+ @format .register
210+ def _ (self , node : FileSystemFile , query ):
211+ template = \
227212"""
228213{{ SEPARATOR }}
229214{{ node.name }}
230215{{ SEPARATOR }}
231216
232217{{ node.content }}
233218"""
234- file_template = self .env .from_string (template )
235- return file_template .render (SEPARATOR = SEPARATOR , node = node , query = query , formatter = self )
219+ file_template = self .env .from_string (template )
220+ return file_template .render (SEPARATOR = SEPARATOR , node = node , query = query , formatter = self )
236221
237- @ self . format .register ( FileSystemDirectory )
238- def _ (node : FileSystemDirectory , query ):
239- template = \
222+ @ format .register
223+ def _ (self , node : FileSystemDirectory , query ):
224+ template = \
240225"""
226+ {% if node.depth == 0 %}
227+ {{ node.name }}:
228+ {{ node.tree }}
229+
230+ {% endif %}
241231{% for child in node.children %}
242232{{ formatter.format(child, query) }}
243233{% endfor %}
244234"""
245- dir_template = self .env .from_string (template )
246- return dir_template .render (node = node , query = query , formatter = self )
235+ dir_template = self .env .from_string (template )
236+ return dir_template .render (node = node , query = query , formatter = self )
247237
248- @self .summary .register (FileSystemDirectory )
249- def _ (node : FileSystemDirectory , query ):
250- template = \
251- """
252- Directory structure:
253- {{ node.tree }}
254- """
255- summary_template = self .env .from_string (template )
256- return summary_template .render (node = node , query = query , formatter = self )
257-
258- @self .format .register (FileSystemSymlink )
259- def _ (node : FileSystemSymlink , query ):
260- template = \
238+ @format .register
239+ def _ (self , node : FileSystemSymlink , query ):
240+ template = \
261241"""
262242{{ SEPARATOR }}
263243{{ node.name }}{% if node.target %} -> {{ node.target }}{% endif %}
264244{{ SEPARATOR }}
265245"""
266- symlink_template = self .env .from_string (template )
267- return symlink_template .render (SEPARATOR = SEPARATOR , node = node , query = query , formatter = self )
246+ symlink_template = self .env .from_string (template )
247+ return symlink_template .render (SEPARATOR = SEPARATOR , node = node , query = query , formatter = self )
248+
249+ @format .register
250+ def _ (self , context : Context , query ):
251+ """Format a Context by formatting all its sources."""
252+ template = \
253+ """
254+ # Generated using https://gitingest.com/{{ context.query.user_name }}/{{ context.query.repo_name }}
255+ Sources used:
256+ {% for source in context.sources %}
257+ - {{ source.name }}: {{ source.__class__.__name__ }}
258+ {% endfor %}
259+
260+ {% for source in context.sources %}
261+ {{ formatter.format(source, context.query) }}
262+ {% endfor %}
263+ # End of generated content
264+ """
265+ context_template = self .env .from_string (template )
266+ return context_template .render (context = context , formatter = self )
268267
269- class StupidFormatter (DefaultFormatter ):
268+
269+ class DebugFormatter :
270270 def __init__ (self ):
271- super ().__init__ ()
271+ self .separator = SEPARATOR
272+ self .env = Environment (loader = BaseLoader ())
272273
273- @self .summary .register (FileSystemTextFile )
274- def _ (node : FileSystemTextFile , query ):
275- template = \
274+ @singledispatchmethod
275+ def format (self , node : Source , query ):
276+ """Format any Source type with debug information."""
277+ # Get the actual class name
278+ class_name = node .__class__ .__name__
279+
280+ # Get all field names (both from dataclass fields and regular attributes)
281+ field_names = []
282+
283+ # Try to get dataclass fields first
284+ try :
285+ if hasattr (node , '__dataclass_fields__' ) and hasattr (node .__dataclass_fields__ , 'keys' ):
286+ field_names .extend (node .__dataclass_fields__ .keys ())
287+ else :
288+ raise AttributeError # Fall through to backup method
289+ except (AttributeError , TypeError ):
290+ # Fall back to getting all non-private attributes
291+ field_names = [attr for attr in dir (node )
292+ if not attr .startswith ('_' )
293+ and not callable (getattr (node , attr , None ))]
294+
295+ # Format the debug output
296+ fields_str = ", " .join (field_names )
297+ template = \
276298"""
277299{{ SEPARATOR }}
278- {{ node.name }}
300+ DEBUG: {{ class_name }}
301+ Fields: {{ fields_str }}
279302{{ SEPARATOR }}
280- FileSystemTextFile
281303"""
304+ debug_template = self .env .from_string (template )
305+ return debug_template .render (
306+ SEPARATOR = SEPARATOR ,
307+ class_name = class_name ,
308+ fields_str = fields_str
309+ )
282310
283- @self .format .register (FileSystemFile )
284- def _ (node : FileSystemFile , query ):
285- template = \
311+
312+ class SummaryFormatter :
313+ """Dedicated formatter for generating summaries of filesystem nodes."""
314+
315+ def __init__ (self ):
316+ self .env = Environment (loader = BaseLoader ())
317+
318+ @singledispatchmethod
319+ def summary (self , node : Source , query ):
320+ return f"{ getattr (node , 'name' , '' )} "
321+
322+ @summary .register
323+ def _ (self , node : FileSystemDirectory , query ):
324+ template = \
286325"""
287- {{ SEPARATOR }}
288- {{ node.name }}
289- {{ SEPARATOR }}
290- FileSystemFile
326+ Directory structure:
327+ {{ node.tree }}
291328"""
292- file_template = self .env .from_string (template )
293- return file_template .render (SEPARATOR = SEPARATOR , node = node , query = query , formatter = self )
329+ summary_template = self .env .from_string (template )
330+ return summary_template .render (node = node , query = query )
294331
295332
296- def generate_digest (context ) -> str :
297- """Generate a digest from a Context object.
298-
299- Parameters
300- ----------
301- context : Context
302- The context object containing nodes, formatter, and query.
303-
304- Returns
305- -------
306- str
307- The formatted digest string with header, content, and footer.
308- """
309- if context .query .user_name and context .query .repo_name :
310- context_header = CONTEXT_HEADER .format (f"/{ context .query .user_name } /{ context .query .repo_name } " )
311- else :
312- context_header = CONTEXT_HEADER .format ("" )
313- context_footer = CONTEXT_FOOTER
314- formatted = []
315- for node in context .nodes :
316- formatted .append (context .formatter .format (node , context .query ))
317- return context_header + "\n " .join (formatted ) + context_footer
333+ @summary .register
334+ def _ (self , context : Context , query ):
335+ template = \
336+ """
337+ {{ context.summary }}
338+ """
339+ summary_template = self .env .from_string (template )
340+ return summary_template .render (context = context , query = query )
0 commit comments