fastled 1.1.16__py2.py3-none-any.whl → 1.1.18__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,3 @@
1
1
  """FastLED Wasm Compiler package."""
2
2
 
3
- __version__ = "1.1.16"
3
+ __version__ = "1.1.18"
fastled/app.py CHANGED
@@ -82,38 +82,56 @@ def parse_args() -> argparse.Namespace:
82
82
  action="store_true",
83
83
  help="Enable profiling for web compilation",
84
84
  )
85
- build_mode = parser.add_mutually_exclusive_group()
86
- build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
87
- build_mode.add_argument(
88
- "--quick",
85
+ parser.add_argument(
86
+ "--force-compile",
89
87
  action="store_true",
90
- default=True,
91
- help="Build in quick mode (default)",
88
+ help="Skips the test to see if the current directory is a valid FastLED sketch directory",
92
89
  )
93
- build_mode.add_argument(
94
- "--release", action="store_true", help="Build in release mode"
90
+ parser.add_argument(
91
+ "--no-auto-updates",
92
+ action="store_true",
93
+ help="Disable automatic updates of the wasm compiler image when using docker.",
95
94
  )
96
- build_mode.add_argument(
95
+ parser.add_argument(
96
+ "--update",
97
+ action="store_true",
98
+ help="Update the wasm compiler (if necessary) before running",
99
+ )
100
+ parser.add_argument(
97
101
  "--localhost",
98
102
  "--local",
103
+ "-l",
99
104
  action="store_true",
100
105
  help="Use localhost for web compilation from an instance of fastled --server, creating it if necessary",
101
106
  )
102
- build_mode.add_argument(
107
+ parser.add_argument(
103
108
  "--server",
109
+ "-s",
104
110
  action="store_true",
105
111
  help="Run the server in the current directory, volume mapping fastled if we are in the repo",
106
112
  )
107
-
113
+ build_mode = parser.add_mutually_exclusive_group()
114
+ build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
108
115
  build_mode.add_argument(
109
- "--force-compile",
116
+ "--quick",
110
117
  action="store_true",
111
- help="Skips the test to see if the current directory is a valid FastLED sketch directory",
118
+ default=True,
119
+ help="Build in quick mode (default)",
120
+ )
121
+ build_mode.add_argument(
122
+ "--release", action="store_true", help="Build in release mode"
112
123
  )
113
124
 
114
125
  cwd_is_fastled = looks_like_fastled_repo(Path(os.getcwd()))
115
126
 
116
127
  args = parser.parse_args()
128
+ if args.update:
129
+ args.auto_update = True
130
+ elif args.no_auto_updates:
131
+ args.auto_update = False
132
+ else:
133
+ args.auto_update = None
134
+
117
135
  if not cwd_is_fastled and not args.localhost and not args.web and not args.server:
118
136
  print(f"Using web compiler at {DEFAULT_URL}")
119
137
  args.web = DEFAULT_URL
@@ -199,8 +217,8 @@ def run_web_compiler(
199
217
 
200
218
 
201
219
  def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServer:
220
+ auto_update = args.auto_update
202
221
  is_local_host = "localhost" in args.web or "127.0.0.1" in args.web or args.localhost
203
-
204
222
  # test to see if there is already a local host server
205
223
  local_host_needs_server = False
206
224
  if is_local_host:
@@ -225,7 +243,7 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
225
243
  else:
226
244
  try:
227
245
  print("No local server found, starting one...")
228
- compile_server = CompileServer()
246
+ compile_server = CompileServer(auto_updates=auto_update)
229
247
  print("Waiting for the local compiler to start...")
230
248
  if not compile_server.wait_for_startup():
231
249
  print("Failed to start local compiler.")
@@ -397,7 +415,8 @@ def run_client(args: argparse.Namespace) -> int:
397
415
 
398
416
  def run_server(args: argparse.Namespace) -> int:
399
417
  interactive = args.interactive
400
- compile_server = CompileServer(interactive=interactive)
418
+ auto_update = args.auto_update
419
+ compile_server = CompileServer(interactive=interactive, auto_updates=auto_update)
401
420
  if not interactive:
402
421
  print(f"Server started at {compile_server.url()}")
403
422
  compile_server.wait_for_startup()
fastled/compile_server.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import subprocess
2
2
  import time
3
+ from datetime import datetime, timezone
3
4
  from pathlib import Path
4
5
 
5
6
  import httpx
@@ -20,6 +21,7 @@ class CompileServer:
20
21
  self,
21
22
  container_name=_DEFAULT_CONTAINER_NAME,
22
23
  interactive: bool = False,
24
+ auto_updates: bool | None = None,
23
25
  ) -> None:
24
26
 
25
27
  cwd = Path(".").resolve()
@@ -35,6 +37,7 @@ class CompileServer:
35
37
  self.fastled_src_dir: Path | None = fastled_src_dir
36
38
  self.interactive = interactive
37
39
  self.running_container: RunningContainer | None = None
40
+ self.auto_updates = auto_updates
38
41
  self._port = self._start()
39
42
  # fancy print
40
43
  if not interactive:
@@ -90,26 +93,25 @@ class CompileServer:
90
93
  print("Compiling server starting")
91
94
 
92
95
  # Ensure Docker is running
93
- with self.docker.get_lock():
94
- if not self.docker.is_running():
95
- if not self.docker.start():
96
- print("Docker could not be started. Exiting.")
97
- raise RuntimeError("Docker could not be started. Exiting.")
98
- from datetime import datetime, timezone
99
-
100
- now = datetime.now(timezone.utc)
101
- now_str = now.strftime("%Y-%m-%d %H %Z")
96
+ if not self.docker.is_running():
97
+ if not self.docker.start():
98
+ print("Docker could not be started. Exiting.")
99
+ raise RuntimeError("Docker could not be started. Exiting.")
100
+ now = datetime.now(timezone.utc)
101
+ now_str = now.strftime("%Y-%m-%d %H %Z")
102
+
103
+ upgrade = False
104
+ if self.auto_updates is None:
102
105
  prev_date_str = DISK_CACHE.get("last-update")
103
-
104
- upgrade = False
105
106
  if prev_date_str != now_str:
106
- print("New day, upgrading Docker image")
107
+ print("One hour has passed, checking docker for updates")
107
108
  upgrade = True
108
-
109
- self.docker.validate_or_download_image(
110
- image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
111
- )
112
- DISK_CACHE.put("last-update", now_str)
109
+ else:
110
+ upgrade = self.auto_updates
111
+ self.docker.validate_or_download_image(
112
+ image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
113
+ )
114
+ DISK_CACHE.put("last-update", now_str)
113
115
 
114
116
  print("Docker image now validated")
115
117
  port = SERVER_PORT
fastled/docker_manager.py CHANGED
@@ -45,8 +45,10 @@ def _win32_docker_location() -> str | None:
45
45
  return None
46
46
 
47
47
 
48
- _HERE = Path(__file__).parent
49
- _FILE_LOCK = FileLock(str(_HERE / "fled.lock"))
48
+ def get_lock(image_name: str) -> FileLock:
49
+ """Get the file lock for this DockerManager instance."""
50
+ lock_file = CONFIG_DIR / f"{image_name}.lock"
51
+ return FileLock(str(lock_file))
50
52
 
51
53
 
52
54
  class RunningContainer:
@@ -90,10 +92,6 @@ class DockerManager:
90
92
  self.client: DockerClient = docker.from_env()
91
93
  self.first_run = False
92
94
 
93
- def get_lock(self) -> FileLock:
94
- """Get the file lock for this DockerManager instance."""
95
- return _FILE_LOCK
96
-
97
95
  @staticmethod
98
96
  def is_docker_installed() -> bool:
99
97
  """Check if Docker is installed on the system."""
@@ -178,53 +176,54 @@ class DockerManager:
178
176
  If upgrade is True, will pull the latest version even if image exists locally.
179
177
  """
180
178
  print(f"Validating image {image_name}:{tag}...")
179
+ remote_image_hash_from_local_image: str | None = None
180
+ remote_image_hash: str | None = None
181
181
 
182
- try:
183
- local_image = self.client.images.get(f"{image_name}:{tag}")
184
- print(f"Image {image_name}:{tag} is already available.")
182
+ with get_lock(f"{image_name}-{tag}"):
183
+ try:
184
+ local_image = self.client.images.get(f"{image_name}:{tag}")
185
+ print(f"Image {image_name}:{tag} is already available.")
185
186
 
186
- if upgrade:
187
- remote_image = self.client.images.get_registry_data(
188
- f"{image_name}:{tag}"
189
- )
190
- remote_image_hash = remote_image.id
191
- try:
192
- remote_image_hash_from_local_image = DISK_CACHE.get(local_image.id)
193
- except KeyboardInterrupt:
194
- raise
195
- except Exception:
196
- remote_image_hash_from_local_image = None
197
- import traceback
198
- import warnings
199
-
200
- stack = traceback.format_exc()
201
- warnings.warn(
202
- f"Error getting remote image hash from local image: {stack}"
187
+ if upgrade:
188
+ remote_image = self.client.images.get_registry_data(
189
+ f"{image_name}:{tag}"
203
190
  )
204
- if remote_image_hash_from_local_image == remote_image_hash:
205
- print(f"Local image {image_name}:{tag} is up to date.")
206
- return
191
+ remote_image_hash = remote_image.id
207
192
 
208
- # Quick check for latest version
193
+ try:
194
+ remote_image_hash_from_local_image = DISK_CACHE.get(
195
+ local_image.id
196
+ )
197
+ except KeyboardInterrupt:
198
+ raise
199
+ except Exception:
200
+ remote_image_hash_from_local_image = None
201
+ stack = traceback.format_exc()
202
+ warnings.warn(
203
+ f"Error getting remote image hash from local image: {stack}"
204
+ )
205
+ if remote_image_hash_from_local_image == remote_image_hash:
206
+ print(f"Local image {image_name}:{tag} is up to date.")
207
+ return
209
208
 
210
- print(f"Pulling newer version of {image_name}:{tag}...")
211
- _ = self.client.images.pull(image_name, tag=tag)
212
- print(f"Updated to newer version of {image_name}:{tag}")
213
- local_image_hash = self.client.images.get(f"{image_name}:{tag}").id
214
- DISK_CACHE.put(local_image_hash, remote_image_hash)
209
+ # Quick check for latest version
215
210
 
216
- except docker.errors.ImageNotFound:
217
- print(f"Image {image_name}:{tag} not found. Downloading...")
218
- self.client.images.pull(image_name, tag=tag)
219
- try:
220
- local_image = self.client.images.get(f"{image_name}:{tag}")
221
- local_image_hash = local_image.id
222
- DISK_CACHE.put(local_image_hash, remote_image_hash)
223
- print(f"Image {image_name}:{tag} downloaded successfully.")
224
- except docker.errors.ImageNotFound:
225
- import warnings
211
+ print(f"Pulling newer version of {image_name}:{tag}...")
212
+ _ = self.client.images.pull(image_name, tag=tag)
213
+ print(f"Updated to newer version of {image_name}:{tag}")
214
+ local_image_hash = self.client.images.get(f"{image_name}:{tag}").id
215
+ if remote_image_hash is not None:
216
+ DISK_CACHE.put(local_image_hash, remote_image_hash)
226
217
 
227
- warnings.warn(f"Image {image_name}:{tag} not found after download.")
218
+ except docker.errors.ImageNotFound:
219
+ print(f"Image {image_name}:{tag} not found. Downloading...")
220
+ self.client.images.pull(image_name, tag=tag)
221
+ try:
222
+ local_image = self.client.images.get(f"{image_name}:{tag}")
223
+ local_image_hash = local_image.id
224
+ print(f"Image {image_name}:{tag} downloaded successfully.")
225
+ except docker.errors.ImageNotFound:
226
+ warnings.warn(f"Image {image_name}:{tag} not found after download.")
228
227
 
229
228
  def tag_image(self, image_name: str, old_tag: str, new_tag: str) -> None:
230
229
  """
fastled/filewatcher.py CHANGED
@@ -1,196 +1,202 @@
1
- """File system watcher implementation using watchdog
2
- """
3
-
4
- import hashlib
5
- import os
6
- import queue
7
- import threading
8
- import time
9
- from contextlib import redirect_stdout
10
- from multiprocessing import Process, Queue
11
- from pathlib import Path
12
- from queue import Empty
13
- from typing import Dict, Set
14
-
15
- from watchdog.events import FileSystemEvent, FileSystemEventHandler
16
- from watchdog.observers import Observer
17
- from watchdog.observers.api import BaseObserver
18
-
19
-
20
- class MyEventHandler(FileSystemEventHandler):
21
- def __init__(
22
- self,
23
- change_queue: queue.Queue,
24
- excluded_patterns: Set[str],
25
- file_hashes: Dict[str, str],
26
- ) -> None:
27
- super().__init__()
28
- self.change_queue = change_queue
29
- self.excluded_patterns = excluded_patterns
30
- self.file_hashes = file_hashes
31
-
32
- def _get_file_hash(self, filepath: str) -> str:
33
- try:
34
- with open(filepath, "rb") as f:
35
- return hashlib.md5(f.read()).hexdigest()
36
- except Exception: # pylint: disable=broad-except
37
- return ""
38
-
39
- def on_modified(self, event: FileSystemEvent) -> None:
40
- if not event.is_directory:
41
- path = Path(event.src_path)
42
- # Check if any part of the path matches excluded patterns
43
- if not any(part in self.excluded_patterns for part in path.parts):
44
- new_hash = self._get_file_hash(event.src_path)
45
- if new_hash and new_hash != self.file_hashes.get(event.src_path):
46
- self.file_hashes[event.src_path] = new_hash
47
- self.change_queue.put(event.src_path)
48
-
49
-
50
- class FileChangedNotifier(threading.Thread):
51
- """Watches a directory for file changes and queues notifications"""
52
-
53
- def __init__(
54
- self,
55
- path: str,
56
- debounce_seconds: float = 1.0,
57
- excluded_patterns: list[str] | None = None,
58
- ) -> None:
59
- """Initialize the notifier with a path to watch
60
-
61
- Args:
62
- path: Directory path to watch for changes
63
- debounce_seconds: Minimum time between notifications for the same file
64
- excluded_patterns: List of directory/file patterns to exclude from watching
65
- """
66
- super().__init__(daemon=True)
67
- self.path = path
68
- self.observer: BaseObserver | None = None
69
- self.event_handler: MyEventHandler | None = None
70
-
71
- # Combine default and user-provided patterns
72
- self.excluded_patterns = (
73
- set(excluded_patterns) if excluded_patterns is not None else set()
74
- )
75
- self.stopped = False
76
- self.change_queue: queue.Queue = queue.Queue()
77
- self.last_notification: Dict[str, float] = {}
78
- self.file_hashes: Dict[str, str] = {}
79
- self.debounce_seconds = debounce_seconds
80
-
81
- def stop(self) -> None:
82
- """Stop watching for changes"""
83
- print("watcher stop")
84
- self.stopped = True
85
- if self.observer:
86
- self.observer.stop()
87
- self.observer.join()
88
- self.observer = None
89
- self.event_handler = None
90
-
91
- def run(self) -> None:
92
- """Thread main loop - starts watching for changes"""
93
- self.event_handler = MyEventHandler(
94
- self.change_queue, self.excluded_patterns, self.file_hashes
95
- )
96
- self.observer = Observer()
97
- self.observer.schedule(self.event_handler, self.path, recursive=True)
98
- self.observer.start()
99
-
100
- try:
101
- while not self.stopped:
102
- time.sleep(0.1)
103
- except KeyboardInterrupt:
104
- print("File watcher stopped by user.")
105
- finally:
106
- self.stop()
107
-
108
- def get_next_change(self, timeout: float = 0.001) -> str | None:
109
- """Get the next file change event from the queue
110
-
111
- Args:
112
- timeout: How long to wait for next change in seconds
113
-
114
- Returns:
115
- Changed filepath or None if no change within timeout
116
- """
117
- try:
118
- filepath = self.change_queue.get(timeout=timeout)
119
- current_time = time.time()
120
-
121
- # Check if we've seen this file recently
122
- last_time = self.last_notification.get(filepath, 0)
123
- if current_time - last_time < self.debounce_seconds:
124
- return None
125
-
126
- self.last_notification[filepath] = current_time
127
- return filepath
128
- except KeyboardInterrupt:
129
- raise
130
- except queue.Empty:
131
- return None
132
-
133
- def get_all_changes(self, timeout: float = 0.001) -> list[str]:
134
- """Get all file change events from the queue
135
-
136
- Args:
137
- timeout: How long to wait for next change in seconds
138
-
139
- Returns:
140
- List of changed filepaths
141
- """
142
- changed_files = []
143
- while True:
144
- changed_file = self.get_next_change(timeout=timeout)
145
- if changed_file is None:
146
- break
147
- changed_files.append(changed_file)
148
- # clear all the changes from the queue
149
- self.change_queue.queue.clear()
150
- return changed_files
151
-
152
-
153
- def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
154
- with open(os.devnull, "w") as fnull: # Redirect to /dev/null
155
- with redirect_stdout(fnull):
156
- watcher = FileChangedNotifier(
157
- str(root), excluded_patterns=excluded_patterns
158
- )
159
- watcher.start()
160
- while True:
161
- try:
162
- changed_files = watcher.get_all_changes()
163
- for file in changed_files:
164
- queue.put(file)
165
- except KeyboardInterrupt:
166
- break
167
- watcher.stop()
168
-
169
-
170
- class FileWatcherProcess:
171
- def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
172
- self.queue: Queue = Queue()
173
- self.process = Process(
174
- target=_process_wrapper,
175
- args=(root, excluded_patterns, self.queue),
176
- daemon=True,
177
- )
178
- self.process.start()
179
-
180
- def stop(self):
181
- self.process.terminate()
182
- self.process.join()
183
- self.queue.close()
184
- self.queue.join_thread()
185
-
186
- def get_all_changes(self, timeout: float | None = None) -> list[str]:
187
- changed_files = []
188
- block = timeout is not None
189
-
190
- while True:
191
- try:
192
- changed_file = self.queue.get(block=block, timeout=timeout)
193
- changed_files.append(changed_file)
194
- except Empty:
195
- break
196
- return changed_files
1
+ """File system watcher implementation using watchdog
2
+ """
3
+
4
+ import hashlib
5
+ import os
6
+ import queue
7
+ import threading
8
+ import time
9
+ from contextlib import redirect_stdout
10
+ from multiprocessing import Process, Queue
11
+ from pathlib import Path
12
+ from queue import Empty
13
+ from typing import Dict, Set
14
+
15
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
16
+ from watchdog.observers import Observer
17
+ from watchdog.observers.api import BaseObserver
18
+
19
+
20
+ class MyEventHandler(FileSystemEventHandler):
21
+ def __init__(
22
+ self,
23
+ change_queue: queue.Queue,
24
+ excluded_patterns: Set[str],
25
+ file_hashes: Dict[str, str],
26
+ ) -> None:
27
+ super().__init__()
28
+ self.change_queue = change_queue
29
+ self.excluded_patterns = excluded_patterns
30
+ self.file_hashes = file_hashes
31
+
32
+ def _get_file_hash(self, filepath: str) -> str:
33
+ try:
34
+ with open(filepath, "rb") as f:
35
+ return hashlib.md5(f.read()).hexdigest()
36
+ except Exception: # pylint: disable=broad-except
37
+ return ""
38
+
39
+ def on_modified(self, event: FileSystemEvent) -> None:
40
+ if not event.is_directory:
41
+ # Convert src_path to str if it's bytes
42
+ src_path = (
43
+ event.src_path.decode()
44
+ if isinstance(event.src_path, bytes)
45
+ else event.src_path
46
+ )
47
+ path = Path(src_path)
48
+ # Check if any part of the path matches excluded patterns
49
+ if not any(part in self.excluded_patterns for part in path.parts):
50
+ new_hash = self._get_file_hash(src_path)
51
+ if new_hash and new_hash != self.file_hashes.get(src_path):
52
+ self.file_hashes[src_path] = new_hash
53
+ self.change_queue.put(src_path)
54
+
55
+
56
+ class FileChangedNotifier(threading.Thread):
57
+ """Watches a directory for file changes and queues notifications"""
58
+
59
+ def __init__(
60
+ self,
61
+ path: str,
62
+ debounce_seconds: float = 1.0,
63
+ excluded_patterns: list[str] | None = None,
64
+ ) -> None:
65
+ """Initialize the notifier with a path to watch
66
+
67
+ Args:
68
+ path: Directory path to watch for changes
69
+ debounce_seconds: Minimum time between notifications for the same file
70
+ excluded_patterns: List of directory/file patterns to exclude from watching
71
+ """
72
+ super().__init__(daemon=True)
73
+ self.path = path
74
+ self.observer: BaseObserver | None = None
75
+ self.event_handler: MyEventHandler | None = None
76
+
77
+ # Combine default and user-provided patterns
78
+ self.excluded_patterns = (
79
+ set(excluded_patterns) if excluded_patterns is not None else set()
80
+ )
81
+ self.stopped = False
82
+ self.change_queue: queue.Queue = queue.Queue()
83
+ self.last_notification: Dict[str, float] = {}
84
+ self.file_hashes: Dict[str, str] = {}
85
+ self.debounce_seconds = debounce_seconds
86
+
87
+ def stop(self) -> None:
88
+ """Stop watching for changes"""
89
+ print("watcher stop")
90
+ self.stopped = True
91
+ if self.observer:
92
+ self.observer.stop()
93
+ self.observer.join()
94
+ self.observer = None
95
+ self.event_handler = None
96
+
97
+ def run(self) -> None:
98
+ """Thread main loop - starts watching for changes"""
99
+ self.event_handler = MyEventHandler(
100
+ self.change_queue, self.excluded_patterns, self.file_hashes
101
+ )
102
+ self.observer = Observer()
103
+ self.observer.schedule(self.event_handler, self.path, recursive=True)
104
+ self.observer.start()
105
+
106
+ try:
107
+ while not self.stopped:
108
+ time.sleep(0.1)
109
+ except KeyboardInterrupt:
110
+ print("File watcher stopped by user.")
111
+ finally:
112
+ self.stop()
113
+
114
+ def get_next_change(self, timeout: float = 0.001) -> str | None:
115
+ """Get the next file change event from the queue
116
+
117
+ Args:
118
+ timeout: How long to wait for next change in seconds
119
+
120
+ Returns:
121
+ Changed filepath or None if no change within timeout
122
+ """
123
+ try:
124
+ filepath = self.change_queue.get(timeout=timeout)
125
+ current_time = time.time()
126
+
127
+ # Check if we've seen this file recently
128
+ last_time = self.last_notification.get(filepath, 0)
129
+ if current_time - last_time < self.debounce_seconds:
130
+ return None
131
+
132
+ self.last_notification[filepath] = current_time
133
+ return filepath
134
+ except KeyboardInterrupt:
135
+ raise
136
+ except queue.Empty:
137
+ return None
138
+
139
+ def get_all_changes(self, timeout: float = 0.001) -> list[str]:
140
+ """Get all file change events from the queue
141
+
142
+ Args:
143
+ timeout: How long to wait for next change in seconds
144
+
145
+ Returns:
146
+ List of changed filepaths
147
+ """
148
+ changed_files = []
149
+ while True:
150
+ changed_file = self.get_next_change(timeout=timeout)
151
+ if changed_file is None:
152
+ break
153
+ changed_files.append(changed_file)
154
+ # clear all the changes from the queue
155
+ self.change_queue.queue.clear()
156
+ return changed_files
157
+
158
+
159
+ def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
160
+ with open(os.devnull, "w") as fnull: # Redirect to /dev/null
161
+ with redirect_stdout(fnull):
162
+ watcher = FileChangedNotifier(
163
+ str(root), excluded_patterns=excluded_patterns
164
+ )
165
+ watcher.start()
166
+ while True:
167
+ try:
168
+ changed_files = watcher.get_all_changes()
169
+ for file in changed_files:
170
+ queue.put(file)
171
+ except KeyboardInterrupt:
172
+ break
173
+ watcher.stop()
174
+
175
+
176
+ class FileWatcherProcess:
177
+ def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
178
+ self.queue: Queue = Queue()
179
+ self.process = Process(
180
+ target=_process_wrapper,
181
+ args=(root, excluded_patterns, self.queue),
182
+ daemon=True,
183
+ )
184
+ self.process.start()
185
+
186
+ def stop(self):
187
+ self.process.terminate()
188
+ self.process.join()
189
+ self.queue.close()
190
+ self.queue.join_thread()
191
+
192
+ def get_all_changes(self, timeout: float | None = None) -> list[str]:
193
+ changed_files = []
194
+ block = timeout is not None
195
+
196
+ while True:
197
+ try:
198
+ changed_file = self.queue.get(block=block, timeout=timeout)
199
+ changed_files.append(changed_file)
200
+ except Empty:
201
+ break
202
+ return changed_files
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.16
3
+ Version: 1.1.18
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -161,6 +161,8 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` is
161
161
 
162
162
  # Revisions
163
163
 
164
+ * 1.1.18 - Fixes for when the image has never been downloaded.
165
+ * 1.1.17 - Added `--update` and `--no-auto-update` to control whether the compiler in docker mode will try to update.
164
166
  * 1.1.16 - Rewrote docker logic to use container suspension and resumption. Much much faster.
165
167
  * 1.1.15 - Fixed logic for considering ipv6 addresses. Auto selection of ipv6 is now restored.
166
168
  * 1.1.14 - Fixes for regression in using --server and --localhost as two instances, this is now under test.
@@ -1,10 +1,10 @@
1
- fastled/__init__.py,sha256=lSQZ1Dcii_FVM8VSJksjq9KpLmMsNl8jlUnsYp7Ko7I,64
2
- fastled/app.py,sha256=5ZKCN8XwcK6wfbp7hQRg-hUkZOSMJgmiTksQ8oX2rME,15094
1
+ fastled/__init__.py,sha256=IHbpe-aq0aVLbSOXEoXCoEu9sNWQTsV5OEghbd4DLsE,64
2
+ fastled/app.py,sha256=dUCHM-1yQGrpTtfUCn1NYlG7hIJm_Tg445zhyxsMBhY,15715
3
3
  fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
4
4
  fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
5
- fastled/compile_server.py,sha256=IKdW82dswZSWrp8oCH4khAMYqz6Vf_65m1ad8m2aSHA,5304
6
- fastled/docker_manager.py,sha256=m09qpT5GnmofPGkPIV6KDJWYduRdRZhQAntBpJoV-24,19722
7
- fastled/filewatcher.py,sha256=fJNMQRDCpihSL4nQeYPqbD4m1Jzjcz_-YRAo-wlPW6k,6518
5
+ fastled/compile_server.py,sha256=aBdpILSRrDsCJ5e9g5uwIqt9bcqE_8FrSddCV2ygtrI,5401
6
+ fastled/docker_manager.py,sha256=AzQBXqWB1Uq_Qb1mp1NS4hIbkC8aYp-pzpYhFRpI5ww,19940
7
+ fastled/filewatcher.py,sha256=5dVmjEG23kMeJa29tRVm5XKSr9sTD4ME2boo-CFDuUM,6910
8
8
  fastled/keyboard.py,sha256=rqndglWYzRy6oiqHgsmx1peLd0Yrpci01zGENlCzh_s,2576
9
9
  fastled/open_browser.py,sha256=RRHcsZ5Vzsw1AuZUEYuSfjKmf_9j3NGMDUR-FndHmqs,1483
10
10
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
@@ -12,9 +12,9 @@ fastled/sketch.py,sha256=KhhPFqlFVlBk8YrzFy7-ioe7zEzecgrVLhyFbLpBp7k,1845
12
12
  fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
13
13
  fastled/web_compile.py,sha256=KuvKGdX6SSUUqC7YgX4T9SMSP5wdcPUhpg9-K9zPoTI,10378
14
14
  fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
15
- fastled-1.1.16.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
16
- fastled-1.1.16.dist-info/METADATA,sha256=a7SXnewYqDzmkJF6sUkgfn0IwYH_yByh3HbUfxnhDYg,13375
17
- fastled-1.1.16.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
18
- fastled-1.1.16.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
19
- fastled-1.1.16.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
20
- fastled-1.1.16.dist-info/RECORD,,
15
+ fastled-1.1.18.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
16
+ fastled-1.1.18.dist-info/METADATA,sha256=5crAxI6_10i2pfPFIX8KxFCmX_3yyvGWZKjBR6JIRMU,13562
17
+ fastled-1.1.18.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
18
+ fastled-1.1.18.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
19
+ fastled-1.1.18.dist-info/top_level.txt,sha256=xfG6Z_ol9V5YmBROkZq2QTRwjbS2ouCUxaTJsOwfkOo,14
20
+ fastled-1.1.18.dist-info/RECORD,,