1+ from __future__ import annotations
2+
13import argparse
24import os
35import platform
1315from explain_this_repo .file_reader import read_local_file
1416from explain_this_repo .generate import generate_explanation
1517from explain_this_repo .github import (
18+ fetch_directory_contents ,
1619 fetch_file_result ,
1720 fetch_languages ,
1821 fetch_readme ,
1922 fetch_repo ,
2023)
2124from explain_this_repo .local_reader import read_local_repo_signal_files
2225from explain_this_repo .prompt import (
26+ build_directory_prompt ,
27+ build_directory_quick_prompt ,
28+ build_directory_simple_prompt ,
2329 build_file_prompt ,
2430 build_file_quick_prompt ,
2531 build_file_simple_prompt ,
@@ -313,7 +319,7 @@ def _classify_target(target: str) -> str:
313319 if os .path .isdir (target ):
314320 return "directory"
315321 if _looks_like_github_file_target (target ):
316- return "github_file "
322+ return "github_directory "
317323 return "github"
318324
319325
@@ -324,6 +330,43 @@ def _extract_file_signals(read_result) -> dict:
324330 "size_bytes" : read_result .size_bytes ,
325331 }
326332
333+
334+ def _extract_directory_signals (contents : list [dict ]) -> dict :
335+ files : list [str ] = []
336+ directories : list [str ] = []
337+ extensions : dict [str , int ] = {}
338+
339+ for entry in contents :
340+ if not isinstance (entry , dict ):
341+ continue
342+
343+ entry_type = str (entry .get ("type" ) or "" ).lower ()
344+ name = str (entry .get ("name" ) or entry .get ("path" ) or "" ).strip ()
345+
346+ if not name :
347+ continue
348+
349+ if entry_type == "dir" :
350+ directories .append (name )
351+ continue
352+
353+ files .append (name )
354+ extension = os .path .splitext (name )[1 ].lower ().lstrip ("." ) or "no_extension"
355+ extensions [extension ] = extensions .get (extension , 0 ) + 1
356+
357+ files .sort (key = str .lower )
358+ directories .sort (key = str .lower )
359+ extensions = dict (sorted (extensions .items (), key = lambda item : (- item [1 ], item [0 ])))
360+
361+ return {
362+ "files" : files ,
363+ "directories" : directories ,
364+ "file_count" : len (files ),
365+ "dir_count" : len (directories ),
366+ "extensions" : extensions ,
367+ }
368+
369+
327370def _handle_file_mode (args , llm : str | None ) -> None :
328371 if args .stack :
329372 print ("error: --stack is not supported for file targets" )
@@ -399,22 +442,29 @@ def _handle_file_mode(args, llm: str | None) -> None:
399442 print (f"Open { args .output } to read it." )
400443
401444
402- def _handle_github_file_mode (args , llm : str | None ) -> None :
445+ def _handle_github_file_mode (
446+ args ,
447+ llm : str | None ,
448+ owner : str | None = None ,
449+ repo : str | None = None ,
450+ file_path : str | None = None ,
451+ ) -> None :
403452 if args .stack :
404453 print ("error: --stack is not supported for GitHub file targets" )
405454 raise SystemExit (1 )
406455
407- try :
408- owner , repo , file_path = resolve_github_file_target (args .repository )
409- except ValueError as e :
410- print (f"error: { str (e )} " )
411- raise SystemExit (1 )
456+ if owner is None or repo is None or file_path is None :
457+ try :
458+ owner , repo , file_path = resolve_github_file_target (args .repository )
459+ except ValueError as e :
460+ print (f"error: { e } " )
461+ raise SystemExit (1 )
412462
413463 try :
414464 with console .status (f"Fetching { owner } /{ repo } /{ file_path } ..." , spinner = "dots" ):
415465 read_result = fetch_file_result (owner , repo , file_path )
416466 except Exception as e :
417- print (f"error: { str ( e ) } " )
467+ print (f"error: { e } " )
418468 raise SystemExit (1 )
419469
420470 display_path = f"{ owner } /{ repo } /{ read_result .path } "
@@ -473,6 +523,94 @@ def _handle_github_file_mode(args, llm: str | None) -> None:
473523 print (f"Open { args .output } to read it." )
474524
475525
526+ def _handle_github_directory_mode (
527+ args ,
528+ llm : str | None ,
529+ owner : str | None = None ,
530+ repo : str | None = None ,
531+ directory_path : str | None = None ,
532+ ) -> None :
533+ if args .stack :
534+ print ("error: --stack is not supported for GitHub directory targets" )
535+ raise SystemExit (1 )
536+
537+ if owner is None or repo is None or directory_path is None :
538+ try :
539+ owner , repo , directory_path = resolve_github_file_target (args .repository )
540+ except ValueError as e :
541+ print (f"error: { e } " )
542+ raise SystemExit (1 )
543+
544+ try :
545+ with console .status (f"Fetching { owner } /{ repo } /{ directory_path } ..." , spinner = "dots" ):
546+ contents = fetch_directory_contents (owner , repo , directory_path )
547+ except Exception as e :
548+ message = str (e ).strip ()
549+ lowered = message .lower ()
550+
551+ if "file" in lowered and "directory" in lowered :
552+ _handle_github_file_mode (
553+ args ,
554+ llm ,
555+ owner = owner ,
556+ repo = repo ,
557+ file_path = directory_path ,
558+ )
559+ return
560+
561+ print (f"error: { message } " )
562+ raise SystemExit (1 )
563+
564+ display_path = f"{ owner } /{ repo } /{ directory_path } "
565+ print (f"Analyzing GitHub directory: { display_path } " )
566+
567+ signals = _extract_directory_signals (contents )
568+
569+ if args .quick :
570+ prompt = build_directory_quick_prompt (
571+ directory_path = display_path ,
572+ signals = signals ,
573+ )
574+
575+ with console .status ("Generating explanation..." , spinner = "dots" ):
576+ output = generate_with_exit (prompt , llm = llm )
577+
578+ print ("Quick summary 🎉" )
579+ print (output .strip ())
580+ return
581+
582+ if args .simple :
583+ prompt = build_directory_simple_prompt (
584+ directory_path = display_path ,
585+ signals = signals ,
586+ )
587+
588+ with console .status ("Generating explanation..." , spinner = "dots" ):
589+ output = generate_with_exit (prompt , llm = llm )
590+
591+ print ("Simple summary 🎉" )
592+ print (output .strip ())
593+ return
594+
595+ prompt = build_directory_prompt (
596+ directory_path = display_path ,
597+ signals = signals ,
598+ detailed = args .detailed ,
599+ )
600+
601+ with console .status ("Generating explanation..." , spinner = "dots" ):
602+ output = generate_with_exit (prompt , llm = llm )
603+
604+ print (f"Writing { args .output } ..." )
605+ write_output (output , args .output )
606+
607+ word_count = len (output .split ())
608+ print (f"{ args .output } generated successfully 🎉" )
609+ print (f"Words: { word_count } " )
610+ print (f"Location: { os .path .abspath (args .output )} " )
611+ print (f"Open { args .output } to read it." )
612+
613+
476614def _handle_directory_mode (args , llm : str | None ) -> None :
477615 local_path = os .path .abspath (args .repository )
478616
@@ -662,10 +800,10 @@ def main():
662800 " explainthisrepo owner/repo --quick\n "
663801 " explainthisrepo owner/repo --simple\n "
664802 " explainthisrepo owner/repo --stack\n "
665- " explainthisrepo owner/repo/path/to/file.py \n "
666- " explainthisrepo owner/repo/path/to/file.py --quick\n "
667- " explainthisrepo owner/repo/path/to/file.py --simple\n "
668- " explainthisrepo owner/repo/path/to/file.py --detailed\n "
803+ " explainthisrepo owner/repo/packages/react-dom \n "
804+ " explainthisrepo owner/repo/packages/react-dom --quick\n "
805+ " explainthisrepo owner/repo/packages/react-dom --simple\n "
806+ " explainthisrepo owner/repo/packages/react-dom --detailed\n "
669807 " explainthisrepo init\n "
670808 " explainthisrepo owner/repo --llm gemini\n "
671809 " explainthisrepo owner/repo --llm openai\n "
@@ -739,7 +877,7 @@ def main():
739877 parser .add_argument (
740878 "repository" ,
741879 nargs = "?" ,
742- help = "GitHub repository (owner/repo or URL), local directory, GitHub file , or local file" ,
880+ help = "GitHub repository (owner/repo or URL), GitHub file or directory path, local directory , or local file" ,
743881 )
744882
745883 mode_group = parser .add_mutually_exclusive_group ()
@@ -805,8 +943,8 @@ def main():
805943 _handle_file_mode (args , llm )
806944 elif mode == "directory" :
807945 _handle_directory_mode (args , llm )
808- elif mode == "github_file " :
809- _handle_github_file_mode (args , llm )
946+ elif mode == "github_directory " :
947+ _handle_github_directory_mode (args , llm )
810948 else :
811949 _handle_github_mode (args , llm )
812950
@@ -820,4 +958,4 @@ def _run():
820958
821959
822960if __name__ == "__main__" :
823- _run ()
961+ _run ()
0 commit comments