22
33import os
44import typer
5- import fnmatch # Necessario per il pattern matching
5+ import fnmatch
66from rich .console import Console
77from rich .progress import Progress
88import tomli
99import chardet
10+ # --- NUOVI IMPORT PER LA VERSIONE ---
11+ from importlib .metadata import version as get_package_version , PackageNotFoundError
1012from typing import List , Dict , Any , Set , Optional
1113
1214from deepbase .toon import generate_toon_representation
1315from deepbase .parsers import get_document_structure
1416
15- # ... (LE CONFIGURAZIONI DEFAULT_CONFIG e HELPER RIMANGONO INVARIATE) ...
16- # Assicurati di copiare le funzioni: load_config, is_significant_file,
17- # generate_directory_tree, get_all_significant_files, read_file_content
18- # dalla versione precedente.
19-
17+ # --- CONFIGURAZIONI (Invariate) ---
2018DEFAULT_CONFIG = {
2119 "ignore_dirs" : {
2220 "__pycache__" , ".git" , ".idea" , ".vscode" , "venv" , ".venv" , "env" ,
2523 "site" , "*.egg-info" , "coverage"
2624 },
2725 "significant_extensions" : {
28- ".py" , ".java" , ".js" , ".jsx" , ".ts" , ".tsx" , ".html" , ".css" , ".scss" , ".sql" , # Aggiunto jsx/tsx
26+ ".py" , ".java" , ".js" , ".jsx" , ".ts" , ".tsx" , ".html" , ".css" , ".scss" , ".sql" ,
2927 ".md" , ".json" , ".xml" , ".yml" , ".yaml" , ".sh" , ".bat" , "Dockerfile" ,
3028 ".dockerignore" , ".gitignore" , "requirements.txt" , "pom.xml" , "gradlew" ,
31- "pyproject.toml" , "setup.py" , "package.json" , "tsconfig.json" # Aggiunto package/ts config
29+ "pyproject.toml" , "setup.py" , "package.json" , "tsconfig.json" ,
30+ ".tex" , ".bib" , ".sty" , ".cls"
3231 }
3332}
3433
3938)
4039console = Console ()
4140
42- # ... [INSERISCI QUI LE FUNZIONI HELPER: load_config, generate_directory_tree, etc.] ...
43- # Per brevità non le ripeto se non sono cambiate, ma devono esserci nel file finale.
44-
41+ # --- HELPER FUNCTIONS (Invariate) ---
4542def load_config (root_dir : str ) -> Dict [str , Any ]:
46- """Loads configuration from .deepbase.toml if available."""
4743 config_path = os .path .join (root_dir , ".deepbase.toml" )
4844 config = DEFAULT_CONFIG .copy ()
4945 if os .path .exists (config_path ):
@@ -59,7 +55,7 @@ def load_config(root_dir: str) -> Dict[str, Any]:
5955def is_significant_file (file_path : str , significant_extensions : Set [str ]) -> bool :
6056 file_name = os .path .basename (file_path )
6157 if file_name in significant_extensions : return True
62- _ , ext = os .path .splitext (file_name )
58+ _ , ext = os .path .splitext (file_path ) # Corretto os.path.splitext( file_name) -> file_path per sicurezza
6359 return ext in significant_extensions
6460
6561def generate_directory_tree (root_dir : str , config : Dict [str , Any ]) -> str :
@@ -99,93 +95,99 @@ def read_file_content(file_path: str) -> str:
9995 except Exception as e :
10096 return f"!!! Error reading file: { e } !!!"
10197
102-
103- # --- NEW HELPER FOR FOCUS ---
10498def matches_focus (file_path : str , root_dir : str , focus_patterns : List [str ]) -> bool :
105- """Check if the file path matches any of the focus patterns."""
10699 if not focus_patterns :
107100 return False
108-
109- # Rendi il path relativo per il matching (es. src/main.py)
110101 rel_path = os .path .relpath (file_path , root_dir )
111- # Supporta anche slash normali su Windows
112102 rel_path_fwd = rel_path .replace (os .sep , '/' )
113-
114103 for pattern in focus_patterns :
115- # Se il pattern finisce con /, matchiamo una directory e tutto il contenuto
116104 clean_pattern = pattern .replace (os .sep , '/' )
117-
118- # Match esatto file, match wildcard o match startswith directory
119105 if fnmatch .fnmatch (rel_path_fwd , clean_pattern ):
120106 return True
121- if clean_pattern in rel_path_fwd : # Match parziale semplice (contiene stringa)
107+ if clean_pattern in rel_path_fwd :
122108 return True
123-
124109 return False
125110
126- # --- Legge il file di focus ---
127111def load_focus_patterns_from_file (file_path : str ) -> List [str ]:
128- """Legge pattern da un file di testo (uno per riga), ignorando # commenti."""
129112 patterns = []
130113 if os .path .exists (file_path ):
131114 try :
132115 with open (file_path , 'r' , encoding = 'utf-8' ) as f :
133116 lines = f .readlines ()
134117 for line in lines :
135118 line = line .strip ()
136- # Ignora righe vuote o che iniziano con #
137119 if line and not line .startswith ("#" ):
138120 patterns .append (line )
139121 except Exception as e :
140122 console .print (f"[bold yellow]Warning:[/bold yellow] Could not read focus file '{ file_path } ': { e } " )
141123 else :
142124 console .print (f"[bold yellow]Warning:[/bold yellow] Focus file '{ file_path } ' not found." )
143125 return patterns
126+
127+
128+ # --- FUNZIONE CALLBACK PER VERSIONE ---
129+ def version_callback (value : bool ):
130+ if value :
131+ try :
132+ v = get_package_version ("deepbase" )
133+ console .print (f"DeepBase version: [bold cyan]{ v } [/bold cyan]" )
134+ except PackageNotFoundError :
135+ console .print ("DeepBase version: [yellow]unknown (editable/dev mode)[/yellow]" )
136+ raise typer .Exit ()
137+
144138# --- MAIN COMMAND ---
145139
146140@app .command ()
147141def create (
148- target : str = typer .Argument (..., help = "The file or directory to scan." ),
149- output : str = typer .Option ("llm_context.md" , "--output" , "-o" , help = "The output file." ),
150- verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Show detailed output." ),
151- include_all : bool = typer .Option (False , "--all" , "-a" , help = "Include full content of ALL files." ),
152- toon_mode : bool = typer .Option (False , "--toon" , "-t" , help = "Use 'Skeleton' mode for non-focused files." ),
142+ # Nota: Ho reso 'target' facoltativo (Optional) nel type hint solo per evitare errori
143+ # statici se non viene passato quando si usa --version,
144+ # ma Typer lo gestirà comunque come richiesto se non eseguiamo la callback.
145+ target : str = typer .Argument (
146+ None , # Default a None per permettere a --version di funzionare senza target
147+ help = "The file or directory to scan."
148+ ),
153149
154- # 1. Focus Flag (Manual)
155- focus : Optional [List [str ]] = typer .Option (
156- None , "--focus" , "-f" ,
157- help = "Pattern to focus on. Can be used multiple times."
150+ # --- FLAG VERSIONE ---
151+ version : Optional [bool ] = typer .Option (
152+ None , "--version" , "-v" ,
153+ callback = version_callback ,
154+ is_eager = True , # IMPORTANTE: Processa questo flag prima di controllare gli argomenti required
155+ help = "Show the application version and exit."
158156 ),
157+
158+ output : str = typer .Option ("llm_context.md" , "--output" , "-o" , help = "The output file." ),
159159
160- # 2. Focus File (File based)
161- focus_file : Optional [str ] = typer .Option (
162- None , "--focus-file" , "-ff" ,
163- help = "Path to a text file containing a list of focus patterns (one per line)."
164- )
160+ # NOTA: Ho cambiato lo short flag di verbose da -v a -V per lasciare -v alla version
161+ verbose : bool = typer .Option (False , "--verbose" , "-V" , help = "Show detailed output." ),
162+
163+ include_all : bool = typer .Option (False , "--all" , "-a" , help = "Include full content of ALL files." ),
164+ toon_mode : bool = typer .Option (False , "--toon" , "-t" , help = "Use 'Skeleton' mode for non-focused files." ),
165+ focus : Optional [List [str ]] = typer .Option (None , "--focus" , "-f" , help = "Pattern to focus on." ),
166+ focus_file : Optional [str ] = typer .Option (None , "--focus-file" , "-ff" , help = "Path to focus patterns file." )
165167):
166168 """
167169 Analyzes a directory OR a single file.
168170 Hybrid workflow with Context Skeleton + Focused Content.
169171 """
172+
173+ # Se target è None (succede solo se uno lancia deepbase senza argomenti e senza --version)
174+ if target is None :
175+ # Mostra help ed esci
176+ ctx = typer .get_current_context ()
177+ console .print ("[red]Error: Missing argument 'TARGET'.[/red]" )
178+ console .print (ctx .get_help ())
179+ raise typer .Exit (code = 1 )
180+
170181 if not os .path .exists (target ):
171182 console .print (f"[bold red]Error:[/bold red] Target not found: '{ target } '" )
172183 raise typer .Exit (code = 1 )
173184
174- # --- LOGICA DI MERGE DEI FOCUS PATTERNS ---
185+ # --- LOGICA FOCUS MERGE ---
175186 active_focus_patterns = []
176-
177- # Aggiungi quelli da CLI
178- if focus :
179- active_focus_patterns .extend (focus )
180-
181- # Aggiungi quelli da FILE
187+ if focus : active_focus_patterns .extend (focus )
182188 if focus_file :
183189 file_patterns = load_focus_patterns_from_file (focus_file )
184- if file_patterns :
185- active_focus_patterns .extend (file_patterns )
186- console .print (f"[green]Loaded { len (file_patterns )} patterns from '{ focus_file } '[/green]" )
187-
188- # Pulizia duplicati (opzionale ma utile)
190+ if file_patterns : active_focus_patterns .extend (file_patterns )
189191 active_focus_patterns = list (set (active_focus_patterns ))
190192
191193 console .print (f"[bold green]Analyzing '{ target } '...[/bold green]" )
@@ -212,7 +214,7 @@ def fmt_separator(): return "-" * 40 + "\n\n"
212214 try :
213215 with open (output , "w" , encoding = "utf-8" ) as outfile :
214216
215- # CASE 1: SINGLE FILE (Minimally affected)
217+ # CASE 1: SINGLE FILE
216218 if os .path .isfile (target ):
217219 filename = os .path .basename (target )
218220 outfile .write (f"# File Structure Analysis: { filename } \n \n " )
@@ -241,24 +243,17 @@ def fmt_separator(): return "-" * 40 + "\n\n"
241243 outfile .write ("\n \n " )
242244
243245 # 2. Content Generation
244- # Check based on MERGED active_focus_patterns
245246 if include_all or toon_mode or active_focus_patterns :
246-
247247 section_title = "FILE CONTENTS (HYBRID)" if (toon_mode and active_focus_patterns ) else \
248248 ("SEMANTIC SKELETONS (TOON)" if toon_mode else "FILE CONTENTS" )
249-
250249 outfile .write (fmt_header (section_title ))
251-
252250 files = get_all_significant_files (target , config )
253251
254252 with Progress (console = console ) as progress :
255253 task = progress .add_task ("[cyan]Processing..." , total = len (files ))
256254 for fpath in files :
257255 rel_path = os .path .relpath (fpath , target ).replace ('\\ ' , '/' )
258-
259- # DECISION MATRIX based on active_focus_patterns
260256 is_in_focus = active_focus_patterns and matches_focus (fpath , target , active_focus_patterns )
261-
262257 should_write_full = include_all or is_in_focus
263258 should_write_toon = toon_mode and not should_write_full
264259
@@ -267,18 +262,13 @@ def fmt_separator(): return "-" * 40 + "\n\n"
267262 continue
268263
269264 progress .update (task , advance = 1 , description = f"[cyan]{ rel_path } [/cyan]" )
270-
271265 marker = ""
272266 if is_in_focus and toon_mode : marker = " [FOCUSED - FULL CONTENT]"
273267
274268 outfile .write (fmt_file_start (rel_path + marker ))
275269 content = read_file_content (fpath )
276-
277- if should_write_full :
278- outfile .write (content )
279- elif should_write_toon :
280- outfile .write (generate_toon_representation (fpath , content ))
281-
270+ if should_write_full : outfile .write (content )
271+ elif should_write_toon : outfile .write (generate_toon_representation (fpath , content ))
282272 outfile .write (fmt_file_end (rel_path ))
283273 outfile .write (fmt_separator ())
284274 else :
@@ -290,6 +280,5 @@ def fmt_separator(): return "-" * 40 + "\n\n"
290280 console .print (f"\n [bold red]Error:[/bold red] { e } " )
291281 raise typer .Exit (code = 1 )
292282
293-
294283if __name__ == "__main__" :
295284 app ()
0 commit comments