quantum-framework 0.9.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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
quantum/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Quantum main package
|
quantum/cli/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum CLI Commands
|
|
3
|
+
|
|
4
|
+
Each command module provides a click command group or command.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from quantum.cli.commands.new import new
|
|
8
|
+
from quantum.cli.commands.dev import dev
|
|
9
|
+
from quantum.cli.commands.build import build
|
|
10
|
+
from quantum.cli.commands.serve import serve
|
|
11
|
+
from quantum.cli.commands.test import test
|
|
12
|
+
from quantum.cli.commands.lint import lint
|
|
13
|
+
from quantum.cli.commands.docs import docs
|
|
14
|
+
|
|
15
|
+
__all__ = ['new', 'dev', 'build', 'serve', 'test', 'lint', 'docs']
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum CLI - Build Command
|
|
3
|
+
|
|
4
|
+
Build Quantum applications for production.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, List
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from quantum.cli.utils import get_console, find_project_root, find_q_files
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Available build targets
|
|
19
|
+
TARGETS = ['html', 'desktop', 'mobile', 'textual', 'all']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command('build')
|
|
23
|
+
@click.option('--target', '-t', type=click.Choice(TARGETS), default='html',
|
|
24
|
+
help='Build target (html, desktop, mobile, textual, all)')
|
|
25
|
+
@click.option('--output', '-o', type=click.Path(), default='./dist',
|
|
26
|
+
help='Output directory')
|
|
27
|
+
@click.option('--minify', is_flag=True, default=True, help='Minify output')
|
|
28
|
+
@click.option('--no-minify', is_flag=True, help='Disable minification')
|
|
29
|
+
@click.option('--sourcemap', is_flag=True, help='Generate source maps')
|
|
30
|
+
@click.option('--watch', '-w', is_flag=True, help='Watch mode - rebuild on changes')
|
|
31
|
+
@click.option('--clean', is_flag=True, help='Clean output directory before build')
|
|
32
|
+
@click.option('--config', '-c', type=click.Path(), default='quantum.config.yaml',
|
|
33
|
+
help='Config file path')
|
|
34
|
+
@click.option('--debug', is_flag=True, help='Debug mode with verbose output')
|
|
35
|
+
@click.option('--quiet', '-q', is_flag=True, help='Quiet mode')
|
|
36
|
+
def build(
|
|
37
|
+
target: str,
|
|
38
|
+
output: str,
|
|
39
|
+
minify: bool,
|
|
40
|
+
no_minify: bool,
|
|
41
|
+
sourcemap: bool,
|
|
42
|
+
watch: bool,
|
|
43
|
+
clean: bool,
|
|
44
|
+
config: str,
|
|
45
|
+
debug: bool,
|
|
46
|
+
quiet: bool
|
|
47
|
+
):
|
|
48
|
+
"""Build Quantum application for production.
|
|
49
|
+
|
|
50
|
+
Compiles .q files to the specified target format.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
|
|
54
|
+
quantum build
|
|
55
|
+
|
|
56
|
+
quantum build --target desktop
|
|
57
|
+
|
|
58
|
+
quantum build --target all --output ./build
|
|
59
|
+
|
|
60
|
+
quantum build --target mobile --no-minify
|
|
61
|
+
"""
|
|
62
|
+
console = get_console(quiet=quiet)
|
|
63
|
+
|
|
64
|
+
# Find project root
|
|
65
|
+
project_root = find_project_root()
|
|
66
|
+
if not project_root:
|
|
67
|
+
console.error("No Quantum project found. Run from a project directory or use 'quantum new'")
|
|
68
|
+
raise click.Abort()
|
|
69
|
+
|
|
70
|
+
output_dir = Path(output)
|
|
71
|
+
if not output_dir.is_absolute():
|
|
72
|
+
output_dir = project_root / output
|
|
73
|
+
|
|
74
|
+
# Resolve minify flag
|
|
75
|
+
should_minify = minify and not no_minify
|
|
76
|
+
|
|
77
|
+
console.header(
|
|
78
|
+
"Building Quantum Application",
|
|
79
|
+
f"Target: {target} | Output: {output_dir}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Clean output directory
|
|
83
|
+
if clean and output_dir.exists():
|
|
84
|
+
with console.spinner("Cleaning output directory..."):
|
|
85
|
+
shutil.rmtree(output_dir)
|
|
86
|
+
console.info(f"Cleaned: {output_dir}")
|
|
87
|
+
|
|
88
|
+
# Create output directory
|
|
89
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
# Find .q files
|
|
92
|
+
q_files = find_q_files(project_root)
|
|
93
|
+
if not q_files:
|
|
94
|
+
console.error("No .q files found in project")
|
|
95
|
+
raise click.Abort()
|
|
96
|
+
|
|
97
|
+
console.info(f"Found {len(q_files)} .q files")
|
|
98
|
+
|
|
99
|
+
# Determine targets to build
|
|
100
|
+
if target == 'all':
|
|
101
|
+
targets_to_build = ['html', 'desktop', 'mobile', 'textual']
|
|
102
|
+
else:
|
|
103
|
+
targets_to_build = [target]
|
|
104
|
+
|
|
105
|
+
# Build each target
|
|
106
|
+
results = {}
|
|
107
|
+
for build_target in targets_to_build:
|
|
108
|
+
console.print()
|
|
109
|
+
console.info(f"Building for target: [bold]{build_target}[/bold]")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
result = _build_target(
|
|
113
|
+
project_root=project_root,
|
|
114
|
+
q_files=q_files,
|
|
115
|
+
target=build_target,
|
|
116
|
+
output_dir=output_dir / build_target if target == 'all' else output_dir,
|
|
117
|
+
minify=should_minify,
|
|
118
|
+
sourcemap=sourcemap,
|
|
119
|
+
debug=debug,
|
|
120
|
+
console=console
|
|
121
|
+
)
|
|
122
|
+
results[build_target] = result
|
|
123
|
+
console.success(f"Built {build_target}: {result['output_path']}")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
console.error(f"Failed to build {build_target}: {e}")
|
|
127
|
+
if debug:
|
|
128
|
+
import traceback
|
|
129
|
+
console.print(traceback.format_exc())
|
|
130
|
+
results[build_target] = {'error': str(e)}
|
|
131
|
+
|
|
132
|
+
# Summary
|
|
133
|
+
console.print()
|
|
134
|
+
successful = [t for t, r in results.items() if 'error' not in r]
|
|
135
|
+
failed = [t for t, r in results.items() if 'error' in r]
|
|
136
|
+
|
|
137
|
+
if successful:
|
|
138
|
+
console.panel(
|
|
139
|
+
f"[bold green]Successfully built:[/bold green] {', '.join(successful)}\n"
|
|
140
|
+
f"[bold]Output:[/bold] {output_dir}",
|
|
141
|
+
title="Build Complete"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if failed:
|
|
145
|
+
console.warning(f"Failed targets: {', '.join(failed)}")
|
|
146
|
+
|
|
147
|
+
# Watch mode
|
|
148
|
+
if watch:
|
|
149
|
+
console.print()
|
|
150
|
+
console.info("Watching for changes... (Ctrl+C to stop)")
|
|
151
|
+
_watch_and_rebuild(
|
|
152
|
+
project_root=project_root,
|
|
153
|
+
targets=targets_to_build,
|
|
154
|
+
output_dir=output_dir,
|
|
155
|
+
minify=should_minify,
|
|
156
|
+
sourcemap=sourcemap,
|
|
157
|
+
debug=debug,
|
|
158
|
+
console=console,
|
|
159
|
+
target=target
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _build_target(
|
|
164
|
+
project_root: Path,
|
|
165
|
+
q_files: List[Path],
|
|
166
|
+
target: str,
|
|
167
|
+
output_dir: Path,
|
|
168
|
+
minify: bool,
|
|
169
|
+
sourcemap: bool,
|
|
170
|
+
debug: bool,
|
|
171
|
+
console
|
|
172
|
+
) -> dict:
|
|
173
|
+
"""Build a specific target."""
|
|
174
|
+
import sys
|
|
175
|
+
|
|
176
|
+
from quantum.core.parser import QuantumParser
|
|
177
|
+
from quantum.core.ast_nodes import ApplicationNode
|
|
178
|
+
|
|
179
|
+
parser = QuantumParser()
|
|
180
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
|
|
182
|
+
built_files = []
|
|
183
|
+
|
|
184
|
+
with console.progress("Building...", total=len(q_files)) as advance:
|
|
185
|
+
for q_file in q_files:
|
|
186
|
+
try:
|
|
187
|
+
# Parse file
|
|
188
|
+
ast = parser.parse_file(str(q_file))
|
|
189
|
+
|
|
190
|
+
# Only build ApplicationNode files
|
|
191
|
+
if isinstance(ast, ApplicationNode):
|
|
192
|
+
output_path = _build_application(
|
|
193
|
+
app=ast,
|
|
194
|
+
target=target,
|
|
195
|
+
output_dir=output_dir,
|
|
196
|
+
minify=minify,
|
|
197
|
+
debug=debug
|
|
198
|
+
)
|
|
199
|
+
built_files.append(output_path)
|
|
200
|
+
|
|
201
|
+
if advance:
|
|
202
|
+
advance()
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
if debug:
|
|
206
|
+
console.warning(f"Skipping {q_file.name}: {e}")
|
|
207
|
+
|
|
208
|
+
# Copy static assets
|
|
209
|
+
assets_dir = project_root / 'assets'
|
|
210
|
+
if assets_dir.exists():
|
|
211
|
+
target_assets = output_dir / 'assets'
|
|
212
|
+
if target_assets.exists():
|
|
213
|
+
shutil.rmtree(target_assets)
|
|
214
|
+
shutil.copytree(assets_dir, target_assets)
|
|
215
|
+
console.info(f"Copied assets to {target_assets}")
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
'target': target,
|
|
219
|
+
'output_path': str(output_dir),
|
|
220
|
+
'files_built': len(built_files),
|
|
221
|
+
'minified': minify,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _build_application(
|
|
226
|
+
app,
|
|
227
|
+
target: str,
|
|
228
|
+
output_dir: Path,
|
|
229
|
+
minify: bool,
|
|
230
|
+
debug: bool
|
|
231
|
+
) -> str:
|
|
232
|
+
"""Build an application node to target."""
|
|
233
|
+
from quantum.runtime.ui_builder import UIBuilder, UIBuildError
|
|
234
|
+
|
|
235
|
+
# Determine builder based on app type
|
|
236
|
+
app_type = getattr(app, 'app_type', 'html')
|
|
237
|
+
|
|
238
|
+
if app_type == 'game':
|
|
239
|
+
from quantum.runtime.game_builder import GameBuilder
|
|
240
|
+
builder = GameBuilder()
|
|
241
|
+
ext = '.html'
|
|
242
|
+
elif app_type == 'terminal':
|
|
243
|
+
from quantum.runtime.terminal_builder import TerminalBuilder
|
|
244
|
+
builder = TerminalBuilder()
|
|
245
|
+
ext = '.py'
|
|
246
|
+
elif app_type == 'testing':
|
|
247
|
+
from quantum.runtime.testing_builder import TestingBuilder
|
|
248
|
+
builder = TestingBuilder()
|
|
249
|
+
ext = '.py'
|
|
250
|
+
elif app_type == 'ui' or target in ('html', 'desktop', 'mobile', 'textual'):
|
|
251
|
+
builder = UIBuilder()
|
|
252
|
+
# Map target to extension
|
|
253
|
+
ext_map = {
|
|
254
|
+
'html': '.html',
|
|
255
|
+
'desktop': '.py',
|
|
256
|
+
'mobile': '.js',
|
|
257
|
+
'textual': '.py',
|
|
258
|
+
}
|
|
259
|
+
ext = ext_map.get(target, '.html')
|
|
260
|
+
else:
|
|
261
|
+
# Default to HTML
|
|
262
|
+
builder = UIBuilder()
|
|
263
|
+
ext = '.html'
|
|
264
|
+
target = 'html'
|
|
265
|
+
|
|
266
|
+
# Build output
|
|
267
|
+
output_file = output_dir / f"{app.app_id}{ext}"
|
|
268
|
+
|
|
269
|
+
if hasattr(builder, 'build_to_file'):
|
|
270
|
+
# UI builder with target
|
|
271
|
+
if isinstance(builder, UIBuilder):
|
|
272
|
+
output_path = builder.build_to_file(app, target=target, output_path=str(output_file))
|
|
273
|
+
else:
|
|
274
|
+
output_path = builder.build_to_file(app, output_path=str(output_file))
|
|
275
|
+
else:
|
|
276
|
+
# Generic builder
|
|
277
|
+
code = builder.build(app)
|
|
278
|
+
output_file.write_text(code, encoding='utf-8')
|
|
279
|
+
output_path = str(output_file)
|
|
280
|
+
|
|
281
|
+
# Minify if requested
|
|
282
|
+
if minify and ext == '.html':
|
|
283
|
+
_minify_html(output_path)
|
|
284
|
+
elif minify and ext == '.js':
|
|
285
|
+
_minify_js(output_path)
|
|
286
|
+
elif minify and ext == '.css':
|
|
287
|
+
_minify_css(output_path)
|
|
288
|
+
|
|
289
|
+
return output_path
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _minify_html(path: str) -> None:
|
|
293
|
+
"""Minify HTML file."""
|
|
294
|
+
try:
|
|
295
|
+
import re
|
|
296
|
+
content = Path(path).read_text(encoding='utf-8')
|
|
297
|
+
|
|
298
|
+
# Basic HTML minification
|
|
299
|
+
# Remove comments
|
|
300
|
+
content = re.sub(r'<!--(?!\[if).*?-->', '', content, flags=re.DOTALL)
|
|
301
|
+
# Remove extra whitespace between tags
|
|
302
|
+
content = re.sub(r'>\s+<', '><', content)
|
|
303
|
+
# Remove leading/trailing whitespace
|
|
304
|
+
content = '\n'.join(line.strip() for line in content.splitlines() if line.strip())
|
|
305
|
+
|
|
306
|
+
Path(path).write_text(content, encoding='utf-8')
|
|
307
|
+
except Exception:
|
|
308
|
+
pass # Minification is optional
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _minify_js(path: str) -> None:
|
|
312
|
+
"""Minify JavaScript file."""
|
|
313
|
+
try:
|
|
314
|
+
import re
|
|
315
|
+
content = Path(path).read_text(encoding='utf-8')
|
|
316
|
+
|
|
317
|
+
# Basic JS minification
|
|
318
|
+
# Remove single-line comments (but not URLs)
|
|
319
|
+
content = re.sub(r'(?<!:)//.*$', '', content, flags=re.MULTILINE)
|
|
320
|
+
# Remove multi-line comments
|
|
321
|
+
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
|
|
322
|
+
# Remove extra whitespace
|
|
323
|
+
content = ' '.join(content.split())
|
|
324
|
+
|
|
325
|
+
Path(path).write_text(content, encoding='utf-8')
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _minify_css(path: str) -> None:
|
|
331
|
+
"""Minify CSS file."""
|
|
332
|
+
try:
|
|
333
|
+
import re
|
|
334
|
+
content = Path(path).read_text(encoding='utf-8')
|
|
335
|
+
|
|
336
|
+
# Basic CSS minification
|
|
337
|
+
# Remove comments
|
|
338
|
+
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
|
|
339
|
+
# Remove extra whitespace
|
|
340
|
+
content = re.sub(r'\s+', ' ', content)
|
|
341
|
+
# Remove space around special chars
|
|
342
|
+
content = re.sub(r'\s*([{};:,])\s*', r'\1', content)
|
|
343
|
+
|
|
344
|
+
Path(path).write_text(content, encoding='utf-8')
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _watch_and_rebuild(
|
|
350
|
+
project_root: Path,
|
|
351
|
+
targets: List[str],
|
|
352
|
+
output_dir: Path,
|
|
353
|
+
minify: bool,
|
|
354
|
+
sourcemap: bool,
|
|
355
|
+
debug: bool,
|
|
356
|
+
console,
|
|
357
|
+
target: str
|
|
358
|
+
):
|
|
359
|
+
"""Watch for changes and rebuild."""
|
|
360
|
+
import time
|
|
361
|
+
import signal
|
|
362
|
+
|
|
363
|
+
last_mtimes = {}
|
|
364
|
+
|
|
365
|
+
def get_file_mtimes():
|
|
366
|
+
mtimes = {}
|
|
367
|
+
for ext in ['.q', '.yaml', '.yml']:
|
|
368
|
+
for f in project_root.rglob(f'*{ext}'):
|
|
369
|
+
try:
|
|
370
|
+
mtimes[f] = f.stat().st_mtime
|
|
371
|
+
except OSError:
|
|
372
|
+
pass
|
|
373
|
+
return mtimes
|
|
374
|
+
|
|
375
|
+
def signal_handler(sig, frame):
|
|
376
|
+
console.print()
|
|
377
|
+
console.info("Stopping watch mode...")
|
|
378
|
+
raise SystemExit(0)
|
|
379
|
+
|
|
380
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
381
|
+
last_mtimes = get_file_mtimes()
|
|
382
|
+
|
|
383
|
+
while True:
|
|
384
|
+
time.sleep(1)
|
|
385
|
+
current_mtimes = get_file_mtimes()
|
|
386
|
+
|
|
387
|
+
changed = []
|
|
388
|
+
for f, mtime in current_mtimes.items():
|
|
389
|
+
if f not in last_mtimes or mtime > last_mtimes[f]:
|
|
390
|
+
changed.append(f)
|
|
391
|
+
|
|
392
|
+
if changed:
|
|
393
|
+
console.print()
|
|
394
|
+
for f in changed:
|
|
395
|
+
rel_path = f.relative_to(project_root)
|
|
396
|
+
console.info(f"Changed: {rel_path}")
|
|
397
|
+
|
|
398
|
+
console.info("Rebuilding...")
|
|
399
|
+
|
|
400
|
+
q_files = find_q_files(project_root)
|
|
401
|
+
for build_target in targets:
|
|
402
|
+
try:
|
|
403
|
+
_build_target(
|
|
404
|
+
project_root=project_root,
|
|
405
|
+
q_files=q_files,
|
|
406
|
+
target=build_target,
|
|
407
|
+
output_dir=output_dir / build_target if target == 'all' else output_dir,
|
|
408
|
+
minify=minify,
|
|
409
|
+
sourcemap=sourcemap,
|
|
410
|
+
debug=debug,
|
|
411
|
+
console=console
|
|
412
|
+
)
|
|
413
|
+
console.success(f"Rebuilt {build_target}")
|
|
414
|
+
except Exception as e:
|
|
415
|
+
console.error(f"Failed to rebuild {build_target}: {e}")
|
|
416
|
+
|
|
417
|
+
last_mtimes = current_mtimes
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum CLI - Dev Command
|
|
3
|
+
|
|
4
|
+
Start development server with hot reload.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import signal
|
|
11
|
+
import threading
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Set, List
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from quantum.cli.utils import get_console, find_project_root, find_q_files
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command('dev')
|
|
22
|
+
@click.option('--port', '-p', type=int, default=8080, help='Port to run on')
|
|
23
|
+
@click.option('--host', '-h', type=str, default='0.0.0.0', help='Host to bind to')
|
|
24
|
+
@click.option('--no-reload', is_flag=True, help='Disable hot reload')
|
|
25
|
+
@click.option('--config', '-c', type=click.Path(), default='quantum.config.yaml',
|
|
26
|
+
help='Config file path')
|
|
27
|
+
@click.option('--debug', is_flag=True, help='Enable debug mode')
|
|
28
|
+
@click.option('--quiet', '-q', is_flag=True, help='Quiet mode')
|
|
29
|
+
@click.option('--ws-port', type=int, default=35729, help='WebSocket port for hot reload')
|
|
30
|
+
@click.pass_context
|
|
31
|
+
def dev(ctx, port: int, host: str, no_reload: bool, config: str, debug: bool, quiet: bool, ws_port: int):
|
|
32
|
+
"""Start development server with hot reload.
|
|
33
|
+
|
|
34
|
+
Watches .q files for changes and automatically reloads.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
|
|
38
|
+
quantum dev
|
|
39
|
+
|
|
40
|
+
quantum dev --port 3000
|
|
41
|
+
|
|
42
|
+
quantum dev --no-reload --debug
|
|
43
|
+
|
|
44
|
+
quantum dev --ws-port 35730
|
|
45
|
+
"""
|
|
46
|
+
console = get_console(quiet=quiet)
|
|
47
|
+
|
|
48
|
+
# Find project root
|
|
49
|
+
project_root = find_project_root()
|
|
50
|
+
if not project_root:
|
|
51
|
+
console.warning("No Quantum project found in current directory.")
|
|
52
|
+
console.info("Creating a minimal dev server for current directory...")
|
|
53
|
+
project_root = Path.cwd()
|
|
54
|
+
|
|
55
|
+
config_path = project_root / config
|
|
56
|
+
|
|
57
|
+
console.header(
|
|
58
|
+
"Quantum Development Server",
|
|
59
|
+
f"Project: {project_root.name}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Find .q files
|
|
63
|
+
q_files = find_q_files(project_root)
|
|
64
|
+
console.info(f"Found {len(q_files)} .q files")
|
|
65
|
+
|
|
66
|
+
# Hot reload manager
|
|
67
|
+
hot_reload_manager = None
|
|
68
|
+
|
|
69
|
+
if not no_reload:
|
|
70
|
+
try:
|
|
71
|
+
from quantum.cli.hot_reload import HotReloadManager, ReloadType
|
|
72
|
+
|
|
73
|
+
# Determine watch paths
|
|
74
|
+
watch_paths = [project_root]
|
|
75
|
+
|
|
76
|
+
# Also watch components directory if it exists
|
|
77
|
+
components_dir = project_root / 'components'
|
|
78
|
+
if components_dir.exists():
|
|
79
|
+
watch_paths.append(components_dir)
|
|
80
|
+
|
|
81
|
+
# Watch static directory for CSS/JS changes
|
|
82
|
+
static_dir = project_root / 'static'
|
|
83
|
+
if static_dir.exists():
|
|
84
|
+
watch_paths.append(static_dir)
|
|
85
|
+
|
|
86
|
+
def on_reload(changes, reload_type):
|
|
87
|
+
"""Callback when files change."""
|
|
88
|
+
console.print()
|
|
89
|
+
for change in changes:
|
|
90
|
+
try:
|
|
91
|
+
rel_path = change.path.relative_to(project_root)
|
|
92
|
+
except ValueError:
|
|
93
|
+
rel_path = change.path
|
|
94
|
+
|
|
95
|
+
timestamp = datetime.now().strftime('%H:%M:%S')
|
|
96
|
+
console.info(f"[dim]{timestamp}[/dim] {change.change_type}: [path]{rel_path}[/path]")
|
|
97
|
+
|
|
98
|
+
if reload_type == ReloadType.CSS:
|
|
99
|
+
console.info("[bold green]CSS updated[/bold green] (no full reload)")
|
|
100
|
+
else:
|
|
101
|
+
console.info("[bold blue]Reloading...[/bold blue]")
|
|
102
|
+
|
|
103
|
+
hot_reload_manager = HotReloadManager(
|
|
104
|
+
watch_paths=watch_paths,
|
|
105
|
+
extensions=['.q', '.yaml', '.yml', '.css', '.js', '.html'],
|
|
106
|
+
ws_host='localhost',
|
|
107
|
+
ws_port=ws_port,
|
|
108
|
+
debounce_ms=100,
|
|
109
|
+
on_reload=on_reload,
|
|
110
|
+
console=console
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
hot_reload_manager.start()
|
|
114
|
+
|
|
115
|
+
except ImportError as e:
|
|
116
|
+
console.warning(f"Hot reload dependencies not fully available: {e}")
|
|
117
|
+
console.info("Install with: pip install watchdog websockets")
|
|
118
|
+
hot_reload_manager = None
|
|
119
|
+
|
|
120
|
+
# Display server info
|
|
121
|
+
url = f"http://{host if host != '0.0.0.0' else 'localhost'}:{port}"
|
|
122
|
+
|
|
123
|
+
info_lines = [
|
|
124
|
+
f"[bold]Server:[/bold] {url}",
|
|
125
|
+
f"[bold]Hot Reload:[/bold] {'Disabled' if no_reload else 'Enabled'}",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
if not no_reload and hot_reload_manager:
|
|
129
|
+
info_lines.append(f"[bold]WebSocket:[/bold] ws://localhost:{ws_port}")
|
|
130
|
+
|
|
131
|
+
info_lines.append(f"[bold]Debug:[/bold] {'On' if debug else 'Off'}")
|
|
132
|
+
|
|
133
|
+
console.panel(
|
|
134
|
+
'\n'.join(info_lines),
|
|
135
|
+
title="Development Server",
|
|
136
|
+
style="green"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Setup signal handler
|
|
140
|
+
def signal_handler(sig, frame):
|
|
141
|
+
console.print()
|
|
142
|
+
console.info("Shutting down...")
|
|
143
|
+
if hot_reload_manager:
|
|
144
|
+
hot_reload_manager.stop()
|
|
145
|
+
sys.exit(0)
|
|
146
|
+
|
|
147
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
148
|
+
if hasattr(signal, 'SIGTERM'):
|
|
149
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Import and start server
|
|
153
|
+
|
|
154
|
+
from quantum.runtime.web_server import QuantumWebServer, start_server
|
|
155
|
+
|
|
156
|
+
console.info(f"Starting server on {url}")
|
|
157
|
+
console.print()
|
|
158
|
+
console.print("[dim]Press Ctrl+C to stop[/dim]")
|
|
159
|
+
console.print()
|
|
160
|
+
|
|
161
|
+
# Start the server with hot reload config
|
|
162
|
+
start_server(
|
|
163
|
+
str(config_path),
|
|
164
|
+
port=port,
|
|
165
|
+
hot_reload=not no_reload,
|
|
166
|
+
hot_reload_port=ws_port if not no_reload else None
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
except ImportError as e:
|
|
170
|
+
console.error(f"Failed to import Quantum runtime: {e}")
|
|
171
|
+
console.info("Make sure you're in the Quantum project directory")
|
|
172
|
+
if hot_reload_manager:
|
|
173
|
+
hot_reload_manager.stop()
|
|
174
|
+
raise click.Abort()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
console.error(f"Server error: {e}")
|
|
177
|
+
if debug:
|
|
178
|
+
import traceback
|
|
179
|
+
console.print(traceback.format_exc())
|
|
180
|
+
if hot_reload_manager:
|
|
181
|
+
hot_reload_manager.stop()
|
|
182
|
+
raise click.Abort()
|
|
183
|
+
finally:
|
|
184
|
+
if hot_reload_manager:
|
|
185
|
+
hot_reload_manager.stop()
|