fastled 1.1.7__py2.py3-none-any.whl → 1.1.16__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/filewatcher.py CHANGED
@@ -1,146 +1,196 @@
1
- """File system watcher implementation using watchdog
2
- """
3
-
4
- import hashlib
5
- import queue
6
- import threading
7
- import time
8
- from pathlib import Path
9
- from typing import Dict, Set
10
-
11
- from watchdog.events import FileSystemEvent, FileSystemEventHandler
12
- from watchdog.observers import Observer
13
- from watchdog.observers.api import BaseObserver
14
-
15
-
16
- class MyEventHandler(FileSystemEventHandler):
17
- def __init__(
18
- self,
19
- change_queue: queue.Queue,
20
- excluded_patterns: Set[str],
21
- file_hashes: Dict[str, str],
22
- ) -> None:
23
- super().__init__()
24
- self.change_queue = change_queue
25
- self.excluded_patterns = excluded_patterns
26
- self.file_hashes = file_hashes
27
-
28
- def _get_file_hash(self, filepath: str) -> str:
29
- try:
30
- with open(filepath, "rb") as f:
31
- return hashlib.md5(f.read()).hexdigest()
32
- except Exception: # pylint: disable=broad-except
33
- return ""
34
-
35
- def on_modified(self, event: FileSystemEvent) -> None:
36
- if not event.is_directory:
37
- path = Path(event.src_path)
38
- # Check if any part of the path matches excluded patterns
39
- if not any(part in self.excluded_patterns for part in path.parts):
40
- new_hash = self._get_file_hash(event.src_path)
41
- if new_hash and new_hash != self.file_hashes.get(event.src_path):
42
- self.file_hashes[event.src_path] = new_hash
43
- self.change_queue.put(event.src_path)
44
-
45
-
46
- class FileChangedNotifier(threading.Thread):
47
- """Watches a directory for file changes and queues notifications"""
48
-
49
- def __init__(
50
- self,
51
- path: str,
52
- debounce_seconds: float = 1.0,
53
- excluded_patterns: list[str] | None = None,
54
- ) -> None:
55
- """Initialize the notifier with a path to watch
56
-
57
- Args:
58
- path: Directory path to watch for changes
59
- debounce_seconds: Minimum time between notifications for the same file
60
- excluded_patterns: List of directory/file patterns to exclude from watching
61
- """
62
- super().__init__(daemon=True)
63
- self.path = path
64
- self.observer: BaseObserver | None = None
65
- self.event_handler: MyEventHandler | None = None
66
-
67
- # Combine default and user-provided patterns
68
- self.excluded_patterns = (
69
- set(excluded_patterns) if excluded_patterns is not None else set()
70
- )
71
- self.stopped = False
72
- self.change_queue: queue.Queue = queue.Queue()
73
- self.last_notification: Dict[str, float] = {}
74
- self.file_hashes: Dict[str, str] = {}
75
- self.debounce_seconds = debounce_seconds
76
-
77
- def stop(self) -> None:
78
- """Stop watching for changes"""
79
- print("watcher stop")
80
- self.stopped = True
81
- if self.observer:
82
- self.observer.stop()
83
- self.observer.join()
84
- self.observer = None
85
- self.event_handler = None
86
-
87
- def run(self) -> None:
88
- """Thread main loop - starts watching for changes"""
89
- self.event_handler = MyEventHandler(
90
- self.change_queue, self.excluded_patterns, self.file_hashes
91
- )
92
- self.observer = Observer()
93
- self.observer.schedule(self.event_handler, self.path, recursive=True)
94
- self.observer.start()
95
-
96
- try:
97
- while not self.stopped:
98
- time.sleep(0.1)
99
- except KeyboardInterrupt:
100
- print("File watcher stopped by user.")
101
- finally:
102
- self.stop()
103
-
104
- def get_next_change(self, timeout: float = 0.001) -> str | None:
105
- """Get the next file change event from the queue
106
-
107
- Args:
108
- timeout: How long to wait for next change in seconds
109
-
110
- Returns:
111
- Changed filepath or None if no change within timeout
112
- """
113
- try:
114
- filepath = self.change_queue.get(timeout=timeout)
115
- current_time = time.time()
116
-
117
- # Check if we've seen this file recently
118
- last_time = self.last_notification.get(filepath, 0)
119
- if current_time - last_time < self.debounce_seconds:
120
- return None
121
-
122
- self.last_notification[filepath] = current_time
123
- return filepath
124
- except KeyboardInterrupt:
125
- raise
126
- except queue.Empty:
127
- return None
128
-
129
- def get_all_changes(self, timeout: float = 0.001) -> list[str]:
130
- """Get all file change events from the queue
131
-
132
- Args:
133
- timeout: How long to wait for next change in seconds
134
-
135
- Returns:
136
- List of changed filepaths
137
- """
138
- changed_files = []
139
- while True:
140
- changed_file = self.get_next_change(timeout=timeout)
141
- if changed_file is None:
142
- break
143
- changed_files.append(changed_file)
144
- # clear all the changes from the queue
145
- self.change_queue.queue.clear()
146
- 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
+ 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
fastled/keyboard.py ADDED
@@ -0,0 +1,91 @@
1
+ import os
2
+ import select
3
+ import sys
4
+ import time
5
+ from multiprocessing import Process, Queue
6
+ from queue import Empty
7
+
8
+
9
+ class SpaceBarWatcher:
10
+ def __init__(self) -> None:
11
+ self.queue: Queue = Queue()
12
+ self.queue_cancel: Queue = Queue()
13
+ self.process = Process(target=self._watch_for_space)
14
+ self.process.start()
15
+
16
+ def _watch_for_space(self) -> None:
17
+ # Set stdin to non-blocking mode
18
+ fd = sys.stdin.fileno()
19
+
20
+ if os.name == "nt": # Windows
21
+ import msvcrt
22
+
23
+ while True:
24
+ # Check for cancel signal
25
+ try:
26
+ self.queue_cancel.get(timeout=0.1)
27
+ break
28
+ except Empty:
29
+ pass
30
+
31
+ # Check if there's input ready
32
+ if msvcrt.kbhit(): # type: ignore
33
+ char = msvcrt.getch().decode() # type: ignore
34
+ if char == " ":
35
+ self.queue.put(ord(" "))
36
+
37
+ else: # Unix-like systems
38
+ import termios
39
+ import tty
40
+
41
+ old_settings = termios.tcgetattr(fd) # type: ignore
42
+ try:
43
+ tty.setraw(fd) # type: ignore
44
+ while True:
45
+ # Check for cancel signal
46
+ try:
47
+ self.queue_cancel.get(timeout=0.1)
48
+ break
49
+ except Empty:
50
+ pass
51
+
52
+ # Check if there's input ready
53
+ if select.select([sys.stdin], [], [], 0.1)[0]:
54
+ char = sys.stdin.read(1)
55
+ if char == " ":
56
+ self.queue.put(ord(" "))
57
+ finally:
58
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore
59
+
60
+ def space_bar_pressed(self) -> bool:
61
+ found = False
62
+ while not self.queue.empty():
63
+ key = self.queue.get()
64
+ if key == ord(" "):
65
+ found = True
66
+ return found
67
+
68
+ def stop(self) -> None:
69
+ self.queue_cancel.put(True)
70
+ self.process.terminate()
71
+ self.process.join()
72
+ self.queue.close()
73
+ self.queue.join_thread()
74
+
75
+
76
+ def main() -> None:
77
+ watcher = SpaceBarWatcher()
78
+ try:
79
+ while True:
80
+ if watcher.space_bar_pressed():
81
+ break
82
+ time.sleep(1)
83
+ finally:
84
+ watcher.stop()
85
+
86
+
87
+ if __name__ == "__main__":
88
+ try:
89
+ main()
90
+ except KeyboardInterrupt:
91
+ print("Keyboard interrupt detected.")
fastled/open_browser.py CHANGED
@@ -24,7 +24,11 @@ def _open_browser_python(fastled_js: Path, port: int) -> subprocess.Popen:
24
24
  ]
25
25
 
26
26
  # Start the process
27
- process = subprocess.Popen(cmd)
27
+ process = subprocess.Popen(
28
+ cmd,
29
+ # stdout=subprocess.DEVNULL,
30
+ stderr=subprocess.DEVNULL,
31
+ )
28
32
  return process
29
33
 
30
34
 
fastled/util.py ADDED
@@ -0,0 +1,10 @@
1
+ import hashlib
2
+ from pathlib import Path
3
+
4
+
5
+ def hash_file(file_path: Path) -> str:
6
+ hasher = hashlib.sha256()
7
+ with open(file_path, "rb") as f:
8
+ for chunk in iter(lambda: f.read(4096), b""):
9
+ hasher.update(chunk)
10
+ return hasher.hexdigest()