onelaraveljs 1.0.0 → 1.1.3
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.
- package/README.md +1 -1
- package/bin/onejs-build.js +32 -0
- package/index.js +3 -1
- package/package.json +11 -3
- package/scripts/README-template-compiler.md +133 -0
- package/scripts/README.md +61 -0
- package/scripts/__pycache__/build.cpython-314.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
- package/scripts/build.py +574 -0
- package/scripts/check-system-errors.php +214 -0
- package/scripts/compile.py +101 -0
- package/scripts/compiler/README_CONFIG.md +196 -0
- package/scripts/compiler/__init__.py +18 -0
- package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/binding_directive_service.py +103 -0
- package/scripts/compiler/class_binding_handler.py +347 -0
- package/scripts/compiler/cli.py +34 -0
- package/scripts/compiler/code_generator.py +141 -0
- package/scripts/compiler/compiler.config.json +36 -0
- package/scripts/compiler/compiler_utils.py +55 -0
- package/scripts/compiler/conditional_handlers.py +252 -0
- package/scripts/compiler/config.py +107 -0
- package/scripts/compiler/declaration_tracker.py +420 -0
- package/scripts/compiler/directive_processors.py +603 -0
- package/scripts/compiler/echo_processor.py +667 -0
- package/scripts/compiler/event_directive_processor.py +1099 -0
- package/scripts/compiler/fetch_parser.py +49 -0
- package/scripts/compiler/function_generators.py +310 -0
- package/scripts/compiler/loop_handlers.py +224 -0
- package/scripts/compiler/main_compiler.py +1763 -0
- package/scripts/compiler/parsers.py +1418 -0
- package/scripts/compiler/php_converter.py +470 -0
- package/scripts/compiler/php_js_converter.py +603 -0
- package/scripts/compiler/register_parser.py +480 -0
- package/scripts/compiler/section_handlers.py +122 -0
- package/scripts/compiler/show_directive_handler.py +85 -0
- package/scripts/compiler/style_directive_handler.py +169 -0
- package/scripts/compiler/template_analyzer.py +162 -0
- package/scripts/compiler/template_processor.py +1167 -0
- package/scripts/compiler/template_processors.py +1557 -0
- package/scripts/compiler/test_compiler.py +69 -0
- package/scripts/compiler/utils.py +54 -0
- package/scripts/compiler/variables_analyzer.py +135 -0
- package/scripts/compiler/view_identifier_generator.py +278 -0
- package/scripts/compiler/wrapper_parser.py +78 -0
- package/scripts/dev-context.js +311 -0
- package/scripts/dev.js +109 -0
- package/scripts/generate-assets-order.js +208 -0
- package/scripts/migrate-namespace.php +146 -0
- package/scripts/node/MIGRATION.md +190 -0
- package/scripts/node/README.md +269 -0
- package/scripts/node/build.js +208 -0
- package/scripts/node/compiler/compiler-utils.js +38 -0
- package/scripts/node/compiler/conditional-handlers.js +45 -0
- package/scripts/node/compiler/config.js +178 -0
- package/scripts/node/compiler/directive-processors.js +51 -0
- package/scripts/node/compiler/event-directive-processor.js +182 -0
- package/scripts/node/compiler/function-generators.js +239 -0
- package/scripts/node/compiler/loop-handlers.js +45 -0
- package/scripts/node/compiler/main-compiler.js +236 -0
- package/scripts/node/compiler/parsers.js +358 -0
- package/scripts/node/compiler/php-converter.js +227 -0
- package/scripts/node/compiler/register-parser.js +32 -0
- package/scripts/node/compiler/section-handlers.js +46 -0
- package/scripts/node/compiler/template-analyzer.js +50 -0
- package/scripts/node/compiler/template-processor.js +371 -0
- package/scripts/node/compiler/template-processors.js +219 -0
- package/scripts/node/compiler/utils.js +203 -0
- package/scripts/node/compiler/wrapper-parser.js +25 -0
- package/scripts/node/package.json +24 -0
- package/scripts/node/test-compiler.js +52 -0
- package/scripts/node-run.cjs +28 -0
- package/scripts/standardize-directories.php +92 -0
- package/src/core/ViewManager.js +4 -4
- package/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Binding Directive Service - Xử lý @val và @bind directives
|
|
3
|
+
@val và @bind là alias của nhau, cùng tạo ra data-binding attribute
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
class BindingDirectiveService:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def process_binding_directive(self, content, directive_pattern='val|bind'):
|
|
13
|
+
"""
|
|
14
|
+
Process binding directives (@val and @bind are aliases)
|
|
15
|
+
@val($userState->name) -> data-binding="userState.name"
|
|
16
|
+
@bind($username) -> data-binding="username"
|
|
17
|
+
Both directives produce the same output
|
|
18
|
+
"""
|
|
19
|
+
def replace_binding_with_nested_parens(content):
|
|
20
|
+
result = content
|
|
21
|
+
while True:
|
|
22
|
+
# Find @val or @bind directive
|
|
23
|
+
match = re.search(rf'@({directive_pattern})\s*\(', result)
|
|
24
|
+
if not match:
|
|
25
|
+
break
|
|
26
|
+
|
|
27
|
+
start_pos = match.end() - 1 # Position of opening (
|
|
28
|
+
paren_count = 0
|
|
29
|
+
i = start_pos
|
|
30
|
+
|
|
31
|
+
# Find matching closing parenthesis
|
|
32
|
+
while i < len(result):
|
|
33
|
+
if result[i] == '(':
|
|
34
|
+
paren_count += 1
|
|
35
|
+
elif result[i] == ')':
|
|
36
|
+
paren_count -= 1
|
|
37
|
+
if paren_count == 0:
|
|
38
|
+
# Found matching closing parenthesis
|
|
39
|
+
expression = result[start_pos + 1:i].strip()
|
|
40
|
+
binding_value = self._convert_php_to_binding(expression)
|
|
41
|
+
replacement = f'data-binding="{binding_value}" data-view-id="${{__VIEW_ID__}}"'
|
|
42
|
+
result = result[:match.start()] + replacement + result[i + 1:]
|
|
43
|
+
break
|
|
44
|
+
i += 1
|
|
45
|
+
else:
|
|
46
|
+
# No matching parenthesis found, break to avoid infinite loop
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
return replace_binding_with_nested_parens(content)
|
|
52
|
+
|
|
53
|
+
def process_val_directive(self, content):
|
|
54
|
+
"""
|
|
55
|
+
Process @val directive (alias of process_binding_directive)
|
|
56
|
+
@val($userState->name) -> data-binding="userState.name"
|
|
57
|
+
@val($user['name']) -> data-binding="user.name"
|
|
58
|
+
"""
|
|
59
|
+
return self.process_binding_directive(content, 'val')
|
|
60
|
+
|
|
61
|
+
def process_bind_directive(self, content):
|
|
62
|
+
"""
|
|
63
|
+
Process @bind directive (alias of process_binding_directive)
|
|
64
|
+
@bind($username) -> data-binding="username"
|
|
65
|
+
"""
|
|
66
|
+
return self.process_binding_directive(content, 'bind')
|
|
67
|
+
|
|
68
|
+
def _convert_php_to_binding(self, php_expression):
|
|
69
|
+
"""
|
|
70
|
+
Convert PHP expression to JavaScript binding notation
|
|
71
|
+
$userState->name -> userState.name
|
|
72
|
+
$user['name'] -> user.name
|
|
73
|
+
$username -> username
|
|
74
|
+
User::find($id)->profile->displayName -> User.find(id).profile.displayName
|
|
75
|
+
"""
|
|
76
|
+
# Remove leading/trailing whitespace
|
|
77
|
+
php_expression = php_expression.strip()
|
|
78
|
+
|
|
79
|
+
# Convert $variable to variable (remove $ prefix)
|
|
80
|
+
result = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'\1', php_expression)
|
|
81
|
+
|
|
82
|
+
# Convert PHP static method accessor (::) to (.)
|
|
83
|
+
result = re.sub(r'::', '.', result)
|
|
84
|
+
|
|
85
|
+
# Convert object accessor (->) to (.)
|
|
86
|
+
result = re.sub(r'->', '.', result)
|
|
87
|
+
|
|
88
|
+
# Convert array access ['key'] to .key
|
|
89
|
+
result = re.sub(r"\['([^']+)'\]", r'.\1', result)
|
|
90
|
+
result = re.sub(r'\["([^"]+)"\]', r'.\1', result)
|
|
91
|
+
|
|
92
|
+
# Convert numeric array access [0] to .0 (though this is less common)
|
|
93
|
+
result = re.sub(r'\[(\d+)\]', r'.\1', result)
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def process_all_binding_directives(self, content):
|
|
98
|
+
"""
|
|
99
|
+
Process both @val and @bind directives in content (they are aliases)
|
|
100
|
+
This method processes both directives in a single pass
|
|
101
|
+
"""
|
|
102
|
+
# Process both @val and @bind directives together (they are aliases)
|
|
103
|
+
return self.process_binding_directive(content, 'val|bind')
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Class Binding Handler - Xử lý @class directive
|
|
3
|
+
Supports: static classes, conditional classes, và mixed binding
|
|
4
|
+
Uses separate __classBinding() method for reactive class management
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from php_js_converter import php_to_js_advanced
|
|
9
|
+
|
|
10
|
+
class ClassBindingHandler:
|
|
11
|
+
def __init__(self, state_variables=None):
|
|
12
|
+
self.state_variables = state_variables or set()
|
|
13
|
+
|
|
14
|
+
def process_class_directive(self, content):
|
|
15
|
+
"""
|
|
16
|
+
Process @class directive with multiple formats:
|
|
17
|
+
1. @class('static-class') - static class
|
|
18
|
+
2. @class('class', $condition) - conditional class
|
|
19
|
+
3. @class(['class1' => $cond1, 'class2' => $cond2]) - array binding
|
|
20
|
+
4. @class(['static', 'dynamic' => $cond]) - mixed
|
|
21
|
+
|
|
22
|
+
Multiple @class on same line will be merged into one
|
|
23
|
+
|
|
24
|
+
Output format:
|
|
25
|
+
- Static only: class="static-class"
|
|
26
|
+
- With dynamic: ${this.__classBinding([...])}
|
|
27
|
+
|
|
28
|
+
Note: Supports multi-line directives within HTML tag attributes
|
|
29
|
+
"""
|
|
30
|
+
result = content
|
|
31
|
+
|
|
32
|
+
# Process all @class directives globally (supports multi-line)
|
|
33
|
+
while True:
|
|
34
|
+
match = re.search(r'@class\s*\(', result)
|
|
35
|
+
if not match:
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
# Find matching closing parenthesis (can span multiple lines)
|
|
39
|
+
start_pos = match.end() - 1
|
|
40
|
+
paren_count = 0
|
|
41
|
+
i = start_pos
|
|
42
|
+
|
|
43
|
+
while i < len(result):
|
|
44
|
+
if result[i] == '(':
|
|
45
|
+
paren_count += 1
|
|
46
|
+
elif result[i] == ')':
|
|
47
|
+
paren_count -= 1
|
|
48
|
+
if paren_count == 0:
|
|
49
|
+
# Found complete directive
|
|
50
|
+
expression = result[start_pos + 1:i].strip()
|
|
51
|
+
replacement = self._generate_class_output(expression)
|
|
52
|
+
result = result[:match.start()] + replacement + result[i + 1:]
|
|
53
|
+
break
|
|
54
|
+
i += 1
|
|
55
|
+
else:
|
|
56
|
+
# No matching closing paren found - skip this one
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
def _merge_multiple_class_directives(self, line):
|
|
62
|
+
"""
|
|
63
|
+
Merge multiple @class directives on the same line into one
|
|
64
|
+
"""
|
|
65
|
+
all_bindings = []
|
|
66
|
+
|
|
67
|
+
# Extract all @class expressions
|
|
68
|
+
while True:
|
|
69
|
+
match = re.search(r'@class\s*\(', line)
|
|
70
|
+
if not match:
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
start_pos = match.end() - 1
|
|
74
|
+
paren_count = 0
|
|
75
|
+
i = start_pos
|
|
76
|
+
|
|
77
|
+
# Find matching closing parenthesis
|
|
78
|
+
while i < len(line):
|
|
79
|
+
if line[i] == '(':
|
|
80
|
+
paren_count += 1
|
|
81
|
+
elif line[i] == ')':
|
|
82
|
+
paren_count -= 1
|
|
83
|
+
if paren_count == 0:
|
|
84
|
+
expression = line[start_pos + 1:i].strip()
|
|
85
|
+
bindings = self._parse_class_expression(expression)
|
|
86
|
+
all_bindings.extend(bindings)
|
|
87
|
+
# Remove this @class directive
|
|
88
|
+
line = line[:match.start()] + line[i + 1:]
|
|
89
|
+
break
|
|
90
|
+
i += 1
|
|
91
|
+
else:
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
# Generate single output for all merged bindings
|
|
95
|
+
if all_bindings:
|
|
96
|
+
replacement = self._generate_output_from_bindings(all_bindings)
|
|
97
|
+
# Insert at the first @class position (which is now gone)
|
|
98
|
+
# Find a good insertion point - before the first > or at the start
|
|
99
|
+
insertion_point = line.find('>')
|
|
100
|
+
if insertion_point > 0:
|
|
101
|
+
line = line[:insertion_point] + ' ' + replacement + line[insertion_point:]
|
|
102
|
+
else:
|
|
103
|
+
line = line + ' ' + replacement
|
|
104
|
+
|
|
105
|
+
return line
|
|
106
|
+
|
|
107
|
+
def _process_single_class_directive(self, line):
|
|
108
|
+
"""
|
|
109
|
+
Process a single @class directive on a line
|
|
110
|
+
"""
|
|
111
|
+
match = re.search(r'@class\s*\(', line)
|
|
112
|
+
if not match:
|
|
113
|
+
return line
|
|
114
|
+
|
|
115
|
+
start_pos = match.end() - 1
|
|
116
|
+
paren_count = 0
|
|
117
|
+
i = start_pos
|
|
118
|
+
|
|
119
|
+
while i < len(line):
|
|
120
|
+
if line[i] == '(':
|
|
121
|
+
paren_count += 1
|
|
122
|
+
elif line[i] == ')':
|
|
123
|
+
paren_count -= 1
|
|
124
|
+
if paren_count == 0:
|
|
125
|
+
expression = line[start_pos + 1:i].strip()
|
|
126
|
+
replacement = self._generate_class_output(expression)
|
|
127
|
+
line = line[:match.start()] + replacement + line[i + 1:]
|
|
128
|
+
break
|
|
129
|
+
i += 1
|
|
130
|
+
|
|
131
|
+
return line
|
|
132
|
+
|
|
133
|
+
def _generate_class_output(self, expression):
|
|
134
|
+
"""
|
|
135
|
+
Generate output for class directive
|
|
136
|
+
Returns either static class="..." or ${this.__attr({"class": {...}})}
|
|
137
|
+
"""
|
|
138
|
+
bindings = self._parse_class_expression(expression)
|
|
139
|
+
return self._generate_output_from_bindings(bindings)
|
|
140
|
+
|
|
141
|
+
def _generate_output_from_bindings(self, bindings):
|
|
142
|
+
"""
|
|
143
|
+
Generate output from a list of bindings
|
|
144
|
+
Returns either static class="..." or ${this.__classBinding([...])}
|
|
145
|
+
"""
|
|
146
|
+
# Check if all bindings are static
|
|
147
|
+
has_dynamic = any(b['type'] == 'binding' for b in bindings)
|
|
148
|
+
|
|
149
|
+
if not has_dynamic:
|
|
150
|
+
# All static - output direct class attribute
|
|
151
|
+
static_classes = [b['value'] for b in bindings]
|
|
152
|
+
return f'class="{" ".join(static_classes)}"'
|
|
153
|
+
else:
|
|
154
|
+
# Has dynamic - use __classBinding
|
|
155
|
+
return self._generate_class_binding_config(bindings)
|
|
156
|
+
|
|
157
|
+
def _generate_class_binding_config(self, bindings):
|
|
158
|
+
"""
|
|
159
|
+
Generate __classBinding() config
|
|
160
|
+
Format: ${this.__classBinding([{type: "static", value: "...", states: [...], checker: () => ...}])}
|
|
161
|
+
"""
|
|
162
|
+
binding_configs = []
|
|
163
|
+
|
|
164
|
+
for binding in bindings:
|
|
165
|
+
if binding['type'] == 'static':
|
|
166
|
+
binding_configs.append(f'{{type: "static", value: "{binding["value"]}"}}')
|
|
167
|
+
elif binding['type'] == 'binding':
|
|
168
|
+
states = binding.get('states', [])
|
|
169
|
+
states_str = ', '.join([f'"{s}"' for s in states])
|
|
170
|
+
checker = binding['checker']
|
|
171
|
+
class_name = binding['value']
|
|
172
|
+
binding_configs.append(f'{{type: "binding", value: "{class_name}", states: [{states_str}], checker: () => {checker}}}')
|
|
173
|
+
|
|
174
|
+
configs_joined = ', '.join(binding_configs)
|
|
175
|
+
return f'${{this.__classBinding([{configs_joined}])}}'
|
|
176
|
+
|
|
177
|
+
def _parse_class_expression(self, expression):
|
|
178
|
+
"""
|
|
179
|
+
Parse class expression and return list of binding dicts
|
|
180
|
+
Returns: [{"type": "static"|"binding", "value": "class-name", ...}, ...]
|
|
181
|
+
"""
|
|
182
|
+
expression = expression.strip()
|
|
183
|
+
|
|
184
|
+
# Case 1: Simple string - @class('class')
|
|
185
|
+
if self._is_simple_string(expression):
|
|
186
|
+
class_name = self._extract_string_value(expression)
|
|
187
|
+
return [{"type": "static", "value": class_name}]
|
|
188
|
+
|
|
189
|
+
# Case 2: Two arguments - @class('class', $condition)
|
|
190
|
+
if ',' in expression and not expression.startswith('['):
|
|
191
|
+
parts = self._split_arguments(expression)
|
|
192
|
+
if len(parts) == 2:
|
|
193
|
+
class_name = self._extract_string_value(parts[0].strip())
|
|
194
|
+
condition = parts[1].strip()
|
|
195
|
+
states = self._extract_state_variables(condition)
|
|
196
|
+
js_condition = self._convert_php_to_js(condition)
|
|
197
|
+
return [{
|
|
198
|
+
"type": "binding",
|
|
199
|
+
"value": class_name,
|
|
200
|
+
"states": states,
|
|
201
|
+
"checker": js_condition
|
|
202
|
+
}]
|
|
203
|
+
|
|
204
|
+
# Case 3: Array format - @class(['class1' => $cond, 'class2'])
|
|
205
|
+
if expression.startswith('[') and expression.endswith(']'):
|
|
206
|
+
return self._parse_array_expression(expression)
|
|
207
|
+
|
|
208
|
+
# Fallback: treat as static class
|
|
209
|
+
return [{"type": "static", "value": expression}]
|
|
210
|
+
|
|
211
|
+
def _parse_array_expression(self, expression):
|
|
212
|
+
"""
|
|
213
|
+
Parse array expression: ['class1' => $cond, 'static', 'class2' => $cond2]
|
|
214
|
+
Returns list of binding dicts
|
|
215
|
+
"""
|
|
216
|
+
# Remove outer brackets
|
|
217
|
+
inner = expression[1:-1].strip()
|
|
218
|
+
|
|
219
|
+
# Split by comma, but respect nested structures
|
|
220
|
+
items = self._split_array_items(inner)
|
|
221
|
+
|
|
222
|
+
bindings = []
|
|
223
|
+
for item in items:
|
|
224
|
+
item = item.strip()
|
|
225
|
+
if not item:
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Check if it's a key => value pair
|
|
229
|
+
if '=>' in item:
|
|
230
|
+
parts = item.split('=>', 1)
|
|
231
|
+
class_name = self._extract_string_value(parts[0].strip())
|
|
232
|
+
condition = parts[1].strip()
|
|
233
|
+
|
|
234
|
+
# Check if condition is a static string
|
|
235
|
+
if self._is_simple_string(condition):
|
|
236
|
+
# Static value like 'demo' => 'dump'
|
|
237
|
+
static_value = self._extract_string_value(condition)
|
|
238
|
+
bindings.append({"type": "static", "value": static_value})
|
|
239
|
+
else:
|
|
240
|
+
# Dynamic condition
|
|
241
|
+
states = self._extract_state_variables(condition)
|
|
242
|
+
js_condition = self._convert_php_to_js(condition)
|
|
243
|
+
bindings.append({
|
|
244
|
+
"type": "binding",
|
|
245
|
+
"value": class_name,
|
|
246
|
+
"states": states,
|
|
247
|
+
"checker": js_condition
|
|
248
|
+
})
|
|
249
|
+
else:
|
|
250
|
+
# Static class without condition
|
|
251
|
+
class_name = self._extract_string_value(item)
|
|
252
|
+
bindings.append({"type": "static", "value": class_name})
|
|
253
|
+
|
|
254
|
+
return bindings
|
|
255
|
+
|
|
256
|
+
def _split_array_items(self, content):
|
|
257
|
+
"""
|
|
258
|
+
Split array items by comma, respecting nested structures
|
|
259
|
+
"""
|
|
260
|
+
items = []
|
|
261
|
+
current = []
|
|
262
|
+
depth = 0
|
|
263
|
+
in_string = False
|
|
264
|
+
string_char = None
|
|
265
|
+
i = 0
|
|
266
|
+
|
|
267
|
+
while i < len(content):
|
|
268
|
+
char = content[i]
|
|
269
|
+
|
|
270
|
+
# Handle string boundaries
|
|
271
|
+
if char in ('"', "'") and (i == 0 or content[i-1] != '\\'):
|
|
272
|
+
if not in_string:
|
|
273
|
+
in_string = True
|
|
274
|
+
string_char = char
|
|
275
|
+
elif char == string_char:
|
|
276
|
+
in_string = False
|
|
277
|
+
string_char = None
|
|
278
|
+
|
|
279
|
+
# Handle nested structures
|
|
280
|
+
if not in_string:
|
|
281
|
+
if char in '([{':
|
|
282
|
+
depth += 1
|
|
283
|
+
elif char in ')]}':
|
|
284
|
+
depth -= 1
|
|
285
|
+
elif char == ',' and depth == 0:
|
|
286
|
+
items.append(''.join(current))
|
|
287
|
+
current = []
|
|
288
|
+
i += 1
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
current.append(char)
|
|
292
|
+
i += 1
|
|
293
|
+
|
|
294
|
+
if current:
|
|
295
|
+
items.append(''.join(current))
|
|
296
|
+
|
|
297
|
+
return items
|
|
298
|
+
|
|
299
|
+
def _split_arguments(self, expression):
|
|
300
|
+
"""
|
|
301
|
+
Split function arguments by comma, respecting nested structures
|
|
302
|
+
"""
|
|
303
|
+
return self._split_array_items(expression)
|
|
304
|
+
|
|
305
|
+
def _is_simple_string(self, expression):
|
|
306
|
+
"""
|
|
307
|
+
Check if expression is a simple quoted string
|
|
308
|
+
"""
|
|
309
|
+
expression = expression.strip()
|
|
310
|
+
return (expression.startswith("'") and expression.endswith("'")) or \
|
|
311
|
+
(expression.startswith('"') and expression.endswith('"'))
|
|
312
|
+
|
|
313
|
+
def _extract_string_value(self, expression):
|
|
314
|
+
"""
|
|
315
|
+
Extract string value from quoted string
|
|
316
|
+
'class' -> class
|
|
317
|
+
"class" -> class
|
|
318
|
+
"""
|
|
319
|
+
expression = expression.strip()
|
|
320
|
+
if (expression.startswith("'") and expression.endswith("'")) or \
|
|
321
|
+
(expression.startswith('"') and expression.endswith('"')):
|
|
322
|
+
return expression[1:-1]
|
|
323
|
+
return expression
|
|
324
|
+
|
|
325
|
+
def _extract_state_variables(self, php_expression):
|
|
326
|
+
"""
|
|
327
|
+
Extract state variable names from PHP expression
|
|
328
|
+
$abc -> abc
|
|
329
|
+
$abc->test($cde) -> abc, cde
|
|
330
|
+
$demo && !$on || !$r->e -> demo, on, r
|
|
331
|
+
"""
|
|
332
|
+
# Find all $variable patterns
|
|
333
|
+
variables = set()
|
|
334
|
+
matches = re.findall(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', php_expression)
|
|
335
|
+
for var in matches:
|
|
336
|
+
# Only include if it's a state variable
|
|
337
|
+
if var in self.state_variables or True: # Include all for now
|
|
338
|
+
variables.add(var)
|
|
339
|
+
|
|
340
|
+
return sorted(list(variables))
|
|
341
|
+
|
|
342
|
+
def _convert_php_to_js(self, php_expression):
|
|
343
|
+
"""
|
|
344
|
+
Convert PHP expression to JavaScript
|
|
345
|
+
Uses the existing php_to_js_advanced function
|
|
346
|
+
"""
|
|
347
|
+
return php_to_js_advanced(php_expression)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command line interface cho Blade Compiler
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
8
|
+
from main_compiler import BladeCompiler
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
if len(sys.argv) < 3:
|
|
12
|
+
print("Sử dụng: python cli.py <input.blade> <output.js>")
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
|
|
15
|
+
input_file = sys.argv[1]
|
|
16
|
+
output_file = sys.argv[2]
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
with open(input_file, 'r', encoding='utf-8') as f:
|
|
20
|
+
blade_code = f.read()
|
|
21
|
+
|
|
22
|
+
compiler = BladeCompiler()
|
|
23
|
+
js_code = compiler.compile_blade_to_js(blade_code, 'test')
|
|
24
|
+
|
|
25
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
26
|
+
f.write(js_code)
|
|
27
|
+
|
|
28
|
+
print(f"Đã compile thành công từ {input_file} sang {output_file}")
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Lỗi: {e}")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generator cho JavaScript code output
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from config import JS_FUNCTION_PREFIX, HTML_ATTR_PREFIX
|
|
8
|
+
from variables_analyzer import analyze_render_uses_vars, analyze_sections_info
|
|
9
|
+
|
|
10
|
+
def generate_prerender(has_await, has_fetch, extended_view, sections_code, template_content, vars_line, view_id_line, output_content_line="", uses_vars=True, sections_info=None):
|
|
11
|
+
"""Generate prerender function based on @await or @fetch directive presence"""
|
|
12
|
+
if sections_info is None:
|
|
13
|
+
sections_info = {}
|
|
14
|
+
|
|
15
|
+
if not has_await and not has_fetch and not sections_code:
|
|
16
|
+
return "function(__$spaViewData$__ = {}) {\n return null;\n}"
|
|
17
|
+
|
|
18
|
+
if not uses_vars and not sections_info:
|
|
19
|
+
return "function(__$spaViewData$__ = {}) {\n return null;\n}"
|
|
20
|
+
|
|
21
|
+
if not has_await and not has_fetch and not vars_line:
|
|
22
|
+
return "function(__$spaViewData$__ = {}) {\n return null;\n}"
|
|
23
|
+
|
|
24
|
+
# Initialize prerender_sections
|
|
25
|
+
prerender_sections = []
|
|
26
|
+
|
|
27
|
+
if sections_info:
|
|
28
|
+
# Process sections based on their configuration
|
|
29
|
+
for section_name, section_info in sections_info.items():
|
|
30
|
+
section_type = section_info.get("type", "short")
|
|
31
|
+
preloader = section_info.get("preloader", False)
|
|
32
|
+
use_vars = section_info.get("useVars", False)
|
|
33
|
+
|
|
34
|
+
if section_type == "long" and not use_vars:
|
|
35
|
+
# Long section không dùng biến - render trực tiếp trong prerender
|
|
36
|
+
# Không cần đợi fetch/await vì không dùng dynamic data
|
|
37
|
+
section_pattern = f'{JS_FUNCTION_PREFIX}\.section\(\'{section_name}\',[^)]+\)'
|
|
38
|
+
section_match = re.search(section_pattern, sections_code, re.DOTALL)
|
|
39
|
+
if section_match:
|
|
40
|
+
prerender_sections.append("${" + section_match.group(0) + "}")
|
|
41
|
+
elif section_type == "short" and not use_vars:
|
|
42
|
+
# Short section không dùng biến - render trực tiếp trong prerender
|
|
43
|
+
section_pattern = f'{JS_FUNCTION_PREFIX}\.section\(\'{section_name}\',[^)]+\)'
|
|
44
|
+
section_match = re.search(section_pattern, sections_code, re.DOTALL)
|
|
45
|
+
if section_match:
|
|
46
|
+
prerender_sections.append("${" + section_match.group(0) + "}")
|
|
47
|
+
elif preloader:
|
|
48
|
+
# Section có preloader (dùng biến + có fetch/await) - tạo section preloader
|
|
49
|
+
prerender_sections.append("${" + JS_FUNCTION_PREFIX + ".section('" + section_name + "', `<div class=\"" + HTML_ATTR_PREFIX + "preloader\" ref=\"${__VIEW_ID__}\" data-section-name=\"" + section_name + "\">${" + JS_FUNCTION_PREFIX + ".text('loading')}</div>`)}")
|
|
50
|
+
|
|
51
|
+
# Generate function based on context
|
|
52
|
+
if extended_view:
|
|
53
|
+
if has_await or has_fetch:
|
|
54
|
+
return f"""function(__$spaViewData$__ = {{}}) {{
|
|
55
|
+
{vars_line}{view_id_line} let __outputRenderedContent__ = '';
|
|
56
|
+
try {{
|
|
57
|
+
__outputRenderedContent__ = `{chr(10).join(prerender_sections) if prerender_sections else sections_code}`;
|
|
58
|
+
}} catch(e) {{
|
|
59
|
+
__outputRenderedContent__ = this.showError(e.message);
|
|
60
|
+
console.warn(e);
|
|
61
|
+
}}
|
|
62
|
+
return this.__extends('{extended_view}');
|
|
63
|
+
}}"""
|
|
64
|
+
else:
|
|
65
|
+
return f"""function(__$spaViewData$__ = {{}}) {{
|
|
66
|
+
{view_id_line} let __outputRenderedContent__ = '';
|
|
67
|
+
try {{
|
|
68
|
+
__outputRenderedContent__ = `{chr(10).join(prerender_sections) if prerender_sections else sections_code}`;
|
|
69
|
+
}} catch(e) {{
|
|
70
|
+
__outputRenderedContent__ = this.showError(e.message);
|
|
71
|
+
console.warn(e);
|
|
72
|
+
}}
|
|
73
|
+
return this.__extends('{extended_view}');
|
|
74
|
+
}}"""
|
|
75
|
+
else:
|
|
76
|
+
if has_await or has_fetch:
|
|
77
|
+
return f"""function(__$spaViewData$__ = {{}}) {{
|
|
78
|
+
{vars_line}{view_id_line} let __outputRenderedContent__ = '';
|
|
79
|
+
try {{
|
|
80
|
+
__outputRenderedContent__ = `<div class="{HTML_ATTR_PREFIX}preloader" ref="${{__VIEW_ID__}}" data-view-name="${{__VIEW_PATH__}}">${{{JS_FUNCTION_PREFIX}.text('loading')}}</div>`;
|
|
81
|
+
}} catch(e) {{
|
|
82
|
+
__outputRenderedContent__ = this.showError(e.message);
|
|
83
|
+
console.warn(e);
|
|
84
|
+
}}
|
|
85
|
+
return __outputRenderedContent__;
|
|
86
|
+
}}"""
|
|
87
|
+
else:
|
|
88
|
+
static_content = sections_code if sections_code else template_content
|
|
89
|
+
return f"""function(__$spaViewData$__ = {{}}) {{
|
|
90
|
+
{vars_line}{view_id_line} let __outputRenderedContent__ = '';
|
|
91
|
+
try {{
|
|
92
|
+
__outputRenderedContent__ = `{static_content}`;
|
|
93
|
+
}} catch(e) {{
|
|
94
|
+
__outputRenderedContent__ = this.showError(e.message);
|
|
95
|
+
console.warn(e);
|
|
96
|
+
}}
|
|
97
|
+
return __outputRenderedContent__;
|
|
98
|
+
}}"""
|
|
99
|
+
|
|
100
|
+
def generate_view_engine(view_name, extended_view, sections_info, has_await, has_fetch, fetch_config, uses_vars, has_sections, has_section_preload, has_prerender, prerender_func, render_func, init_func, css_functions, wrapper_config=None):
|
|
101
|
+
"""Generate complete view engine code"""
|
|
102
|
+
|
|
103
|
+
sections_json = json.dumps(sections_info, ensure_ascii=False, separators=(',', ':'))
|
|
104
|
+
|
|
105
|
+
if extended_view:
|
|
106
|
+
super_view_config = f"'{extended_view}'"
|
|
107
|
+
has_super_view = "true"
|
|
108
|
+
else:
|
|
109
|
+
super_view_config = "null"
|
|
110
|
+
has_super_view = "false"
|
|
111
|
+
|
|
112
|
+
# Add wrapper config to view engine object
|
|
113
|
+
wrapper_config_line = ""
|
|
114
|
+
if wrapper_config:
|
|
115
|
+
wrapper_config_line = f",\n wrapperConfig: {wrapper_config}"
|
|
116
|
+
|
|
117
|
+
return f"""function(data = {{}}) {{
|
|
118
|
+
onst __VIEW_PATH__ = '{view_name}';
|
|
119
|
+
const __VIEW_ID__ = {JS_FUNCTION_PREFIX}.generateViewId();
|
|
120
|
+
const self = new {JS_FUNCTION_PREFIX}.Engine('{view_name}', {{
|
|
121
|
+
superView: {super_view_config},
|
|
122
|
+
hasSuperView: {has_super_view},
|
|
123
|
+
sections: {sections_json}{wrapper_config_line},
|
|
124
|
+
hasAwaitData: {str(has_await).lower()},
|
|
125
|
+
hasFetchData: {str(has_fetch).lower()},
|
|
126
|
+
fetch: {json.dumps(fetch_config, ensure_ascii=False).replace('"`', '`').replace('`"', '`') if fetch_config else 'null'},
|
|
127
|
+
data: data,
|
|
128
|
+
viewId: __VIEW_ID__,
|
|
129
|
+
path: __VIEW_PATH__,
|
|
130
|
+
usesVars: {str(uses_vars).lower()},
|
|
131
|
+
hasSections: {str(has_sections).lower()},
|
|
132
|
+
hasSectionPreload: {str(has_section_preload).lower()},
|
|
133
|
+
hasPrerender: {str(has_prerender).lower()},
|
|
134
|
+
prerender: {prerender_func},
|
|
135
|
+
render: {render_func}{css_functions}
|
|
136
|
+
}});
|
|
137
|
+
return self;
|
|
138
|
+
function onStateChange(state) {{
|
|
139
|
+
self.data = state;
|
|
140
|
+
}}
|
|
141
|
+
}}"""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"paths": {
|
|
3
|
+
"views_input": "resources/views",
|
|
4
|
+
"js_input": "resources/js/onejs",
|
|
5
|
+
"build_output": "resources/js/build",
|
|
6
|
+
"build_scopes": "resources/js/build/scopes",
|
|
7
|
+
"public_static": "public/static",
|
|
8
|
+
"app_output": "public/static/app",
|
|
9
|
+
"scopes_output": "public/static/app/scopes"
|
|
10
|
+
},
|
|
11
|
+
"files": {
|
|
12
|
+
"view_templates": "view.templates.js",
|
|
13
|
+
"wrapper": "wraper.js",
|
|
14
|
+
"main": "main.js"
|
|
15
|
+
},
|
|
16
|
+
"patterns": {
|
|
17
|
+
"blade": "**/*.blade.php",
|
|
18
|
+
"js": "**/*.js"
|
|
19
|
+
},
|
|
20
|
+
"settings": {
|
|
21
|
+
"default_scope": "web",
|
|
22
|
+
"auto_create_dirs": true,
|
|
23
|
+
"verbose": false
|
|
24
|
+
},
|
|
25
|
+
"build_directories": [
|
|
26
|
+
"components",
|
|
27
|
+
"web",
|
|
28
|
+
"admin",
|
|
29
|
+
"layouts",
|
|
30
|
+
"partials",
|
|
31
|
+
"custom",
|
|
32
|
+
"base",
|
|
33
|
+
"test",
|
|
34
|
+
"templates"
|
|
35
|
+
]
|
|
36
|
+
}
|