onelaraveljs 1.0.0 → 1.1.1
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/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 +573 -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 +200 -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/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test cases cho Blade Compiler
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from main_compiler import BladeCompiler
|
|
6
|
+
|
|
7
|
+
def test_basic_compilation():
|
|
8
|
+
"""Test basic blade compilation"""
|
|
9
|
+
compiler = BladeCompiler()
|
|
10
|
+
|
|
11
|
+
blade_code = """@vars($users = [], $title = 'Test')
|
|
12
|
+
<h1>{{ $title }}</h1>
|
|
13
|
+
<ul>
|
|
14
|
+
@foreach($users as $user)
|
|
15
|
+
<li>{{ $user->name }}</li>
|
|
16
|
+
@endforeach
|
|
17
|
+
</ul>"""
|
|
18
|
+
|
|
19
|
+
result = compiler.compile_blade_to_js(blade_code, 'test')
|
|
20
|
+
print("=== BASIC COMPILATION TEST ===")
|
|
21
|
+
print(result)
|
|
22
|
+
print()
|
|
23
|
+
|
|
24
|
+
def test_extends_compilation():
|
|
25
|
+
"""Test extends compilation"""
|
|
26
|
+
compiler = BladeCompiler()
|
|
27
|
+
|
|
28
|
+
blade_code = """@extends('layouts.app')
|
|
29
|
+
|
|
30
|
+
@section('title', 'Home Page')
|
|
31
|
+
|
|
32
|
+
@section('content')
|
|
33
|
+
<h1>Welcome!</h1>
|
|
34
|
+
<p>{{ $message }}</p>
|
|
35
|
+
@endsection"""
|
|
36
|
+
|
|
37
|
+
result = compiler.compile_blade_to_js(blade_code, 'home')
|
|
38
|
+
print("=== EXTENDS COMPILATION TEST ===")
|
|
39
|
+
print(result)
|
|
40
|
+
print()
|
|
41
|
+
|
|
42
|
+
def test_async_compilation():
|
|
43
|
+
"""Test async directives compilation"""
|
|
44
|
+
compiler = BladeCompiler()
|
|
45
|
+
|
|
46
|
+
blade_code = """@vars($users)
|
|
47
|
+
@fetch('/api/users')
|
|
48
|
+
@await($users)
|
|
49
|
+
|
|
50
|
+
<div>
|
|
51
|
+
<h1>Users</h1>
|
|
52
|
+
@foreach($users as $user)
|
|
53
|
+
<div>{{ $user->name }}</div>
|
|
54
|
+
@endforeach
|
|
55
|
+
</div>"""
|
|
56
|
+
|
|
57
|
+
result = compiler.compile_blade_to_js(blade_code, 'users')
|
|
58
|
+
print("=== ASYNC COMPILATION TEST ===")
|
|
59
|
+
print(result)
|
|
60
|
+
print()
|
|
61
|
+
|
|
62
|
+
def run_all_tests():
|
|
63
|
+
"""Run all test cases"""
|
|
64
|
+
test_basic_compilation()
|
|
65
|
+
test_extends_compilation()
|
|
66
|
+
test_async_compilation()
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
run_all_tests()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Các hàm tiện ích chung cho compiler
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
def extract_balanced_parentheses(text, start_pos):
|
|
9
|
+
"""Extract content inside balanced parentheses starting from start_pos"""
|
|
10
|
+
if start_pos >= len(text) or text[start_pos] != '(':
|
|
11
|
+
return None, start_pos
|
|
12
|
+
|
|
13
|
+
paren_count = 0
|
|
14
|
+
content_start = start_pos + 1
|
|
15
|
+
i = start_pos
|
|
16
|
+
|
|
17
|
+
while i < len(text):
|
|
18
|
+
if text[i] == '(':
|
|
19
|
+
paren_count += 1
|
|
20
|
+
elif text[i] == ')':
|
|
21
|
+
paren_count -= 1
|
|
22
|
+
if paren_count == 0:
|
|
23
|
+
content = text[content_start:i]
|
|
24
|
+
return content, i + 1
|
|
25
|
+
i += 1
|
|
26
|
+
|
|
27
|
+
# Unbalanced parentheses
|
|
28
|
+
return text[content_start:], len(text)
|
|
29
|
+
|
|
30
|
+
def format_attrs(attrs_dict):
|
|
31
|
+
"""Format attributes dictionary to JavaScript object string"""
|
|
32
|
+
return json.dumps(attrs_dict, separators=(',', ':'))
|
|
33
|
+
|
|
34
|
+
def normalize_quotes(expr):
|
|
35
|
+
"""Normalize single quotes to double quotes in JSON"""
|
|
36
|
+
if not expr:
|
|
37
|
+
return expr
|
|
38
|
+
|
|
39
|
+
# Replace single quotes with double quotes, but be careful with nested quotes
|
|
40
|
+
expr = re.sub(r"'([^']*)'", r'"\1"', expr)
|
|
41
|
+
return expr
|
|
42
|
+
|
|
43
|
+
def format_js_output(content, indent_level=0):
|
|
44
|
+
"""Format JavaScript output with proper indentation"""
|
|
45
|
+
lines = content.split('\n')
|
|
46
|
+
formatted_lines = []
|
|
47
|
+
|
|
48
|
+
for line in lines:
|
|
49
|
+
if line.strip():
|
|
50
|
+
formatted_lines.append(' ' * indent_level + line.strip())
|
|
51
|
+
else:
|
|
52
|
+
formatted_lines.append('')
|
|
53
|
+
|
|
54
|
+
return '\n'.join(formatted_lines)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Phân tích việc sử dụng biến trong template
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from config import JS_FUNCTION_PREFIX
|
|
7
|
+
|
|
8
|
+
def analyze_section_variables(section_content, vars_declaration):
|
|
9
|
+
"""Analyze if a section uses variables from @vars directive"""
|
|
10
|
+
if not vars_declaration:
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
# Extract variable names from vars_declaration
|
|
14
|
+
vars_match = re.search(r'let\s*\{\s*([^}]+)\s*\}', vars_declaration)
|
|
15
|
+
if not vars_match:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
vars_content = vars_match.group(1)
|
|
19
|
+
var_names = []
|
|
20
|
+
for var_part in vars_content.split(','):
|
|
21
|
+
var_name = var_part.split('=')[0].strip()
|
|
22
|
+
var_names.append(var_name)
|
|
23
|
+
|
|
24
|
+
# Check if section content uses any of these variables
|
|
25
|
+
for var_name in var_names:
|
|
26
|
+
patterns = [
|
|
27
|
+
f'${{{var_name}}}',
|
|
28
|
+
f'${{{JS_FUNCTION_PREFIX}.escString({var_name})}}',
|
|
29
|
+
f'${{{JS_FUNCTION_PREFIX}.foreach({var_name}',
|
|
30
|
+
f', {var_name})',
|
|
31
|
+
f'({var_name})',
|
|
32
|
+
f', {var_name}',
|
|
33
|
+
f' {var_name}',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for pattern in patterns:
|
|
37
|
+
if pattern in section_content:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def analyze_render_uses_vars(sections_code, template_content, vars_declaration):
|
|
43
|
+
"""Analyze if render function uses variables from @vars directive"""
|
|
44
|
+
if not vars_declaration:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# Extract variable names from vars_declaration
|
|
48
|
+
vars_match = re.search(r'let\s*\{\s*([^}]+)\s*\}', vars_declaration)
|
|
49
|
+
if not vars_match:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
vars_content = vars_match.group(1)
|
|
53
|
+
var_names = []
|
|
54
|
+
for var_part in vars_content.split(','):
|
|
55
|
+
var_name = var_part.split('=')[0].strip()
|
|
56
|
+
var_names.append(var_name)
|
|
57
|
+
|
|
58
|
+
# Combine all render content
|
|
59
|
+
render_content = sections_code + template_content
|
|
60
|
+
|
|
61
|
+
# Check if any variable is used in render content
|
|
62
|
+
for var_name in var_names:
|
|
63
|
+
patterns = [
|
|
64
|
+
f'${{{var_name}}}',
|
|
65
|
+
f'${{{JS_FUNCTION_PREFIX}.escString({var_name})}}',
|
|
66
|
+
f'${{{JS_FUNCTION_PREFIX}.foreach({var_name}',
|
|
67
|
+
f', {var_name})',
|
|
68
|
+
f'({var_name})',
|
|
69
|
+
f', {var_name}',
|
|
70
|
+
f' {var_name}',
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for pattern in patterns:
|
|
74
|
+
if pattern in render_content:
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def analyze_sections_info(sections_code, vars_declaration, has_await=False, has_fetch=False):
|
|
80
|
+
"""Analyze sections and return detailed information about each section"""
|
|
81
|
+
sections_info = {}
|
|
82
|
+
|
|
83
|
+
if not sections_code:
|
|
84
|
+
return sections_info
|
|
85
|
+
|
|
86
|
+
# Extract variable names from vars_declaration
|
|
87
|
+
var_names = []
|
|
88
|
+
if vars_declaration:
|
|
89
|
+
vars_match = re.search(r'let\s*\{\s*([^}]+)\s*\}', vars_declaration)
|
|
90
|
+
if vars_match:
|
|
91
|
+
vars_content = vars_match.group(1)
|
|
92
|
+
for var_part in vars_content.split(','):
|
|
93
|
+
var_name = var_part.split('=')[0].strip()
|
|
94
|
+
var_names.append(var_name)
|
|
95
|
+
|
|
96
|
+
# Find all APP.View.section() calls in the sections_code
|
|
97
|
+
section_matches = re.findall(fr'{JS_FUNCTION_PREFIX}\.section\([^)]+\)', sections_code, re.DOTALL)
|
|
98
|
+
|
|
99
|
+
for section_call in section_matches:
|
|
100
|
+
# Extract section name and content
|
|
101
|
+
if ',' in section_call:
|
|
102
|
+
name_match = re.search(fr"{JS_FUNCTION_PREFIX}\.section\(['\"]([^'\"]+)['\"]", section_call)
|
|
103
|
+
if name_match:
|
|
104
|
+
section_name = name_match.group(1)
|
|
105
|
+
|
|
106
|
+
# Find the content part after the first comma
|
|
107
|
+
comma_pos = section_call.find(',')
|
|
108
|
+
section_content = section_call[comma_pos + 1:].strip()
|
|
109
|
+
section_content = section_content.rstrip(');').strip()
|
|
110
|
+
|
|
111
|
+
# Check if this is a long section
|
|
112
|
+
is_long_section = False
|
|
113
|
+
if section_content.startswith('`') and section_content.endswith('`'):
|
|
114
|
+
inner_content = section_content[1:-1]
|
|
115
|
+
is_long_section = ('\n' in inner_content or
|
|
116
|
+
'<' in inner_content or
|
|
117
|
+
len(inner_content.strip()) > 50)
|
|
118
|
+
elif section_content.startswith('`') and not section_content.endswith('`'):
|
|
119
|
+
is_long_section = True
|
|
120
|
+
|
|
121
|
+
# Check if section uses variables
|
|
122
|
+
has_variables = analyze_section_variables(section_content, vars_declaration)
|
|
123
|
+
|
|
124
|
+
# Determine section type and preload status
|
|
125
|
+
section_type = "long" if is_long_section else "short"
|
|
126
|
+
use_vars = has_variables
|
|
127
|
+
preloader = has_variables and (has_await or has_fetch)
|
|
128
|
+
|
|
129
|
+
sections_info[section_name] = {
|
|
130
|
+
"type": section_type,
|
|
131
|
+
"preloader": preloader,
|
|
132
|
+
"useVars": use_vars
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return sections_info
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""
|
|
2
|
+
View Identifier Generator for Blade Compiler
|
|
3
|
+
Generates view identification attributes for server-rendered content
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from utils import extract_balanced_parentheses
|
|
8
|
+
|
|
9
|
+
class ViewIdentifierGenerator:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.view_id_counter = 0
|
|
12
|
+
self.view_hierarchy = {}
|
|
13
|
+
|
|
14
|
+
def generate_view_attributes(self, view_name, view_type='view', parent_view=None):
|
|
15
|
+
"""Generate view identification attributes"""
|
|
16
|
+
view_id = self.generate_view_id(view_name)
|
|
17
|
+
|
|
18
|
+
attributes = {
|
|
19
|
+
'data-spa-view': self.extract_view_scope(view_name),
|
|
20
|
+
'data-spa-view-name': view_name,
|
|
21
|
+
'data-spa-view-path': view_name,
|
|
22
|
+
'data-spa-view-id': view_id,
|
|
23
|
+
'data-spa-view-type': view_type
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if parent_view:
|
|
27
|
+
attributes['data-spa-view-parent'] = parent_view
|
|
28
|
+
|
|
29
|
+
return attributes
|
|
30
|
+
|
|
31
|
+
def generate_view_id(self, view_name):
|
|
32
|
+
"""Generate unique view ID"""
|
|
33
|
+
self.view_id_counter += 1
|
|
34
|
+
# Convert view name to ID format
|
|
35
|
+
clean_name = re.sub(r'[^a-zA-Z0-9]', '-', view_name)
|
|
36
|
+
return f"{clean_name}-{self.view_id_counter}"
|
|
37
|
+
|
|
38
|
+
def extract_view_scope(self, view_name):
|
|
39
|
+
"""Extract view scope from view name"""
|
|
40
|
+
parts = view_name.split('.')
|
|
41
|
+
if len(parts) > 1:
|
|
42
|
+
return parts[-1] # Last part as scope
|
|
43
|
+
return 'main'
|
|
44
|
+
|
|
45
|
+
def process_template_with_identifiers(self, blade_code, view_name, view_type='view'):
|
|
46
|
+
"""Process template and add view identification attributes"""
|
|
47
|
+
|
|
48
|
+
# Generate attributes for main container
|
|
49
|
+
main_attributes = self.generate_view_attributes(view_name, view_type)
|
|
50
|
+
|
|
51
|
+
# Add attributes to main container elements
|
|
52
|
+
blade_code = self.add_attributes_to_containers(blade_code, main_attributes)
|
|
53
|
+
|
|
54
|
+
# Process sections with identifiers
|
|
55
|
+
blade_code = self.process_sections_with_identifiers(blade_code, view_name)
|
|
56
|
+
|
|
57
|
+
# Process conditional blocks with identifiers
|
|
58
|
+
blade_code = self.process_conditionals_with_identifiers(blade_code, view_name)
|
|
59
|
+
|
|
60
|
+
return blade_code
|
|
61
|
+
|
|
62
|
+
def add_attributes_to_containers(self, blade_code, attributes):
|
|
63
|
+
"""Add view identification attributes to container elements"""
|
|
64
|
+
|
|
65
|
+
# Find main container elements (div, section, main, article, etc.)
|
|
66
|
+
container_patterns = [
|
|
67
|
+
r'<div([^>]*)>',
|
|
68
|
+
r'<section([^>]*)>',
|
|
69
|
+
r'<main([^>]*)>',
|
|
70
|
+
r'<article([^>]*)>',
|
|
71
|
+
r'<aside([^>]*)>',
|
|
72
|
+
r'<header([^>]*)>',
|
|
73
|
+
r'<footer([^>]*)>'
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for pattern in container_patterns:
|
|
77
|
+
def add_attributes(match):
|
|
78
|
+
existing_attrs = match.group(1)
|
|
79
|
+
new_attrs = ' '.join([f'{k}="{v}"' for k, v in attributes.items()])
|
|
80
|
+
|
|
81
|
+
if existing_attrs.strip():
|
|
82
|
+
return f'<{match.group(0)[1:-1]} {new_attrs}>'
|
|
83
|
+
else:
|
|
84
|
+
return f'<{match.group(0)[1:-1]} {new_attrs}>'
|
|
85
|
+
|
|
86
|
+
blade_code = re.sub(pattern, add_attributes, blade_code)
|
|
87
|
+
|
|
88
|
+
return blade_code
|
|
89
|
+
|
|
90
|
+
def process_sections_with_identifiers(self, blade_code, view_name):
|
|
91
|
+
"""Process @section directives with view identifiers"""
|
|
92
|
+
|
|
93
|
+
# Find @section directives
|
|
94
|
+
section_pattern = r'@section\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)'
|
|
95
|
+
|
|
96
|
+
def process_section(match):
|
|
97
|
+
section_name = match.group(1)
|
|
98
|
+
section_id = f"{view_name}.{section_name}"
|
|
99
|
+
section_attributes = self.generate_view_attributes(section_id, 'section', view_name)
|
|
100
|
+
|
|
101
|
+
# Add attributes to the section content
|
|
102
|
+
attrs_str = ' '.join([f'{k}="{v}"' for k, v in section_attributes.items()])
|
|
103
|
+
|
|
104
|
+
return f'@section(\'{section_name}\')\n<div {attrs_str}>'
|
|
105
|
+
|
|
106
|
+
blade_code = re.sub(section_pattern, process_section, blade_code)
|
|
107
|
+
|
|
108
|
+
# Find @endsection directives
|
|
109
|
+
endsection_pattern = r'@endsection'
|
|
110
|
+
blade_code = re.sub(endsection_pattern, '</div>\n@endsection', blade_code)
|
|
111
|
+
|
|
112
|
+
return blade_code
|
|
113
|
+
|
|
114
|
+
def process_conditionals_with_identifiers(self, blade_code, view_name):
|
|
115
|
+
"""Process conditional blocks with view identifiers"""
|
|
116
|
+
|
|
117
|
+
# Find @if directives
|
|
118
|
+
if_pattern = r'@if\s*\([^)]+\)'
|
|
119
|
+
|
|
120
|
+
def process_if(match):
|
|
121
|
+
if_content = match.group(0)
|
|
122
|
+
if_id = f"{view_name}.if-{self.view_id_counter}"
|
|
123
|
+
if_attributes = self.generate_view_attributes(if_id, 'conditional', view_name)
|
|
124
|
+
|
|
125
|
+
attrs_str = ' '.join([f'{k}="{v}"' for k, v in if_attributes.items()])
|
|
126
|
+
|
|
127
|
+
return f'{if_content}\n<div {attrs_str}>'
|
|
128
|
+
|
|
129
|
+
blade_code = re.sub(if_pattern, process_if, blade_code)
|
|
130
|
+
|
|
131
|
+
# Find @endif directives
|
|
132
|
+
endif_pattern = r'@endif'
|
|
133
|
+
blade_code = re.sub(endif_pattern, '</div>\n@endif', blade_code)
|
|
134
|
+
|
|
135
|
+
return blade_code
|
|
136
|
+
|
|
137
|
+
def generate_server_side_attributes(self, view_name, view_type='view'):
|
|
138
|
+
"""Generate attributes for server-side rendering"""
|
|
139
|
+
|
|
140
|
+
attributes = self.generate_view_attributes(view_name, view_type)
|
|
141
|
+
|
|
142
|
+
# Add server-side specific attributes
|
|
143
|
+
attributes.update({
|
|
144
|
+
'data-server-rendered': 'true',
|
|
145
|
+
'data-hydration-ready': 'false'
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return attributes
|
|
149
|
+
|
|
150
|
+
def generate_meta_tags(self, view_name, view_type='view'):
|
|
151
|
+
"""Generate meta tags for view identification"""
|
|
152
|
+
|
|
153
|
+
view_id = self.generate_view_id(view_name)
|
|
154
|
+
|
|
155
|
+
meta_tags = f"""
|
|
156
|
+
<!-- View Identification Meta Tags -->
|
|
157
|
+
<meta name="spa-view-name" content="{view_name}">
|
|
158
|
+
<meta name="spa-view-path" content="{view_name}">
|
|
159
|
+
<meta name="spa-view-id" content="{view_id}">
|
|
160
|
+
<meta name="spa-view-type" content="{view_type}">
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
return meta_tags
|
|
164
|
+
|
|
165
|
+
def process_yield_directives(self, blade_code, view_name):
|
|
166
|
+
"""Process @yield directives with view identifiers"""
|
|
167
|
+
|
|
168
|
+
# Find @yield directives
|
|
169
|
+
yield_pattern = r'@yield\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)'
|
|
170
|
+
|
|
171
|
+
def process_yield(match):
|
|
172
|
+
yield_name = match.group(1)
|
|
173
|
+
yield_id = f"{view_name}.yield-{yield_name}"
|
|
174
|
+
yield_attributes = self.generate_view_attributes(yield_id, 'yield', view_name)
|
|
175
|
+
|
|
176
|
+
attrs_str = ' '.join([f'{k}="{v}"' for k, v in yield_attributes.items()])
|
|
177
|
+
|
|
178
|
+
return f'<div {attrs_str}>@yield(\'{yield_name}\')</div>'
|
|
179
|
+
|
|
180
|
+
blade_code = re.sub(yield_pattern, process_yield, blade_code)
|
|
181
|
+
|
|
182
|
+
return blade_code
|
|
183
|
+
|
|
184
|
+
def generate_view_boundary_comments(self, view_name, view_type='view'):
|
|
185
|
+
"""Generate HTML comments for view boundaries"""
|
|
186
|
+
|
|
187
|
+
view_id = self.generate_view_id(view_name)
|
|
188
|
+
|
|
189
|
+
start_comment = f"<!-- SPA VIEW START: {view_name} (ID: {view_id}, Type: {view_type}) -->"
|
|
190
|
+
end_comment = f"<!-- SPA VIEW END: {view_name} -->"
|
|
191
|
+
|
|
192
|
+
return start_comment, end_comment
|
|
193
|
+
|
|
194
|
+
def process_loop_directives(self, blade_code, view_name):
|
|
195
|
+
"""Process loop directives with view identifiers"""
|
|
196
|
+
|
|
197
|
+
# Find @foreach directives
|
|
198
|
+
foreach_pattern = r'@foreach\s*\([^)]+\)'
|
|
199
|
+
|
|
200
|
+
def process_foreach(match):
|
|
201
|
+
foreach_content = match.group(0)
|
|
202
|
+
loop_id = f"{view_name}.loop-{self.view_id_counter}"
|
|
203
|
+
loop_attributes = self.generate_view_attributes(loop_id, 'loop', view_name)
|
|
204
|
+
|
|
205
|
+
attrs_str = ' '.join([f'{k}="{v}"' for k, v in loop_attributes.items()])
|
|
206
|
+
|
|
207
|
+
return f'{foreach_content}\n<div {attrs_str}>'
|
|
208
|
+
|
|
209
|
+
blade_code = re.sub(foreach_pattern, process_foreach, blade_code)
|
|
210
|
+
|
|
211
|
+
# Find @endforeach directives
|
|
212
|
+
endforeach_pattern = r'@endforeach'
|
|
213
|
+
blade_code = re.sub(endforeach_pattern, '</div>\n@endforeach', blade_code)
|
|
214
|
+
|
|
215
|
+
return blade_code
|
|
216
|
+
|
|
217
|
+
def generate_debug_attributes(self, view_name, debug_mode=False):
|
|
218
|
+
"""Generate debug attributes for development"""
|
|
219
|
+
|
|
220
|
+
if not debug_mode:
|
|
221
|
+
return {}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
'data-debug-view': view_name,
|
|
225
|
+
'data-debug-timestamp': str(int(__import__('time').time())),
|
|
226
|
+
'data-debug-version': '1.0.0'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
def process_complete_template(self, blade_code, view_name, view_type='view', debug_mode=False):
|
|
230
|
+
"""Process complete template with all view identifiers"""
|
|
231
|
+
|
|
232
|
+
# Add meta tags
|
|
233
|
+
meta_tags = self.generate_meta_tags(view_name, view_type)
|
|
234
|
+
blade_code = self.insert_meta_tags(blade_code, meta_tags)
|
|
235
|
+
|
|
236
|
+
# Process template with identifiers
|
|
237
|
+
blade_code = self.process_template_with_identifiers(blade_code, view_name, view_type)
|
|
238
|
+
|
|
239
|
+
# Process yield directives
|
|
240
|
+
blade_code = self.process_yield_directives(blade_code, view_name)
|
|
241
|
+
|
|
242
|
+
# Process loop directives
|
|
243
|
+
blade_code = self.process_loop_directives(blade_code, view_name)
|
|
244
|
+
|
|
245
|
+
# Add debug attributes if enabled
|
|
246
|
+
if debug_mode:
|
|
247
|
+
debug_attrs = self.generate_debug_attributes(view_name, debug_mode)
|
|
248
|
+
blade_code = self.add_debug_attributes(blade_code, debug_attrs)
|
|
249
|
+
|
|
250
|
+
return blade_code
|
|
251
|
+
|
|
252
|
+
def insert_meta_tags(self, blade_code, meta_tags):
|
|
253
|
+
"""Insert meta tags into head section"""
|
|
254
|
+
|
|
255
|
+
# Find head tag
|
|
256
|
+
head_pattern = r'(<head[^>]*>)'
|
|
257
|
+
|
|
258
|
+
def insert_meta(match):
|
|
259
|
+
return f"{match.group(1)}\n{meta_tags}"
|
|
260
|
+
|
|
261
|
+
blade_code = re.sub(head_pattern, insert_meta, blade_code, flags=re.IGNORECASE)
|
|
262
|
+
|
|
263
|
+
return blade_code
|
|
264
|
+
|
|
265
|
+
def add_debug_attributes(self, blade_code, debug_attrs):
|
|
266
|
+
"""Add debug attributes to elements"""
|
|
267
|
+
|
|
268
|
+
# Add debug attributes to main containers
|
|
269
|
+
container_pattern = r'<div([^>]*data-spa-view[^>]*)>'
|
|
270
|
+
|
|
271
|
+
def add_debug(match):
|
|
272
|
+
existing_attrs = match.group(1)
|
|
273
|
+
debug_str = ' '.join([f'{k}="{v}"' for k, v in debug_attrs.items()])
|
|
274
|
+
return f'<div {existing_attrs} {debug_str}>'
|
|
275
|
+
|
|
276
|
+
blade_code = re.sub(container_pattern, add_debug, blade_code)
|
|
277
|
+
|
|
278
|
+
return blade_code
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser để đọc file wraper.js và extract nội dung
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
class WrapperParser:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.wrapper_function_content = ""
|
|
11
|
+
self.wrapper_config_content = ""
|
|
12
|
+
|
|
13
|
+
def parse_wrapper_file(self, file_path="resources/js/templates/wraper.js"):
|
|
14
|
+
"""Parse file wraper.js và extract nội dung theo comment đánh dấu"""
|
|
15
|
+
# Reset state trước khi parse
|
|
16
|
+
self.wrapper_function_content = ""
|
|
17
|
+
self.wrapper_config_content = ""
|
|
18
|
+
|
|
19
|
+
# Convert to absolute path if relative
|
|
20
|
+
if not os.path.isabs(file_path):
|
|
21
|
+
# Try path relative to current working directory
|
|
22
|
+
if not os.path.exists(file_path):
|
|
23
|
+
# Try path relative to script directory
|
|
24
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
25
|
+
alt_path = os.path.join(script_dir, "..", "..", file_path)
|
|
26
|
+
if os.path.exists(alt_path):
|
|
27
|
+
file_path = alt_path
|
|
28
|
+
|
|
29
|
+
if not os.path.exists(file_path):
|
|
30
|
+
print(f"Warning: Wrapper file not found: {file_path}")
|
|
31
|
+
return "", ""
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
35
|
+
content = f.read()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error reading wrapper file: {e}")
|
|
38
|
+
return "", ""
|
|
39
|
+
|
|
40
|
+
# Extract function wraper content - từ "// start wrapper" đến "// end wrapper"
|
|
41
|
+
start_marker = "// start wrapper"
|
|
42
|
+
end_marker = "// end wrapper"
|
|
43
|
+
|
|
44
|
+
start_pos = content.find(start_marker)
|
|
45
|
+
if start_pos != -1:
|
|
46
|
+
start_pos = content.find('\n', start_pos) + 1 # Bỏ qua dòng comment
|
|
47
|
+
end_pos = content.find(end_marker)
|
|
48
|
+
if end_pos != -1:
|
|
49
|
+
self.wrapper_function_content = content[start_pos:end_pos].strip()
|
|
50
|
+
else:
|
|
51
|
+
print("Warning: End wrapper marker not found")
|
|
52
|
+
else:
|
|
53
|
+
print("Warning: Start wrapper marker not found")
|
|
54
|
+
|
|
55
|
+
# Extract WRAPPER_CONFIG content - từ "// start wrapper config" đến "// end wrapper config"
|
|
56
|
+
start_config_marker = "// start wrapper config"
|
|
57
|
+
end_config_marker = "// end wrapper config"
|
|
58
|
+
|
|
59
|
+
start_config_pos = content.find(start_config_marker)
|
|
60
|
+
if start_config_pos != -1:
|
|
61
|
+
start_config_pos = content.find('\n', start_config_pos) + 1 # Bỏ qua dòng comment
|
|
62
|
+
end_config_pos = content.find(end_config_marker)
|
|
63
|
+
if end_config_pos != -1:
|
|
64
|
+
self.wrapper_config_content = content[start_config_pos:end_config_pos].strip()
|
|
65
|
+
else:
|
|
66
|
+
print("Warning: End wrapper config marker not found")
|
|
67
|
+
else:
|
|
68
|
+
print("Warning: Start wrapper config marker not found")
|
|
69
|
+
|
|
70
|
+
return self.wrapper_function_content, self.wrapper_config_content
|
|
71
|
+
|
|
72
|
+
def get_wrapper_function_content(self):
|
|
73
|
+
"""Lấy nội dung function wraper"""
|
|
74
|
+
return self.wrapper_function_content
|
|
75
|
+
|
|
76
|
+
def get_wrapper_config_content(self):
|
|
77
|
+
"""Lấy nội dung WRAPPER_CONFIG"""
|
|
78
|
+
return self.wrapper_config_content
|