fastled 1.2.23__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 +352 -0
- fastled/app.py +107 -0
- fastled/assets/example.txt +1 -0
- fastled/cli.py +19 -0
- fastled/client_server.py +401 -0
- fastled/compile_server.py +92 -0
- fastled/compile_server_impl.py +247 -0
- fastled/docker_manager.py +784 -0
- fastled/filewatcher.py +202 -0
- fastled/keyboard.py +116 -0
- fastled/live_client.py +86 -0
- fastled/open_browser.py +161 -0
- fastled/open_browser2.py +111 -0
- fastled/parse_args.py +195 -0
- fastled/paths.py +4 -0
- fastled/project_init.py +129 -0
- fastled/select_sketch_directory.py +35 -0
- fastled/settings.py +13 -0
- fastled/site/build.py +457 -0
- fastled/sketch.py +97 -0
- fastled/spinner.py +34 -0
- fastled/string_diff.py +42 -0
- fastled/test/can_run_local_docker_tests.py +13 -0
- fastled/test/examples.py +49 -0
- fastled/types.py +61 -0
- fastled/util.py +10 -0
- fastled/web_compile.py +285 -0
- fastled-1.2.23.dist-info/LICENSE +21 -0
- fastled-1.2.23.dist-info/METADATA +382 -0
- fastled-1.2.23.dist-info/RECORD +33 -0
- fastled-1.2.23.dist-info/WHEEL +5 -0
- fastled-1.2.23.dist-info/entry_points.txt +4 -0
- fastled-1.2.23.dist-info/top_level.txt +1 -0
fastled/__init__.py
ADDED
@@ -0,0 +1,352 @@
|
|
1
|
+
"""FastLED Wasm Compiler package."""
|
2
|
+
|
3
|
+
# context
|
4
|
+
import subprocess
|
5
|
+
from contextlib import contextmanager
|
6
|
+
from multiprocessing import Process
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Generator
|
9
|
+
|
10
|
+
from .compile_server import CompileServer
|
11
|
+
from .live_client import LiveClient
|
12
|
+
from .site.build import build
|
13
|
+
from .types import BuildMode, CompileResult, CompileServerError
|
14
|
+
|
15
|
+
# IMPORTANT! There's a bug in github which will REJECT any version update
|
16
|
+
# that has any other change in the repo. Please bump the version as the
|
17
|
+
# ONLY change in a commit, or else the pypi update and the release will fail.
|
18
|
+
__version__ = "1.2.23"
|
19
|
+
|
20
|
+
|
21
|
+
class Api:
|
22
|
+
@staticmethod
|
23
|
+
def get_examples(host: str | CompileServer | None = None) -> list[str]:
|
24
|
+
from fastled.project_init import get_examples
|
25
|
+
|
26
|
+
if isinstance(host, CompileServer):
|
27
|
+
host = host.url()
|
28
|
+
|
29
|
+
return get_examples(host=host)
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def project_init(
|
33
|
+
example=None, outputdir=None, host: str | CompileServer | None = None
|
34
|
+
) -> Path:
|
35
|
+
from fastled.project_init import project_init
|
36
|
+
|
37
|
+
if isinstance(host, CompileServer):
|
38
|
+
host = host.url()
|
39
|
+
return project_init(example, outputdir, host)
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def web_compile(
|
43
|
+
directory: Path | str,
|
44
|
+
host: str | CompileServer | None = None,
|
45
|
+
build_mode: BuildMode = BuildMode.QUICK,
|
46
|
+
profile: bool = False, # When true then profile information will be enabled and included in the zip.
|
47
|
+
) -> CompileResult:
|
48
|
+
from fastled.web_compile import web_compile
|
49
|
+
|
50
|
+
if isinstance(host, CompileServer):
|
51
|
+
host = host.url()
|
52
|
+
if isinstance(directory, str):
|
53
|
+
directory = Path(directory)
|
54
|
+
out: CompileResult = web_compile(
|
55
|
+
directory, host, build_mode=build_mode, profile=profile
|
56
|
+
)
|
57
|
+
return out
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def live_client(
|
61
|
+
sketch_directory: Path,
|
62
|
+
host: str | CompileServer | None = None,
|
63
|
+
auto_start=True,
|
64
|
+
open_web_browser=True,
|
65
|
+
keep_running=True,
|
66
|
+
build_mode=BuildMode.QUICK,
|
67
|
+
profile=False,
|
68
|
+
) -> LiveClient:
|
69
|
+
return LiveClient(
|
70
|
+
sketch_directory=sketch_directory,
|
71
|
+
host=host,
|
72
|
+
auto_start=auto_start,
|
73
|
+
open_web_browser=open_web_browser,
|
74
|
+
keep_running=keep_running,
|
75
|
+
build_mode=build_mode,
|
76
|
+
profile=profile,
|
77
|
+
)
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def spawn_server(
|
81
|
+
interactive=False,
|
82
|
+
auto_updates=None,
|
83
|
+
auto_start=True,
|
84
|
+
container_name: str | None = None,
|
85
|
+
) -> CompileServer:
|
86
|
+
from fastled.compile_server import CompileServer
|
87
|
+
|
88
|
+
out = CompileServer(
|
89
|
+
container_name=container_name,
|
90
|
+
interactive=interactive,
|
91
|
+
auto_updates=auto_updates,
|
92
|
+
mapped_dir=None,
|
93
|
+
auto_start=auto_start,
|
94
|
+
)
|
95
|
+
return out
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
@contextmanager
|
99
|
+
def server(
|
100
|
+
interactive=False,
|
101
|
+
auto_updates=None,
|
102
|
+
auto_start=True,
|
103
|
+
container_name: str | None = None,
|
104
|
+
) -> Generator[CompileServer, None, None]:
|
105
|
+
server = Api.spawn_server(
|
106
|
+
interactive=interactive,
|
107
|
+
auto_updates=auto_updates,
|
108
|
+
auto_start=auto_start,
|
109
|
+
container_name=container_name,
|
110
|
+
)
|
111
|
+
try:
|
112
|
+
yield server
|
113
|
+
finally:
|
114
|
+
server.stop()
|
115
|
+
|
116
|
+
|
117
|
+
class Docker:
|
118
|
+
@staticmethod
|
119
|
+
def is_installed() -> bool:
|
120
|
+
from fastled.docker_manager import DockerManager
|
121
|
+
|
122
|
+
return DockerManager.is_docker_installed()
|
123
|
+
|
124
|
+
@staticmethod
|
125
|
+
def is_running() -> bool:
|
126
|
+
from fastled.docker_manager import DockerManager
|
127
|
+
|
128
|
+
return DockerManager.is_running()
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
def is_container_running(container_name: str | None = None) -> bool:
|
132
|
+
# from fastled.docker import is_container_running
|
133
|
+
from fastled.docker_manager import DockerManager
|
134
|
+
from fastled.settings import CONTAINER_NAME
|
135
|
+
|
136
|
+
docker_mgr = DockerManager()
|
137
|
+
container_name = container_name or CONTAINER_NAME
|
138
|
+
return docker_mgr.is_container_running(container_name)
|
139
|
+
|
140
|
+
@staticmethod
|
141
|
+
def purge() -> None:
|
142
|
+
from fastled.docker_manager import DockerManager
|
143
|
+
from fastled.settings import IMAGE_NAME
|
144
|
+
|
145
|
+
docker_mgr = DockerManager()
|
146
|
+
docker_mgr.purge(image_name=IMAGE_NAME)
|
147
|
+
|
148
|
+
@staticmethod
|
149
|
+
def build_from_github(
|
150
|
+
url: str = "https://github.com/fastled/fastled",
|
151
|
+
output_dir: Path | str = Path(".cache/fastled"),
|
152
|
+
) -> str:
|
153
|
+
"""Build the FastLED WASM compiler Docker image from a GitHub repository.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
url: GitHub repository URL (default: https://github.com/fastled/fastled)
|
157
|
+
output_dir: Directory to clone the repo into (default: .cache/fastled)
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
Container name.
|
161
|
+
"""
|
162
|
+
|
163
|
+
from fastled.docker_manager import DockerManager
|
164
|
+
from fastled.settings import CONTAINER_NAME, IMAGE_NAME
|
165
|
+
|
166
|
+
if isinstance(output_dir, str):
|
167
|
+
output_dir = Path(output_dir)
|
168
|
+
|
169
|
+
# Create output directory if it doesn't exist
|
170
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
171
|
+
|
172
|
+
# Clone or update the repository
|
173
|
+
if (output_dir / ".git").exists():
|
174
|
+
print(f"Updating existing repository in {output_dir}")
|
175
|
+
# Reset local changes and move HEAD back to handle force pushes
|
176
|
+
subprocess.run(
|
177
|
+
["git", "reset", "--hard", "HEAD~10"],
|
178
|
+
cwd=output_dir,
|
179
|
+
check=True,
|
180
|
+
capture_output=True, # Suppress output of reset
|
181
|
+
)
|
182
|
+
subprocess.run(
|
183
|
+
["git", "pull", "origin", "master"], cwd=output_dir, check=True
|
184
|
+
)
|
185
|
+
else:
|
186
|
+
print(f"Cloning {url} into {output_dir}")
|
187
|
+
subprocess.run(
|
188
|
+
["git", "clone", "--depth", "1", url, str(output_dir)], check=True
|
189
|
+
)
|
190
|
+
|
191
|
+
dockerfile_path = (
|
192
|
+
output_dir / "src" / "platforms" / "wasm" / "compiler" / "Dockerfile"
|
193
|
+
)
|
194
|
+
|
195
|
+
if not dockerfile_path.exists():
|
196
|
+
raise FileNotFoundError(
|
197
|
+
f"Dockerfile not found at {dockerfile_path}. "
|
198
|
+
"This may not be a valid FastLED repository."
|
199
|
+
)
|
200
|
+
|
201
|
+
docker_mgr = DockerManager()
|
202
|
+
|
203
|
+
platform_tag = ""
|
204
|
+
# if "arm" in docker_mgr.architecture():
|
205
|
+
if (
|
206
|
+
"arm"
|
207
|
+
in subprocess.run(["uname", "-m"], capture_output=True).stdout.decode()
|
208
|
+
):
|
209
|
+
platform_tag = "-arm64"
|
210
|
+
|
211
|
+
# Build the image
|
212
|
+
docker_mgr.build_image(
|
213
|
+
image_name=IMAGE_NAME,
|
214
|
+
tag="main",
|
215
|
+
dockerfile_path=dockerfile_path,
|
216
|
+
build_context=output_dir,
|
217
|
+
build_args={"NO_PREWARM": "1"},
|
218
|
+
platform_tag=platform_tag,
|
219
|
+
)
|
220
|
+
|
221
|
+
# Run the container and return it
|
222
|
+
container = docker_mgr.run_container_detached(
|
223
|
+
image_name=IMAGE_NAME,
|
224
|
+
tag="main",
|
225
|
+
container_name=CONTAINER_NAME,
|
226
|
+
command=None, # Use default command from Dockerfile
|
227
|
+
volumes=None, # No volumes needed for build
|
228
|
+
ports=None, # No ports needed for build
|
229
|
+
remove_previous=True, # Remove any existing container
|
230
|
+
)
|
231
|
+
|
232
|
+
return container.name
|
233
|
+
|
234
|
+
@staticmethod
|
235
|
+
def build_from_fastled_repo(
|
236
|
+
project_root: Path | str = Path("."), platform_tag: str = ""
|
237
|
+
) -> str:
|
238
|
+
"""Build the FastLED WASM compiler Docker image, which will be tagged as "main".
|
239
|
+
|
240
|
+
Args:
|
241
|
+
project_root: Path to the FastLED project root directory
|
242
|
+
platform_tag: Optional platform tag (e.g. "-arm64" for ARM builds)
|
243
|
+
|
244
|
+
Returns:
|
245
|
+
The string name of the docker container.
|
246
|
+
"""
|
247
|
+
from fastled.docker_manager import DockerManager
|
248
|
+
from fastled.settings import CONTAINER_NAME, IMAGE_NAME
|
249
|
+
|
250
|
+
if isinstance(project_root, str):
|
251
|
+
project_root = Path(project_root)
|
252
|
+
|
253
|
+
dockerfile_path = (
|
254
|
+
project_root / "src" / "platforms" / "wasm" / "compiler" / "Dockerfile"
|
255
|
+
)
|
256
|
+
|
257
|
+
docker_mgr = DockerManager()
|
258
|
+
|
259
|
+
platform_tag = ""
|
260
|
+
# if "arm" in docker_mgr.architecture():
|
261
|
+
if (
|
262
|
+
"arm"
|
263
|
+
in subprocess.run(["uname", "-m"], capture_output=True).stdout.decode()
|
264
|
+
):
|
265
|
+
platform_tag = "-arm64"
|
266
|
+
|
267
|
+
# if image exists, remove it
|
268
|
+
docker_mgr.purge(image_name=IMAGE_NAME)
|
269
|
+
|
270
|
+
# Build the image
|
271
|
+
docker_mgr.build_image(
|
272
|
+
image_name=IMAGE_NAME,
|
273
|
+
tag="main",
|
274
|
+
dockerfile_path=dockerfile_path,
|
275
|
+
build_context=project_root,
|
276
|
+
build_args={"NO_PREWARM": "1"},
|
277
|
+
platform_tag=platform_tag,
|
278
|
+
)
|
279
|
+
|
280
|
+
# Run the container and return it
|
281
|
+
container = docker_mgr.run_container_detached(
|
282
|
+
image_name=IMAGE_NAME,
|
283
|
+
tag="main",
|
284
|
+
container_name=CONTAINER_NAME,
|
285
|
+
command=None, # Use default command from Dockerfile
|
286
|
+
volumes=None, # No volumes needed for build
|
287
|
+
ports=None, # No ports needed for build
|
288
|
+
remove_previous=True, # Remove any existing container
|
289
|
+
)
|
290
|
+
container_name = f"{container.name}"
|
291
|
+
return container_name
|
292
|
+
|
293
|
+
|
294
|
+
class Tools:
|
295
|
+
@staticmethod
|
296
|
+
def string_diff(needle: str, haystack: list[str]) -> list[tuple[float, str]]:
|
297
|
+
"""Returns a sorted list with the top matches at the beginning."""
|
298
|
+
from fastled.string_diff import string_diff
|
299
|
+
|
300
|
+
return string_diff(needle, haystack)
|
301
|
+
|
302
|
+
|
303
|
+
class Test:
|
304
|
+
__test__ = False # This prevents unittest from recognizing it as a test class.
|
305
|
+
|
306
|
+
@staticmethod
|
307
|
+
def can_run_local_docker_tests() -> bool:
|
308
|
+
from fastled.test.can_run_local_docker_tests import can_run_local_docker_tests
|
309
|
+
|
310
|
+
return can_run_local_docker_tests()
|
311
|
+
|
312
|
+
@staticmethod
|
313
|
+
def test_examples(
|
314
|
+
examples: list[str] | None = None, host: str | CompileServer | None = None
|
315
|
+
) -> dict[str, Exception]:
|
316
|
+
from fastled.test.examples import test_examples
|
317
|
+
|
318
|
+
if isinstance(host, CompileServer):
|
319
|
+
host = host.url()
|
320
|
+
|
321
|
+
return test_examples(examples=examples, host=host)
|
322
|
+
|
323
|
+
@staticmethod
|
324
|
+
def build_site(outputdir: Path, fast: bool | None = None, check: bool = True):
|
325
|
+
"""Builds the FastLED compiler site."""
|
326
|
+
build(outputdir=outputdir, fast=fast, check=check)
|
327
|
+
|
328
|
+
@staticmethod
|
329
|
+
def spawn_http_server(
|
330
|
+
directory: Path | str = Path("."),
|
331
|
+
port: int | None = None,
|
332
|
+
open_browser: bool = True,
|
333
|
+
) -> Process:
|
334
|
+
from fastled.open_browser import open_browser_process
|
335
|
+
|
336
|
+
if isinstance(directory, str):
|
337
|
+
directory = Path(directory)
|
338
|
+
proc: Process = open_browser_process(
|
339
|
+
directory, port=port, open_browser=open_browser
|
340
|
+
)
|
341
|
+
return proc
|
342
|
+
|
343
|
+
|
344
|
+
__all__ = [
|
345
|
+
"Api",
|
346
|
+
"Test",
|
347
|
+
"Build",
|
348
|
+
"CompileServer",
|
349
|
+
"CompileResult",
|
350
|
+
"CompileServerError",
|
351
|
+
"BuildMode",
|
352
|
+
]
|
fastled/app.py
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
"""
|
2
|
+
Uses the latest wasm compiler image to compile the FastLED sketch.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import argparse
|
6
|
+
import sys
|
7
|
+
import time
|
8
|
+
from pathlib import Path
|
9
|
+
|
10
|
+
from fastled.client_server import run_client, run_client_server
|
11
|
+
from fastled.compile_server import CompileServer
|
12
|
+
from fastled.parse_args import parse_args
|
13
|
+
|
14
|
+
|
15
|
+
def run_server(args: argparse.Namespace) -> int:
|
16
|
+
interactive = args.interactive
|
17
|
+
auto_update = args.auto_update
|
18
|
+
mapped_dir = Path(args.directory).absolute() if args.directory else None
|
19
|
+
if interactive and mapped_dir is None:
|
20
|
+
print("Select a sketch when you enter interactive mode.")
|
21
|
+
return 1
|
22
|
+
compile_server = CompileServer(
|
23
|
+
interactive=interactive,
|
24
|
+
auto_updates=auto_update,
|
25
|
+
mapped_dir=mapped_dir,
|
26
|
+
auto_start=True,
|
27
|
+
)
|
28
|
+
|
29
|
+
if not interactive:
|
30
|
+
print(f"Server started at {compile_server.url()}")
|
31
|
+
try:
|
32
|
+
while True:
|
33
|
+
if not compile_server.process_running():
|
34
|
+
print("Server process is not running. Exiting...")
|
35
|
+
return 1
|
36
|
+
time.sleep(0.1)
|
37
|
+
except KeyboardInterrupt:
|
38
|
+
print("\nExiting from server...")
|
39
|
+
return 1
|
40
|
+
finally:
|
41
|
+
compile_server.stop()
|
42
|
+
return 0
|
43
|
+
|
44
|
+
|
45
|
+
def main() -> int:
|
46
|
+
args = parse_args()
|
47
|
+
if args.update:
|
48
|
+
# Force auto_update to ensure update check happens
|
49
|
+
compile_server = CompileServer(interactive=False, auto_updates=True)
|
50
|
+
compile_server.stop()
|
51
|
+
print("Finished updating.")
|
52
|
+
return 0
|
53
|
+
|
54
|
+
if args.build:
|
55
|
+
try:
|
56
|
+
project_root = Path(".").absolute()
|
57
|
+
print(f"Building Docker image at {project_root}")
|
58
|
+
from fastled import Api, Docker
|
59
|
+
|
60
|
+
docker_image_name = Docker.build_from_fastled_repo(
|
61
|
+
project_root=project_root
|
62
|
+
)
|
63
|
+
print(f"Built Docker image: {docker_image_name}")
|
64
|
+
print("Running server")
|
65
|
+
with Api.server(
|
66
|
+
auto_updates=False, container_name=docker_image_name
|
67
|
+
) as server:
|
68
|
+
sketch_dir = Path("examples/wasm")
|
69
|
+
if args.just_compile:
|
70
|
+
rtn = run_client(
|
71
|
+
directory=sketch_dir,
|
72
|
+
host=server,
|
73
|
+
open_web_browser=False,
|
74
|
+
keep_running=False,
|
75
|
+
)
|
76
|
+
if rtn != 0:
|
77
|
+
print(f"Failed to compile: {rtn})")
|
78
|
+
return rtn
|
79
|
+
print(f"Server started at {server.url()}")
|
80
|
+
with Api.live_client(
|
81
|
+
sketch_directory=sketch_dir, host=server
|
82
|
+
) as client:
|
83
|
+
print(f"Client started at {client.url()}")
|
84
|
+
while True:
|
85
|
+
time.sleep(0.1)
|
86
|
+
except KeyboardInterrupt:
|
87
|
+
print("\nExiting from client...")
|
88
|
+
return 1
|
89
|
+
|
90
|
+
if args.server:
|
91
|
+
print("Running in server only mode.")
|
92
|
+
return run_server(args)
|
93
|
+
else:
|
94
|
+
print("Running in client/server mode.")
|
95
|
+
return run_client_server(args)
|
96
|
+
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
# Note that the entry point for the exe is in cli.py
|
100
|
+
try:
|
101
|
+
sys.exit(main())
|
102
|
+
except KeyboardInterrupt:
|
103
|
+
print("\nExiting from main...")
|
104
|
+
sys.exit(1)
|
105
|
+
except Exception as e:
|
106
|
+
print(f"Error: {e}")
|
107
|
+
sys.exit(1)
|
@@ -0,0 +1 @@
|
|
1
|
+
Example assets that will be deployed with python code.
|
fastled/cli.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
Main entry point.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import multiprocessing
|
6
|
+
import sys
|
7
|
+
|
8
|
+
from fastled.app import main as app_main
|
9
|
+
|
10
|
+
|
11
|
+
def main() -> int:
|
12
|
+
"""Main entry point for the template_python_cmd package."""
|
13
|
+
return app_main()
|
14
|
+
|
15
|
+
|
16
|
+
# Cli entry point for the pyinstaller generated exe
|
17
|
+
if __name__ == "__main__":
|
18
|
+
multiprocessing.freeze_support() # needed by pyinstaller.
|
19
|
+
sys.exit(main())
|