dars-framework 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. dars/__init__.py +0 -0
  2. dars/all.py +52 -0
  3. dars/cli/__init__.py +0 -0
  4. dars/cli/hot_reload.py +33 -0
  5. dars/cli/main.py +637 -0
  6. dars/cli/preview.py +419 -0
  7. dars/cli/translations.py +389 -0
  8. dars/components/__init__.py +0 -0
  9. dars/components/advanced/__init__.py +8 -0
  10. dars/components/advanced/accordion.py +21 -0
  11. dars/components/advanced/card.py +28 -0
  12. dars/components/advanced/modal.py +40 -0
  13. dars/components/advanced/navbar.py +31 -0
  14. dars/components/advanced/table.py +24 -0
  15. dars/components/advanced/tabs.py +26 -0
  16. dars/components/basic/__init__.py +34 -0
  17. dars/components/basic/button.py +29 -0
  18. dars/components/basic/checkbox.py +34 -0
  19. dars/components/basic/container.py +23 -0
  20. dars/components/basic/datepicker.py +139 -0
  21. dars/components/basic/image.py +36 -0
  22. dars/components/basic/input.py +50 -0
  23. dars/components/basic/link.py +31 -0
  24. dars/components/basic/page.py +20 -0
  25. dars/components/basic/progressbar.py +17 -0
  26. dars/components/basic/radiobutton.py +34 -0
  27. dars/components/basic/select.py +81 -0
  28. dars/components/basic/slider.py +63 -0
  29. dars/components/basic/spinner.py +11 -0
  30. dars/components/basic/text.py +22 -0
  31. dars/components/basic/textarea.py +46 -0
  32. dars/components/basic/tooltip.py +18 -0
  33. dars/components/layout/__init__.py +0 -0
  34. dars/components/layout/anchor.py +13 -0
  35. dars/components/layout/flex.py +26 -0
  36. dars/components/layout/grid.py +45 -0
  37. dars/core/__init__.py +0 -0
  38. dars/core/app.py +630 -0
  39. dars/core/component.py +25 -0
  40. dars/core/events.py +101 -0
  41. dars/core/properties.py +127 -0
  42. dars/docs/__init__.py +0 -0
  43. dars/exporters/__init__.py +0 -0
  44. dars/exporters/base.py +69 -0
  45. dars/exporters/web/__init__.py +0 -0
  46. dars/exporters/web/html_css_js.py +1406 -0
  47. dars/scripts/__init__.py +0 -0
  48. dars/scripts/script.py +38 -0
  49. dars/templates/__init__.py +0 -0
  50. dars/templates/examples/advanced/all_components_demo.py +87 -0
  51. dars/templates/examples/advanced/dashboard.py +440 -0
  52. dars/templates/examples/advanced/modern_web_app.py +452 -0
  53. dars/templates/examples/basic/flex_layout_responsive.py +13 -0
  54. dars/templates/examples/basic/form_components.py +516 -0
  55. dars/templates/examples/basic/grid_layout_responsive.py +13 -0
  56. dars/templates/examples/basic/hello_world.py +104 -0
  57. dars/templates/examples/basic/layout_multipage_demo.py +23 -0
  58. dars/templates/examples/basic/multipage_example.py +70 -0
  59. dars/templates/examples/basic/pwa_custom_icons.py +31 -0
  60. dars/templates/examples/basic/simple_form.py +377 -0
  61. dars/templates/examples/demo/complete_app.py +720 -0
  62. dars/templates/html/__init__.py +0 -0
  63. dars_framework-1.0.0.dist-info/METADATA +146 -0
  64. dars_framework-1.0.0.dist-info/RECORD +68 -0
  65. dars_framework-1.0.0.dist-info/WHEEL +5 -0
  66. dars_framework-1.0.0.dist-info/entry_points.txt +2 -0
  67. dars_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  68. dars_framework-1.0.0.dist-info/top_level.txt +1 -0
dars/cli/main.py ADDED
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Dars Exporter - Command line tool for exporting Dars applications
4
+ """
5
+ import shutil
6
+ import subprocess
7
+ import venv
8
+ from rich.prompt import Confirm
9
+ from rich.syntax import Syntax
10
+ import argparse
11
+ import os
12
+ import sys
13
+ import time
14
+ import importlib.util
15
+ from pathlib import Path
16
+ from typing import Optional, Dict, Any
17
+
18
+ from rich.console import Console
19
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
20
+ from rich.panel import Panel
21
+ from rich.text import Text
22
+ from rich.table import Table
23
+ from rich.markdown import Markdown
24
+ from rich import print as rprint
25
+ from importlib import resources
26
+
27
+ # Importar exportadores
28
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
29
+
30
+ from dars.core.app import App
31
+ from dars.exporters.web.html_css_js import HTMLCSSJSExporter
32
+ from dars.cli.translations import translator
33
+
34
+ console = Console()
35
+
36
+ class RichHelpFormatter(argparse.HelpFormatter):
37
+ """Custom formatter for argparse help using Rich"""
38
+
39
+ def __init__(self, prog, indent_increment=2, max_help_position=24, width=None):
40
+ super().__init__(prog, indent_increment, max_help_position, width)
41
+
42
+ def format_help(self):
43
+ # Call the original method to get the help text
44
+ help_text = super().format_help()
45
+ return help_text
46
+
47
+ def add_text(self, text):
48
+ # Override this method to prevent the epilog from being shown in the options section
49
+ if text and (text.startswith('\nEjemplos de uso:') or text.startswith('\nUsage examples:')):
50
+ return
51
+ return super().add_text(text)
52
+
53
+ def _format_action(self, action):
54
+ # Check if this is the help action and replace its help message with the translated one
55
+ if action.option_strings and ('-h' in action.option_strings or '--help' in action.option_strings):
56
+ action.help = translator.get('help_arg_message')
57
+ return super()._format_action(action)
58
+
59
+ @classmethod
60
+ def rich_print_help(cls, parser, console=console):
61
+ # Get the standard help text
62
+ help_text = parser.format_help()
63
+
64
+ # Extract the main sections
65
+ sections = {}
66
+ current_section = None
67
+ lines = help_text.split('\n')
68
+ section_content = []
69
+
70
+ for line in lines:
71
+ if line and not line.startswith(' ') and line.endswith(':'):
72
+ # It's a section header
73
+ if current_section:
74
+ sections[current_section] = '\n'.join(section_content)
75
+ current_section = line[:-1] # Remove the colon
76
+ section_content = []
77
+ elif current_section:
78
+ section_content.append(line)
79
+
80
+ # Add the last section
81
+ if current_section and section_content:
82
+ sections[current_section] = '\n'.join(section_content)
83
+
84
+ # Show the program title
85
+ prog_name = parser.prog
86
+ description = parser.description
87
+
88
+ # Main panel
89
+ console.print(Panel(
90
+ Text(prog_name, style="bold cyan", justify="center"),
91
+ subtitle=translator.get('cli_subtitle'),
92
+ border_style="cyan"
93
+ ))
94
+
95
+ # Check if there are examples in the epilog
96
+ epilog_content = ""
97
+ if parser.epilog:
98
+ epilog_content = parser.epilog.strip()
99
+
100
+ # Show each section with style
101
+ for section, content in sections.items():
102
+ if section == 'usage':
103
+ # Usage section
104
+ usage = content.strip()
105
+ console.print(f"\n[bold cyan]{translator.get('usage')}:[/bold cyan]")
106
+ console.print(Syntax(usage, "bash", theme="monokai", word_wrap=True))
107
+ elif 'positional arguments' in section.lower():
108
+ # Positional arguments
109
+ console.print(f"\n[bold cyan]{translator.get('positional_arguments')}:[/bold cyan]")
110
+ _print_arguments_table(content)
111
+ elif 'optional arguments' in section.lower() or 'options' in section.lower():
112
+ # Optional arguments
113
+ console.print(f"\n[bold cyan]{translator.get('options')}:[/bold cyan]")
114
+ _print_arguments_table(content)
115
+ elif section.lower() == 'commands' or 'subcommands' in section.lower():
116
+ # Subcommands
117
+ console.print(f"\n[bold cyan]{translator.get('commands')}:[/bold cyan]")
118
+ _print_arguments_table(content)
119
+ elif 'examples' in section.lower() or section.lower() == 'epilog':
120
+ # We don't process examples here to avoid duplication
121
+ pass
122
+ elif section.lower() != 'usage examples':
123
+ # Other sections (skip 'usage examples' to avoid duplication)
124
+ console.print(f"\n[bold cyan]{section.upper()}:[/bold cyan]")
125
+ console.print(content.strip())
126
+
127
+ # Always show examples at the end
128
+ console.print(f"\n[bold cyan]{translator.get('examples')}:[/bold cyan]")
129
+ # Get the actual examples from translations
130
+ examples_text = translator.get('examples_text')
131
+ examples = [line.strip() for line in examples_text.strip().split('\n') if line.strip()]
132
+
133
+ examples_table = Table(box=None, expand=True, show_header=False, padding=(0, 1, 0, 1))
134
+ examples_table.add_column("Example", overflow="fold")
135
+
136
+ for example in examples:
137
+ if example.strip():
138
+ examples_table.add_row(Syntax(example.strip(), "bash", theme="monokai"))
139
+
140
+ console.print(Panel(examples_table, border_style="cyan", padding=(1, 2)))
141
+
142
+ def _print_arguments_table(content):
143
+ """Prints a table of arguments from the text content"""
144
+ table = Table(show_header=False, box=None, padding=(0, 2, 0, 0), expand=True)
145
+ table.add_column(translator.get('argument_column'), style="bold green", width=30, no_wrap=True)
146
+ table.add_column(translator.get('description_column'), style="dim white", overflow="fold")
147
+
148
+ lines = content.strip().split('\n')
149
+ current_arg = None
150
+ current_desc = []
151
+
152
+ for line in lines:
153
+ if line.strip():
154
+ if not line.startswith(' '):
155
+ # Es un nuevo argumento
156
+ if current_arg:
157
+ # Estilizar el argumento
158
+ styled_arg = current_arg
159
+ if '-' in styled_arg:
160
+ # Resaltar las opciones cortas y largas
161
+ parts = styled_arg.split(', ')
162
+ styled_parts = []
163
+ for part in parts:
164
+ if part.startswith('--'):
165
+ styled_parts.append(f"[cyan]{part}[/cyan]")
166
+ elif part.startswith('-'):
167
+ styled_parts.append(f"[green]{part}[/green]")
168
+ else:
169
+ styled_parts.append(part)
170
+ styled_arg = ", ".join(styled_parts)
171
+
172
+ table.add_row(styled_arg, '\n'.join(current_desc))
173
+
174
+ parts = line.strip().split(' ', 1)
175
+ current_arg = parts[0].strip()
176
+ current_desc = [parts[1].strip()] if len(parts) > 1 else []
177
+ else:
178
+ # Es continuación de la descripción
179
+ current_desc.append(line.strip())
180
+
181
+ # Añadir el último argumento
182
+ if current_arg:
183
+ # Estilizar el último argumento
184
+ styled_arg = current_arg
185
+ if '-' in styled_arg:
186
+ # Resaltar las opciones cortas y largas
187
+ parts = styled_arg.split(', ')
188
+ styled_parts = []
189
+ for part in parts:
190
+ if part.startswith('--'):
191
+ styled_parts.append(f"[cyan]{part}[/cyan]")
192
+ elif part.startswith('-'):
193
+ styled_parts.append(f"[green]{part}[/green]")
194
+ else:
195
+ styled_parts.append(part)
196
+ styled_arg = ", ".join(styled_parts)
197
+
198
+ table.add_row(styled_arg, '\n'.join(current_desc))
199
+
200
+ console.print(table)
201
+
202
+ class DarsExporter:
203
+ """Exportador principal de Dars"""
204
+
205
+ def __init__(self):
206
+ self.exporters = {
207
+ 'html': HTMLCSSJSExporter()
208
+ }
209
+
210
+ def load_app_from_file(self, file_path: str) -> Optional[App]:
211
+ """Loads a Dars application from a Python file"""
212
+ try:
213
+ # Verify that the file exists
214
+ if not os.path.exists(file_path):
215
+ console.print(f"[red]{translator.get('error_file_not_exists')} {file_path}[/red]")
216
+ return None
217
+
218
+ # Load the module dynamically
219
+ spec = importlib.util.spec_from_file_location("user_app", file_path)
220
+ if spec is None or spec.loader is None:
221
+ console.print(f"[red]{translator.get('error_file_load')} {file_path}[/red]")
222
+ return None
223
+
224
+ module = importlib.util.module_from_spec(spec)
225
+
226
+ # Add the file directory to the path for relative imports
227
+ file_dir = os.path.dirname(os.path.abspath(file_path))
228
+
229
+
230
+ spec.loader.exec_module(module)
231
+
232
+ # Look for the 'app' variable in the module
233
+ if hasattr(module, 'app') and isinstance(module.app, App):
234
+ return module.app
235
+ else:
236
+ console.print(f"[red]{translator.get('error_no_app_var')} {file_path}[/red]")
237
+ return None
238
+
239
+ except Exception as e:
240
+ console.print(f"[red]{translator.get('error_loading_file')}: {e}[/red]")
241
+ return None
242
+
243
+ def validate_app(self, app: App) -> bool:
244
+ """Validates a Dars application"""
245
+ errors = app.validate()
246
+
247
+ if errors:
248
+ console.print(f"[red]{translator.get('validation_errors')}[/red]")
249
+ for error in errors:
250
+ console.print(f" • {error}")
251
+ return False
252
+
253
+ return True
254
+
255
+ def export_app(self, app: App, format_name: str, output_path: str, show_preview: bool = False) -> bool:
256
+ """Exports an application to the specified format"""
257
+
258
+ if format_name not in self.exporters:
259
+ console.print(f"[red]{translator.get('error_format_not_supported')} '{format_name}'[/red]")
260
+ self.show_supported_formats()
261
+ return False
262
+
263
+ exporter = self.exporters[format_name]
264
+
265
+ with Progress(
266
+ SpinnerColumn(),
267
+ TextColumn("[progress.description]{task.description}"),
268
+ BarColumn(),
269
+ TaskProgressColumn(),
270
+ console=console
271
+ ) as progress:
272
+
273
+ # Validation task
274
+ task1 = progress.add_task(translator.get('validating_app'), total=100)
275
+ progress.update(task1, advance=30)
276
+
277
+ if not self.validate_app(app):
278
+ progress.update(task1, completed=100)
279
+ return False
280
+
281
+ progress.update(task1, advance=70)
282
+
283
+ # Export task
284
+ task2 = progress.add_task(f"{translator.get('exporting_to')} {format_name}...", total=100)
285
+ progress.update(task2, advance=20)
286
+
287
+ try:
288
+ success = exporter.export(app, output_path)
289
+ progress.update(task2, advance=80)
290
+
291
+ if success:
292
+ progress.update(task1, completed=100)
293
+ progress.update(task2, completed=100)
294
+
295
+ # Show success information
296
+ self.show_export_success(app, format_name, output_path)
297
+
298
+ if show_preview and format_name == 'html':
299
+ self.show_preview_info(output_path)
300
+
301
+ return True
302
+ else:
303
+ console.print(f"[red]{translator.get('error_during_export')} {format_name}[/red]")
304
+ return False
305
+
306
+ except Exception as e:
307
+ console.print(f"[red]{translator.get('error_during_export_exception')}: {e}[/red]")
308
+ return False
309
+
310
+ def show_supported_formats(self):
311
+ """Shows supported formats"""
312
+ table = Table(title=translator.get('supported_export_formats'))
313
+ table.add_column(translator.get('format_name'), style="cyan")
314
+ table.add_column(translator.get('format_description'), style="white")
315
+ table.add_column(translator.get('html_description'), style="green")
316
+
317
+ formats_info = {
318
+ 'html': ('HTML/CSS/JavaScript', 'Web'),
319
+ }
320
+
321
+ for format_name, (description, platform) in formats_info.items():
322
+ table.add_row(format_name, description, platform)
323
+
324
+ console.print(table)
325
+
326
+ def show_export_success(self, app: App, format_name: str, output_path: str):
327
+ """Shows export success information"""
328
+ stats = app.get_stats()
329
+
330
+ panel_content = f"""
331
+ [green]✓[/green] {translator.get('export_completed_successfully')}
332
+
333
+ [bold]{translator.get('application')}:[/bold] {app.title}
334
+ [bold]{translator.get('format')}:[/bold] {format_name}
335
+ [bold]{translator.get('output_directory')}:[/bold] {output_path}
336
+
337
+ [bold]{translator.get('statistics')}:[/bold]
338
+ • {translator.get('total_components')}: {stats['total_components']}
339
+ • {translator.get('max_depth')}: {stats['max_depth']}
340
+ • {translator.get('scripts')}: {stats['scripts_count']}
341
+ • {translator.get('global_styles')}: {stats['global_styles_count']}
342
+ """
343
+
344
+ console.print(Panel(panel_content, title=translator.get('export_successful'), border_style="green"))
345
+
346
+ def show_preview_info(self, output_path: str):
347
+ """Shows information about how to preview the application"""
348
+ index_path = os.path.join(output_path, "index.html")
349
+
350
+ if os.path.exists(index_path):
351
+ console.print(f"\n[bold cyan]{translator.get('to_preview_app')}:[/bold cyan]")
352
+ console.print(f" {translator.get('open_in_browser')}: file://{os.path.abspath(index_path)}")
353
+ console.print(f" {translator.get('or_use')}: dars preview {output_path}")
354
+
355
+ def show_app_info(self, app: App):
356
+ """Shows detailed information about the application"""
357
+ stats = app.get_stats()
358
+
359
+ # Basic information
360
+ info_table = Table(title=f"{translator.get('app_information')}: {app.title}")
361
+ info_table.add_column(translator.get('property_column'), style="cyan")
362
+ info_table.add_column(translator.get('value_column'), style="white")
363
+
364
+ info_table.add_row(translator.get('title'), app.title)
365
+ info_table.add_row(translator.get('total_components'), str(stats['total_components']))
366
+ info_table.add_row(translator.get('max_depth'), str(stats['max_depth']))
367
+ info_table.add_row(translator.get('scripts'), str(stats['scripts_count']))
368
+ info_table.add_row(translator.get('global_styles'), str(stats['global_styles_count']))
369
+ info_table.add_row(translator.get('theme'), app.config.get('theme', 'light'))
370
+ info_table.add_row(translator.get('responsive'), str(app.config.get('responsive', True)))
371
+
372
+ console.print(info_table)
373
+
374
+ # Component tree
375
+ if app.root:
376
+ console.print(f"\n[bold]{translator.get('component_structure')}:[/bold]")
377
+ self.print_component_tree(app.root)
378
+
379
+ def print_component_tree(self, component, level: int = 0):
380
+ """Prints the component tree"""
381
+ indent = " " * level
382
+ component_name = component.__class__.__name__
383
+ component_id = f" (id: {component.id})" if component.id else ""
384
+
385
+ console.print(f"{indent}├─ {component_name}{component_id}")
386
+
387
+ for child in component.children:
388
+ self.print_component_tree(child, level + 1)
389
+
390
+
391
+ def init_project(self, name: str, template: Optional[str] = None):
392
+ # 2. Create main.py with example
393
+ HELLO_WORLD_CODE = """
394
+ from dars.core.app import App
395
+ from dars.components.basic.text import Text
396
+ from dars.components.basic.button import Button
397
+ from dars.components.basic.container import Container
398
+ from dars.scripts.script import InlineScript
399
+
400
+ app = App(title="Hello World - Dars")
401
+
402
+ container = Container(style={
403
+ 'display': 'flex',
404
+ 'flex-direction': 'column',
405
+ 'align-items': 'center',
406
+ 'justify-content': 'center',
407
+ 'min-height': '100vh',
408
+ 'background-color': '#f0f2f5',
409
+ 'font-family': 'Arial, sans-serif'
410
+ })
411
+
412
+ title_text = Text("Hello World!", style={'font-size': '48px', 'color': '#2c3e50'})
413
+ subtitle_text = Text("Your first Dars application", style={'font-size': '20px', 'color': '#7f8c8d'})
414
+ button = Button("Click here!", style={'background-color': '#3498db', 'color': 'white'})
415
+
416
+ script = InlineScript(\"""
417
+ document.querySelector('button')?.addEventListener('click', () => {
418
+ alert('Congratulations! You have created your first Dars application');
419
+ });
420
+ \""")
421
+
422
+ container.add_child(title_text)
423
+ container.add_child(subtitle_text)
424
+ container.add_child(button)
425
+ app.set_root(container)
426
+ app.add_script(script)
427
+ """
428
+ """Initializes a base Dars project, optionally using a template"""
429
+ if os.path.exists(name):
430
+ console.print(f"[red]❌ {translator.get('directory_exists').format(name=name)}[/red]")
431
+ return
432
+
433
+ # 1. Create project directory
434
+ os.makedirs(name)
435
+ console.print(f"[green]✔ {translator.get('directory_created').format(name=name)}[/green]")
436
+
437
+
438
+ # 4. Generate main.py
439
+ main_py = Path(name) / "main.py"
440
+ if template:
441
+ templates = list_templates()
442
+ src = templates[template]
443
+ shutil.copy(src, main_py)
444
+ console.print(f"[green]✔ {translator.get('template_copied').format(template=template)}[/green]")
445
+ else:
446
+ # embedded hello world code...
447
+ main_py.write_text(HELLO_WORLD_CODE, encoding="utf-8")
448
+ console.print(f"[green]✔ {translator.get('main_py_created')}[/green]")
449
+
450
+ # 5. Final instructions
451
+ console.print(f"\n[bold cyan]🎉 {translator.get('project_initialized')}[/bold cyan]")
452
+ console.print(Syntax(f"cd {name}", "bash"))
453
+ console.print(Syntax(f"\n{translator.get('export_command')}:", "bash"))
454
+ console.print(Syntax(f"dars export main.py --format html --output build", "bash"))
455
+ console.print(Syntax(f"\n{translator.get('preview_command')}:", "bash"))
456
+ console.print(Syntax(f"dars preview build", "bash"))
457
+
458
+
459
+ def create_parser() -> argparse.ArgumentParser:
460
+ """Creates the command line argument parser"""
461
+ parser = argparse.ArgumentParser(
462
+ description=translator.get('main_description'),
463
+ formatter_class=RichHelpFormatter,
464
+ epilog="" # Remove epilog to avoid duplication
465
+ )
466
+
467
+ # Add language parameter to the main parser
468
+ parser.add_argument('--lang', '-l', choices=['en', 'es'], default='en',
469
+ help=translator.get('lang_help'))
470
+
471
+ subparsers = parser.add_subparsers(dest='command', help=translator.get('available_commands'))
472
+
473
+ # Export command
474
+ export_parser = subparsers.add_parser('export', help=translator.get('export_help'))
475
+ export_parser.add_argument('file', help=translator.get('file_help'))
476
+ export_parser.add_argument('--format', '-f', required=True,
477
+ choices=["html"],
478
+ help=translator.get('format_help'))
479
+ export_parser.add_argument('--output', '-o', required=True,
480
+ help=translator.get('output_help'))
481
+ export_parser.add_argument('--preview', '-p', action='store_true',
482
+ help=translator.get('preview_help'))
483
+
484
+ # Info command
485
+ info_parser = subparsers.add_parser('info', help=translator.get('info_help'))
486
+ info_parser.add_argument('file', help=translator.get('file_help'))
487
+
488
+ # Formats command
489
+ formats_parser = subparsers.add_parser('formats', help=translator.get('formats_help'))
490
+
491
+ # Preview command
492
+ preview_parser = subparsers.add_parser('preview', help=translator.get('preview_cmd_help'))
493
+ preview_parser.add_argument('path', help=translator.get('path_help'))
494
+
495
+ # Init command
496
+ init_parser = subparsers.add_parser('init', help=translator.get('init_help'))
497
+ init_parser.add_argument('name', help=translator.get('name_help'))
498
+ tmpl_choices = list_templates().keys()
499
+ init_parser.add_argument(
500
+ '-t', '--template',
501
+ choices=tmpl_choices,
502
+ help=translator.get('template_help')
503
+ )
504
+ # Add language option to all subparsers
505
+ for subparser in [export_parser, info_parser, formats_parser, preview_parser, init_parser]:
506
+ subparser.add_argument('--lang', '-l', choices=['en', 'es'], default='en',
507
+ help=translator.get('lang_help'))
508
+
509
+ return parser
510
+ def list_templates() -> Dict[str, Path]:
511
+ """
512
+ Busca carpetas dentro de dars/templates/examples y devuelve un dict
513
+ { "basic/hello_world.py": Path(...), ... }
514
+ """
515
+ tmpl_root = Path(resources.files("dars.templates") / "examples")
516
+ templates = {}
517
+ for category in tmpl_root.iterdir():
518
+ if category.is_dir():
519
+ for py in category.glob("*.py"):
520
+ key = f"{category.name}/{py.stem}"
521
+ templates[key] = py
522
+ return templates
523
+ def main():
524
+ """Main CLI function"""
525
+ # Check for language parameter before parsing arguments
526
+ # If --lang is not specified, it will use the saved preference or default to English
527
+ for i, arg in enumerate(sys.argv):
528
+ if arg in ['--lang', '-l'] and i + 1 < len(sys.argv):
529
+ lang = sys.argv[i + 1]
530
+ if lang in ['en', 'es']:
531
+ # Save the language preference when explicitly specified
532
+ translator.set_language(lang, save=True)
533
+
534
+ # Intercept help before parsing arguments
535
+ if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
536
+ parser = create_parser()
537
+
538
+ # Show banner
539
+ console.print(Panel(
540
+ Text("Dars Exporter", style="bold cyan", justify="center"),
541
+ subtitle=translator.get('main_description'),
542
+ border_style="cyan"
543
+ ))
544
+
545
+ # If it's general help
546
+ if len(sys.argv) == 1 or (len(sys.argv) == 2 and (sys.argv[1] == '-h' or sys.argv[1] == '--help')):
547
+ RichHelpFormatter.rich_print_help(parser)
548
+ return
549
+
550
+ # If it's help for a subcommand
551
+ if len(sys.argv) > 2 and (sys.argv[2] == '-h' or sys.argv[2] == '--help'):
552
+ subcommand = sys.argv[1]
553
+ # Get the corresponding subparser
554
+ subparsers_actions = [action for action in parser._actions
555
+ if isinstance(action, argparse._SubParsersAction)]
556
+ for subparsers_action in subparsers_actions:
557
+ for choice, subparser in subparsers_action.choices.items():
558
+ if choice == subcommand:
559
+ RichHelpFormatter.rich_print_help(subparser)
560
+ return
561
+
562
+ # Continue with normal flow if not help
563
+ parser = create_parser()
564
+ args = parser.parse_args()
565
+
566
+ # Set language from args only if explicitly provided
567
+ # This is already handled in the pre-parsing step above, so we don't need to do it again
568
+ # The translator will already have the correct language set
569
+
570
+ # Show banner for normal commands
571
+ console.print(Panel(
572
+ Text("Dars Exporter", style="bold cyan", justify="center"),
573
+ subtitle=translator.get('cli_subtitle'),
574
+ border_style="cyan"
575
+ ))
576
+
577
+ exporter = DarsExporter()
578
+
579
+ if args.command == 'export':
580
+ # Load application
581
+ app = exporter.load_app_from_file(args.file)
582
+ if app is None:
583
+ sys.exit(1)
584
+
585
+ # Export
586
+ success = exporter.export_app(app, args.format, args.output, args.preview)
587
+ sys.exit(0 if success else 1)
588
+
589
+ elif args.command == 'info':
590
+ # Show information
591
+ app = exporter.load_app_from_file(args.file)
592
+ if app is None:
593
+ sys.exit(1)
594
+
595
+ exporter.show_app_info(app)
596
+
597
+ elif args.command == 'formats':
598
+ # Show formats
599
+ exporter.show_supported_formats()
600
+
601
+ elif args.command == 'init':
602
+ exporter.init_project(args.name, template=args.template)
603
+
604
+
605
+ elif args.command == 'preview':
606
+ index_path = os.path.join(args.path, "index.html")
607
+ if os.path.exists(index_path):
608
+ console.print(f"[green]{translator.get('app_found')}: {args.path} [/green]")
609
+ console.print(f"{translator.get('open_in_browser')}: file://{os.path.abspath(index_path)}")
610
+ console.print(f"{translator.get('view_preview')} [green]y[/green] / [red]n[/red] [y/n] ")
611
+ if input().lower() == 'y':
612
+ # Pass the current language to preview.py
613
+
614
+ import subprocess
615
+ process = None
616
+ try:
617
+ process = subprocess.Popen([sys.executable, '-m', 'dars.cli.preview', args.path])
618
+ process.wait()
619
+ except KeyboardInterrupt:
620
+ if process:
621
+ process.terminate()
622
+ process.wait()
623
+ finally:
624
+ if process and process.poll() is None:
625
+ process.terminate()
626
+ process.wait()
627
+ else:
628
+ console.print(f"[red]{translator.get('index_not_found')} {args.path}[/red]")
629
+
630
+
631
+ else:
632
+ # Usar nuestro formateador personalizado en lugar del estándar
633
+ RichHelpFormatter.rich_print_help(parser)
634
+
635
+ if __name__ == "__main__":
636
+ main()
637
+