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.
- dars/__init__.py +0 -0
- dars/all.py +52 -0
- dars/cli/__init__.py +0 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +637 -0
- dars/cli/preview.py +419 -0
- dars/cli/translations.py +389 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +21 -0
- dars/components/advanced/card.py +28 -0
- dars/components/advanced/modal.py +40 -0
- dars/components/advanced/navbar.py +31 -0
- dars/components/advanced/table.py +24 -0
- dars/components/advanced/tabs.py +26 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +29 -0
- dars/components/basic/checkbox.py +34 -0
- dars/components/basic/container.py +23 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +50 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +17 -0
- dars/components/basic/radiobutton.py +34 -0
- dars/components/basic/select.py +81 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +11 -0
- dars/components/basic/text.py +22 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +18 -0
- dars/components/layout/__init__.py +0 -0
- dars/components/layout/anchor.py +13 -0
- dars/components/layout/flex.py +26 -0
- dars/components/layout/grid.py +45 -0
- dars/core/__init__.py +0 -0
- dars/core/app.py +630 -0
- dars/core/component.py +25 -0
- dars/core/events.py +101 -0
- dars/core/properties.py +127 -0
- dars/docs/__init__.py +0 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +69 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +1406 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/script.py +38 -0
- dars/templates/__init__.py +0 -0
- dars/templates/examples/advanced/all_components_demo.py +87 -0
- dars/templates/examples/advanced/dashboard.py +440 -0
- dars/templates/examples/advanced/modern_web_app.py +452 -0
- dars/templates/examples/basic/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/form_components.py +516 -0
- dars/templates/examples/basic/grid_layout_responsive.py +13 -0
- dars/templates/examples/basic/hello_world.py +104 -0
- dars/templates/examples/basic/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/multipage_example.py +70 -0
- dars/templates/examples/basic/pwa_custom_icons.py +31 -0
- dars/templates/examples/basic/simple_form.py +377 -0
- dars/templates/examples/demo/complete_app.py +720 -0
- dars/templates/html/__init__.py +0 -0
- dars_framework-1.0.0.dist-info/METADATA +146 -0
- dars_framework-1.0.0.dist-info/RECORD +68 -0
- dars_framework-1.0.0.dist-info/WHEEL +5 -0
- dars_framework-1.0.0.dist-info/entry_points.txt +2 -0
- dars_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
|