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 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())