fastled 1.0.10__py2.py3-none-any.whl → 1.0.11__py2.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.
- fastled/__init__.py +1 -1
- fastled/app.py +58 -32
- fastled/compile_server.py +216 -251
- fastled/docker_manager.py +3 -1
- fastled/filewatcher.py +44 -17
- fastled/keyboard.py +89 -0
- fastled/open_browser.py +5 -1
- fastled/web_compile.py +3 -3
- {fastled-1.0.10.dist-info → fastled-1.0.11.dist-info}/METADATA +3 -1
- fastled-1.0.11.dist-info/RECORD +20 -0
- fastled/check_cpp_syntax.py +0 -34
- fastled-1.0.10.dist-info/RECORD +0 -20
- {fastled-1.0.10.dist-info → fastled-1.0.11.dist-info}/LICENSE +0 -0
- {fastled-1.0.10.dist-info → fastled-1.0.11.dist-info}/WHEEL +0 -0
- {fastled-1.0.10.dist-info → fastled-1.0.11.dist-info}/entry_points.txt +0 -0
- {fastled-1.0.10.dist-info → fastled-1.0.11.dist-info}/top_level.txt +0 -0
fastled/__init__.py
CHANGED
fastled/app.py
CHANGED
@@ -17,7 +17,8 @@ from fastled import __version__
|
|
17
17
|
from fastled.build_mode import BuildMode, get_build_mode
|
18
18
|
from fastled.compile_server import CompileServer
|
19
19
|
from fastled.docker_manager import DockerManager
|
20
|
-
from fastled.filewatcher import
|
20
|
+
from fastled.filewatcher import FileWatcherProcess
|
21
|
+
from fastled.keyboard import SpaceBarWatcher
|
21
22
|
from fastled.open_browser import open_browser_thread
|
22
23
|
from fastled.sketch import looks_like_sketch_directory
|
23
24
|
from fastled.web_compile import web_compile
|
@@ -291,41 +292,66 @@ def run_client(args: argparse.Namespace) -> int:
|
|
291
292
|
compile_server.stop()
|
292
293
|
return 1
|
293
294
|
|
294
|
-
# Watch mode
|
295
295
|
print("\nWatching for changes. Press Ctrl+C to stop...")
|
296
|
-
|
297
|
-
# watcher.start()
|
298
|
-
|
299
|
-
from multiprocessing import Process, Queue
|
300
|
-
|
301
|
-
proc: Process
|
302
|
-
queue: Queue
|
303
|
-
proc, queue = create_file_watcher_process(
|
296
|
+
sketch_filewatcher = FileWatcherProcess(
|
304
297
|
args.directory, excluded_patterns=["fastled_js"]
|
305
298
|
)
|
306
299
|
|
300
|
+
source_code_watcher: FileWatcherProcess | None = None
|
301
|
+
if compile_server and compile_server.using_fastled_src_dir_volume():
|
302
|
+
assert compile_server.fastled_src_dir is not None
|
303
|
+
source_code_watcher = FileWatcherProcess(
|
304
|
+
compile_server.fastled_src_dir, excluded_patterns=[]
|
305
|
+
)
|
306
|
+
|
307
|
+
def trigger_rebuild_if_sketch_changed(
|
308
|
+
last_compiled_result: CompiledResult,
|
309
|
+
) -> CompiledResult:
|
310
|
+
changed_files = sketch_filewatcher.get_all_changes()
|
311
|
+
if changed_files:
|
312
|
+
print(f"\nChanges detected in {changed_files}")
|
313
|
+
last_hash_value = last_compiled_result.hash_value
|
314
|
+
out = compile_function(last_hash_value=last_hash_value)
|
315
|
+
if not out.success:
|
316
|
+
print("\nRecompilation failed.")
|
317
|
+
else:
|
318
|
+
print("\nRecompilation successful.")
|
319
|
+
return out
|
320
|
+
return last_compiled_result
|
321
|
+
|
307
322
|
try:
|
308
323
|
while True:
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
324
|
+
last_compiled_result = trigger_rebuild_if_sketch_changed(
|
325
|
+
last_compiled_result
|
326
|
+
)
|
327
|
+
if compile_server and not compile_server.proceess_running():
|
328
|
+
print("Server process is not running. Exiting...")
|
329
|
+
return 1
|
330
|
+
if source_code_watcher is not None:
|
331
|
+
changed_files = source_code_watcher.get_all_changes()
|
332
|
+
if changed_files:
|
333
|
+
print(f"\nChanges detected in FastLED source code: {changed_files}")
|
334
|
+
print("Press space bar to trigger compile.")
|
335
|
+
|
336
|
+
space_key_watcher = SpaceBarWatcher()
|
337
|
+
try:
|
338
|
+
while True:
|
339
|
+
if space_key_watcher.space_bar_pressed():
|
340
|
+
print("Space bar pressed, triggering recompile...")
|
341
|
+
last_compiled_result = compile_function(
|
342
|
+
last_hash_value=None
|
343
|
+
)
|
344
|
+
print("Finished recompile.")
|
345
|
+
break
|
346
|
+
elif len(sketch_filewatcher.get_all_changes()) > 0:
|
347
|
+
last_compiled_result = compile_function(
|
348
|
+
last_hash_value=None
|
349
|
+
)
|
350
|
+
break
|
351
|
+
time.sleep(0.1)
|
352
|
+
finally:
|
353
|
+
space_key_watcher.stop()
|
354
|
+
|
329
355
|
except KeyboardInterrupt:
|
330
356
|
print("\nStopping watch mode...")
|
331
357
|
return 0
|
@@ -333,7 +359,7 @@ def run_client(args: argparse.Namespace) -> int:
|
|
333
359
|
print(f"Error: {e}")
|
334
360
|
return 1
|
335
361
|
finally:
|
336
|
-
|
362
|
+
sketch_filewatcher.stop()
|
337
363
|
if compile_server:
|
338
364
|
compile_server.stop()
|
339
365
|
if browser_proc:
|
@@ -372,8 +398,8 @@ def main() -> int:
|
|
372
398
|
|
373
399
|
if __name__ == "__main__":
|
374
400
|
try:
|
401
|
+
os.chdir("../fastled")
|
375
402
|
sys.argv.append("examples/SdCard")
|
376
|
-
sys.argv.append("--local")
|
377
403
|
sys.exit(main())
|
378
404
|
except KeyboardInterrupt:
|
379
405
|
print("\nExiting from main...")
|
fastled/compile_server.py
CHANGED
@@ -1,251 +1,216 @@
|
|
1
|
-
import socket
|
2
|
-
import subprocess
|
3
|
-
import
|
4
|
-
import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
import
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
self.
|
47
|
-
self.
|
48
|
-
self.
|
49
|
-
self.
|
50
|
-
self.
|
51
|
-
|
52
|
-
|
53
|
-
self.
|
54
|
-
|
55
|
-
def port(self) -> int:
|
56
|
-
return self._port
|
57
|
-
|
58
|
-
def url(self) -> str:
|
59
|
-
return f"http://localhost:{self._port}"
|
60
|
-
|
61
|
-
def wait_for_startup(self, timeout: int = 100) -> bool:
|
62
|
-
"""Wait for the server to start up."""
|
63
|
-
start_time = time.time()
|
64
|
-
while time.time() - start_time < timeout:
|
65
|
-
# ping the server to see if it's up
|
66
|
-
if not self._port:
|
67
|
-
return False
|
68
|
-
# use httpx to ping the server
|
69
|
-
# if successful, return True
|
70
|
-
try:
|
71
|
-
response = httpx.get(
|
72
|
-
f"http://localhost:{self._port}", follow_redirects=True
|
73
|
-
)
|
74
|
-
if response.status_code < 400:
|
75
|
-
return True
|
76
|
-
except KeyboardInterrupt:
|
77
|
-
raise
|
78
|
-
except Exception:
|
79
|
-
pass
|
80
|
-
time.sleep(0.1)
|
81
|
-
if not self.running:
|
82
|
-
return False
|
83
|
-
return False
|
84
|
-
|
85
|
-
def _start(self) -> int:
|
86
|
-
print("Compiling server starting")
|
87
|
-
self.running = True
|
88
|
-
# Ensure Docker is running
|
89
|
-
with self.docker.get_lock():
|
90
|
-
if not self.docker.is_running():
|
91
|
-
if not self.docker.start():
|
92
|
-
print("Docker could not be started. Exiting.")
|
93
|
-
raise RuntimeError("Docker could not be started. Exiting.")
|
94
|
-
|
95
|
-
# Clean up any existing container with the same name
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
if self.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
if self.running_process
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
self.
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
cp
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
except KeyboardInterrupt:
|
218
|
-
if self.running_process:
|
219
|
-
self.running_process.kill()
|
220
|
-
self.running_process.wait()
|
221
|
-
except Exception as e:
|
222
|
-
print(f"Error stopping Docker container: {e}")
|
223
|
-
finally:
|
224
|
-
self.running_process = None
|
225
|
-
# Signal the server thread to stop
|
226
|
-
self.running = False
|
227
|
-
if self.thread:
|
228
|
-
self.thread.join(timeout=10) # Wait up to 10 seconds for thread to finish
|
229
|
-
if self.thread.is_alive():
|
230
|
-
print("Warning: Server thread did not terminate properly")
|
231
|
-
|
232
|
-
print("Compile server stopped")
|
233
|
-
|
234
|
-
def _server_loop(self) -> None:
|
235
|
-
try:
|
236
|
-
while self.running:
|
237
|
-
if self.running_process:
|
238
|
-
# Read Docker container output
|
239
|
-
# Check if Docker process is still running
|
240
|
-
if self.running_process.poll() is not None:
|
241
|
-
print("Docker server stopped unexpectedly")
|
242
|
-
self.running = False
|
243
|
-
break
|
244
|
-
|
245
|
-
time.sleep(0.1) # Prevent busy waiting
|
246
|
-
except KeyboardInterrupt:
|
247
|
-
print("Server thread stopped by user.")
|
248
|
-
except Exception as e:
|
249
|
-
print(f"Error in server thread: {e}")
|
250
|
-
finally:
|
251
|
-
self.running = False
|
1
|
+
import socket
|
2
|
+
import subprocess
|
3
|
+
import time
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
|
8
|
+
from fastled.docker_manager import DockerManager
|
9
|
+
from fastled.sketch import looks_like_fastled_repo
|
10
|
+
|
11
|
+
_DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
|
12
|
+
|
13
|
+
SERVER_PORT = 9021
|
14
|
+
|
15
|
+
|
16
|
+
def find_available_port(start_port: int = SERVER_PORT) -> int:
|
17
|
+
"""Find an available port starting from the given port."""
|
18
|
+
port = start_port
|
19
|
+
end_port = start_port + 1000
|
20
|
+
while True:
|
21
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
22
|
+
if s.connect_ex(("localhost", port)) != 0:
|
23
|
+
return port
|
24
|
+
port += 1
|
25
|
+
if port >= end_port:
|
26
|
+
raise RuntimeError("No available ports found")
|
27
|
+
|
28
|
+
|
29
|
+
class CompileServer:
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
container_name=_DEFAULT_CONTAINER_NAME,
|
33
|
+
interactive: bool = False,
|
34
|
+
) -> None:
|
35
|
+
|
36
|
+
cwd = Path(".").resolve()
|
37
|
+
fastled_src_dir: Path | None = None
|
38
|
+
if looks_like_fastled_repo(cwd):
|
39
|
+
print(
|
40
|
+
"Looks like a FastLED repo, using it as the source directory and mapping it into the server."
|
41
|
+
)
|
42
|
+
fastled_src_dir = cwd / "src"
|
43
|
+
|
44
|
+
self.container_name = container_name
|
45
|
+
self.docker = DockerManager(container_name=container_name)
|
46
|
+
self.running = False
|
47
|
+
self.running_process: subprocess.Popen | None = None
|
48
|
+
self.fastled_src_dir: Path | None = fastled_src_dir
|
49
|
+
self.interactive = interactive
|
50
|
+
self._port = self._start()
|
51
|
+
|
52
|
+
def using_fastled_src_dir_volume(self) -> bool:
|
53
|
+
return self.fastled_src_dir is not None
|
54
|
+
|
55
|
+
def port(self) -> int:
|
56
|
+
return self._port
|
57
|
+
|
58
|
+
def url(self) -> str:
|
59
|
+
return f"http://localhost:{self._port}"
|
60
|
+
|
61
|
+
def wait_for_startup(self, timeout: int = 100) -> bool:
|
62
|
+
"""Wait for the server to start up."""
|
63
|
+
start_time = time.time()
|
64
|
+
while time.time() - start_time < timeout:
|
65
|
+
# ping the server to see if it's up
|
66
|
+
if not self._port:
|
67
|
+
return False
|
68
|
+
# use httpx to ping the server
|
69
|
+
# if successful, return True
|
70
|
+
try:
|
71
|
+
response = httpx.get(
|
72
|
+
f"http://localhost:{self._port}", follow_redirects=True
|
73
|
+
)
|
74
|
+
if response.status_code < 400:
|
75
|
+
return True
|
76
|
+
except KeyboardInterrupt:
|
77
|
+
raise
|
78
|
+
except Exception:
|
79
|
+
pass
|
80
|
+
time.sleep(0.1)
|
81
|
+
if not self.running:
|
82
|
+
return False
|
83
|
+
return False
|
84
|
+
|
85
|
+
def _start(self) -> int:
|
86
|
+
print("Compiling server starting")
|
87
|
+
self.running = True
|
88
|
+
# Ensure Docker is running
|
89
|
+
with self.docker.get_lock():
|
90
|
+
if not self.docker.is_running():
|
91
|
+
if not self.docker.start():
|
92
|
+
print("Docker could not be started. Exiting.")
|
93
|
+
raise RuntimeError("Docker could not be started. Exiting.")
|
94
|
+
|
95
|
+
# Clean up any existing container with the same name
|
96
|
+
try:
|
97
|
+
container_exists = (
|
98
|
+
subprocess.run(
|
99
|
+
["docker", "inspect", self.container_name],
|
100
|
+
capture_output=True,
|
101
|
+
text=True,
|
102
|
+
).returncode
|
103
|
+
== 0
|
104
|
+
)
|
105
|
+
if container_exists:
|
106
|
+
print("Cleaning up existing container")
|
107
|
+
subprocess.run(
|
108
|
+
["docker", "rm", "-f", self.container_name],
|
109
|
+
check=False,
|
110
|
+
)
|
111
|
+
except KeyboardInterrupt:
|
112
|
+
raise
|
113
|
+
except Exception as e:
|
114
|
+
print(f"Warning: Failed to remove existing container: {e}")
|
115
|
+
|
116
|
+
print("Ensuring Docker image exists at latest version")
|
117
|
+
if not self.docker.ensure_image_exists():
|
118
|
+
print("Failed to ensure Docker image exists.")
|
119
|
+
raise RuntimeError("Failed to ensure Docker image exists")
|
120
|
+
|
121
|
+
print("Docker image now validated")
|
122
|
+
port = find_available_port()
|
123
|
+
print(f"Found an available port: {port}")
|
124
|
+
if self.interactive:
|
125
|
+
server_command = ["/bin/bash"]
|
126
|
+
else:
|
127
|
+
server_command = ["python", "/js/run.py", "server", "--allow-shutdown"]
|
128
|
+
print(f"Started Docker container with command: {server_command}")
|
129
|
+
ports = {port: 80}
|
130
|
+
volumes = None
|
131
|
+
if self.fastled_src_dir:
|
132
|
+
print(
|
133
|
+
f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
|
134
|
+
)
|
135
|
+
volumes = {
|
136
|
+
str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
|
137
|
+
}
|
138
|
+
if not self.interactive:
|
139
|
+
# no auto-update because the source directory is mapped in.
|
140
|
+
# This should be automatic now.
|
141
|
+
server_command.append("--no-auto-update") # stop git repo updates.
|
142
|
+
self.running_process = self.docker.run_container(
|
143
|
+
server_command, ports=ports, volumes=volumes, tty=self.interactive
|
144
|
+
)
|
145
|
+
print("Compile server starting")
|
146
|
+
time.sleep(3)
|
147
|
+
if self.running_process.poll() is not None:
|
148
|
+
print("Server failed to start")
|
149
|
+
self.running = False
|
150
|
+
raise RuntimeError("Server failed to start")
|
151
|
+
return port
|
152
|
+
|
153
|
+
def proceess_running(self) -> bool:
|
154
|
+
if self.running_process is None:
|
155
|
+
return False
|
156
|
+
return self.running_process.poll() is None
|
157
|
+
|
158
|
+
def stop(self) -> None:
|
159
|
+
print(f"Stopping server on port {self._port}")
|
160
|
+
# # attempt to send a shutdown signal to the server
|
161
|
+
# try:
|
162
|
+
# httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
|
163
|
+
# # except Exception:
|
164
|
+
# except Exception as e:
|
165
|
+
# print(f"Failed to send shutdown signal: {e}")
|
166
|
+
# pass
|
167
|
+
try:
|
168
|
+
# Stop the Docker container
|
169
|
+
cp: subprocess.CompletedProcess
|
170
|
+
cp = subprocess.run(
|
171
|
+
["docker", "stop", self.container_name],
|
172
|
+
capture_output=True,
|
173
|
+
text=True,
|
174
|
+
check=True,
|
175
|
+
)
|
176
|
+
if cp.returncode != 0:
|
177
|
+
print(f"Failed to stop Docker container: {cp.stderr}")
|
178
|
+
|
179
|
+
cp = subprocess.run(
|
180
|
+
["docker", "rm", self.container_name],
|
181
|
+
capture_output=True,
|
182
|
+
text=True,
|
183
|
+
check=True,
|
184
|
+
)
|
185
|
+
if cp.returncode != 0:
|
186
|
+
print(f"Failed to remove Docker container: {cp.stderr}")
|
187
|
+
|
188
|
+
# Close the stdout pipe
|
189
|
+
if self.running_process and self.running_process.stdout:
|
190
|
+
self.running_process.stdout.close()
|
191
|
+
|
192
|
+
# Wait for the process to fully terminate with a timeout
|
193
|
+
self.running_process.wait(timeout=10)
|
194
|
+
if self.running_process.returncode is None:
|
195
|
+
# kill
|
196
|
+
self.running_process.kill()
|
197
|
+
if self.running_process.returncode is not None:
|
198
|
+
print(
|
199
|
+
f"Server stopped with return code {self.running_process.returncode}"
|
200
|
+
)
|
201
|
+
|
202
|
+
except subprocess.TimeoutExpired:
|
203
|
+
# Force kill if it doesn't stop gracefully
|
204
|
+
if self.running_process:
|
205
|
+
self.running_process.kill()
|
206
|
+
self.running_process.wait()
|
207
|
+
except KeyboardInterrupt:
|
208
|
+
if self.running_process:
|
209
|
+
self.running_process.kill()
|
210
|
+
self.running_process.wait()
|
211
|
+
except Exception as e:
|
212
|
+
print(f"Error stopping Docker container: {e}")
|
213
|
+
finally:
|
214
|
+
self.running_process = None
|
215
|
+
self.running = False
|
216
|
+
print("Compile server stopped")
|
fastled/docker_manager.py
CHANGED
@@ -212,6 +212,7 @@ class DockerManager:
|
|
212
212
|
cmd: list[str],
|
213
213
|
volumes: dict[str, dict[str, str]] | None = None,
|
214
214
|
ports: dict[int, int] | None = None,
|
215
|
+
tty: bool = False,
|
215
216
|
) -> subprocess.Popen:
|
216
217
|
"""Run the Docker container with the specified volume.
|
217
218
|
|
@@ -226,7 +227,8 @@ class DockerManager:
|
|
226
227
|
print("Creating new container...")
|
227
228
|
docker_command = ["docker", "run"]
|
228
229
|
|
229
|
-
if
|
230
|
+
if tty:
|
231
|
+
assert sys.stdout.isatty(), "TTY mode requires a TTY"
|
230
232
|
docker_command.append("-it")
|
231
233
|
# Attach volumes if specified
|
232
234
|
docker_command += [
|
fastled/filewatcher.py
CHANGED
@@ -2,11 +2,14 @@
|
|
2
2
|
"""
|
3
3
|
|
4
4
|
import hashlib
|
5
|
+
import os
|
5
6
|
import queue
|
6
7
|
import threading
|
7
8
|
import time
|
9
|
+
from contextlib import redirect_stdout
|
8
10
|
from multiprocessing import Process, Queue
|
9
11
|
from pathlib import Path
|
12
|
+
from queue import Empty
|
10
13
|
from typing import Dict, Set
|
11
14
|
|
12
15
|
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
@@ -148,22 +151,46 @@ class FileChangedNotifier(threading.Thread):
|
|
148
151
|
|
149
152
|
|
150
153
|
def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
154
|
+
with open(os.devnull, "w") as fnull: # Redirect to /dev/null
|
155
|
+
with redirect_stdout(fnull):
|
156
|
+
watcher = FileChangedNotifier(
|
157
|
+
str(root), excluded_patterns=excluded_patterns
|
158
|
+
)
|
159
|
+
watcher.start()
|
160
|
+
while True:
|
161
|
+
try:
|
162
|
+
changed_files = watcher.get_all_changes()
|
163
|
+
for file in changed_files:
|
164
|
+
queue.put(file)
|
165
|
+
except KeyboardInterrupt:
|
166
|
+
break
|
167
|
+
watcher.stop()
|
168
|
+
|
169
|
+
|
170
|
+
class FileWatcherProcess:
|
171
|
+
def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
|
172
|
+
self.queue: Queue = Queue()
|
173
|
+
self.process = Process(
|
174
|
+
target=_process_wrapper,
|
175
|
+
args=(root, excluded_patterns, self.queue),
|
176
|
+
daemon=True,
|
177
|
+
)
|
178
|
+
self.process.start()
|
179
|
+
|
180
|
+
def stop(self):
|
181
|
+
self.process.terminate()
|
182
|
+
self.process.join()
|
183
|
+
self.queue.close()
|
184
|
+
self.queue.join_thread()
|
161
185
|
|
186
|
+
def get_all_changes(self, timeout: float | None = None) -> list[str]:
|
187
|
+
changed_files = []
|
188
|
+
block = timeout is not None
|
162
189
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
190
|
+
while True:
|
191
|
+
try:
|
192
|
+
changed_file = self.queue.get(block=block, timeout=timeout)
|
193
|
+
changed_files.append(changed_file)
|
194
|
+
except Empty:
|
195
|
+
break
|
196
|
+
return changed_files
|
fastled/keyboard.py
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
import os
|
2
|
+
import select
|
3
|
+
import sys
|
4
|
+
import time
|
5
|
+
from multiprocessing import Process, Queue
|
6
|
+
from queue import Empty
|
7
|
+
|
8
|
+
|
9
|
+
class SpaceBarWatcher:
|
10
|
+
def __init__(self) -> None:
|
11
|
+
self.queue: Queue = Queue()
|
12
|
+
self.queue_cancel: Queue = Queue()
|
13
|
+
self.process = Process(target=self._watch_for_space)
|
14
|
+
self.process.start()
|
15
|
+
|
16
|
+
def _watch_for_space(self) -> None:
|
17
|
+
# Set stdin to non-blocking mode
|
18
|
+
fd = sys.stdin.fileno()
|
19
|
+
if os.name != "nt": # Unix-like systems
|
20
|
+
import termios
|
21
|
+
import tty
|
22
|
+
|
23
|
+
old_settings = termios.tcgetattr(fd) # type: ignore
|
24
|
+
try:
|
25
|
+
tty.setraw(fd) # type: ignore
|
26
|
+
while True:
|
27
|
+
# Check for cancel signal
|
28
|
+
try:
|
29
|
+
self.queue_cancel.get(timeout=0.1)
|
30
|
+
break
|
31
|
+
except Empty:
|
32
|
+
pass
|
33
|
+
|
34
|
+
# Check if there's input ready
|
35
|
+
if select.select([sys.stdin], [], [], 0.1)[0]:
|
36
|
+
char = sys.stdin.read(1)
|
37
|
+
if char == " ":
|
38
|
+
self.queue.put(ord(" "))
|
39
|
+
finally:
|
40
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore
|
41
|
+
else: # Windows
|
42
|
+
import msvcrt
|
43
|
+
|
44
|
+
while True:
|
45
|
+
# Check for cancel signal
|
46
|
+
try:
|
47
|
+
self.queue_cancel.get(timeout=0.1)
|
48
|
+
break
|
49
|
+
except Empty:
|
50
|
+
pass
|
51
|
+
|
52
|
+
# Check if there's input ready
|
53
|
+
if msvcrt.kbhit():
|
54
|
+
char = msvcrt.getch().decode()
|
55
|
+
if char == " ":
|
56
|
+
self.queue.put(ord(" "))
|
57
|
+
|
58
|
+
def space_bar_pressed(self) -> bool:
|
59
|
+
found = False
|
60
|
+
while not self.queue.empty():
|
61
|
+
key = self.queue.get()
|
62
|
+
if key == ord(" "):
|
63
|
+
found = True
|
64
|
+
return found
|
65
|
+
|
66
|
+
def stop(self) -> None:
|
67
|
+
self.queue_cancel.put(True)
|
68
|
+
self.process.terminate()
|
69
|
+
self.process.join()
|
70
|
+
self.queue.close()
|
71
|
+
self.queue.join_thread()
|
72
|
+
|
73
|
+
|
74
|
+
def main() -> None:
|
75
|
+
watcher = SpaceBarWatcher()
|
76
|
+
try:
|
77
|
+
while True:
|
78
|
+
if watcher.space_bar_pressed():
|
79
|
+
break
|
80
|
+
time.sleep(1)
|
81
|
+
finally:
|
82
|
+
watcher.stop()
|
83
|
+
|
84
|
+
|
85
|
+
if __name__ == "__main__":
|
86
|
+
try:
|
87
|
+
main()
|
88
|
+
except KeyboardInterrupt:
|
89
|
+
print("Keyboard interrupt detected.")
|
fastled/open_browser.py
CHANGED
@@ -24,7 +24,11 @@ def _open_browser_python(fastled_js: Path, port: int) -> subprocess.Popen:
|
|
24
24
|
]
|
25
25
|
|
26
26
|
# Start the process
|
27
|
-
process = subprocess.Popen(
|
27
|
+
process = subprocess.Popen(
|
28
|
+
cmd,
|
29
|
+
# stdout=subprocess.DEVNULL,
|
30
|
+
stderr=subprocess.DEVNULL,
|
31
|
+
)
|
28
32
|
return process
|
29
33
|
|
30
34
|
|
fastled/web_compile.py
CHANGED
@@ -4,7 +4,7 @@ import os
|
|
4
4
|
import shutil
|
5
5
|
import tempfile
|
6
6
|
import zipfile
|
7
|
-
from concurrent.futures import
|
7
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
8
8
|
from dataclasses import dataclass
|
9
9
|
from pathlib import Path
|
10
10
|
|
@@ -20,7 +20,7 @@ ENDPOINT_COMPILED_WASM = "compile/wasm"
|
|
20
20
|
_TIMEOUT = 60 * 4 # 2 mins timeout
|
21
21
|
_AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
|
22
22
|
ENABLE_EMBEDDED_DATA = True
|
23
|
-
|
23
|
+
_EXECUTOR = ProcessPoolExecutor(max_workers=8)
|
24
24
|
|
25
25
|
|
26
26
|
@dataclass
|
@@ -161,7 +161,7 @@ def web_compile(
|
|
161
161
|
ip_versions = [True, False] if "localhost" not in host else [True]
|
162
162
|
for ipv4 in ip_versions:
|
163
163
|
for url in urls:
|
164
|
-
f =
|
164
|
+
f = _EXECUTOR.submit(_test_connection, url, ipv4)
|
165
165
|
futures.append(f)
|
166
166
|
|
167
167
|
succeeded = False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fastled
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.11
|
4
4
|
Summary: FastLED Wasm Compiler
|
5
5
|
Home-page: https://github.com/zackees/fastled-wasm
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -16,6 +16,7 @@ Requires-Dist: watchdog
|
|
16
16
|
Requires-Dist: livereload
|
17
17
|
Requires-Dist: download
|
18
18
|
Requires-Dist: filelock
|
19
|
+
Requires-Dist: windows-curses; platform_system == "Windows"
|
19
20
|
|
20
21
|
# FastLED wasm compiler
|
21
22
|
|
@@ -93,6 +94,7 @@ provide shims for most of the common api points.
|
|
93
94
|
|
94
95
|
# Revisions
|
95
96
|
|
97
|
+
* 1.1.11 - Dev improvement: FastLED src code volume mapped into docker will just in time update without having to manually trigger it.
|
96
98
|
* 1.1.10 - Swap large assets with embedded placeholders. This helps video sketches upload and compile instantly. Assets are re-added on after compile artifacts are returned.
|
97
99
|
* 1.1.9 - Remove auto server and instead tell the user corrective action to take.
|
98
100
|
* 1.1.8 - Program now knows it's own version which will be displayed with help file. Use `--version` to get it directly.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
fastled/__init__.py,sha256=f3KhION06nm0tJFF8POOIXhKebdmWIMlx8mVEmVFUrw,64
|
2
|
+
fastled/app.py,sha256=WpcXlNUJzMPmH6BZIK6S15SGiAWtQmXungxlw0uOooc,13955
|
3
|
+
fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
|
4
|
+
fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
|
5
|
+
fastled/compile_server.py,sha256=-Qd9x0l-KfiFRGqZYOi3BRD7iEqfJmJU6EgFY-QJodg,7919
|
6
|
+
fastled/docker_manager.py,sha256=gaDZpFV7E-9JhYIn6ahkP--9dGT32-WR5wZaU-o--6g,9107
|
7
|
+
fastled/filewatcher.py,sha256=fJNMQRDCpihSL4nQeYPqbD4m1Jzjcz_-YRAo-wlPW6k,6518
|
8
|
+
fastled/keyboard.py,sha256=PiAUhga8R_crMqFQRwjKyg6bgVTMlpezWa0gaGmL_So,2542
|
9
|
+
fastled/open_browser.py,sha256=RRHcsZ5Vzsw1AuZUEYuSfjKmf_9j3NGMDUR-FndHmqs,1483
|
10
|
+
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
11
|
+
fastled/sketch.py,sha256=KhhPFqlFVlBk8YrzFy7-ioe7zEzecgrVLhyFbLpBp7k,1845
|
12
|
+
fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
|
13
|
+
fastled/web_compile.py,sha256=jN9p-ODWm_EspFlsT4M7SfyI_wAdi17u7_JEIef5WVw,9645
|
14
|
+
fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
15
|
+
fastled-1.0.11.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
16
|
+
fastled-1.0.11.dist-info/METADATA,sha256=vi_VLpN90hbv8hxpGib2nRtRTYj5GBnoakQccKh7iuY,6780
|
17
|
+
fastled-1.0.11.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
|
18
|
+
fastled-1.0.11.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
19
|
+
fastled-1.0.11.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
|
20
|
+
fastled-1.0.11.dist-info/RECORD,,
|
fastled/check_cpp_syntax.py
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
from pygments import lex
|
2
|
-
from pygments.lexers import CppLexer
|
3
|
-
from pygments.token import Token
|
4
|
-
|
5
|
-
|
6
|
-
def check_cpp_syntax(code):
|
7
|
-
try:
|
8
|
-
# Tokenize the code to check for basic syntax issues
|
9
|
-
for token_type, token_value in lex(code, CppLexer()):
|
10
|
-
if token_type == Token.Error:
|
11
|
-
print(f"Syntax error detected: {token_value}")
|
12
|
-
return False
|
13
|
-
print("No syntax errors detected.")
|
14
|
-
return True
|
15
|
-
except Exception as e:
|
16
|
-
print(f"Error during syntax check: {e}")
|
17
|
-
return False
|
18
|
-
|
19
|
-
|
20
|
-
def main():
|
21
|
-
file_path = input("Enter the path to your C++ file: ")
|
22
|
-
try:
|
23
|
-
with open(file_path, "r") as file:
|
24
|
-
code = file.read()
|
25
|
-
if check_cpp_syntax(code):
|
26
|
-
print("The file can now be sent to the server.")
|
27
|
-
else:
|
28
|
-
print("Please fix the syntax errors before sending.")
|
29
|
-
except FileNotFoundError:
|
30
|
-
print("File not found. Please check the path and try again.")
|
31
|
-
|
32
|
-
|
33
|
-
if __name__ == "__main__":
|
34
|
-
main()
|
fastled-1.0.10.dist-info/RECORD
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
fastled/__init__.py,sha256=8Zhmsg5KFQ2ogDbb2OFvaS5FPnsi76DJDmIxJh6z5Yg,64
|
2
|
-
fastled/app.py,sha256=cRLPDB_GR1WkRNbmHPyyDiG4DAqpiB6MpFIyfa33_c4,12522
|
3
|
-
fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
|
4
|
-
fastled/check_cpp_syntax.py,sha256=YxRJm7cFPv4bdhL1v_KOkBz8RL86ihayoJYvclr69ms,1024
|
5
|
-
fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
|
6
|
-
fastled/compile_server.py,sha256=8avG0mAjdjU0w1ej4LiKVGLgkHi8dZxT48TLuGecj7A,9451
|
7
|
-
fastled/docker_manager.py,sha256=WcOKa3EpIPAjICPfTL87CUYuAmX9KYT6L_Hcqbj95eE,9028
|
8
|
-
fastled/filewatcher.py,sha256=gwxNINttXQXJk3P4CpCav3-UwO3cbCg9k0ySGWOQYh0,5686
|
9
|
-
fastled/open_browser.py,sha256=-VhpGmydwLCcXmrDD2esMEdJPZYcoX2Mt73eb88Nna0,1392
|
10
|
-
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
11
|
-
fastled/sketch.py,sha256=KhhPFqlFVlBk8YrzFy7-ioe7zEzecgrVLhyFbLpBp7k,1845
|
12
|
-
fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
|
13
|
-
fastled/web_compile.py,sha256=Wl06e4nTAEXcn7N03eM0qLsMy4dPv6_hyZ4r7zyJJ1A,9649
|
14
|
-
fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
15
|
-
fastled-1.0.10.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
16
|
-
fastled-1.0.10.dist-info/METADATA,sha256=XmFMC3Pd1YlhemfA_aGSSetoNLvWnD4YFtOuPsxpRT4,6581
|
17
|
-
fastled-1.0.10.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
|
18
|
-
fastled-1.0.10.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
19
|
-
fastled-1.0.10.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
|
20
|
-
fastled-1.0.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|