fastled 1.1.34__py2.py3-none-any.whl → 1.1.36__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 CHANGED
@@ -1,3 +1,70 @@
1
1
  """FastLED Wasm Compiler package."""
2
2
 
3
- __version__ = "1.1.34"
3
+ from pathlib import Path
4
+
5
+ from .compile_server import CompileServer
6
+ from .types import WebCompileResult
7
+
8
+ __version__ = "1.1.36"
9
+
10
+
11
+ class Api:
12
+ @staticmethod
13
+ def get_examples():
14
+ from fastled.project_init import get_examples
15
+
16
+ return get_examples()
17
+
18
+ @staticmethod
19
+ def project_init(
20
+ example=None, outputdir=None, host: str | CompileServer | None = None
21
+ ) -> Path:
22
+ from fastled.project_init import project_init
23
+
24
+ if isinstance(host, CompileServer):
25
+ host = host.url()
26
+ return project_init(example, outputdir, host)
27
+
28
+ @staticmethod
29
+ def web_compile(
30
+ directory, host: str | CompileServer | None = None
31
+ ) -> WebCompileResult:
32
+ from fastled.web_compile import web_compile
33
+
34
+ if isinstance(host, CompileServer):
35
+ host = host.url()
36
+
37
+ out: WebCompileResult = web_compile(directory, host)
38
+ return out
39
+
40
+ @staticmethod
41
+ def spawn_server(
42
+ sketch_directory: Path | None = None,
43
+ interactive=False,
44
+ auto_updates=None,
45
+ auto_start=True,
46
+ container_name: str | None = None,
47
+ ):
48
+ from fastled.compile_server import CompileServer
49
+
50
+ out = CompileServer(
51
+ container_name=container_name,
52
+ interactive=interactive,
53
+ auto_updates=auto_updates,
54
+ mapped_dir=sketch_directory,
55
+ auto_start=auto_start,
56
+ )
57
+ return out
58
+
59
+
60
+ class Test:
61
+ @staticmethod
62
+ def test_examples(
63
+ examples: list[str] | None = None, host: str | CompileServer | None = None
64
+ ) -> Exception | None:
65
+ from fastled.test.examples import test_examples
66
+
67
+ if isinstance(host, CompileServer):
68
+ host = host.url()
69
+
70
+ return test_examples(examples=examples, host=host)
fastled/app.py CHANGED
@@ -17,14 +17,17 @@ def run_server(args: argparse.Namespace) -> int:
17
17
  auto_update = args.auto_update
18
18
  mapped_dir = Path(args.directory).absolute() if args.directory else None
19
19
  compile_server = CompileServer(
20
- interactive=interactive, auto_updates=auto_update, mapped_dir=mapped_dir
20
+ interactive=interactive,
21
+ auto_updates=auto_update,
22
+ mapped_dir=mapped_dir,
23
+ auto_start=True,
21
24
  )
25
+
22
26
  if not interactive:
23
27
  print(f"Server started at {compile_server.url()}")
24
- compile_server.wait_for_startup()
25
28
  try:
26
29
  while True:
27
- if not compile_server.proceess_running():
30
+ if not compile_server.process_running():
28
31
  print("Server process is not running. Exiting...")
29
32
  return 1
30
33
  time.sleep(0.1)
fastled/client_server.py CHANGED
@@ -8,10 +8,10 @@ from pathlib import Path
8
8
  from fastled.build_mode import BuildMode, get_build_mode
9
9
  from fastled.compile_server import CompileServer
10
10
  from fastled.docker_manager import DockerManager
11
- from fastled.env import DEFAULT_URL
12
11
  from fastled.filewatcher import FileWatcherProcess
13
12
  from fastled.keyboard import SpaceBarWatcher
14
13
  from fastled.open_browser import open_browser_process
14
+ from fastled.settings import DEFAULT_URL
15
15
  from fastled.sketch import looks_like_sketch_directory
16
16
 
17
17
  # CompiledResult
@@ -112,7 +112,7 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
112
112
  print("No local server found, starting one...")
113
113
  compile_server = CompileServer(auto_updates=auto_update)
114
114
  print("Waiting for the local compiler to start...")
115
- if not compile_server.wait_for_startup():
115
+ if not compile_server.ping():
116
116
  print("Failed to start local compiler.")
117
117
  raise RuntimeError("Failed to start local compiler.")
118
118
  return compile_server
@@ -254,7 +254,7 @@ def run_client_server(args: argparse.Namespace) -> int:
254
254
  if changed:
255
255
  print_status()
256
256
  continue
257
- if compile_server and not compile_server.proceess_running():
257
+ if compile_server and not compile_server.process_running():
258
258
  print("Server process is not running. Exiting...")
259
259
  return 1
260
260
  if source_code_watcher is not None:
fastled/compile_server.py CHANGED
@@ -1,193 +1,82 @@
1
- import subprocess
2
- import time
3
- from datetime import datetime, timezone
4
1
  from pathlib import Path
5
2
 
6
- import httpx
7
-
8
- from fastled.docker_manager import (
9
- DISK_CACHE,
10
- Container,
11
- DockerManager,
12
- RunningContainer,
13
- )
14
- from fastled.sketch import looks_like_fastled_repo
15
-
16
- _IMAGE_NAME = "niteris/fastled-wasm"
17
- _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
18
-
19
- SERVER_PORT = 9021
20
-
21
- SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
3
+ from fastled.build_mode import BuildMode
4
+ from fastled.types import WebCompileResult
22
5
 
23
6
 
24
7
  class CompileServer:
25
8
  def __init__(
26
9
  self,
27
- container_name=_DEFAULT_CONTAINER_NAME,
28
10
  interactive: bool = False,
29
11
  auto_updates: bool | None = None,
30
12
  mapped_dir: Path | None = None,
13
+ auto_start: bool = True,
14
+ container_name: str | None = None,
31
15
  ) -> None:
32
- if interactive and not mapped_dir:
33
- raise ValueError(
34
- "Interactive mode requires a mapped directory point to a sketch"
35
- )
36
- if not interactive and mapped_dir:
37
- raise ValueError("Mapped directory is only used in interactive mode")
38
- cwd = Path(".").resolve()
39
- fastled_src_dir: Path | None = None
40
- if looks_like_fastled_repo(cwd):
41
- print(
42
- "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
43
- )
44
- fastled_src_dir = cwd / "src"
45
-
46
- self.container_name = container_name
47
- self.mapped_dir = mapped_dir
48
- self.docker = DockerManager()
49
- self.fastled_src_dir: Path | None = fastled_src_dir
50
- self.interactive = interactive
51
- self.running_container: RunningContainer | None = None
52
- self.auto_updates = auto_updates
53
- self._port = self._start()
54
- # fancy print
55
- if not interactive:
56
- msg = f"# FastLED Compile Server started at {self.url()} #"
57
- print("\n" + "#" * len(msg))
58
- print(msg)
59
- print("#" * len(msg) + "\n")
16
+ from fastled.compile_server_impl import ( # avoid circular import
17
+ CompileServerImpl,
18
+ )
19
+
20
+ self.impl = CompileServerImpl(
21
+ container_name=container_name,
22
+ interactive=interactive,
23
+ auto_updates=auto_updates,
24
+ mapped_dir=mapped_dir,
25
+ auto_start=auto_start,
26
+ )
27
+
28
+ def start(self, wait_for_startup=True) -> None:
29
+ # from fastled.compile_server_impl import CompileServerImpl # avoid circular import
30
+ self.impl.start(wait_for_startup=wait_for_startup)
31
+
32
+ def web_compile(
33
+ self,
34
+ directory: Path | str,
35
+ build_mode: BuildMode = BuildMode.QUICK,
36
+ profile: bool = False,
37
+ ) -> WebCompileResult:
38
+ return self.impl.web_compile(
39
+ directory=directory, build_mode=build_mode, profile=profile
40
+ )
41
+
42
+ def project_init(
43
+ self, example: str | None = None, outputdir: Path | None = None
44
+ ) -> None:
45
+ from fastled.project_init import project_init # avoid circular import
46
+
47
+ project_init(example=example, outputdir=outputdir)
60
48
 
61
49
  @property
62
50
  def running(self) -> bool:
63
- if not self._port:
64
- return False
65
- if not DockerManager.is_docker_installed():
66
- return False
67
- if not DockerManager.is_running():
68
- return False
69
- return self.docker.is_container_running(self.container_name)
51
+ return self.impl.running
52
+
53
+ @property
54
+ def fastled_src_dir(self) -> Path | None:
55
+ return self.impl.fastled_src_dir
70
56
 
71
57
  def using_fastled_src_dir_volume(self) -> bool:
72
- return self.fastled_src_dir is not None
58
+ return self.impl.using_fastled_src_dir_volume()
73
59
 
74
60
  def port(self) -> int:
75
- return self._port
61
+ return self.impl.port()
76
62
 
77
63
  def url(self) -> str:
78
- return f"http://localhost:{self._port}"
64
+ return self.impl.url()
65
+
66
+ def ping(self) -> bool:
67
+ return self.impl.ping()
79
68
 
69
+ # by default this is automatically called by the constructor, unless
70
+ # auto_start is set to False.
80
71
  def wait_for_startup(self, timeout: int = 100) -> bool:
81
72
  """Wait for the server to start up."""
82
- start_time = time.time()
83
- while time.time() - start_time < timeout:
84
- # ping the server to see if it's up
85
- if not self._port:
86
- return False
87
- # use httpx to ping the server
88
- # if successful, return True
89
- try:
90
- response = httpx.get(
91
- f"http://localhost:{self._port}", follow_redirects=True
92
- )
93
- if response.status_code < 400:
94
- return True
95
- except KeyboardInterrupt:
96
- raise
97
- except Exception:
98
- pass
99
- time.sleep(0.1)
100
- if not self.docker.is_container_running(self.container_name):
101
- return False
102
- return False
73
+ return self.impl.wait_for_startup(timeout=timeout)
103
74
 
104
75
  def _start(self) -> int:
105
- print("Compiling server starting")
106
-
107
- # Ensure Docker is running
108
- if not self.docker.is_running():
109
- if not self.docker.start():
110
- print("Docker could not be started. Exiting.")
111
- raise RuntimeError("Docker could not be started. Exiting.")
112
- now = datetime.now(timezone.utc)
113
- now_str = now.strftime("%Y-%m-%d")
114
-
115
- upgrade = False
116
- if self.auto_updates is None:
117
- prev_date_str = DISK_CACHE.get("last-update")
118
- if prev_date_str != now_str:
119
- print("One day has passed, checking docker for updates")
120
- upgrade = True
121
- else:
122
- upgrade = self.auto_updates
123
- self.docker.validate_or_download_image(
124
- image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
125
- )
126
- DISK_CACHE.put("last-update", now_str)
127
-
128
- print("Docker image now validated")
129
- port = SERVER_PORT
130
- if self.interactive:
131
- server_command = ["/bin/bash"]
132
- else:
133
- server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
134
- ports = {80: port}
135
- volumes = None
136
- if self.fastled_src_dir:
137
- print(
138
- f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
139
- )
140
- volumes = {
141
- str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
142
- }
143
- if self.interactive:
144
- # add the mapped directory to the container
145
- print(f"Mounting {self.mapped_dir} into container /mapped")
146
- # volumes = {str(self.mapped_dir): {"bind": "/mapped", "mode": "rw"}}
147
- # add it
148
- assert self.mapped_dir is not None
149
- dir_name = self.mapped_dir.name
150
- if not volumes:
151
- volumes = {}
152
- volumes[str(self.mapped_dir)] = {
153
- "bind": f"/mapped/{dir_name}",
154
- "mode": "rw",
155
- }
156
-
157
- cmd_str = subprocess.list2cmdline(server_command)
158
- if not self.interactive:
159
- container: Container = self.docker.run_container_detached(
160
- image_name=_IMAGE_NAME,
161
- tag="main",
162
- container_name=self.container_name,
163
- command=cmd_str,
164
- ports=ports,
165
- volumes=volumes,
166
- remove_previous=self.interactive,
167
- )
168
- self.running_container = self.docker.attach_and_run(container)
169
- assert self.running_container is not None, "Container should be running"
170
- print("Compile server starting")
171
- return port
172
- else:
173
- self.docker.run_container_interactive(
174
- image_name=_IMAGE_NAME,
175
- tag="main",
176
- container_name=self.container_name,
177
- command=cmd_str,
178
- ports=ports,
179
- volumes=volumes,
180
- )
181
-
182
- print("Exiting interactive mode")
183
- return port
184
-
185
- def proceess_running(self) -> bool:
186
- return self.docker.is_container_running(self.container_name)
76
+ return self.impl._start()
187
77
 
188
78
  def stop(self) -> None:
189
- # print(f"Stopping server on port {self._port}")
190
- if self.running_container:
191
- self.running_container.stop()
192
- self.docker.suspend_container(self.container_name)
193
- print("Compile server stopped")
79
+ return self.impl.stop()
80
+
81
+ def process_running(self) -> bool:
82
+ return self.impl.process_running()
@@ -0,0 +1,244 @@
1
+ import subprocess
2
+ import time
3
+ import warnings
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+
9
+ from fastled.build_mode import BuildMode
10
+ from fastled.docker_manager import (
11
+ DISK_CACHE,
12
+ Container,
13
+ DockerManager,
14
+ RunningContainer,
15
+ )
16
+ from fastled.settings import SERVER_PORT
17
+ from fastled.sketch import looks_like_fastled_repo
18
+ from fastled.types import WebCompileResult
19
+
20
+ _IMAGE_NAME = "niteris/fastled-wasm"
21
+ _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
22
+
23
+
24
+ SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
25
+
26
+
27
+ class CompileServerImpl:
28
+ def __init__(
29
+ self,
30
+ interactive: bool = False,
31
+ auto_updates: bool | None = None,
32
+ mapped_dir: Path | None = None,
33
+ auto_start: bool = True,
34
+ container_name: str | None = None,
35
+ ) -> None:
36
+ container_name = container_name or _DEFAULT_CONTAINER_NAME
37
+ if interactive and not mapped_dir:
38
+ raise ValueError(
39
+ "Interactive mode requires a mapped directory point to a sketch"
40
+ )
41
+ if not interactive and mapped_dir:
42
+ raise ValueError("Mapped directory is only used in interactive mode")
43
+ cwd = Path(".").resolve()
44
+ fastled_src_dir: Path | None = None
45
+ if looks_like_fastled_repo(cwd):
46
+ print(
47
+ "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
48
+ )
49
+ fastled_src_dir = cwd / "src"
50
+
51
+ self.container_name = container_name
52
+ self.mapped_dir = mapped_dir
53
+ self.docker = DockerManager()
54
+ self.fastled_src_dir: Path | None = fastled_src_dir
55
+ self.interactive = interactive
56
+ self.running_container: RunningContainer | None = None
57
+ self.auto_updates = auto_updates
58
+ # self._port = self._start()
59
+ self._port = 0 # 0 until compile server is started
60
+ # fancy print
61
+ if not interactive:
62
+ msg = f"# FastLED Compile Server started at {self.url()} #"
63
+ print("\n" + "#" * len(msg))
64
+ print(msg)
65
+ print("#" * len(msg) + "\n")
66
+ if auto_start:
67
+ self.start()
68
+
69
+ def start(self, wait_for_startup=True) -> None:
70
+ if self._port != 0:
71
+ warnings.warn("Server has already been started")
72
+ self._port = self._start()
73
+ if wait_for_startup:
74
+ self.wait_for_startup()
75
+
76
+ def web_compile(
77
+ self,
78
+ directory: Path | str,
79
+ build_mode: BuildMode = BuildMode.QUICK,
80
+ profile: bool = False,
81
+ ) -> WebCompileResult:
82
+ from fastled.web_compile import web_compile # avoid circular import
83
+
84
+ if not self._port:
85
+ raise RuntimeError("Server has not been started yet")
86
+ if not self.ping():
87
+ raise RuntimeError("Server is not running")
88
+ out: WebCompileResult = web_compile(
89
+ directory, host=self.url(), build_mode=build_mode, profile=profile
90
+ )
91
+ return out
92
+
93
+ def project_init(
94
+ self, example: str | None = None, outputdir: Path | None = None
95
+ ) -> None:
96
+ from fastled.project_init import project_init # avoid circular import
97
+
98
+ project_init(example=example, outputdir=outputdir)
99
+
100
+ @property
101
+ def running(self) -> bool:
102
+ if not self._port:
103
+ return False
104
+ if not DockerManager.is_docker_installed():
105
+ return False
106
+ if not DockerManager.is_running():
107
+ return False
108
+ return self.docker.is_container_running(self.container_name)
109
+
110
+ def using_fastled_src_dir_volume(self) -> bool:
111
+ return self.fastled_src_dir is not None
112
+
113
+ def port(self) -> int:
114
+ if self._port == 0:
115
+ warnings.warn("Server has not been started yet")
116
+ return self._port
117
+
118
+ def url(self) -> str:
119
+ if self._port == 0:
120
+ warnings.warn("Server has not been started yet")
121
+ return f"http://localhost:{self._port}"
122
+
123
+ def ping(self) -> bool:
124
+ try:
125
+ response = httpx.get(
126
+ f"http://localhost:{self._port}", follow_redirects=True
127
+ )
128
+ if response.status_code < 400:
129
+ return True
130
+ except KeyboardInterrupt:
131
+ raise
132
+ except Exception:
133
+ pass
134
+ return False
135
+
136
+ # by default this is automatically called by the constructor, unless
137
+ # auto_start is set to False.
138
+ def wait_for_startup(self, timeout: int = 100) -> bool:
139
+ """Wait for the server to start up."""
140
+ start_time = time.time()
141
+ while time.time() - start_time < timeout:
142
+ # ping the server to see if it's up
143
+ if not self._port:
144
+ return False
145
+ # use httpx to ping the server
146
+ # if successful, return True
147
+ if self.ping():
148
+ return True
149
+ time.sleep(0.1)
150
+ if not self.docker.is_container_running(self.container_name):
151
+ return False
152
+ return False
153
+
154
+ def _start(self) -> int:
155
+ print("Compiling server starting")
156
+
157
+ # Ensure Docker is running
158
+ if not self.docker.is_running():
159
+ if not self.docker.start():
160
+ print("Docker could not be started. Exiting.")
161
+ raise RuntimeError("Docker could not be started. Exiting.")
162
+ now = datetime.now(timezone.utc)
163
+ now_str = now.strftime("%Y-%m-%d")
164
+
165
+ upgrade = False
166
+ if self.auto_updates is None:
167
+ prev_date_str = DISK_CACHE.get("last-update")
168
+ if prev_date_str != now_str:
169
+ print("One day has passed, checking docker for updates")
170
+ upgrade = True
171
+ else:
172
+ upgrade = self.auto_updates
173
+ self.docker.validate_or_download_image(
174
+ image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
175
+ )
176
+ DISK_CACHE.put("last-update", now_str)
177
+
178
+ print("Docker image now validated")
179
+ port = SERVER_PORT
180
+ if self.interactive:
181
+ server_command = ["/bin/bash"]
182
+ else:
183
+ server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
184
+ ports = {80: port}
185
+ volumes = None
186
+ if self.fastled_src_dir:
187
+ print(
188
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
189
+ )
190
+ volumes = {
191
+ str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
192
+ }
193
+ if self.interactive:
194
+ # add the mapped directory to the container
195
+ print(f"Mounting {self.mapped_dir} into container /mapped")
196
+ # volumes = {str(self.mapped_dir): {"bind": "/mapped", "mode": "rw"}}
197
+ # add it
198
+ assert self.mapped_dir is not None
199
+ dir_name = self.mapped_dir.name
200
+ if not volumes:
201
+ volumes = {}
202
+ volumes[str(self.mapped_dir)] = {
203
+ "bind": f"/mapped/{dir_name}",
204
+ "mode": "rw",
205
+ }
206
+
207
+ cmd_str = subprocess.list2cmdline(server_command)
208
+ if not self.interactive:
209
+ container: Container = self.docker.run_container_detached(
210
+ image_name=_IMAGE_NAME,
211
+ tag="main",
212
+ container_name=self.container_name,
213
+ command=cmd_str,
214
+ ports=ports,
215
+ volumes=volumes,
216
+ remove_previous=self.interactive,
217
+ )
218
+ self.running_container = self.docker.attach_and_run(container)
219
+ assert self.running_container is not None, "Container should be running"
220
+ print("Compile server starting")
221
+ return port
222
+ else:
223
+ self.docker.run_container_interactive(
224
+ image_name=_IMAGE_NAME,
225
+ tag="main",
226
+ container_name=self.container_name,
227
+ command=cmd_str,
228
+ ports=ports,
229
+ volumes=volumes,
230
+ )
231
+
232
+ print("Exiting interactive mode")
233
+ return port
234
+
235
+ def process_running(self) -> bool:
236
+ return self.docker.is_container_running(self.container_name)
237
+
238
+ def stop(self) -> None:
239
+ if self.running_container:
240
+ self.running_container.detach()
241
+ self.running_container = None
242
+ self.docker.suspend_container(self.container_name)
243
+ self._port = 0
244
+ print("Compile server stopped")
fastled/docker_manager.py CHANGED
@@ -89,7 +89,7 @@ class RunningContainer:
89
89
  print(f"Error monitoring logs: {e}")
90
90
  break
91
91
 
92
- def stop(self) -> None:
92
+ def detach(self) -> None:
93
93
  """Stop monitoring the container logs"""
94
94
  self.running = False
95
95
  self.thread.join()
fastled/parse_args.py CHANGED
@@ -5,9 +5,9 @@ from pathlib import Path
5
5
 
6
6
  from fastled import __version__
7
7
  from fastled.docker_manager import DockerManager
8
- from fastled.env import DEFAULT_URL
9
8
  from fastled.project_init import project_init
10
9
  from fastled.select_sketch_directory import select_sketch_directory
10
+ from fastled.settings import DEFAULT_URL
11
11
  from fastled.sketch import (
12
12
  find_sketch_directories,
13
13
  looks_like_fastled_repo,
@@ -150,6 +150,7 @@ def parse_args() -> argparse.Namespace:
150
150
  if looks_like_sketch_directory(maybe_sketch_dir):
151
151
  args.directory = str(maybe_sketch_dir)
152
152
  else:
153
+ print("Searching for sketch directories...")
153
154
  sketch_directories = find_sketch_directories(maybe_sketch_dir)
154
155
  selected_dir = select_sketch_directory(
155
156
  sketch_directories, cwd_is_fastled
fastled/project_init.py CHANGED
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
 
4
4
  import httpx
5
5
 
6
- from fastled.env import DEFAULT_URL
6
+ from fastled.settings import DEFAULT_URL
7
7
 
8
8
  ENDPOINT_PROJECT_INIT = f"{DEFAULT_URL}/project/init"
9
9
  ENDPOINT_INFO = f"{DEFAULT_URL}/info"
@@ -33,13 +33,17 @@ def _prompt_for_example() -> str:
33
33
  return answer
34
34
 
35
35
 
36
- def project_init(example: str | None = None, outputdir: Path | None = None) -> Path:
36
+ def project_init(
37
+ example: str | None = "PROMPT", # prompt for example
38
+ outputdir: Path | None = None,
39
+ host: str | None = None,
40
+ ) -> Path:
37
41
  """
38
42
  Initialize a new FastLED project.
39
43
  """
40
-
44
+ host = host or DEFAULT_URL
41
45
  outputdir = outputdir or Path("fastled")
42
- if example is None:
46
+ if example == "PROMPT" or example is None:
43
47
  try:
44
48
  example = _prompt_for_example()
45
49
  except httpx.HTTPStatusError:
@@ -48,7 +52,8 @@ def project_init(example: str | None = None, outputdir: Path | None = None) -> P
48
52
  )
49
53
  example = DEFAULT_EXAMPLE
50
54
  assert example is not None
51
- response = httpx.get(f"{ENDPOINT_PROJECT_INIT}/{example}", timeout=20)
55
+ endpoint_url = f"{host}/project/init/{example}"
56
+ response = httpx.get(endpoint_url, timeout=20)
52
57
  response.raise_for_status()
53
58
  content = response.content
54
59
  tmpzip = outputdir / "fastled.zip"
@@ -6,3 +6,4 @@ IS_ARM: bool = "arm" in MACHINE or "aarch64" in MACHINE
6
6
  PLATFORM_TAG: str = "-arm64" if IS_ARM else ""
7
7
  CONTAINER_NAME = f"fastled-wasm-compiler{PLATFORM_TAG}"
8
8
  DEFAULT_URL = str(os.environ.get("FASTLED_URL", "https://fastled.onrender.com"))
9
+ SERVER_PORT = 9021
fastled/sketch.py CHANGED
@@ -1,8 +1,11 @@
1
1
  import os
2
2
  from pathlib import Path
3
3
 
4
+ _MAX_FILES_SEARCH_LIMIT = 10000
5
+
4
6
 
5
7
  def find_sketch_directories(directory: Path) -> list[Path]:
8
+ file_count = 0
6
9
  sketch_directories: list[Path] = []
7
10
  # search all the paths one level deep
8
11
  for path in directory.iterdir():
@@ -10,6 +13,12 @@ def find_sketch_directories(directory: Path) -> list[Path]:
10
13
  dir_name = path.name
11
14
  if str(dir_name).startswith("."):
12
15
  continue
16
+ file_count += 1
17
+ if file_count > _MAX_FILES_SEARCH_LIMIT:
18
+ print(
19
+ f"More than {_MAX_FILES_SEARCH_LIMIT} files found. Stopping search."
20
+ )
21
+ break
13
22
 
14
23
  if looks_like_sketch_directory(path, quick=True):
15
24
  sketch_directories.append(path)
@@ -24,6 +33,7 @@ def find_sketch_directories(directory: Path) -> list[Path]:
24
33
 
25
34
 
26
35
  def get_sketch_files(directory: Path) -> list[Path]:
36
+ file_count = 0
27
37
  files: list[Path] = []
28
38
  for root, dirs, filenames in os.walk(directory):
29
39
  # ignore hidden directories
@@ -32,10 +42,21 @@ def get_sketch_files(directory: Path) -> list[Path]:
32
42
  dirs[:] = [d for d in dirs if "fastled_js" not in d]
33
43
  # ignore hidden files
34
44
  filenames = [f for f in filenames if not f.startswith(".")]
45
+ outer_break = False
35
46
  for filename in filenames:
36
47
  if "platformio.ini" in filename:
37
48
  continue
49
+ file_count += 1
50
+ if file_count > _MAX_FILES_SEARCH_LIMIT:
51
+ print(
52
+ f"More than {_MAX_FILES_SEARCH_LIMIT} files found. Stopping search."
53
+ )
54
+ outer_break = True
55
+ break
38
56
  files.append(Path(root) / filename)
57
+ if outer_break:
58
+ break
59
+
39
60
  return files
40
61
 
41
62
 
@@ -0,0 +1,16 @@
1
+ from fastled import WebCompileResult
2
+
3
+
4
+ def test_examples(
5
+ examples: list[str] | None = None, host: str | None = None
6
+ ) -> Exception | None:
7
+ """Test the examples in the given directory."""
8
+ from fastled import Api
9
+
10
+ examples = Api.get_examples() if examples is None else examples
11
+ for example in examples:
12
+ print(f"Compiling example: {example}")
13
+ out: WebCompileResult = Api.web_compile(example, host=host)
14
+ if not out.success:
15
+ return Exception(out.stdout)
16
+ return None
fastled/types.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Any
2
3
 
3
4
 
4
5
  @dataclass
@@ -8,3 +9,17 @@ class CompiledResult:
8
9
  success: bool
9
10
  fastled_js: str
10
11
  hash_value: str | None
12
+
13
+
14
+ @dataclass
15
+ class WebCompileResult:
16
+ success: bool
17
+ stdout: str
18
+ hash_value: str | None
19
+ zip_bytes: bytes
20
+
21
+ def __bool__(self) -> bool:
22
+ return self.success
23
+
24
+ def to_dict(self) -> dict[str, Any]:
25
+ return self.__dict__.copy()
fastled/web_compile.py CHANGED
@@ -12,8 +12,9 @@ from pathlib import Path
12
12
  import httpx
13
13
 
14
14
  from fastled.build_mode import BuildMode
15
- from fastled.compile_server import SERVER_PORT
15
+ from fastled.settings import SERVER_PORT
16
16
  from fastled.sketch import get_sketch_files
17
+ from fastled.types import WebCompileResult
17
18
  from fastled.util import hash_file
18
19
 
19
20
  DEFAULT_HOST = "https://fastled.onrender.com"
@@ -31,17 +32,6 @@ class ConnectionResult:
31
32
  ipv4: bool
32
33
 
33
34
 
34
- @dataclass
35
- class WebCompileResult:
36
- success: bool
37
- stdout: str
38
- hash_value: str | None
39
- zip_bytes: bytes
40
-
41
- def __bool__(self) -> bool:
42
- return self.success
43
-
44
-
45
35
  def _sanitize_host(host: str) -> str:
46
36
  if host.startswith("http"):
47
37
  return host
@@ -165,12 +155,14 @@ def find_good_connection(
165
155
 
166
156
 
167
157
  def web_compile(
168
- directory: Path,
158
+ directory: Path | str,
169
159
  host: str | None = None,
170
160
  auth_token: str | None = None,
171
161
  build_mode: BuildMode | None = None,
172
162
  profile: bool = False,
173
163
  ) -> WebCompileResult:
164
+ if isinstance(directory, str):
165
+ directory = Path(directory)
174
166
  host = _sanitize_host(host or DEFAULT_HOST)
175
167
  build_mode = build_mode or BuildMode.QUICK
176
168
  print("Compiling on", host)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.34
3
+ Version: 1.1.36
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -163,6 +163,8 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` is
163
163
 
164
164
  # Revisions
165
165
 
166
+ * 1.1.36 - We now have an api. `from fastled import Api` and `from fastled import Test` for testing.
167
+ * 1.1.35 - When searching for files cap the limit at a high amount to prevent hang.
166
168
  * 1.1.34 - On windows check to make sure we are in linux container mode, if not try to switch and if that fails then use `--web` compiler.
167
169
  * 1.1.33 - Auto updating frequency has been reduced from one hour to one day. To update immediatly use `--update`.
168
170
  * 1.1.32 - `--init` now asks for which example you want, then tells you where the example was downloaded to. No longer auto-compiles.
@@ -0,0 +1,30 @@
1
+ fastled/__init__.py,sha256=CAu_kGMgCncOxL47XeHzp8Ahj-O2Le86k5XwknbHYbA,1840
2
+ fastled/app.py,sha256=3xg7oVD-UYnKPU8SAY-Cs5UnAYdwpdpuEFRR2N8P1Tg,1787
3
+ fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
4
+ fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
5
+ fastled/client_server.py,sha256=rre250O1uCx6JFi_nn6fhKUncgKOw3F7BgN9O9wbCEM,11440
6
+ fastled/compile_server.py,sha256=QxkXLpj9q7BWhPbS2NPeFF4WRne-358o-8fg4VEDH1o,2427
7
+ fastled/compile_server_impl.py,sha256=cB5WZ9znNhCFe2HKl-p1ZonfYKeaJOUyV3uHfkQtTZk,8481
8
+ fastled/docker_manager.py,sha256=zBCFGk2P3_bS7_SUQ5j2lpsOS3RvIzXYkrJXC6xP69k,25383
9
+ fastled/filewatcher.py,sha256=LwEQJkqADsArZyY499RLAer6JjJyDwaQBcAvT7xmp3c,6708
10
+ fastled/keyboard.py,sha256=Zz_ggxOUTX2XQEy6K6kAoorVlUev4wEk9Awpvv9aStA,3241
11
+ fastled/open_browser.py,sha256=vzMBcpDNY0f-Bx9KmEILKDANZ6gvsywCVwn1FRhPXh4,1770
12
+ fastled/parse_args.py,sha256=37WsELphNEqGQgmjppZx6uMWE2E-dZ58zCKUl-3mr3Q,6150
13
+ fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
14
+ fastled/project_init.py,sha256=5Wy-HONa81Ws0KHyUTzjkYWAXbDm1y7X58whKJbV5tc,2084
15
+ fastled/select_sketch_directory.py,sha256=TZdCjl1D7YMKjodMTvDRurPcpAmN3x0TcJxffER2NfM,1314
16
+ fastled/settings.py,sha256=3eMKv0tLXgIQ0CFDboIp_l5_71rzIIyWg353YjnYJnc,323
17
+ fastled/sketch.py,sha256=483TrrIdZJfo1MIu5FkD-V5OGmOfHmsZ2f6VvNsJBJM,3299
18
+ fastled/spinner.py,sha256=VHxmvB92P0Z_zYxRajb5HiNmkHHvZ5dG7hKtZltzpcs,867
19
+ fastled/string_diff.py,sha256=UR1oRhg9lsPzAG4bn_MwJMCn0evP5AigkBiwLiI9fgA,1354
20
+ fastled/types.py,sha256=4uSSuOUhZptTreor6CXXGSk-FIMOdwHfdvFI-4Yx6AY,475
21
+ fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
22
+ fastled/web_compile.py,sha256=0jXFeej-YhOxcjIau8Ru8qEu-ULlsFjaufbqKYUUXjQ,10312
23
+ fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
24
+ fastled/test/examples.py,sha256=kUM1aDK-cD9WhbFzNPMXt_-LYqHHVgV2rLiEnzLpFW0,522
25
+ fastled-1.1.36.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
26
+ fastled-1.1.36.dist-info/METADATA,sha256=2OuHb_W1FUx8PRj91B2cqABJ3UqU0TkABsr_21nwaak,15254
27
+ fastled-1.1.36.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
28
+ fastled-1.1.36.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
29
+ fastled-1.1.36.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
30
+ fastled-1.1.36.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- fastled/__init__.py,sha256=TRIZQh3TjPKDDHL3ZVGeuIOaPljhjQmkhKhLO6W6U4k,61
2
- fastled/app.py,sha256=Zw1RS72yyIZSDt1_08MDSzfY4YwLWzarwXM4nvy8GE4,1787
3
- fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
4
- fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
5
- fastled/client_server.py,sha256=IK_u71XbmOAKBX1ovHATsCmfUON8VVJJu-XtObTuYJI,11448
6
- fastled/compile_server.py,sha256=QnvlomTbFG2OmFj4ChuOXa0yBhhOvR8Bp1iIFsD3hbU,6717
7
- fastled/docker_manager.py,sha256=x_lMreCkcY4ywLqU0pedTug80eo578ssaGUPystoDIs,25381
8
- fastled/env.py,sha256=8wctQwl5qE4CI8NBugHtgMmUfEfHZ869JX5lGdSOJxc,304
9
- fastled/filewatcher.py,sha256=LwEQJkqADsArZyY499RLAer6JjJyDwaQBcAvT7xmp3c,6708
10
- fastled/keyboard.py,sha256=Zz_ggxOUTX2XQEy6K6kAoorVlUev4wEk9Awpvv9aStA,3241
11
- fastled/open_browser.py,sha256=vzMBcpDNY0f-Bx9KmEILKDANZ6gvsywCVwn1FRhPXh4,1770
12
- fastled/parse_args.py,sha256=OZkVjRNlparNYR0F9Ic4ud2B0pMSn4fnmVwUY1Va89o,6084
13
- fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
14
- fastled/project_init.py,sha256=ndQICZ9rBtfEPOnucZ3cgRE67PUKc66CJYO4Tkwmt_M,1932
15
- fastled/select_sketch_directory.py,sha256=TZdCjl1D7YMKjodMTvDRurPcpAmN3x0TcJxffER2NfM,1314
16
- fastled/sketch.py,sha256=5nRjg281lMH8Bo9wKjbcpTQCfEP574ZCG-lukvFmyQ8,2656
17
- fastled/spinner.py,sha256=VHxmvB92P0Z_zYxRajb5HiNmkHHvZ5dG7hKtZltzpcs,867
18
- fastled/string_diff.py,sha256=UR1oRhg9lsPzAG4bn_MwJMCn0evP5AigkBiwLiI9fgA,1354
19
- fastled/types.py,sha256=dDIsGHJkHNJ7B61wNp6X0JSLs_nrHiq7RlNqNWbwFec,194
20
- fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
21
- fastled/web_compile.py,sha256=KuvKGdX6SSUUqC7YgX4T9SMSP5wdcPUhpg9-K9zPoTI,10378
22
- fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
23
- fastled-1.1.34.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
24
- fastled-1.1.34.dist-info/METADATA,sha256=G3dvar486uKa16kYTZvDdD75n6MtqybcAh3g_H_d4E0,15063
25
- fastled-1.1.34.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
26
- fastled-1.1.34.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
27
- fastled-1.1.34.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
28
- fastled-1.1.34.dist-info/RECORD,,