22CLI interface for abx-dl using rich-click.
33"""
44
5+ import sys
56from pathlib import Path
67
78import rich_click as click
89from rich .console import Console
9- from rich .live import Live
1010from rich .table import Table
1111from rich .progress import Progress , SpinnerColumn , TextColumn , BarColumn , TaskProgressColumn
1212
1313from .dependencies import load_binary , install_binary
14- from .executor import download_live , ArchiveResult
15- from .plugins import discover_plugins , get_plugin_names
14+ from .executor import download
15+ from .models import ArchiveResult
16+ from .plugins import discover_plugins
1617
1718console = Console ()
1819
@@ -26,16 +27,10 @@ class DefaultGroup(click.Group):
2627 """A click Group that runs 'dl' command by default if a URL is found in args."""
2728
2829 def resolve_command (self , ctx , args ):
29- # If no args or first arg is a known command, proceed normally
3030 if not args :
3131 return super ().resolve_command (ctx , args )
32-
33- cmd_name = args [0 ]
34- # If it's a known command, proceed normally
35- if cmd_name in self .commands :
32+ if args [0 ] in self .commands :
3633 return super ().resolve_command (ctx , args )
37-
38- # Otherwise assume it's a URL and use 'dl' command
3934 return super ().resolve_command (ctx , ['dl' ] + args )
4035
4136
@@ -67,88 +62,52 @@ def cli(ctx):
6762def dl (ctx , url : str , plugin_list : str | None , output_dir : str | None , timeout : int | None ):
6863 """Download a URL using all enabled plugins."""
6964 plugins = ctx .obj ['plugins' ]
70-
7165 selected = [p .strip () for p in plugin_list .split (',' )] if plugin_list else None
7266 out_path = Path (output_dir ) if output_dir else Path .cwd ()
67+ config_overrides = {'TIMEOUT' : timeout } if timeout else {}
68+ is_tty = sys .stdout .isatty ()
7369
74- config_overrides = {}
75- if timeout :
76- config_overrides ['TIMEOUT' ] = timeout
70+ results : list [ArchiveResult ] = []
71+ gen = download (url , plugins , out_path , selected , config_overrides or None )
7772
78- console .print (f"[bold blue]Downloading:[/bold blue] { url } " )
79- console .print (f"[dim]Output: { out_path .absolute ()} [/dim]" )
73+ if is_tty :
74+ # Rich progress display for TTY
75+ console .print (f"[bold blue]Downloading:[/bold blue] { url } " )
76+ console .print (f"[dim]Output: { out_path .absolute ()} [/dim]" )
77+ console .print (f"[dim]Plugins: { ', ' .join (selected ) if selected else f'all ({ len (plugins )} available)' } [/dim]\n " )
8078
81- if selected :
82- console .print (f"[dim]Plugins: { ', ' .join (selected )} [/dim]" )
83- else :
84- console .print (f"[dim]Plugins: all ({ len (plugins )} available)[/dim]" )
85-
86- console .print ()
87-
88- # Run with live progress
89- hook_results : list [ArchiveResult ] = []
90-
91- with Progress (
92- SpinnerColumn (),
93- TextColumn ("[progress.description]{task.description}" ),
94- BarColumn (),
95- TaskProgressColumn (),
96- console = console ,
97- ) as progress :
98- result_gen = download_live (
99- url = url ,
100- plugins = plugins ,
101- output_dir = out_path ,
102- selected_plugins = selected ,
103- config_overrides = config_overrides if config_overrides else None ,
104- )
105-
106- # Get total hooks count and create progress task
107- total_hooks , gen = result_gen
108- task = progress .add_task ("[cyan]Running plugins..." , total = total_hooks )
109-
110- for hook_result in gen :
111- hook_results .append (hook_result )
112- status_icon = {"succeeded" : "[green]✓[/green]" , "failed" : "[red]✗[/red]" , "skipped" : "[yellow]○[/yellow]" }.get (hook_result .status , "?" )
113- progress .update (task , advance = 1 , description = f"{ status_icon } { hook_result .hook .plugin_name } " )
114-
115- console .print ()
116-
117- # Show results table
118- table = Table (title = "Results" )
119- table .add_column ("Plugin" , style = "cyan" )
120- table .add_column ("Status" , style = "bold" )
121- table .add_column ("Output" )
122-
123- for hook_result in hook_results :
124- status_style = {
125- 'succeeded' : '[green]succeeded[/green]' ,
126- 'failed' : '[red]failed[/red]' ,
127- 'skipped' : '[yellow]skipped[/yellow]' ,
128- }.get (hook_result .status , hook_result .status )
129-
130- output = hook_result .output_path or hook_result .error or ''
131- if len (output ) > 50 :
132- output = output [:47 ] + '...'
133-
134- table .add_row (
135- hook_result .hook .plugin_name ,
136- status_style ,
137- output ,
138- )
79+ # Count total hooks for progress bar
80+ total = sum (len (p .get_crawl_hooks ()) + len (p .get_snapshot_hooks ()) for p in plugins .values ())
13981
140- console .print (table )
82+ with Progress (SpinnerColumn (), TextColumn ("[progress.description]{task.description}" ), BarColumn (), TaskProgressColumn (), console = console ) as progress :
83+ task = progress .add_task ("[cyan]Running plugins..." , total = total )
84+ for ar in gen :
85+ results .append (ar )
86+ icon = {"succeeded" : "[green]✓[/green]" , "failed" : "[red]✗[/red]" , "skipped" : "[yellow]○[/yellow]" }.get (ar .status , "?" )
87+ progress .update (task , advance = 1 , description = f"{ icon } { ar .plugin } " )
14188
142- # Summary
143- succeeded = [r for r in hook_results if r .status == 'succeeded' ]
144- failed = [r for r in hook_results if r .status == 'failed' ]
145- skipped = [r for r in hook_results if r .status == 'skipped' ]
89+ # Results table
90+ console .print ()
91+ table = Table (title = "Results" )
92+ table .add_column ("Plugin" , style = "cyan" )
93+ table .add_column ("Status" , style = "bold" )
94+ table .add_column ("Output" )
14695
147- console .print ()
148- console .print (f"[green]{ len (succeeded )} succeeded[/green], "
149- f"[red]{ len (failed )} failed[/red], "
150- f"[yellow]{ len (skipped )} skipped[/yellow]" )
151- console .print (f"[dim]Output: { out_path .absolute ()} [/dim]" )
96+ for ar in results :
97+ status_style = {'succeeded' : '[green]succeeded[/green]' , 'failed' : '[red]failed[/red]' , 'skipped' : '[yellow]skipped[/yellow]' }.get (ar .status , ar .status )
98+ output = ar .output_str or ar .error or ''
99+ table .add_row (ar .plugin , status_style , output [:50 ] + '...' if len (output ) > 50 else output )
100+
101+ console .print (table )
102+ console .print ()
103+ console .print (f"[green]{ sum (1 for r in results if r .status == 'succeeded' )} succeeded[/green], "
104+ f"[red]{ sum (1 for r in results if r .status == 'failed' )} failed[/red], "
105+ f"[yellow]{ sum (1 for r in results if r .status == 'skipped' )} skipped[/yellow]" )
106+ console .print (f"[dim]Output: { out_path .absolute ()} [/dim]" )
107+ else :
108+ # JSONL output for non-TTY (handled by executor, just consume generator)
109+ for ar in gen :
110+ results .append (ar )
152111
153112
154113@cli .command ()
@@ -166,7 +125,6 @@ def plugins(ctx):
166125 plugin = all_plugins [name ]
167126 hooks_count = len (plugin .get_snapshot_hooks ())
168127 binaries = ', ' .join (b .get ('name' , '' ) for b in plugin .binaries ) or '-'
169-
170128 table .add_row (name , str (hooks_count ), binaries )
171129
172130 console .print (table )
@@ -179,21 +137,14 @@ def plugins(ctx):
179137def install (ctx , plugin_names : str | None ):
180138 """Install dependencies for plugins."""
181139 all_plugins = ctx .obj .get ('plugins' , discover_plugins ())
182-
183- if plugin_names :
184- names = [n .strip () for n in plugin_names .split (',' )]
185- plugins_to_install = {n : all_plugins [n ] for n in names if n in all_plugins }
186- else :
187- plugins_to_install = all_plugins
140+ plugins_to_install = {n : all_plugins [n ] for n in plugin_names .split (',' ) if n in all_plugins } if plugin_names else all_plugins
188141
189142 console .print ("[bold]Installing plugin dependencies...[/bold]\n " )
190143
191144 for name , plugin in plugins_to_install .items ():
192145 if not plugin .binaries :
193146 continue
194-
195147 console .print (f"[cyan]{ name } [/cyan]" )
196-
197148 for spec in plugin .binaries :
198149 binary = install_binary (spec )
199150 if binary .is_valid :
@@ -210,12 +161,7 @@ def install(ctx, plugin_names: str | None):
210161def check (ctx , plugin_names : str | None ):
211162 """Check if plugin dependencies are available."""
212163 all_plugins = ctx .obj .get ('plugins' , discover_plugins ())
213-
214- if plugin_names :
215- names = [n .strip () for n in plugin_names .split (',' )]
216- plugins_to_check = {n : all_plugins [n ] for n in names if n in all_plugins }
217- else :
218- plugins_to_check = all_plugins
164+ plugins_to_check = {n : all_plugins [n ] for n in plugin_names .split (',' ) if n in all_plugins } if plugin_names else all_plugins
219165
220166 table = Table (title = "Dependency Status" )
221167 table .add_column ("Plugin" , style = "cyan" )
@@ -225,33 +171,17 @@ def check(ctx, plugin_names: str | None):
225171 table .add_column ("Path" )
226172
227173 all_ok = True
228-
229174 for name , plugin in sorted (plugins_to_check .items ()):
230175 if not plugin .binaries :
231176 continue
232-
233177 for spec in plugin .binaries :
234178 binary = load_binary (spec )
235- if binary .is_valid :
236- status = "[green]✓[/green]"
237- else :
238- status = "[red]✗[/red]"
239- all_ok = False
240-
241- table .add_row (
242- name ,
243- binary .name ,
244- status ,
245- str (binary .loaded_version ) if binary .loaded_version else '-' ,
246- str (binary .loaded_abspath ) if binary .loaded_abspath else '-' ,
247- )
179+ status = "[green]✓[/green]" if binary .is_valid else "[red]✗[/red]"
180+ all_ok = all_ok and binary .is_valid
181+ table .add_row (name , binary .name , status , str (binary .loaded_version or '-' ), str (binary .loaded_abspath or '-' ))
248182
249183 console .print (table )
250-
251- if all_ok :
252- console .print ("\n [bold green]All dependencies available![/bold green]" )
253- else :
254- console .print ("\n [bold yellow]Some dependencies missing. Run 'abx-dl install' to install them.[/bold yellow]" )
184+ console .print (f"\n [bold green]All dependencies available![/bold green]" if all_ok else "\n [bold yellow]Some dependencies missing. Run 'abx-dl install' to install them.[/bold yellow]" )
255185
256186
257187@cli .command ()
@@ -267,31 +197,23 @@ def info(ctx, plugin_name: str):
267197 return
268198
269199 plugin = all_plugins [plugin_name ]
270-
271200 console .print (f"[bold cyan]{ plugin .name } [/bold cyan]" )
272201 console .print (f"[dim]Path: { plugin .path } [/dim]\n " )
273202
274- # Config options
275203 if plugin .config_schema :
276204 console .print ("[bold]Config options:[/bold]" )
277205 for key , prop in plugin .config_schema .items ():
278- default = prop .get ('default' , '-' )
279- desc = prop .get ('description' , '' )
280- console .print (f" { key } ={ default } " )
281- if desc :
282- console .print (f" [dim]{ desc } [/dim]" )
206+ console .print (f" { key } ={ prop .get ('default' , '-' )} " )
207+ if prop .get ('description' ):
208+ console .print (f" [dim]{ prop ['description' ]} [/dim]" )
283209 console .print ()
284210
285- # Binaries
286211 if plugin .binaries :
287212 console .print ("[bold]Binaries:[/bold]" )
288213 for binary in plugin .binaries :
289- name = binary .get ('name' , '?' )
290- providers = binary .get ('binproviders' , 'env' )
291- console .print (f" { name } (providers: { providers } )" )
214+ console .print (f" { binary .get ('name' , '?' )} (providers: { binary .get ('binproviders' , 'env' )} )" )
292215 console .print ()
293216
294- # Hooks
295217 hooks = plugin .get_snapshot_hooks ()
296218 if hooks :
297219 console .print ("[bold]Hooks:[/bold]" )
@@ -301,7 +223,6 @@ def info(ctx, plugin_name: str):
301223
302224
303225def main ():
304- """Entry point for CLI."""
305226 cli (obj = {})
306227
307228
0 commit comments