fastled 1.2.55__py3-none-any.whl → 1.2.57__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
@@ -13,7 +13,7 @@ from .types import BuildMode, CompileResult, CompileServerError
13
13
  # IMPORTANT! There's a bug in github which will REJECT any version update
14
14
  # that has any other change in the repo. Please bump the version as the
15
15
  # ONLY change in a commit, or else the pypi update and the release will fail.
16
- __version__ = "1.2.55"
16
+ __version__ = "1.2.57"
17
17
 
18
18
  DOCKER_FILE = (
19
19
  "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlxbWcpUXPpjqs
3
+ DJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcHS+pkgXnG46g6bUcL/AK5Ba08
4
+ vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh2jSCeOTagNoaHLYEugARkkEu
5
+ 0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU7K+BLWsCiZ308NMpzHF5APZ6
6
+ YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXWyyPG0jwPQyjBot/cNtt8GrsN
7
+ gcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jprHEsw+/UhnG6Qhksm8C/DN9kP
8
+ hselewffAgMBAAECggEARjQ6YTo+Mkvf8WGGbRjLxteJRiBX7lKOD+V7aY2ce06P
9
+ 21LREbbTCm+vljXZN2OnqvJomsjNLCsH21+jaTOIZg5x79LyDn2Au7N8CWdELwVT
10
+ mTbBO2Ql63P4R0UY54onGYNcOeV6z+OX9u7a8L/qYHCxFdHalBZpsfj0gjaQeStJ
11
+ JSnvGjo6tKkwC/nUmX01qEVQgUO1+39WYqCaIWjijZNXt6XiKclEuu1AkL0u6Mpt
12
+ CzvzEDrEA66D0Lvl3Tek9B4O16Oie5anNnNMHigwU9yVc6dI8vDCRSEiz7laPTFK
13
+ xzOCQmqPGClKXkX3U+OvZp/Ss9U26Wpu0AbRKTvzAQKBgQDsMR9NjMpOmUaWkAwl
14
+ 1wlUxsZ9YkRuTy7R3RfIdYWj6Lcoc4/iN0qILFM7xidkHhYTFqnsnP1SQkV6lEHV
15
+ OalYxZu9F2l1rHPc8G5YWh/KOg1rAEI47MVT4iwhA2fw6JLti/rm25AeSTMjSTqu
16
+ ht3146036opcIF3v86oGUrSXDwKBgQD5CsNcwLeUDGXozjq62T8/mTYwd2Tw3aiY
17
+ KaGp+exAW321vYm5SKsMitBMGU2tGFlv5eptSI48h7SCpgnszaexw7yj30KuvqjG
18
+ bBqq/MsKuXHyn2sG0A7MJ6zfk+4l46B45blDJZ+x7xL0dyS4UCU3zUeesgSGo4zK
19
+ ZOspPIQCMQKBgQCk35VuWP1P6IbxyxPvxi/pUeh01gfWyMdyD9fuQrtLM8PHJQQn
20
+ cVlBvU9MxoHwzV+za3qqhNwAc+p0KtHZuiqQoUCZuqIPVpZ6gAtG+YJ/dA6xxrhz
21
+ bDRC3frYALyp2m/WCoTWaiYsPgTIePHRqqt+XbQo+DwlGyL3wSvKxijx2QKBgCb0
22
+ OwioEE70/X/DukX9szn0chh0pHJUiYl7gZD/yadraCdkRUWZC0BD+j7c+lxn4Z1y
23
+ HhAH+E+Zfm+tHwJOTLuufTQ4uMpygh2/TRCPyAaeaSdlLi17n8TpM84o6mg8yZ3/
24
+ eNH68Za4aYOZm0HFL30h++DjwXd534zM6keh8pgRAoGBAKUrsjDGjuSo8l1fi4Cq
25
+ INu/rQop2h/db02zyJP5q7NKhE1nqogeLwwn+2M/LtHQ1nIzZR+rvrLNgt6oWY31
26
+ sPsv8JUfVT8GmdhU9KKmizK6eUu3rWdj2+rJARmuEaPmHcD5O6oJaGU0qadqQP34
27
+ H+enwWmpyZXAIbEu/q63DFhV
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEfTCCAuWgAwIBAgIRAPb7jkLrCuqToG+s3AQYeuUwDQYJKoZIhvcNAQELBQAw
3
+ gakxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE/MD0GA1UECww2REVT
4
+ S1RPUC1JMzcxOERPXFphY2ggVm9yaGllc0BERVNLVE9QLUkzNzE4RE8gKG5pdGVy
5
+ aXMpMUYwRAYDVQQDDD1ta2NlcnQgREVTS1RPUC1JMzcxOERPXFphY2ggVm9yaGll
6
+ c0BERVNLVE9QLUkzNzE4RE8gKG5pdGVyaXMpMB4XDTI1MDQyODAwMzk1MFoXDTI3
7
+ MDcyODAwMzk1MFowajEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
8
+ ZmljYXRlMT8wPQYDVQQLDDZERVNLVE9QLUkzNzE4RE9cWmFjaCBWb3JoaWVzQERF
9
+ U0tUT1AtSTM3MThETyAobml0ZXJpcykwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
10
+ ggEKAoIBAQDlxbWcpUXPpjqsDJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcH
11
+ S+pkgXnG46g6bUcL/AK5Ba08vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh
12
+ 2jSCeOTagNoaHLYEugARkkEu0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU
13
+ 7K+BLWsCiZ308NMpzHF5APZ6YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXW
14
+ yyPG0jwPQyjBot/cNtt8GrsNgcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jpr
15
+ HEsw+/UhnG6Qhksm8C/DN9kPhselewffAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIF
16
+ oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSPBydvhr9wI+FsoW/H
17
+ WK3DbS8IUDAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGB
18
+ AJVrF1yczZaxt+A2AhdeFbJQUR6NzGBTc20YeWF1YzLV5sV3QVumwZLP2M9ggRgd
19
+ xWV0xfwUHobFQk6RIPTADcLKctiurql0cgF4DPnpWVvto9RM00U3AkQcMj3xtKBV
20
+ wUqo83TcbqgL+euudFZ09gGTs9u9AENaZPcMh+rW8DDO92t+EwMI/IfopxVOJGUB
21
+ RSM3yTwV93BMYBuddt8mclzLzPK/1WONfsHU2xEascaHR1tYMOmJN9Vq4o0fzWxo
22
+ a2vI6K0aJqZV/ztdXq3akwLc6/e9hyptHWa0i/022xVCyNWIlnuEhT7ENMPxh6rX
23
+ ZCQCZVnhcSWAyFjggLJql3aSID5fPF8rmN7wWsB/I5pl9qwMR1/THMPrm5aWn1Xj
24
+ xW6PxkSGm73kd57DH7tqm5HTd8eYCbnsFofI9rC7xI6HCfwchKp+YHvIEu/LJ56E
25
+ FLnCZW/orYkHCzWntzxv1bddrw1BwaNR8Q+mu3imRP8fuyXb2UkFkINVVyOOWHuW
26
+ Kw==
27
+ -----END CERTIFICATE-----
fastled/filewatcher.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """File system watcher implementation using watchdog"""
2
2
 
3
+ import _thread
3
4
  import hashlib
4
5
  import os
5
6
  import queue
@@ -229,34 +230,11 @@ class FileWatcherProcess:
229
230
  return changed_files
230
231
 
231
232
 
232
- # DEBOUNCE_SECONDS = 4
233
- # LAST_TIME = 0.0
234
- # WATCHED_FILES: list[str] = []
235
-
236
- # def debounced_sketch_filewatcher_get_all_changes() -> list[str]:
237
- # nonlocal DEBOUNCE_SECONDS
238
- # nonlocal LAST_TIME
239
- # nonlocal WATCHED_FILES
240
- # current_time = time.time()
241
- # new_files = sketch_filewatcher.get_all_changes()
242
- # if new_files:
243
- # WATCHED_FILES.extend(new_files)
244
- # print(f"Changes detected in {new_files}")
245
- # LAST_TIME = current_time
246
- # return []
247
- # diff = current_time - LAST_TIME
248
- # if diff > DEBOUNCE_SECONDS and len(WATCHED_FILES) > 0:
249
- # LAST_TIME = current_time
250
- # WATCHED_FILES, changed_files = [], WATCHED_FILES
251
- # changed_files = sorted(list(set(changed_files)))
252
- # return changed_files
253
- # return []
254
-
255
-
256
- class DebouncedFileWatcherProcess:
233
+ class DebouncedFileWatcherProcess(threading.Thread):
257
234
  """
258
235
  Wraps a FileWatcherProcess to batch rapid-fire change events
259
236
  and only emit them once the debounce interval has passed.
237
+ Runs in its own thread, polling every 0.1 s.
260
238
  """
261
239
 
262
240
  def __init__(
@@ -264,35 +242,62 @@ class DebouncedFileWatcherProcess:
264
242
  watcher: FileWatcherProcess,
265
243
  debounce_seconds: float = FILE_CHANGED_DEBOUNCE_SECONDS,
266
244
  ) -> None:
245
+ super().__init__(daemon=True)
267
246
  self.watcher = watcher
268
247
  self.debounce_seconds = debounce_seconds
269
- self._last_time = 0.0
270
- self._watched_files: list[str] = []
248
+
249
+ # internal buffer & timing
250
+ self._buffer: list[str] = []
251
+ self._last_time: float = 0.0
252
+
253
+ # queue of flushed batches
254
+ self._out_queue: queue.Queue[list[str]] = queue.Queue()
255
+ self._stop_event = threading.Event()
256
+
257
+ # record timestamp of last event for external inspection
258
+ self.last_event_time: float | None = None
259
+
260
+ self.start()
261
+
262
+ def run(self) -> None:
263
+ try:
264
+ while not self._stop_event.is_set():
265
+ now = time.time()
266
+ # non-blocking poll of raw watcher events
267
+ new = self.watcher.get_all_changes(timeout=0)
268
+ if new:
269
+ self._buffer.extend(new)
270
+ self._last_time = now
271
+ self.last_event_time = now
272
+
273
+ # if buffer exists and debounce interval elapsed, flush it
274
+ if self._buffer and (now - self._last_time) > self.debounce_seconds:
275
+ batch = sorted(set(self._buffer))
276
+ self._buffer.clear()
277
+ self._last_time = now
278
+ self._out_queue.put(batch)
279
+
280
+ time.sleep(0.1)
281
+ except KeyboardInterrupt:
282
+ _thread.interrupt_main()
283
+ raise
271
284
 
272
285
  def get_all_changes(self, timeout: float | None = None) -> list[str]:
273
286
  """
274
- Polls the underlying watcher for raw events, accumulates them,
275
- and once no new events arrive for `debounce_seconds`, flushes
276
- a sorted, unique list of paths.
287
+ Return the next debounced batch of paths, or [] if none arrives
288
+ within `timeout` seconds.
277
289
  """
278
- now = time.time()
279
- # pull in any new raw events
280
- new = self.watcher.get_all_changes(timeout=timeout)
281
- if new:
282
- self._watched_files.extend(new)
283
- # reset the window
284
- self._last_time = now
290
+ try:
291
+ if timeout is None:
292
+ # non-blocking
293
+ return self._out_queue.get_nowait()
294
+ else:
295
+ return self._out_queue.get(timeout=timeout)
296
+ except queue.Empty:
285
297
  return []
286
298
 
287
- # if the window has elapsed, flush
288
- if self._watched_files and (now - self._last_time) > self.debounce_seconds:
289
- batch = sorted(set(self._watched_files))
290
- self._watched_files.clear()
291
- self._last_time = now
292
- return batch
293
-
294
- return []
295
-
296
299
  def stop(self) -> None:
297
- """Tear down the underlying watcher process."""
300
+ """Stop the polling thread and tear down the underlying watcher."""
301
+ self._stop_event.set()
302
+ self.join()
298
303
  self.watcher.stop()
fastled/keyz.py ADDED
@@ -0,0 +1,30 @@
1
+ import importlib.resources as pkg_resources
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+
6
+ @dataclass
7
+ class SslConfig:
8
+ certfile: Path
9
+ keyfile: Path
10
+
11
+
12
+ def get_asset_path(filename: str) -> Path | None:
13
+ """Locate a file from the fastled.assets package resources."""
14
+ try:
15
+ resource = pkg_resources.files("fastled.assets").joinpath(filename)
16
+ # Convert to Path for file-system access
17
+ path = Path(str(resource))
18
+ return path if path.exists() else None
19
+ except (ModuleNotFoundError, AttributeError):
20
+ return None
21
+
22
+
23
+ def get_ssl_config() -> SslConfig:
24
+ """Get the keys for the server"""
25
+ certfile = get_asset_path("localhost-key.pem")
26
+ keyfile = get_asset_path("localhost.pem")
27
+ if certfile is None or keyfile is None:
28
+ raise ValueError("Could not find keys for server")
29
+ # return certfile, keyfile
30
+ return SslConfig(certfile=certfile, keyfile=keyfile)
fastled/open_browser.py CHANGED
@@ -5,30 +5,23 @@ import webbrowser
5
5
  from multiprocessing import Process
6
6
  from pathlib import Path
7
7
 
8
- DEFAULT_PORT = 8089 # different than live version.
8
+ from fastled.keyz import get_ssl_config
9
9
 
10
+ DEFAULT_PORT = 8089 # different than live version.
10
11
  PYTHON_EXE = sys.executable
12
+ SSL_CONFIG = get_ssl_config()
13
+
14
+
15
+ # print(f"SSL Config: {SSL_CONFIG.certfile}, {SSL_CONFIG.keyfile}")
11
16
 
12
17
 
13
18
  def open_http_server_subprocess(
14
- fastled_js: Path, port: int, open_browser: bool
19
+ fastled_js: Path,
20
+ port: int,
21
+ open_browser: bool,
15
22
  ) -> None:
16
- """Start livereload server in the fastled_js directory and return the process"""
17
- import shutil
18
-
19
23
  try:
20
- if shutil.which("live-server") is not None:
21
- cmd = [
22
- "live-server",
23
- f"--port={port}",
24
- "--host=localhost",
25
- ".",
26
- ]
27
- if not open_browser:
28
- cmd.append("--no-browser")
29
- subprocess.run(cmd, shell=True, cwd=fastled_js)
30
- return
31
-
24
+ # Fallback to our Python server
32
25
  cmd = [
33
26
  PYTHON_EXE,
34
27
  "-m",
@@ -37,8 +30,23 @@ def open_http_server_subprocess(
37
30
  "--port",
38
31
  str(port),
39
32
  ]
40
- # return subprocess.Popen(cmd) # type ignore
41
- # pipe stderr and stdout to null
33
+ if open_browser:
34
+ cmd.append("--open-browser")
35
+ # Pass SSL flags if available
36
+ if SSL_CONFIG.certfile and SSL_CONFIG.keyfile:
37
+ cmd.extend(
38
+ [
39
+ "--cert",
40
+ str(SSL_CONFIG.certfile),
41
+ "--key",
42
+ str(SSL_CONFIG.keyfile),
43
+ ]
44
+ )
45
+ print(
46
+ f"Running server on port {port} with certs: {SSL_CONFIG.certfile}, {SSL_CONFIG.keyfile}"
47
+ )
48
+ print(f"Command: {subprocess.list2cmdline(cmd)}")
49
+ # Suppress output
42
50
  subprocess.run(
43
51
  cmd,
44
52
  stdout=subprocess.DEVNULL,
@@ -87,54 +95,34 @@ def wait_for_server(port: int, timeout: int = 10) -> None:
87
95
  raise TimeoutError("Could not connect to server")
88
96
 
89
97
 
90
- def _background_npm_install_live_server() -> None:
91
- import shutil
92
- import time
93
-
94
- if shutil.which("npm") is None:
95
- return
96
-
97
- if shutil.which("live-server") is not None:
98
- return
99
-
100
- time.sleep(3)
101
- subprocess.run(
102
- ["npm", "install", "-g", "live-server"],
103
- stdout=subprocess.DEVNULL,
104
- stderr=subprocess.DEVNULL,
105
- )
106
-
107
-
108
98
  def open_browser_process(
109
- fastled_js: Path, port: int | None = None, open_browser: bool = True
99
+ fastled_js: Path,
100
+ port: int | None = None,
101
+ open_browser: bool = True,
110
102
  ) -> Process:
111
- import shutil
112
103
 
113
- """Start livereload server in the fastled_js directory and return the process"""
114
- if port is not None:
115
- if not is_port_free(port):
116
- raise ValueError(f"Port {port} was specified but in use")
117
- else:
104
+ if port is not None and not is_port_free(port):
105
+ raise ValueError(f"Port {port} was specified but in use")
106
+ if port is None:
118
107
  port = find_free_port(DEFAULT_PORT)
119
- out: Process = Process(
108
+
109
+ proc = Process(
120
110
  target=open_http_server_subprocess,
121
- args=(fastled_js, port, False),
111
+ args=(fastled_js, port, open_browser),
122
112
  daemon=True,
123
113
  )
124
- out.start()
114
+ proc.start()
125
115
  wait_for_server(port)
126
116
  if open_browser:
127
- print(f"Opening browser to http://localhost:{port}")
128
- webbrowser.open(url=f"http://localhost:{port}", new=1, autoraise=True)
129
-
130
- # start a deamon thread to install live-server
131
- if shutil.which("live-server") is None:
132
- import threading
133
-
134
- t = threading.Thread(target=_background_npm_install_live_server)
135
- t.daemon = True
136
- t.start()
137
- return out
117
+ print(
118
+ f"Opening browser to http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}"
119
+ )
120
+ webbrowser.open(
121
+ url=f"http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}",
122
+ new=1,
123
+ autoraise=True,
124
+ )
125
+ return proc
138
126
 
139
127
 
140
128
  if __name__ == "__main__":
@@ -144,9 +132,7 @@ if __name__ == "__main__":
144
132
  description="Open a browser to the fastled_js directory"
145
133
  )
146
134
  parser.add_argument(
147
- "fastled_js",
148
- type=Path,
149
- help="Path to the fastled_js directory",
135
+ "fastled_js", type=Path, help="Path to the fastled_js directory"
150
136
  )
151
137
  parser.add_argument(
152
138
  "--port",
fastled/open_browser2.py CHANGED
@@ -1,90 +1,56 @@
1
1
  import argparse
2
+ import importlib.resources as pkg_resources
2
3
  from pathlib import Path
3
4
 
4
- from livereload import Server
5
+ from .server_fastapi_cli import run_fastapi_server_proces
5
6
 
6
7
 
7
- def _run_flask_server(fastled_js: Path, port: int) -> None:
8
- """Run Flask server with live reload in a subprocess"""
8
+ def get_asset_path(filename: str) -> Path | None:
9
+ """Locate a file from the fastled.assets package resources."""
9
10
  try:
10
- from flask import Flask, send_from_directory
11
-
12
- app = Flask(__name__)
13
-
14
- # Must be a full path or flask will fail to find the file.
15
- fastled_js = fastled_js.resolve()
16
-
17
- @app.route("/")
18
- def serve_index():
19
- return send_from_directory(fastled_js, "index.html")
20
-
21
- @app.route("/<path:path>")
22
- def serve_files(path):
23
- response = send_from_directory(fastled_js, path)
24
- # Some servers don't set the Content-Type header for a bunch of files.
25
- if path.endswith(".js"):
26
- response.headers["Content-Type"] = "application/javascript"
27
- if path.endswith(".css"):
28
- response.headers["Content-Type"] = "text/css"
29
- if path.endswith(".wasm"):
30
- response.headers["Content-Type"] = "application/wasm"
31
- if path.endswith(".json"):
32
- response.headers["Content-Type"] = "application/json"
33
- if path.endswith(".png"):
34
- response.headers["Content-Type"] = "image/png"
35
- if path.endswith(".jpg"):
36
- response.headers["Content-Type"] = "image/jpeg"
37
- if path.endswith(".jpeg"):
38
- response.headers["Content-Type"] = "image/jpeg"
39
- if path.endswith(".gif"):
40
- response.headers["Content-Type"] = "image/gif"
41
- if path.endswith(".svg"):
42
- response.headers["Content-Type"] = "image/svg+xml"
43
- if path.endswith(".ico"):
44
- response.headers["Content-Type"] = "image/x-icon"
45
- if path.endswith(".html"):
46
- response.headers["Content-Type"] = "text/html"
47
-
48
- # now also add headers to force no caching
49
- response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
50
- response.headers["Pragma"] = "no-cache"
51
- response.headers["Expires"] = "0"
52
- return response
53
-
54
- server = Server(app.wsgi_app)
55
- # Watch index.html for changes
56
- server.watch(str(fastled_js / "index.html"))
57
- # server.watch(str(fastled_js / "index.js"))
58
- # server.watch(str(fastled_js / "index.css"))
59
- # Start the server
60
- server.serve(port=port, debug=True)
61
- except KeyboardInterrupt:
62
- import _thread
63
-
64
- _thread.interrupt_main()
65
- except Exception as e:
66
- print(f"Failed to run Flask server: {e}")
67
- import _thread
68
-
69
- _thread.interrupt_main()
70
-
71
-
72
- def run(path: Path, port: int) -> None:
73
- """Run the Flask server."""
11
+ resource = pkg_resources.files("fastled.assets").joinpath(filename)
12
+ # Convert to Path for file-system access
13
+ path = Path(str(resource))
14
+ return path if path.exists() else None
15
+ except (ModuleNotFoundError, AttributeError):
16
+ return None
17
+
18
+
19
+ def _open_browser(url: str) -> None:
20
+ import webview
21
+
22
+ webview.create_window("FastLED", url, fullscreen=True)
23
+ webview.start()
24
+
25
+
26
+ def run(
27
+ path: Path,
28
+ port: int,
29
+ open_browser: bool,
30
+ certfile: Path | None = None,
31
+ keyfile: Path | None = None,
32
+ ) -> None:
33
+ """Run the server, using package assets if explicit paths are not provided"""
34
+ # Use package resources if no explicit path
35
+ if certfile is None:
36
+ certfile = get_asset_path("localhost.pem")
37
+ if keyfile is None:
38
+ keyfile = get_asset_path("localhost-key.pem")
39
+
40
+ # _run_flask_server(path, port, certfile, keyfile)
41
+ # run_fastapi_server_proces(port=port, path=path, certfile=certfile, keyfile=keyfile)
42
+ proc = run_fastapi_server_proces(port=port, cwd=path)
43
+ if open_browser:
44
+ _open_browser(f"http://localhost:{port}/")
74
45
  try:
75
- _run_flask_server(path, port)
76
- import warnings
77
-
78
- warnings.warn("Flask server has stopped")
46
+ proc.join()
79
47
  except KeyboardInterrupt:
80
48
  import _thread
81
49
 
82
50
  _thread.interrupt_main()
83
- pass
84
51
 
85
52
 
86
53
  def parse_args() -> argparse.Namespace:
87
- """Parse the command line arguments."""
88
54
  parser = argparse.ArgumentParser(
89
55
  description="Open a browser to the fastled_js directory"
90
56
  )
@@ -95,16 +61,36 @@ def parse_args() -> argparse.Namespace:
95
61
  "--port",
96
62
  "-p",
97
63
  type=int,
98
- required=True,
99
- help="Port to run the server on (default: %(default)s)",
64
+ default=5500,
65
+ help="Port to run the server on (default: 5500)",
66
+ )
67
+ parser.add_argument(
68
+ "--cert", type=Path, help="(Optional) Path to SSL certificate (PEM format)"
69
+ )
70
+ parser.add_argument(
71
+ "--key", type=Path, help="(Optional) Path to SSL private key (PEM format)"
72
+ )
73
+ parser.add_argument(
74
+ "--open-browser",
75
+ action="store_true",
100
76
  )
101
77
  return parser.parse_args()
102
78
 
103
79
 
104
80
  def main() -> None:
105
- """Main function."""
106
81
  args = parse_args()
107
- run(args.fastled_js, args.port)
82
+ open_browser: bool = args.open_browser
83
+ fastled_js: Path = args.fastled_js
84
+ port: int = args.port
85
+ cert: Path | None = args.cert
86
+ key: Path | None = args.key
87
+ run(
88
+ path=fastled_js,
89
+ port=port,
90
+ open_browser=open_browser,
91
+ certfile=cert,
92
+ keyfile=key,
93
+ )
108
94
 
109
95
 
110
96
  if __name__ == "__main__":
@@ -0,0 +1,60 @@
1
+ from pathlib import Path
2
+
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.responses import FileResponse
5
+
6
+ # your existing MIME mapping, e.g.
7
+ MAPPING = {
8
+ "js": "application/javascript",
9
+ "css": "text/css",
10
+ "wasm": "application/wasm",
11
+ "json": "application/json",
12
+ "png": "image/png",
13
+ "jpg": "image/jpeg",
14
+ "jpeg": "image/jpeg",
15
+ "gif": "image/gif",
16
+ "svg": "image/svg+xml",
17
+ "ico": "image/x-icon",
18
+ "html": "text/html",
19
+ }
20
+
21
+
22
+ """Run FastAPI server with live reload or HTTPS depending on args."""
23
+ app = FastAPI()
24
+ base = Path(".")
25
+
26
+
27
+ def no_cache_headers() -> dict[str, str]:
28
+ return {
29
+ "Cache-Control": "no-cache, no-store, must-revalidate",
30
+ "Pragma": "no-cache",
31
+ "Expires": "0",
32
+ }
33
+
34
+
35
+ @app.get("/")
36
+ async def serve_index():
37
+ index_path = base / "index.html"
38
+ if not index_path.exists():
39
+ raise HTTPException(status_code=404, detail="index.html not found")
40
+ return FileResponse(
41
+ index_path,
42
+ media_type=MAPPING.get("html"),
43
+ headers=no_cache_headers(),
44
+ )
45
+
46
+
47
+ @app.get("/{path:path}")
48
+ async def serve_files(path: str):
49
+ file_path = base / path
50
+ if not file_path.exists() or not file_path.is_file():
51
+ raise HTTPException(status_code=404, detail=f"{path} not found")
52
+
53
+ ext = path.rsplit(".", 1)[-1].lower()
54
+ media_type = MAPPING.get(ext)
55
+
56
+ return FileResponse(
57
+ file_path,
58
+ media_type=media_type,
59
+ headers=no_cache_headers(),
60
+ )
@@ -0,0 +1,60 @@
1
+ from multiprocessing import Process
2
+ from pathlib import Path
3
+
4
+ import uvicorn
5
+
6
+ # MAPPING = {
7
+ # "js": "application/javascript",
8
+ # "css": "text/css",
9
+ # "wasm": "application/wasm",
10
+ # "json": "application/json",
11
+ # "png": "image/png",
12
+ # "jpg": "image/jpeg",
13
+ # "jpeg": "image/jpeg",
14
+ # "gif": "image/gif",
15
+ # "svg": "image/svg+xml",
16
+ # "ico": "image/x-icon",
17
+ # "html": "text/html",
18
+ # }
19
+
20
+
21
+ def _run_fastapi_server(
22
+ port: int,
23
+ cwd: Path,
24
+ certfile: Path | None = None,
25
+ keyfile: Path | None = None,
26
+ ) -> None:
27
+ # Uvicorn “reload” will watch your Python files for changes.
28
+ import os
29
+
30
+ os.chdir(cwd)
31
+ uvicorn.run(
32
+ "fastled.server_fastapi:app",
33
+ host="127.0.0.1",
34
+ port=port,
35
+ reload=True,
36
+ ssl_certfile=certfile,
37
+ ssl_keyfile=keyfile,
38
+ )
39
+
40
+
41
+ def run_fastapi_server_proces(
42
+ port: int,
43
+ cwd: Path | None = None,
44
+ certfile: Path | None = None,
45
+ keyfile: Path | None = None,
46
+ ) -> Process:
47
+ """Run the FastAPI server in a separate process."""
48
+ cwd = cwd or Path(".")
49
+ process = Process(
50
+ target=_run_fastapi_server,
51
+ args=(port, cwd, certfile, keyfile),
52
+ )
53
+ process.start()
54
+ return process
55
+
56
+
57
+ if __name__ == "__main__":
58
+ # Example usage
59
+ proc = run_fastapi_server_proces(port=8000)
60
+ proc.join()
fastled/settings.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import platform
3
3
 
4
- FILE_CHANGED_DEBOUNCE_SECONDS = 4.0
4
+ FILE_CHANGED_DEBOUNCE_SECONDS = 2.0
5
5
  MACHINE = platform.machine().lower()
6
6
  IS_ARM: bool = "arm" in MACHINE or "aarch64" in MACHINE
7
7
  PLATFORM_TAG: str = "-arm64" if IS_ARM else ""
fastled/string_diff.py CHANGED
@@ -13,10 +13,26 @@ def string_diff(
13
13
  def normalize(s: str) -> str:
14
14
  return s.lower() if ignore_case else s
15
15
 
16
- # distances = [
17
- # #Levenshtein.distance(normalize(input_string), normalize(s)) for s in string_list
18
- # fuzz.partial_ratio(normalize(input_string), normalize(s)) for s in string_list
19
- # ]
16
+ map_string: dict[str, str] = {}
17
+
18
+ if ignore_case:
19
+ map_string = {s.lower(): s for s in string_list}
20
+ else:
21
+ map_string = {s: s for s in string_list}
22
+
23
+ if ignore_case:
24
+ string_list = [s.lower() for s in string_list]
25
+ input_string = input_string.lower()
26
+
27
+ is_substring = False
28
+ for s in string_list:
29
+ if input_string in s:
30
+ is_substring = True
31
+ break
32
+
33
+ if is_substring:
34
+ string_list = [s for s in string_list if input_string in s]
35
+
20
36
  distances: list[float] = []
21
37
  for s in string_list:
22
38
  dist = fuzz.token_sort_ratio(normalize(input_string), normalize(s))
@@ -25,7 +41,9 @@ def string_diff(
25
41
  out: list[tuple[float, str]] = []
26
42
  for i, d in enumerate(distances):
27
43
  if d == min_distance:
28
- out.append((i, string_list[i]))
44
+ s = string_list[i]
45
+ s_mapped = map_string.get(s, s)
46
+ out.append((i, s_mapped))
29
47
 
30
48
  return out
31
49
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.55
3
+ Version: 1.2.57
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -19,8 +19,9 @@ Requires-Dist: disklru>=2.0.1
19
19
  Requires-Dist: appdirs>=1.4.4
20
20
  Requires-Dist: rapidfuzz>=3.10.1
21
21
  Requires-Dist: progress>=1.6
22
- Requires-Dist: Flask>=3.0.0
23
- Requires-Dist: livereload
22
+ Requires-Dist: fastapi>=0.115.12
23
+ Requires-Dist: uvicorn>=0.34.2
24
+ Requires-Dist: pywebview>=5.4
24
25
  Dynamic: home-page
25
26
  Dynamic: license-file
26
27
  Dynamic: maintainer
@@ -295,8 +296,8 @@ A: `delay()` will block `loop()` which blocks the main thread of the browser. Th
295
296
  Q: How can I get the compiled size of my FastLED sketch smaller?
296
297
  A: A big chunk of space is being used by unnecessary javascript `emscripten` bundling. The wasm_compiler_settings.py file in the FastLED repo can tweak this.
297
298
 
298
- # Revisions
299
299
 
300
+ # Revisions
300
301
  * 1.2.31 - Bunch of fixes and ease of use while compiling code in the repo.
301
302
  * 1.2.22 - Prefer to use `live-server` from npm. If npm exists on the system then do a background install of `live-server` for next run.
302
303
  * 1.2.20 - Fixed up path issue for web browser launch for hot reload.
@@ -382,3 +383,15 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` bun
382
383
  * 1.0.2 - Small bug with new installs.
383
384
  * 1.0.1 - Re-use is no longer the default, due to problems.
384
385
  * 1.0.0 - Initial release.
386
+
387
+
388
+ # TODO
389
+ * `live-server --https --cert=localhost.pem --key=localhost-key.pem --port=5500`
390
+ * `live-server --port=8416 --host=localhost . --https --cert=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost-key.pem --key=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost.pem --no-browser`
391
+
392
+
393
+
394
+ live-server --https --port=8416 --host=localhost . --cert=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost-key.pem --key=C:/Users/niteris/dev/fastled-wasm/src/fastled/assets/localhost.pem --no-browser
395
+
396
+
397
+ live-server --https --cert=src/fastled/assets/localhost.pem --key=src/fastled/assets/localhost-key.pem --port=5500
@@ -1,4 +1,4 @@
1
- fastled/__init__.py,sha256=6kRImRI2Oc4dHucIY5jBajNGE5d3zpUrExS-ZTRUcow,6734
1
+ fastled/__init__.py,sha256=TQuJVAWhfzDR6ewPqYml0-qv9yV7urg0rd51MWlv84U,6734
2
2
  fastled/app.py,sha256=Ozn7OJ0rz3CEHsJKMhFLxV7TisRKo4DuFpVhrf5FQNw,3944
3
3
  fastled/cli.py,sha256=FjVr31ht0UPlAcmX-84NwfAGMQHTkrCe4o744jCAxiw,375
4
4
  fastled/cli_test.py,sha256=qJB9yLRFR3OwOwdIWSQ0fQsWLnA37v5pDccufiP_hTs,512
@@ -6,31 +6,36 @@ fastled/client_server.py,sha256=Pr_b_406XpB6QLrlRMV3mwM82x25s1bIGFU0O0XA3yk,1493
6
6
  fastled/compile_server.py,sha256=ul3eiZNX2wwmInooo3PJC3_kNpdejYVDIo94G3sV9HQ,2941
7
7
  fastled/compile_server_impl.py,sha256=xhC7WBlIfvghgpxAP8VwBeM13NuTA99cI47prODW-IU,9927
8
8
  fastled/docker_manager.py,sha256=VVyfl4HNsSB95nlENuccZFR0qL3Y0ZJgPCFyHKa9A14,31459
9
- fastled/filewatcher.py,sha256=40XZ2euBg5ZMHsjv8obxHApVAUE5ztUEd7FTGqtgsMc,9826
9
+ fastled/filewatcher.py,sha256=3qS3L7zMQhFuVrkeGn1djsB_cB6x_E2YGJmmQWVAU_w,10033
10
10
  fastled/interactive_srcs.py,sha256=F5nHdJc60xsnmOtnKhngE9JytqGn56PmYw_MVSIX1ac,138
11
11
  fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
12
+ fastled/keyz.py,sha256=WjfZHtZHEB8wNh1YYZGYwjx1jQV-6wwhCbYn4Zp7xs4,938
12
13
  fastled/live_client.py,sha256=MDauol0mxtXggV1Pv9ahC0Jjg_4wnnV6FjGEtdd9cxU,2763
13
- fastled/open_browser.py,sha256=KX2h9PUaPsKcwZ84i01DrXOnNpvaKLpB63u5kzcnEKQ,4342
14
- fastled/open_browser2.py,sha256=jUgN81bEYX-sr0zKTVJkwj9tXEVq7aZTxGUP_ShyCbs,3614
14
+ fastled/open_browser.py,sha256=ls3nJBh61iV95Lq4n0zVhmtQ_xgXjpa5bMVOPRM5ldI,3988
15
+ fastled/open_browser2.py,sha256=LT4EDAmOjusZfO1zTs3LyPFY6k3KjLsR4M3HVgJbnAY,2667
15
16
  fastled/parse_args.py,sha256=xgjxirQVX3FQxHZNVJtQQxTOeSJRQdGUNQ7W33Y880Y,8051
16
17
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
17
18
  fastled/project_init.py,sha256=OCIqpGmRMHNQtlXMTfX1x4Sou5WZgJtBNqPhnXbFFLI,4113
18
19
  fastled/select_sketch_directory.py,sha256=TZdCjl1D7YMKjodMTvDRurPcpAmN3x0TcJxffER2NfM,1314
19
- fastled/settings.py,sha256=Gr_KewMhBHTnlPYesHkbN_A3sEkjdVTh1pkNpkZwZrk,468
20
+ fastled/server_fastapi.py,sha256=BLWERhxXcNxKk7Ub7eLRL1qgXBIgQw24-Zv5vek2X7A,1478
21
+ fastled/server_fastapi_cli.py,sha256=i-Paq59dFpl-RV1xb7DycTHuDjaMhAo7u_WdVaSUDTY,1347
22
+ fastled/settings.py,sha256=URgM6ZPlQYF-0ZTEhQCX8isLR6CbmYGwhDX4uXbh-ZI,468
20
23
  fastled/sketch.py,sha256=tHckjDj8P6BI_LWzUFM071a9qcqPs-r-qFWIe50P5Xw,3391
21
24
  fastled/spinner.py,sha256=VHxmvB92P0Z_zYxRajb5HiNmkHHvZ5dG7hKtZltzpcs,867
22
- fastled/string_diff.py,sha256=UR1oRhg9lsPzAG4bn_MwJMCn0evP5AigkBiwLiI9fgA,1354
25
+ fastled/string_diff.py,sha256=9Q_36W0j7HvTnvfemdq9Ldi5RznGj0duX0Y3sZmVwJI,1730
23
26
  fastled/types.py,sha256=ZFqHxYIGSeQb9FiImA5KDXZLhmmVSjOrIQDduwxmCZw,4494
24
27
  fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
25
28
  fastled/web_compile.py,sha256=H2Tm5kMBaRjqdHqxv7L3-xSeYNHi0k0P-XM0Cn9esOo,10360
26
29
  fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
30
+ fastled/assets/localhost-key.pem,sha256=Q-CNO_UoOd8fFNN4ljcnqwUeCMhzTplRjLO2x0pYRlU,1704
31
+ fastled/assets/localhost.pem,sha256=QTwUtTwjYWbm9m3pHW2IlK2nFZJ8b0pppxPjhgVZqQo,1619
27
32
  fastled/site/build.py,sha256=pjsaeHQQh_Zdh1g5Q78lYlUT7UPDFuNgW5YZkpsPxWc,14436
28
33
  fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
29
34
  fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
30
35
  fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
31
- fastled-1.2.55.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
32
- fastled-1.2.55.dist-info/METADATA,sha256=fLJj2AsQnGgflwpxrBCI-Ktt047LhKacc_1mc2Kr0sU,21668
33
- fastled-1.2.55.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
34
- fastled-1.2.55.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
35
- fastled-1.2.55.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
36
- fastled-1.2.55.dist-info/RECORD,,
36
+ fastled-1.2.57.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
37
+ fastled-1.2.57.dist-info/METADATA,sha256=HzmflBOGtZA3gUqFVXKHMQ0NZmJN8nYFDjUvMgH49Uo,22374
38
+ fastled-1.2.57.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
39
+ fastled-1.2.57.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
40
+ fastled-1.2.57.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
41
+ fastled-1.2.57.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5