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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Transaction Parser - Parse q:transaction statements
|
|
3
|
+
|
|
4
|
+
Handles atomic database transactions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.ast_nodes import TransactionNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TransactionParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:transaction statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Atomic transaction blocks
|
|
19
|
+
- Automatic rollback on error
|
|
20
|
+
- Nested statement execution
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['transaction']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> TransactionNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:transaction statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:transaction
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
TransactionNode AST node
|
|
36
|
+
"""
|
|
37
|
+
# datasource= is optional. Requiring it was stricter than the runtime
|
|
38
|
+
# (DatabaseService.begin_transaction defaults it) and stricter than
|
|
39
|
+
# everything written against it: all four q:transaction blocks in this
|
|
40
|
+
# repository omit it, and three of them are tests that had been
|
|
41
|
+
# failing on this line — "Transaction requires 'datasource'
|
|
42
|
+
# attribute" — rather than on anything about transactions.
|
|
43
|
+
datasource = self.get_attr(element, 'datasource')
|
|
44
|
+
|
|
45
|
+
transaction_node = TransactionNode(datasource or '')
|
|
46
|
+
|
|
47
|
+
# Isolation level. Everything written in this repository and in the
|
|
48
|
+
# docs spells it isolationLevel; the parser read `isolation`, so the
|
|
49
|
+
# level was silently dropped every time. Both spellings work.
|
|
50
|
+
transaction_node.isolation = (
|
|
51
|
+
self.get_attr(element, 'isolationLevel')
|
|
52
|
+
or self.get_attr(element, 'isolation')
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Parse child statements
|
|
56
|
+
for child in element:
|
|
57
|
+
statement = self.parse_statement(child)
|
|
58
|
+
if statement:
|
|
59
|
+
transaction_node.add_statement(statement)
|
|
60
|
+
|
|
61
|
+
if not datasource:
|
|
62
|
+
# Take it from the queries being wrapped, which is what the author
|
|
63
|
+
# means by wrapping them. Only if none of them names one does this
|
|
64
|
+
# fall back to "default".
|
|
65
|
+
transaction_node.datasource = (
|
|
66
|
+
self._datasource_of_children(transaction_node) or 'default'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return transaction_node
|
|
70
|
+
|
|
71
|
+
def _datasource_of_children(self, transaction_node) -> str:
|
|
72
|
+
"""The datasource of the first child query that names one."""
|
|
73
|
+
for statement in getattr(transaction_node, 'statements', []) or []:
|
|
74
|
+
name = getattr(statement, 'datasource', None)
|
|
75
|
+
if name:
|
|
76
|
+
return name
|
|
77
|
+
return ''
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DispatchEvent Parser - Parse q:dispatchEvent statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import DispatchEventNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DispatchEventParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:dispatchEvent statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:dispatchEvent event="userCreated" data="{userData}" />
|
|
17
|
+
<q:dispatchEvent event="orderPlaced" queue="orders" priority="high" />
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def tag_names(self) -> List[str]:
|
|
22
|
+
return ['dispatchEvent']
|
|
23
|
+
|
|
24
|
+
def parse(self, element: ET.Element) -> DispatchEventNode:
|
|
25
|
+
"""Parse q:dispatchEvent element"""
|
|
26
|
+
event = element.get('event')
|
|
27
|
+
|
|
28
|
+
if not event:
|
|
29
|
+
from quantum.core.parser import QuantumParseError
|
|
30
|
+
raise QuantumParseError("dispatchEvent requires 'event' attribute")
|
|
31
|
+
|
|
32
|
+
dispatch_node = DispatchEventNode(event)
|
|
33
|
+
|
|
34
|
+
# Optional attributes
|
|
35
|
+
dispatch_node.data = element.get('data')
|
|
36
|
+
dispatch_node.queue = element.get('queue')
|
|
37
|
+
dispatch_node.exchange = element.get('exchange')
|
|
38
|
+
dispatch_node.routing_key = element.get('routingKey')
|
|
39
|
+
dispatch_node.priority = element.get('priority', 'normal')
|
|
40
|
+
dispatch_node.delay = element.get('delay')
|
|
41
|
+
dispatch_node.ttl = element.get('ttl')
|
|
42
|
+
dispatch_node.metadata = element.get('metadata')
|
|
43
|
+
|
|
44
|
+
return dispatch_node
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Forms & Actions Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for form handling tags: action, redirect, flash
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .action_parser import ActionParser
|
|
8
|
+
from .redirect_parser import RedirectParser
|
|
9
|
+
from .flash_parser import FlashParser
|
|
10
|
+
|
|
11
|
+
__all__ = ['ActionParser', 'RedirectParser', 'FlashParser']
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Action Parser - Parse q:action statements for form handling
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import ActionNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActionParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:action statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:action name="createUser" method="POST">
|
|
17
|
+
<q:param name="email" type="email" required="true" />
|
|
18
|
+
<q:query datasource="db">...</q:query>
|
|
19
|
+
<q:redirect url="/users" flash="User created!" />
|
|
20
|
+
</q:action>
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['action']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> ActionNode:
|
|
28
|
+
"""Parse q:action element"""
|
|
29
|
+
name = element.get('name', '')
|
|
30
|
+
method = element.get('method', 'POST')
|
|
31
|
+
action_node = ActionNode(name, method)
|
|
32
|
+
|
|
33
|
+
# Parse optional attributes
|
|
34
|
+
if element.get('csrf') == 'false':
|
|
35
|
+
action_node.validate_csrf = False
|
|
36
|
+
if element.get('rate_limit'):
|
|
37
|
+
action_node.rate_limit = element.get('rate_limit')
|
|
38
|
+
if element.get('require_auth') == 'true':
|
|
39
|
+
action_node.require_auth = True
|
|
40
|
+
|
|
41
|
+
# Parse children (params, statements)
|
|
42
|
+
for child in element:
|
|
43
|
+
child_type = self._get_element_name(child)
|
|
44
|
+
|
|
45
|
+
if child_type == 'param':
|
|
46
|
+
param = self.parse_param(child)
|
|
47
|
+
action_node.add_param(param)
|
|
48
|
+
else:
|
|
49
|
+
# Parse other statements (query, set, redirect, etc.)
|
|
50
|
+
statement = self.parse_child(child)
|
|
51
|
+
if statement:
|
|
52
|
+
action_node.add_statement(statement)
|
|
53
|
+
|
|
54
|
+
return action_node
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flash Parser - Parse q:flash statements for flash messages
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import FlashNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FlashParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:flash statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:flash type="success" message="User created!" />
|
|
17
|
+
<q:flash type="error" message="{errorMessage}" />
|
|
18
|
+
<q:flash type="warning">Please verify your email</q:flash>
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def tag_names(self) -> List[str]:
|
|
23
|
+
return ['flash']
|
|
24
|
+
|
|
25
|
+
def parse(self, element: ET.Element) -> FlashNode:
|
|
26
|
+
"""Parse q:flash element"""
|
|
27
|
+
flash_type = element.get('type', 'info')
|
|
28
|
+
message = element.get('message')
|
|
29
|
+
|
|
30
|
+
# If message not in attribute, check text content
|
|
31
|
+
if not message and element.text:
|
|
32
|
+
message = element.text.strip()
|
|
33
|
+
|
|
34
|
+
return FlashNode(message, flash_type)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Redirect Parser - Parse q:redirect statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import RedirectNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RedirectParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:redirect statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:redirect url="/thank-you" />
|
|
17
|
+
<q:redirect url="/users/{userId}" />
|
|
18
|
+
<q:redirect url="/products" flash="Product created!" />
|
|
19
|
+
<q:redirect url="/error" status="500" flash="Error occurred" />
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def tag_names(self) -> List[str]:
|
|
24
|
+
return ['redirect']
|
|
25
|
+
|
|
26
|
+
def parse(self, element: ET.Element) -> RedirectNode:
|
|
27
|
+
"""Parse q:redirect element"""
|
|
28
|
+
# Accept to= as an alias for url= (both appear across examples)
|
|
29
|
+
url = element.get('url') or element.get('to', '')
|
|
30
|
+
flash = element.get('flash')
|
|
31
|
+
status = int(element.get('status', '302'))
|
|
32
|
+
|
|
33
|
+
return RedirectNode(url, flash, status)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for function-related tags: function, return, param
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .function_parser import FunctionParser
|
|
8
|
+
from .return_parser import ReturnParser
|
|
9
|
+
from .param_parser import ParamParser
|
|
10
|
+
|
|
11
|
+
__all__ = ['FunctionParser', 'ReturnParser', 'ParamParser']
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Function Parser - Parse q:function statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import FunctionNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FunctionParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:function statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:function name="calculateTotal" returnType="number">
|
|
17
|
+
<q:param name="items" type="array" />
|
|
18
|
+
<q:set name="total" value="0" />
|
|
19
|
+
<q:loop array="{items}" item="item">
|
|
20
|
+
<q:set name="total" value="{total + item.price}" />
|
|
21
|
+
</q:loop>
|
|
22
|
+
</q:function>
|
|
23
|
+
|
|
24
|
+
<q:function name="getUser" endpoint="/api/users/{id}" method="GET">
|
|
25
|
+
<q:param name="id" type="integer" source="path" />
|
|
26
|
+
<q:query datasource="db">SELECT * FROM users WHERE id = :id</q:query>
|
|
27
|
+
</q:function>
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def tag_names(self) -> List[str]:
|
|
32
|
+
return ['function']
|
|
33
|
+
|
|
34
|
+
def parse(self, element: ET.Element) -> FunctionNode:
|
|
35
|
+
"""Parse q:function element"""
|
|
36
|
+
name = element.get('name')
|
|
37
|
+
|
|
38
|
+
if not name:
|
|
39
|
+
from quantum.core.parser import QuantumParseError
|
|
40
|
+
raise QuantumParseError("Function requires 'name' attribute")
|
|
41
|
+
|
|
42
|
+
func_node = FunctionNode(name)
|
|
43
|
+
|
|
44
|
+
# Core attributes
|
|
45
|
+
func_node.return_type = element.get('returnType', 'any')
|
|
46
|
+
func_node.scope = element.get('scope', 'component')
|
|
47
|
+
func_node.access = element.get('access', 'public')
|
|
48
|
+
func_node.description = element.get('description')
|
|
49
|
+
func_node.hint = element.get('hint')
|
|
50
|
+
|
|
51
|
+
# Validation
|
|
52
|
+
func_node.validate_params = element.get('validate', 'false').lower() == 'true'
|
|
53
|
+
|
|
54
|
+
# Performance
|
|
55
|
+
cache_attr = element.get('cache')
|
|
56
|
+
if cache_attr:
|
|
57
|
+
if cache_attr.lower() == 'true':
|
|
58
|
+
func_node.cache = True
|
|
59
|
+
elif cache_attr.endswith('s'): # "60s"
|
|
60
|
+
func_node.cache = True
|
|
61
|
+
try:
|
|
62
|
+
func_node.cache_ttl = int(cache_attr[:-1])
|
|
63
|
+
except ValueError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
func_node.memoize = element.get('memoize', 'false').lower() == 'true'
|
|
67
|
+
func_node.pure = element.get('pure', 'false').lower() == 'true'
|
|
68
|
+
|
|
69
|
+
# Behavior
|
|
70
|
+
func_node.async_func = element.get('async', 'false').lower() == 'true'
|
|
71
|
+
|
|
72
|
+
retry_attr = element.get('retry')
|
|
73
|
+
if retry_attr:
|
|
74
|
+
try:
|
|
75
|
+
func_node.retry = int(retry_attr)
|
|
76
|
+
except ValueError:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
timeout_attr = element.get('timeout')
|
|
80
|
+
if timeout_attr:
|
|
81
|
+
func_node.timeout = timeout_attr
|
|
82
|
+
|
|
83
|
+
# REST API (optional)
|
|
84
|
+
endpoint = element.get('endpoint')
|
|
85
|
+
if endpoint:
|
|
86
|
+
method = element.get('method', 'GET')
|
|
87
|
+
func_node.enable_rest(endpoint, method)
|
|
88
|
+
|
|
89
|
+
# REST-specific attributes
|
|
90
|
+
if element.get('produces'):
|
|
91
|
+
func_node.rest_config.produces = element.get('produces')
|
|
92
|
+
|
|
93
|
+
if element.get('consumes'):
|
|
94
|
+
func_node.rest_config.consumes = element.get('consumes')
|
|
95
|
+
|
|
96
|
+
if element.get('auth'):
|
|
97
|
+
func_node.rest_config.auth = element.get('auth')
|
|
98
|
+
|
|
99
|
+
if element.get('roles'):
|
|
100
|
+
roles_str = element.get('roles')
|
|
101
|
+
func_node.rest_config.roles = [r.strip() for r in roles_str.split(',')]
|
|
102
|
+
|
|
103
|
+
if element.get('rateLimit'):
|
|
104
|
+
func_node.rest_config.rate_limit = element.get('rateLimit')
|
|
105
|
+
|
|
106
|
+
cors_attr = element.get('cors')
|
|
107
|
+
if cors_attr and cors_attr.lower() == 'true':
|
|
108
|
+
func_node.rest_config.cors = True
|
|
109
|
+
|
|
110
|
+
status_attr = element.get('status')
|
|
111
|
+
if status_attr:
|
|
112
|
+
try:
|
|
113
|
+
func_node.rest_config.status = int(status_attr)
|
|
114
|
+
except ValueError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
# Parse function params and body
|
|
118
|
+
for child in element:
|
|
119
|
+
child_type = self._get_element_name(child)
|
|
120
|
+
|
|
121
|
+
if child_type == 'param':
|
|
122
|
+
param = self.parse_param(child)
|
|
123
|
+
func_node.add_param(param)
|
|
124
|
+
elif child_type == 'return':
|
|
125
|
+
# ReturnParser is deliberately not in the tag registry (it is
|
|
126
|
+
# an internal parser), so parse_child() returns None for it —
|
|
127
|
+
# which silently dropped every <q:return> from every function
|
|
128
|
+
# body, making functions return nothing. Parse it directly.
|
|
129
|
+
from quantum.core.parsers.functions.return_parser import ReturnParser
|
|
130
|
+
func_node.add_statement(ReturnParser(self._parser).parse(child))
|
|
131
|
+
else:
|
|
132
|
+
# Parse body statements
|
|
133
|
+
statement = self.parse_child(child)
|
|
134
|
+
if statement:
|
|
135
|
+
func_node.add_statement(statement)
|
|
136
|
+
|
|
137
|
+
return func_node
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Param Parser - Parse q:param statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import QuantumParam
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParamParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:param statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:param name="email" type="email" required="true" />
|
|
17
|
+
<q:param name="password" type="string" minlength="8" />
|
|
18
|
+
<q:param name="age" type="integer" min="0" max="150" />
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def tag_names(self) -> List[str]:
|
|
23
|
+
return ['param']
|
|
24
|
+
|
|
25
|
+
def parse(self, element: ET.Element) -> QuantumParam:
|
|
26
|
+
"""Parse q:param element"""
|
|
27
|
+
param = QuantumParam(
|
|
28
|
+
name=element.get('name', ''),
|
|
29
|
+
type=element.get('type', 'string'),
|
|
30
|
+
required=element.get('required', 'false').lower() == 'true',
|
|
31
|
+
default=element.get('default'),
|
|
32
|
+
validation=element.get('validation'),
|
|
33
|
+
description=element.get('description')
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# REST-specific
|
|
37
|
+
param.source = element.get('source', 'auto')
|
|
38
|
+
|
|
39
|
+
# Validation
|
|
40
|
+
param.validate_rule = element.get('validate')
|
|
41
|
+
param.pattern = element.get('pattern')
|
|
42
|
+
param.min = element.get('min')
|
|
43
|
+
param.max = element.get('max')
|
|
44
|
+
|
|
45
|
+
minlength_str = element.get('minlength')
|
|
46
|
+
if minlength_str:
|
|
47
|
+
try:
|
|
48
|
+
param.minlength = int(minlength_str)
|
|
49
|
+
except ValueError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
maxlength_str = element.get('maxlength')
|
|
53
|
+
if maxlength_str:
|
|
54
|
+
try:
|
|
55
|
+
param.maxlength = int(maxlength_str)
|
|
56
|
+
except ValueError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
param.range = element.get('range')
|
|
60
|
+
param.enum = element.get('enum')
|
|
61
|
+
|
|
62
|
+
# File upload
|
|
63
|
+
param.maxsize = element.get('maxsize')
|
|
64
|
+
param.accept = element.get('accept')
|
|
65
|
+
|
|
66
|
+
return param
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Return Parser - Parse q:return statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import QuantumReturn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReturnParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:return statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:return name="result" type="string" value="{computedValue}" />
|
|
17
|
+
<q:return type="integer" value="42" />
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def tag_names(self) -> List[str]:
|
|
22
|
+
return ['return']
|
|
23
|
+
|
|
24
|
+
def parse(self, element: ET.Element) -> QuantumReturn:
|
|
25
|
+
"""Parse q:return element"""
|
|
26
|
+
return QuantumReturn(
|
|
27
|
+
name=element.get('name'),
|
|
28
|
+
type=element.get('type', 'string'),
|
|
29
|
+
value=element.get('value', ''),
|
|
30
|
+
description=element.get('description')
|
|
31
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Component Call Parser - Parse component invocations
|
|
3
|
+
|
|
4
|
+
Handles parsing of component calls (PascalCase tags).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Optional, Dict, Any
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
10
|
+
from quantum.core.ast_nodes import ComponentCallNode, TextNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComponentCallParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for component call elements.
|
|
16
|
+
|
|
17
|
+
Component calls are identified by PascalCase naming convention:
|
|
18
|
+
- <MyComponent /> -> calls imported component "MyComponent"
|
|
19
|
+
- <UserCard name="John" /> -> calls "UserCard" with props
|
|
20
|
+
|
|
21
|
+
These are similar to React/Vue component usage.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
"""
|
|
27
|
+
Component call parser doesn't register for specific tags.
|
|
28
|
+
Instead, it's called when a PascalCase tag is detected.
|
|
29
|
+
"""
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
def parse(self, element: ET.Element) -> ComponentCallNode:
|
|
33
|
+
"""
|
|
34
|
+
Parse component call element.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
element: XML element representing component call
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
ComponentCallNode AST node
|
|
41
|
+
"""
|
|
42
|
+
component_name = self._get_local_name(element)
|
|
43
|
+
|
|
44
|
+
# Get props from attributes
|
|
45
|
+
props = self._parse_props(element)
|
|
46
|
+
|
|
47
|
+
# Create component call node
|
|
48
|
+
call_node = ComponentCallNode(component_name, props)
|
|
49
|
+
|
|
50
|
+
# Parse children (slot content)
|
|
51
|
+
call_node.children = self._parse_children(element)
|
|
52
|
+
|
|
53
|
+
return call_node
|
|
54
|
+
|
|
55
|
+
def _get_local_name(self, element: ET.Element) -> str:
|
|
56
|
+
"""Extract local name from element tag."""
|
|
57
|
+
tag = element.tag
|
|
58
|
+
if '}' in tag:
|
|
59
|
+
return tag.split('}')[1]
|
|
60
|
+
if ':' in tag:
|
|
61
|
+
return tag.split(':')[1]
|
|
62
|
+
return tag
|
|
63
|
+
|
|
64
|
+
def _parse_props(self, element: ET.Element) -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Parse element attributes as component props.
|
|
67
|
+
|
|
68
|
+
Handles:
|
|
69
|
+
- Simple string values: name="John"
|
|
70
|
+
- Databinding expressions: user="{currentUser}"
|
|
71
|
+
- Boolean shorthand: disabled (treated as disabled="true")
|
|
72
|
+
"""
|
|
73
|
+
props = {}
|
|
74
|
+
|
|
75
|
+
for key, value in element.attrib.items():
|
|
76
|
+
# Skip namespace declarations
|
|
77
|
+
if key.startswith('xmlns'):
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Handle namespaced attributes
|
|
81
|
+
if '}' in key:
|
|
82
|
+
key = key.split('}')[1]
|
|
83
|
+
|
|
84
|
+
props[key] = value
|
|
85
|
+
|
|
86
|
+
return props
|
|
87
|
+
|
|
88
|
+
def _parse_children(self, element: ET.Element) -> List:
|
|
89
|
+
"""Parse child elements as slot content."""
|
|
90
|
+
children = []
|
|
91
|
+
|
|
92
|
+
# Add leading text if present
|
|
93
|
+
if element.text and element.text.strip():
|
|
94
|
+
children.append(TextNode(element.text))
|
|
95
|
+
|
|
96
|
+
# Parse child elements
|
|
97
|
+
for child in element:
|
|
98
|
+
child_node = self.parse_statement(child)
|
|
99
|
+
if child_node:
|
|
100
|
+
children.append(child_node)
|
|
101
|
+
|
|
102
|
+
# Add tail text after child
|
|
103
|
+
if child.tail and child.tail.strip():
|
|
104
|
+
children.append(TextNode(child.tail))
|
|
105
|
+
|
|
106
|
+
return children
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def is_component_call(tag_name: str) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if tag name represents a component call.
|
|
112
|
+
|
|
113
|
+
Components use PascalCase naming:
|
|
114
|
+
- MyComponent -> True
|
|
115
|
+
- UserCard -> True
|
|
116
|
+
- div -> False
|
|
117
|
+
- myComponent -> False
|
|
118
|
+
"""
|
|
119
|
+
if not tag_name:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
# Must start with uppercase letter
|
|
123
|
+
if not tag_name[0].isupper():
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# Should not be all uppercase (likely a constant/builtin)
|
|
127
|
+
if tag_name.isupper():
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
return True
|