pyreact-framework 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyreact/__init__.py +144 -0
- pyreact/cli/__init__.py +3 -0
- pyreact/cli/main.py +512 -0
- pyreact/core/__init__.py +80 -0
- pyreact/core/component.py +372 -0
- pyreact/core/context.py +173 -0
- pyreact/core/element.py +208 -0
- pyreact/core/error_boundary.py +145 -0
- pyreact/core/hooks.py +550 -0
- pyreact/core/memo.py +221 -0
- pyreact/core/portal.py +159 -0
- pyreact/core/reconciler.py +399 -0
- pyreact/core/refs.py +213 -0
- pyreact/core/renderer.py +112 -0
- pyreact/core/scheduler.py +304 -0
- pyreact/devtools/__init__.py +18 -0
- pyreact/devtools/debugger.py +314 -0
- pyreact/devtools/profiler.py +288 -0
- pyreact/dom/__init__.py +64 -0
- pyreact/dom/attributes.py +317 -0
- pyreact/dom/dom_operations.py +333 -0
- pyreact/dom/events.py +349 -0
- pyreact/server/__init__.py +34 -0
- pyreact/server/hydration.py +216 -0
- pyreact/server/ssr.py +344 -0
- pyreact/styles/__init__.py +19 -0
- pyreact/styles/css_module.py +231 -0
- pyreact/styles/styled.py +303 -0
- pyreact/testing/__init__.py +71 -0
- pyreact/testing/fire_event.py +355 -0
- pyreact/testing/screen.py +267 -0
- pyreact/testing/test_renderer.py +232 -0
- pyreact/utils/__init__.py +17 -0
- pyreact/utils/diff.py +182 -0
- pyreact/utils/object_pool.py +216 -0
- pyreact_framework-1.0.0.dist-info/METADATA +363 -0
- pyreact_framework-1.0.0.dist-info/RECORD +41 -0
- pyreact_framework-1.0.0.dist-info/WHEEL +5 -0
- pyreact_framework-1.0.0.dist-info/entry_points.txt +2 -0
- pyreact_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyreact_framework-1.0.0.dist-info/top_level.txt +1 -0
pyreact/__init__.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyReact - Framework Web Declarativo para Python
|
|
3
|
+
================================================
|
|
4
|
+
|
|
5
|
+
PyReact é um framework web declarativo inspirado no React, mas construído
|
|
6
|
+
nativamente para Python. Permite criar interfaces de usuário reativas,
|
|
7
|
+
componentizadas e modernas, sem precisar aprender JavaScript/TypeScript.
|
|
8
|
+
|
|
9
|
+
Quick Start:
|
|
10
|
+
from pyreact import h, render, use_state
|
|
11
|
+
|
|
12
|
+
def Counter(props):
|
|
13
|
+
count, set_count = use_state(0)
|
|
14
|
+
return h('div', {'className': 'counter'},
|
|
15
|
+
h('span', None, f"Count: {count}"),
|
|
16
|
+
h('button', {'onClick': lambda _: set_count(count + 1)}, '+')
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
render(h(Counter, None), document.getElementById('root'))
|
|
20
|
+
|
|
21
|
+
Princípios:
|
|
22
|
+
- Declaratividade: A UI é uma função do estado
|
|
23
|
+
- Componentização: Tudo é um componente
|
|
24
|
+
- Reatividade: Mudanças de estado disparam re-renderizações
|
|
25
|
+
- Isomorfismo: Suporte a Server-Side Rendering
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__version__ = '1.0.0'
|
|
29
|
+
__author__ = 'PyReact Team'
|
|
30
|
+
|
|
31
|
+
# Core
|
|
32
|
+
from .core.element import VNode, h, create_element, is_valid_element, clone_element
|
|
33
|
+
from .core.component import Component, PureComponent
|
|
34
|
+
from .core.renderer import render, hydrate, create_root
|
|
35
|
+
from .core.reconciler import Reconciler
|
|
36
|
+
|
|
37
|
+
# Hooks
|
|
38
|
+
from .core.hooks import (
|
|
39
|
+
use_state,
|
|
40
|
+
use_reducer,
|
|
41
|
+
use_effect,
|
|
42
|
+
use_layout_effect,
|
|
43
|
+
use_context,
|
|
44
|
+
use_ref,
|
|
45
|
+
use_memo,
|
|
46
|
+
use_callback,
|
|
47
|
+
use_imperative_handle,
|
|
48
|
+
use_debug_value,
|
|
49
|
+
use_id,
|
|
50
|
+
use_transition,
|
|
51
|
+
use_deferred_value,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Context
|
|
55
|
+
from .core.context import create_context
|
|
56
|
+
|
|
57
|
+
# Refs
|
|
58
|
+
from .core.refs import create_ref, forward_ref
|
|
59
|
+
|
|
60
|
+
# Portal
|
|
61
|
+
from .core.portal import create_portal
|
|
62
|
+
|
|
63
|
+
# Memo
|
|
64
|
+
from .core.memo import memo, lazy
|
|
65
|
+
|
|
66
|
+
# Error Boundary
|
|
67
|
+
from .core.error_boundary import ErrorBoundary
|
|
68
|
+
|
|
69
|
+
# Scheduler
|
|
70
|
+
from .core.scheduler import Scheduler, Priority
|
|
71
|
+
|
|
72
|
+
# Styles
|
|
73
|
+
from .styles import styled, css_module
|
|
74
|
+
|
|
75
|
+
# Server
|
|
76
|
+
from .server.ssr import render_to_string, render_to_static_markup
|
|
77
|
+
|
|
78
|
+
# DOM
|
|
79
|
+
from .dom.dom_operations import document
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
# Version
|
|
83
|
+
'__version__',
|
|
84
|
+
|
|
85
|
+
# Core
|
|
86
|
+
'VNode',
|
|
87
|
+
'h',
|
|
88
|
+
'create_element',
|
|
89
|
+
'is_valid_element',
|
|
90
|
+
'clone_element',
|
|
91
|
+
'Component',
|
|
92
|
+
'PureComponent',
|
|
93
|
+
'render',
|
|
94
|
+
'hydrate',
|
|
95
|
+
'create_root',
|
|
96
|
+
'Reconciler',
|
|
97
|
+
|
|
98
|
+
# Hooks
|
|
99
|
+
'use_state',
|
|
100
|
+
'use_reducer',
|
|
101
|
+
'use_effect',
|
|
102
|
+
'use_layout_effect',
|
|
103
|
+
'use_context',
|
|
104
|
+
'use_ref',
|
|
105
|
+
'use_memo',
|
|
106
|
+
'use_callback',
|
|
107
|
+
'use_imperative_handle',
|
|
108
|
+
'use_debug_value',
|
|
109
|
+
'use_id',
|
|
110
|
+
'use_transition',
|
|
111
|
+
'use_deferred_value',
|
|
112
|
+
|
|
113
|
+
# Context
|
|
114
|
+
'create_context',
|
|
115
|
+
|
|
116
|
+
# Refs
|
|
117
|
+
'create_ref',
|
|
118
|
+
'forward_ref',
|
|
119
|
+
|
|
120
|
+
# Portal
|
|
121
|
+
'create_portal',
|
|
122
|
+
|
|
123
|
+
# Memo
|
|
124
|
+
'memo',
|
|
125
|
+
'lazy',
|
|
126
|
+
|
|
127
|
+
# Error Boundary
|
|
128
|
+
'ErrorBoundary',
|
|
129
|
+
|
|
130
|
+
# Scheduler
|
|
131
|
+
'Scheduler',
|
|
132
|
+
'Priority',
|
|
133
|
+
|
|
134
|
+
# Styles
|
|
135
|
+
'styled',
|
|
136
|
+
'css_module',
|
|
137
|
+
|
|
138
|
+
# Server
|
|
139
|
+
'render_to_string',
|
|
140
|
+
'render_to_static_markup',
|
|
141
|
+
|
|
142
|
+
# DOM
|
|
143
|
+
'document',
|
|
144
|
+
]
|
pyreact/cli/__init__.py
ADDED
pyreact/cli/main.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyReact CLI
|
|
3
|
+
===========
|
|
4
|
+
|
|
5
|
+
Command-line interface for PyReact development.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_project(name: str) -> None:
|
|
15
|
+
"""Create a new PyReact project"""
|
|
16
|
+
project_dir = Path(name)
|
|
17
|
+
|
|
18
|
+
if project_dir.exists():
|
|
19
|
+
print(f"Error: Directory '{name}' already exists")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
# Create directory structure
|
|
23
|
+
dirs = [
|
|
24
|
+
'src/components',
|
|
25
|
+
'src/hooks',
|
|
26
|
+
'src/pages',
|
|
27
|
+
'src/styles',
|
|
28
|
+
'src/utils',
|
|
29
|
+
'public',
|
|
30
|
+
'tests',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for dir_path in dirs:
|
|
34
|
+
(project_dir / dir_path).mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
# Create pyproject.toml
|
|
37
|
+
pyproject_content = '''[tool.pyreact]
|
|
38
|
+
entry = "src/index.py"
|
|
39
|
+
output = "dist"
|
|
40
|
+
dev_port = 3000
|
|
41
|
+
ssr = true
|
|
42
|
+
css_modules = true
|
|
43
|
+
source_maps = true
|
|
44
|
+
'''
|
|
45
|
+
(project_dir / 'pyproject.toml').write_text(pyproject_content, encoding='utf-8')
|
|
46
|
+
|
|
47
|
+
# Create main index file
|
|
48
|
+
index_content = '''"""
|
|
49
|
+
Main entry point for PyReact application
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from pyreact import h, render, use_state
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def App(props):
|
|
56
|
+
"""Main application component"""
|
|
57
|
+
count, set_count = use_state(0)
|
|
58
|
+
|
|
59
|
+
return h('div', {'className': 'app'},
|
|
60
|
+
h('h1', None, f'Welcome to {props.get("name", "PyReact")}!'),
|
|
61
|
+
h('p', None, 'Edit src/index.py to get started.'),
|
|
62
|
+
h('div', {'className': 'counter'},
|
|
63
|
+
h('span', None, f'Count: {count}'),
|
|
64
|
+
h('button', {'onClick': lambda _: set_count(count + 1)}, '+'),
|
|
65
|
+
h('button', {'onClick': lambda _: set_count(count - 1)}, '-')
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
from pyreact.dom.dom_operations import document
|
|
72
|
+
|
|
73
|
+
root = document.create_element('div')
|
|
74
|
+
root.attributes['id'] = 'root'
|
|
75
|
+
document.body.append_child(root)
|
|
76
|
+
|
|
77
|
+
render(h(App, {'name': 'My App'}), root)
|
|
78
|
+
'''
|
|
79
|
+
(project_dir / 'src' / 'index.py').write_text(index_content, encoding='utf-8')
|
|
80
|
+
|
|
81
|
+
# Create __init__.py files
|
|
82
|
+
init_content = '"""PyReact application package"""'
|
|
83
|
+
(project_dir / 'src' / '__init__.py').write_text(init_content, encoding='utf-8')
|
|
84
|
+
(project_dir / 'src' / 'components' / '__init__.py').write_text(init_content, encoding='utf-8')
|
|
85
|
+
(project_dir / 'src' / 'hooks' / '__init__.py').write_text(init_content, encoding='utf-8')
|
|
86
|
+
(project_dir / 'src' / 'pages' / '__init__.py').write_text(init_content, encoding='utf-8')
|
|
87
|
+
|
|
88
|
+
# Create README
|
|
89
|
+
readme_content = f'''# {name}
|
|
90
|
+
|
|
91
|
+
A PyReact application.
|
|
92
|
+
|
|
93
|
+
## Getting Started
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Install dependencies
|
|
97
|
+
pip install pyreact
|
|
98
|
+
|
|
99
|
+
# Run development server
|
|
100
|
+
pyreact dev
|
|
101
|
+
|
|
102
|
+
# Build for production
|
|
103
|
+
pyreact build
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Project Structure
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
{name}/
|
|
110
|
+
├── src/
|
|
111
|
+
│ ├── components/ # Reusable components
|
|
112
|
+
│ ├── hooks/ # Custom hooks
|
|
113
|
+
│ ├── pages/ # Page components
|
|
114
|
+
│ ├── styles/ # CSS files
|
|
115
|
+
│ └── index.py # Entry point
|
|
116
|
+
├── public/ # Static assets
|
|
117
|
+
├── tests/ # Test files
|
|
118
|
+
└── pyproject.toml # Configuration
|
|
119
|
+
```
|
|
120
|
+
'''
|
|
121
|
+
(project_dir / 'README.md').write_text(readme_content, encoding='utf-8')
|
|
122
|
+
|
|
123
|
+
# Create test file
|
|
124
|
+
test_content = '''"""
|
|
125
|
+
Tests for the application
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
from pyreact.testing import render, screen, fireEvent
|
|
129
|
+
from pyreact import h
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_app_renders():
|
|
133
|
+
"""Test that the app renders"""
|
|
134
|
+
# Add your tests here
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == '__main__':
|
|
139
|
+
import pytest
|
|
140
|
+
pytest.main([__file__, '-v'])
|
|
141
|
+
'''
|
|
142
|
+
(project_dir / 'tests' / 'test_app.py').write_text(test_content, encoding='utf-8')
|
|
143
|
+
|
|
144
|
+
print(f"[OK] Created project '{name}'")
|
|
145
|
+
print(f"\nNext steps:")
|
|
146
|
+
print(f" cd {name}")
|
|
147
|
+
print(f" pyreact dev")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def generate_component(name: str, component_type: str = 'functional') -> None:
|
|
151
|
+
"""Generate a new component"""
|
|
152
|
+
# Determine output directory
|
|
153
|
+
components_dir = Path('src/components')
|
|
154
|
+
if not components_dir.exists():
|
|
155
|
+
components_dir = Path('components')
|
|
156
|
+
|
|
157
|
+
if not components_dir.exists():
|
|
158
|
+
print("Error: Could not find components directory")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
# Create component file
|
|
162
|
+
if component_type == 'class':
|
|
163
|
+
content = f'''"""
|
|
164
|
+
{name} Component
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
from pyreact import h, Component
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class {name}(Component):
|
|
171
|
+
"""Class component: {name}"""
|
|
172
|
+
|
|
173
|
+
def __init__(self, props):
|
|
174
|
+
super().__init__(props)
|
|
175
|
+
self.state = {{}}
|
|
176
|
+
|
|
177
|
+
def render(self):
|
|
178
|
+
return h('div', {{'className': '{name.lower()}'}},
|
|
179
|
+
h('h2', None, '{name}'),
|
|
180
|
+
self.props.get('children', None)
|
|
181
|
+
)
|
|
182
|
+
'''
|
|
183
|
+
else:
|
|
184
|
+
content = f'''"""
|
|
185
|
+
{name} Component
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
from pyreact import h, use_state
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def {name}(props):
|
|
192
|
+
"""Functional component: {name}"""
|
|
193
|
+
|
|
194
|
+
return h('div', {{'className': '{name.lower()}'}},
|
|
195
|
+
h('h2', None, '{name}'),
|
|
196
|
+
props.get('children', None)
|
|
197
|
+
)
|
|
198
|
+
'''
|
|
199
|
+
|
|
200
|
+
file_path = components_dir / f'{name}.py'
|
|
201
|
+
file_path.write_text(content, encoding='utf-8')
|
|
202
|
+
|
|
203
|
+
print(f"[OK] Created component '{name}' at {file_path}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def generate_hook(name: str) -> None:
|
|
207
|
+
"""Generate a new custom hook"""
|
|
208
|
+
hooks_dir = Path('src/hooks')
|
|
209
|
+
if not hooks_dir.exists():
|
|
210
|
+
hooks_dir = Path('hooks')
|
|
211
|
+
|
|
212
|
+
if not hooks_dir.exists():
|
|
213
|
+
print("Error: Could not find hooks directory")
|
|
214
|
+
sys.exit(1)
|
|
215
|
+
|
|
216
|
+
# Remove 'use' prefix if present
|
|
217
|
+
if name.startswith('use_'):
|
|
218
|
+
name = name[4:]
|
|
219
|
+
elif name.startswith('use'):
|
|
220
|
+
name = name[3:]
|
|
221
|
+
|
|
222
|
+
hook_name = f'use_{name.lower()}'
|
|
223
|
+
|
|
224
|
+
content = f'''"""
|
|
225
|
+
{name} Hook
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
from pyreact import use_state, use_effect
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def {hook_name}(initial_value=None):
|
|
232
|
+
"""
|
|
233
|
+
Custom hook: {hook_name}
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
initial_value: Initial value
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
tuple: (value, setter)
|
|
240
|
+
"""
|
|
241
|
+
value, set_value = use_state(initial_value)
|
|
242
|
+
|
|
243
|
+
@use_effect([])
|
|
244
|
+
def setup():
|
|
245
|
+
# Setup logic here
|
|
246
|
+
return lambda: None # Cleanup
|
|
247
|
+
|
|
248
|
+
return value, set_value
|
|
249
|
+
'''
|
|
250
|
+
|
|
251
|
+
file_path = hooks_dir / f'{hook_name}.py'
|
|
252
|
+
file_path.write_text(content, encoding='utf-8')
|
|
253
|
+
|
|
254
|
+
print(f"[OK] Created hook '{hook_name}' at {file_path}")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def run_dev_server(port: int = 3000) -> None:
|
|
258
|
+
"""Run development server"""
|
|
259
|
+
import http.server
|
|
260
|
+
import socketserver
|
|
261
|
+
import threading
|
|
262
|
+
import time
|
|
263
|
+
import webbrowser
|
|
264
|
+
from pathlib import Path
|
|
265
|
+
|
|
266
|
+
# Check if we're in a PyReact project
|
|
267
|
+
if not Path('pyproject.toml').exists():
|
|
268
|
+
print("Error: Not a PyReact project. Run 'pyreact create <name>' first.", flush=True)
|
|
269
|
+
sys.exit(1)
|
|
270
|
+
|
|
271
|
+
# Check if src/index.py exists
|
|
272
|
+
if not Path('src/index.py').exists():
|
|
273
|
+
print("Error: src/index.py not found", flush=True)
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
|
|
276
|
+
# Create public directory if it doesn't exist
|
|
277
|
+
Path('public').mkdir(exist_ok=True)
|
|
278
|
+
|
|
279
|
+
# Generate HTML file
|
|
280
|
+
html_content = '''<!DOCTYPE html>
|
|
281
|
+
<html lang="en">
|
|
282
|
+
<head>
|
|
283
|
+
<meta charset="UTF-8">
|
|
284
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
285
|
+
<title>PyReact App</title>
|
|
286
|
+
<style>
|
|
287
|
+
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
|
288
|
+
.app { max-width: 800px; margin: 0 auto; }
|
|
289
|
+
.counter { display: flex; gap: 10px; align-items: center; }
|
|
290
|
+
button { padding: 5px 15px; font-size: 16px; cursor: pointer; }
|
|
291
|
+
</style>
|
|
292
|
+
</head>
|
|
293
|
+
<body>
|
|
294
|
+
<div id="root"></div>
|
|
295
|
+
<script>
|
|
296
|
+
// PyReact runtime simulation
|
|
297
|
+
const PyReact = {
|
|
298
|
+
h: function(type, props, ...children) {
|
|
299
|
+
return { type, props: props || {}, children: children.flat() };
|
|
300
|
+
},
|
|
301
|
+
render: function(vnode, container) {
|
|
302
|
+
const dom = this.createDom(vnode);
|
|
303
|
+
container.innerHTML = '';
|
|
304
|
+
container.appendChild(dom);
|
|
305
|
+
},
|
|
306
|
+
createDom: function(vnode) {
|
|
307
|
+
if (typeof vnode === 'string') {
|
|
308
|
+
return document.createTextNode(vnode);
|
|
309
|
+
}
|
|
310
|
+
if (!vnode || !vnode.type) {
|
|
311
|
+
return document.createTextNode('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const dom = document.createElement(vnode.type);
|
|
315
|
+
|
|
316
|
+
// Apply props
|
|
317
|
+
for (const [key, value] of Object.entries(vnode.props || {})) {
|
|
318
|
+
if (key === 'className') {
|
|
319
|
+
dom.className = value;
|
|
320
|
+
} else if (key.startsWith('on')) {
|
|
321
|
+
const event = key[2].toLowerCase() + key.slice(3);
|
|
322
|
+
dom.addEventListener(event, value);
|
|
323
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
324
|
+
Object.assign(dom.style, value);
|
|
325
|
+
} else {
|
|
326
|
+
dom.setAttribute(key, value);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Render children
|
|
331
|
+
for (const child of vnode.children || []) {
|
|
332
|
+
dom.appendChild(this.createDom(child));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return dom;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// State management
|
|
340
|
+
let stateValues = [];
|
|
341
|
+
let stateIndex = 0;
|
|
342
|
+
|
|
343
|
+
function useState(initialValue) {
|
|
344
|
+
const currentIndex = stateIndex;
|
|
345
|
+
if (stateValues[currentIndex] === undefined) {
|
|
346
|
+
stateValues[currentIndex] = initialValue;
|
|
347
|
+
}
|
|
348
|
+
const setValue = (newValue) => {
|
|
349
|
+
if (typeof newValue === 'function') {
|
|
350
|
+
stateValues[currentIndex] = newValue(stateValues[currentIndex]);
|
|
351
|
+
} else {
|
|
352
|
+
stateValues[currentIndex] = newValue;
|
|
353
|
+
}
|
|
354
|
+
stateIndex = 0;
|
|
355
|
+
render();
|
|
356
|
+
};
|
|
357
|
+
stateIndex++;
|
|
358
|
+
return [stateValues[currentIndex], setValue];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function render() {
|
|
362
|
+
stateIndex = 0;
|
|
363
|
+
const [count, setCount] = useState(0);
|
|
364
|
+
const root = document.getElementById('root');
|
|
365
|
+
const app = PyReact.h('div', {className: 'app'},
|
|
366
|
+
PyReact.h('h1', null, 'Welcome to PyReact!'),
|
|
367
|
+
PyReact.h('p', null, 'Edit src/index.py to get started.'),
|
|
368
|
+
PyReact.h('div', {className: 'counter'},
|
|
369
|
+
PyReact.h('span', null, 'Count: ' + count),
|
|
370
|
+
PyReact.h('button', {onClick: () => setCount(count + 1)}, '+'),
|
|
371
|
+
PyReact.h('button', {onClick: () => setCount(count - 1)}, '-')
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
PyReact.render(app, root);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Initial render
|
|
378
|
+
render();
|
|
379
|
+
|
|
380
|
+
// Hot reload simulation
|
|
381
|
+
console.log('PyReact dev server running. Edit src/index.py to see changes.');
|
|
382
|
+
</script>
|
|
383
|
+
</body>
|
|
384
|
+
</html>'''
|
|
385
|
+
|
|
386
|
+
html_path = Path('public/index.html')
|
|
387
|
+
html_path.write_text(html_content, encoding='utf-8')
|
|
388
|
+
|
|
389
|
+
# Change to public directory
|
|
390
|
+
original_dir = os.getcwd()
|
|
391
|
+
os.chdir('public')
|
|
392
|
+
|
|
393
|
+
# Create custom handler
|
|
394
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
395
|
+
def log_message(self, format, *args):
|
|
396
|
+
# Suppress default logging
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
# Start server
|
|
400
|
+
try:
|
|
401
|
+
# Allow address reuse
|
|
402
|
+
socketserver.TCPServer.allow_reuse_address = True
|
|
403
|
+
|
|
404
|
+
with socketserver.TCPServer(("", port), Handler) as httpd:
|
|
405
|
+
url = f"http://localhost:{port}"
|
|
406
|
+
print(f"[OK] Development server running at {url}", flush=True)
|
|
407
|
+
print(f"\nPress Ctrl+C to stop the server", flush=True)
|
|
408
|
+
|
|
409
|
+
# Open browser after a short delay
|
|
410
|
+
def open_browser():
|
|
411
|
+
time.sleep(1.5)
|
|
412
|
+
try:
|
|
413
|
+
webbrowser.open(url)
|
|
414
|
+
except:
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
browser_thread = threading.Thread(target=open_browser, daemon=True)
|
|
418
|
+
browser_thread.start()
|
|
419
|
+
|
|
420
|
+
# Serve forever
|
|
421
|
+
try:
|
|
422
|
+
httpd.serve_forever()
|
|
423
|
+
except KeyboardInterrupt:
|
|
424
|
+
pass
|
|
425
|
+
finally:
|
|
426
|
+
os.chdir(original_dir)
|
|
427
|
+
|
|
428
|
+
except OSError as e:
|
|
429
|
+
os.chdir(original_dir)
|
|
430
|
+
if 'Address already in use' in str(e) or e.errno == 10048: # Port already in use (Windows)
|
|
431
|
+
print(f"Error: Port {port} is already in use", flush=True)
|
|
432
|
+
print(f"Try: pyreact dev --port {port + 1}", flush=True)
|
|
433
|
+
else:
|
|
434
|
+
print(f"Error: {e}", flush=True)
|
|
435
|
+
sys.exit(1)
|
|
436
|
+
except KeyboardInterrupt:
|
|
437
|
+
print("\n\n[OK] Server stopped", flush=True)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def build_project() -> None:
|
|
441
|
+
"""Build project for production"""
|
|
442
|
+
print("Building project for production...")
|
|
443
|
+
print("Note: This is a placeholder. Implement actual build process.")
|
|
444
|
+
|
|
445
|
+
# Check for pyproject.toml
|
|
446
|
+
if not Path('pyproject.toml').exists():
|
|
447
|
+
print("Error: pyproject.toml not found")
|
|
448
|
+
sys.exit(1)
|
|
449
|
+
|
|
450
|
+
print("[OK] Build complete (placeholder)")
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def run_tests() -> None:
|
|
454
|
+
"""Run tests"""
|
|
455
|
+
import subprocess
|
|
456
|
+
|
|
457
|
+
print("Running tests...")
|
|
458
|
+
result = subprocess.run(['pytest', 'tests/', '-v'])
|
|
459
|
+
sys.exit(result.returncode)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def main():
|
|
463
|
+
"""Main CLI entry point"""
|
|
464
|
+
parser = argparse.ArgumentParser(
|
|
465
|
+
description='PyReact - Framework Web Declarativo para Python'
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
|
469
|
+
|
|
470
|
+
# Create command
|
|
471
|
+
create_parser = subparsers.add_parser('create', help='Create a new project')
|
|
472
|
+
create_parser.add_argument('name', help='Project name')
|
|
473
|
+
|
|
474
|
+
# Dev command
|
|
475
|
+
dev_parser = subparsers.add_parser('dev', help='Start development server')
|
|
476
|
+
dev_parser.add_argument('--port', type=int, default=3000, help='Port number')
|
|
477
|
+
|
|
478
|
+
# Build command
|
|
479
|
+
subparsers.add_parser('build', help='Build for production')
|
|
480
|
+
|
|
481
|
+
# Test command
|
|
482
|
+
subparsers.add_parser('test', help='Run tests')
|
|
483
|
+
|
|
484
|
+
# Generate command
|
|
485
|
+
gen_parser = subparsers.add_parser('generate', help='Generate component or hook')
|
|
486
|
+
gen_parser.add_argument('type', choices=['component', 'hook'], help='Type to generate')
|
|
487
|
+
gen_parser.add_argument('name', help='Name of component or hook')
|
|
488
|
+
gen_parser.add_argument('--class', dest='class_type', action='store_true',
|
|
489
|
+
help='Generate class component')
|
|
490
|
+
|
|
491
|
+
args = parser.parse_args()
|
|
492
|
+
|
|
493
|
+
if args.command == 'create':
|
|
494
|
+
create_project(args.name)
|
|
495
|
+
elif args.command == 'dev':
|
|
496
|
+
run_dev_server(args.port)
|
|
497
|
+
elif args.command == 'build':
|
|
498
|
+
build_project()
|
|
499
|
+
elif args.command == 'test':
|
|
500
|
+
run_tests()
|
|
501
|
+
elif args.command == 'generate':
|
|
502
|
+
if args.type == 'component':
|
|
503
|
+
component_type = 'class' if args.class_type else 'functional'
|
|
504
|
+
generate_component(args.name, component_type)
|
|
505
|
+
elif args.type == 'hook':
|
|
506
|
+
generate_hook(args.name)
|
|
507
|
+
else:
|
|
508
|
+
parser.print_help()
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
if __name__ == '__main__':
|
|
512
|
+
main()
|