fastled 1.4.45__py3-none-any.whl → 1.4.47__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/__version__.py +1 -1
- fastled/app.py +1 -1
- fastled/client_server.py +9 -1
- fastled/parse_args.py +21 -6
- fastled/print_filter.py +52 -52
- fastled/select_sketch_directory.py +2 -4
- fastled/sketch.py +100 -0
- fastled/version.py +41 -41
- fastled/web_compile.py +9 -7
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/METADATA +532 -532
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/RECORD +15 -15
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/WHEEL +0 -0
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/entry_points.txt +0 -0
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.4.45.dist-info → fastled-1.4.47.dist-info}/top_level.txt +0 -0
fastled/__version__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
2
2
|
# that has any other change in the repo. Please bump the version as the
|
3
3
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
4
|
-
__version__ = "1.4.
|
4
|
+
__version__ = "1.4.47"
|
5
5
|
|
6
6
|
__version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
|
fastled/app.py
CHANGED
@@ -121,7 +121,7 @@ def main() -> int:
|
|
121
121
|
sketch_list: list[Path] = find_sketch_directories()
|
122
122
|
if sketch_list:
|
123
123
|
maybe_dir: str | None = select_sketch_directory(
|
124
|
-
sketch_list, cwd_looks_like_fastled_repo
|
124
|
+
sketch_list, cwd_looks_like_fastled_repo, is_followup=True
|
125
125
|
)
|
126
126
|
if maybe_dir is not None:
|
127
127
|
directory = Path(maybe_dir)
|
fastled/client_server.py
CHANGED
@@ -452,7 +452,15 @@ def run_client(
|
|
452
452
|
) -> tuple[bool, CompileResult]:
|
453
453
|
changed_files = debounced_sketch_watcher.get_all_changes()
|
454
454
|
if changed_files:
|
455
|
-
|
455
|
+
# Filter out any fastled_js changes that slipped through
|
456
|
+
sketch_changes = [
|
457
|
+
f for f in changed_files if "fastled_js" not in Path(f).parts
|
458
|
+
]
|
459
|
+
if not sketch_changes:
|
460
|
+
# All changes were in fastled_js, ignore them
|
461
|
+
return False, last_compiled_result
|
462
|
+
print(f"\nChanges detected in {sketch_changes}")
|
463
|
+
print("Compiling...")
|
456
464
|
last_hash_value = last_compiled_result.hash_value
|
457
465
|
out = compile_function(last_hash_value=last_hash_value)
|
458
466
|
if not out.success:
|
fastled/parse_args.py
CHANGED
@@ -8,6 +8,7 @@ from fastled.project_init import project_init
|
|
8
8
|
from fastled.select_sketch_directory import select_sketch_directory
|
9
9
|
from fastled.settings import DEFAULT_URL, IMAGE_NAME
|
10
10
|
from fastled.sketch import (
|
11
|
+
find_sketch_by_partial_name,
|
11
12
|
find_sketch_directories,
|
12
13
|
looks_like_fastled_repo,
|
13
14
|
looks_like_sketch_directory,
|
@@ -368,7 +369,7 @@ def parse_args() -> Args:
|
|
368
369
|
print("Searching for sketch directories...")
|
369
370
|
sketch_directories = find_sketch_directories(maybe_sketch_dir)
|
370
371
|
selected_dir = select_sketch_directory(
|
371
|
-
sketch_directories, cwd_is_fastled
|
372
|
+
sketch_directories, cwd_is_fastled, is_followup=True
|
372
373
|
)
|
373
374
|
if selected_dir:
|
374
375
|
print(f"Using sketch directory: {selected_dir}")
|
@@ -378,10 +379,24 @@ def parse_args() -> Args:
|
|
378
379
|
"\nYou either need to specify a sketch directory or run in --server mode."
|
379
380
|
)
|
380
381
|
sys.exit(1)
|
381
|
-
elif args.directory is not None
|
382
|
-
|
383
|
-
if
|
384
|
-
|
385
|
-
|
382
|
+
elif args.directory is not None:
|
383
|
+
# Check if directory is a file path
|
384
|
+
if os.path.isfile(args.directory):
|
385
|
+
dir_path = Path(args.directory).parent
|
386
|
+
if looks_like_sketch_directory(dir_path):
|
387
|
+
print(f"Using sketch directory: {dir_path}")
|
388
|
+
args.directory = str(dir_path)
|
389
|
+
# Check if directory exists as a path
|
390
|
+
elif not os.path.exists(args.directory):
|
391
|
+
# Directory doesn't exist - try partial name matching
|
392
|
+
try:
|
393
|
+
matched_dir = find_sketch_by_partial_name(args.directory)
|
394
|
+
print(
|
395
|
+
f"Matched '{args.directory}' to sketch directory: {matched_dir}"
|
396
|
+
)
|
397
|
+
args.directory = str(matched_dir)
|
398
|
+
except ValueError as e:
|
399
|
+
print(f"Error: {e}")
|
400
|
+
sys.exit(1)
|
386
401
|
|
387
402
|
return Args.from_namespace(args)
|
fastled/print_filter.py
CHANGED
@@ -1,52 +1,52 @@
|
|
1
|
-
import re
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
from enum import Enum
|
4
|
-
|
5
|
-
|
6
|
-
class PrintFilter(ABC):
|
7
|
-
"""Abstract base class for filtering text output."""
|
8
|
-
|
9
|
-
def __init__(self, echo: bool = True) -> None:
|
10
|
-
self.echo = echo
|
11
|
-
|
12
|
-
@abstractmethod
|
13
|
-
def filter(self, text: str) -> str:
|
14
|
-
"""Filter the text according to implementation-specific rules."""
|
15
|
-
pass
|
16
|
-
|
17
|
-
def print(self, text: str | bytes) -> str:
|
18
|
-
"""Prints the text to the console after filtering."""
|
19
|
-
if isinstance(text, bytes):
|
20
|
-
text = text.decode("utf-8")
|
21
|
-
text = self.filter(text)
|
22
|
-
if self.echo:
|
23
|
-
print(text, end="")
|
24
|
-
return text
|
25
|
-
|
26
|
-
|
27
|
-
def _handle_ino_cpp(line: str) -> str:
|
28
|
-
if ".ino.cpp" in line[0:30]:
|
29
|
-
# Extract the filename without path and extension
|
30
|
-
match = re.search(r"src/([^/]+)\.ino\.cpp", line)
|
31
|
-
if match:
|
32
|
-
filename = match.group(1)
|
33
|
-
# Replace with examples/Filename/Filename.ino format
|
34
|
-
line = line.replace(
|
35
|
-
f"src/{filename}.ino.cpp", f"examples/{filename}/{filename}.ino"
|
36
|
-
)
|
37
|
-
else:
|
38
|
-
# Fall back to simple extension replacement if regex doesn't match
|
39
|
-
line = line.replace(".ino.cpp", ".ino")
|
40
|
-
return line
|
41
|
-
|
42
|
-
|
43
|
-
class PrintFilterDefault(PrintFilter):
|
44
|
-
"""Provides default filtering for FastLED output."""
|
45
|
-
|
46
|
-
def filter(self, text: str) -> str:
|
47
|
-
return text
|
48
|
-
|
49
|
-
|
50
|
-
class CompileOrLink(Enum):
|
51
|
-
COMPILE = "compile"
|
52
|
-
LINK = "link"
|
1
|
+
import re
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
|
6
|
+
class PrintFilter(ABC):
|
7
|
+
"""Abstract base class for filtering text output."""
|
8
|
+
|
9
|
+
def __init__(self, echo: bool = True) -> None:
|
10
|
+
self.echo = echo
|
11
|
+
|
12
|
+
@abstractmethod
|
13
|
+
def filter(self, text: str) -> str:
|
14
|
+
"""Filter the text according to implementation-specific rules."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
def print(self, text: str | bytes) -> str:
|
18
|
+
"""Prints the text to the console after filtering."""
|
19
|
+
if isinstance(text, bytes):
|
20
|
+
text = text.decode("utf-8")
|
21
|
+
text = self.filter(text)
|
22
|
+
if self.echo:
|
23
|
+
print(text, end="")
|
24
|
+
return text
|
25
|
+
|
26
|
+
|
27
|
+
def _handle_ino_cpp(line: str) -> str:
|
28
|
+
if ".ino.cpp" in line[0:30]:
|
29
|
+
# Extract the filename without path and extension
|
30
|
+
match = re.search(r"src/([^/]+)\.ino\.cpp", line)
|
31
|
+
if match:
|
32
|
+
filename = match.group(1)
|
33
|
+
# Replace with examples/Filename/Filename.ino format
|
34
|
+
line = line.replace(
|
35
|
+
f"src/{filename}.ino.cpp", f"examples/{filename}/{filename}.ino"
|
36
|
+
)
|
37
|
+
else:
|
38
|
+
# Fall back to simple extension replacement if regex doesn't match
|
39
|
+
line = line.replace(".ino.cpp", ".ino")
|
40
|
+
return line
|
41
|
+
|
42
|
+
|
43
|
+
class PrintFilterDefault(PrintFilter):
|
44
|
+
"""Provides default filtering for FastLED output."""
|
45
|
+
|
46
|
+
def filter(self, text: str) -> str:
|
47
|
+
return text
|
48
|
+
|
49
|
+
|
50
|
+
class CompileOrLink(Enum):
|
51
|
+
COMPILE = "compile"
|
52
|
+
LINK = "link"
|
@@ -101,13 +101,11 @@ def select_sketch_directory(
|
|
101
101
|
print(f"\nUsing sketch directory: {sketch_directories[0]}")
|
102
102
|
return str(sketch_directories[0])
|
103
103
|
elif len(sketch_directories) > 1:
|
104
|
-
#
|
104
|
+
# First scan with >4 directories: return None (too many to auto-select)
|
105
105
|
if not is_followup and len(sketch_directories) > 4:
|
106
|
-
print(f"\nFound {len(sketch_directories)} sketch directories.")
|
107
|
-
print("Please specify a sketch directory to avoid ambiguity.")
|
108
106
|
return None
|
109
107
|
|
110
|
-
#
|
108
|
+
# Prompt user to disambiguate
|
111
109
|
result = _disambiguate_user_choice(
|
112
110
|
sketch_directories,
|
113
111
|
option_to_str=lambda x: str(x),
|
fastled/sketch.py
CHANGED
@@ -100,3 +100,103 @@ def looks_like_sketch_directory(directory: Path | str | None, quick=False) -> bo
|
|
100
100
|
if platformini_file:
|
101
101
|
return True
|
102
102
|
return False
|
103
|
+
|
104
|
+
|
105
|
+
def find_sketch_by_partial_name(
|
106
|
+
partial_name: str, search_dir: Path | None = None
|
107
|
+
) -> Path | None:
|
108
|
+
"""
|
109
|
+
Find a sketch directory by partial name match.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
partial_name: Partial name to match against sketch directories
|
113
|
+
search_dir: Directory to search in (defaults to current directory)
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
Path to the matching sketch directory, or None if no unique match found
|
117
|
+
|
118
|
+
Raises:
|
119
|
+
ValueError: If multiple matches are found with no clear best match, or no matches found
|
120
|
+
"""
|
121
|
+
if search_dir is None:
|
122
|
+
search_dir = Path(".")
|
123
|
+
|
124
|
+
# First, find all sketch directories
|
125
|
+
sketch_directories = find_sketch_directories(search_dir)
|
126
|
+
|
127
|
+
# Normalize the partial name to use forward slashes for cross-platform matching
|
128
|
+
partial_name_normalized = partial_name.replace("\\", "/").lower()
|
129
|
+
|
130
|
+
# Get the set of characters in the partial name for similarity check
|
131
|
+
partial_chars = set(partial_name_normalized)
|
132
|
+
|
133
|
+
# Find matches where the partial name appears in the path
|
134
|
+
matches = []
|
135
|
+
for sketch_dir in sketch_directories:
|
136
|
+
# Normalize the sketch directory path to use forward slashes
|
137
|
+
sketch_str_normalized = str(sketch_dir).replace("\\", "/").lower()
|
138
|
+
|
139
|
+
# Character similarity check: at least 50% of partial name chars must be in target
|
140
|
+
target_chars = set(sketch_str_normalized)
|
141
|
+
matching_chars = partial_chars & target_chars
|
142
|
+
similarity = (
|
143
|
+
len(matching_chars) / len(partial_chars) if len(partial_chars) > 0 else 0
|
144
|
+
)
|
145
|
+
|
146
|
+
# Check if partial_name matches the directory name or any part of the path
|
147
|
+
# AND has sufficient character similarity
|
148
|
+
if partial_name_normalized in sketch_str_normalized and similarity >= 0.5:
|
149
|
+
matches.append(sketch_dir)
|
150
|
+
|
151
|
+
if len(matches) == 0:
|
152
|
+
# Check if this is a total mismatch (low character similarity with all sketches)
|
153
|
+
all_low_similarity = True
|
154
|
+
for sketch_dir in sketch_directories:
|
155
|
+
sketch_str_normalized = str(sketch_dir).replace("\\", "/").lower()
|
156
|
+
target_chars = set(sketch_str_normalized)
|
157
|
+
matching_chars = partial_chars & target_chars
|
158
|
+
similarity = (
|
159
|
+
len(matching_chars) / len(partial_chars)
|
160
|
+
if len(partial_chars) > 0
|
161
|
+
else 0
|
162
|
+
)
|
163
|
+
if similarity > 0.5:
|
164
|
+
all_low_similarity = False
|
165
|
+
break
|
166
|
+
|
167
|
+
if all_low_similarity and len(sketch_directories) > 0:
|
168
|
+
# List all available sketches
|
169
|
+
sketches_str = "\n ".join(str(s) for s in sketch_directories)
|
170
|
+
raise ValueError(
|
171
|
+
f"'{partial_name}' does not look like any of the available sketches.\n\n"
|
172
|
+
f"Available sketches:\n {sketches_str}"
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
raise ValueError(f"No sketch directory found matching '{partial_name}'")
|
176
|
+
elif len(matches) == 1:
|
177
|
+
return matches[0]
|
178
|
+
else:
|
179
|
+
# Multiple matches - try to find the best match
|
180
|
+
# Best match criteria: exact match of the final directory name
|
181
|
+
exact_matches = []
|
182
|
+
for match in matches:
|
183
|
+
# Get the final directory name
|
184
|
+
final_dir_name = match.name.lower()
|
185
|
+
if final_dir_name == partial_name_normalized:
|
186
|
+
exact_matches.append(match)
|
187
|
+
|
188
|
+
if len(exact_matches) == 1:
|
189
|
+
# Found exactly one exact match - this is the best match
|
190
|
+
return exact_matches[0]
|
191
|
+
elif len(exact_matches) > 1:
|
192
|
+
# Multiple exact matches - still ambiguous
|
193
|
+
matches_str = "\n ".join(str(m) for m in exact_matches)
|
194
|
+
raise ValueError(
|
195
|
+
f"Multiple sketch directories found matching '{partial_name}':\n {matches_str}"
|
196
|
+
)
|
197
|
+
else:
|
198
|
+
# No exact match - ambiguous partial matches
|
199
|
+
matches_str = "\n ".join(str(m) for m in matches)
|
200
|
+
raise ValueError(
|
201
|
+
f"Multiple sketch directories found matching '{partial_name}':\n {matches_str}"
|
202
|
+
)
|
fastled/version.py
CHANGED
@@ -1,41 +1,41 @@
|
|
1
|
-
from concurrent.futures import Future, ThreadPoolExecutor
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
|
5
|
-
from fastled.__version__ import __version_url_latest__
|
6
|
-
|
7
|
-
|
8
|
-
def _fetch_version() -> str | Exception:
|
9
|
-
"""
|
10
|
-
Helper function to fetch the latest version from the GitHub repository.
|
11
|
-
"""
|
12
|
-
try:
|
13
|
-
response = httpx.get(__version_url_latest__)
|
14
|
-
response.raise_for_status()
|
15
|
-
# Extract the version string from the response text
|
16
|
-
version_line = response.text.split("__version__ = ")[1].split('"')[1]
|
17
|
-
return version_line
|
18
|
-
except Exception as e:
|
19
|
-
return e
|
20
|
-
|
21
|
-
|
22
|
-
def get_latest_version() -> Future[str | Exception]:
|
23
|
-
"""
|
24
|
-
Fetch the latest version from the GitHub repository.
|
25
|
-
Returns a future that will resolve with the version string or an exception.
|
26
|
-
"""
|
27
|
-
executor = ThreadPoolExecutor()
|
28
|
-
return executor.submit(_fetch_version)
|
29
|
-
|
30
|
-
|
31
|
-
def unit_test() -> None:
|
32
|
-
future = get_latest_version()
|
33
|
-
latest_version = future.result() # Wait for the future to complete
|
34
|
-
if isinstance(latest_version, Exception):
|
35
|
-
print(f"Error fetching latest version: {latest_version}")
|
36
|
-
else:
|
37
|
-
print(f"Latest version: {latest_version}")
|
38
|
-
|
39
|
-
|
40
|
-
if __name__ == "__main__":
|
41
|
-
unit_test()
|
1
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from fastled.__version__ import __version_url_latest__
|
6
|
+
|
7
|
+
|
8
|
+
def _fetch_version() -> str | Exception:
|
9
|
+
"""
|
10
|
+
Helper function to fetch the latest version from the GitHub repository.
|
11
|
+
"""
|
12
|
+
try:
|
13
|
+
response = httpx.get(__version_url_latest__)
|
14
|
+
response.raise_for_status()
|
15
|
+
# Extract the version string from the response text
|
16
|
+
version_line = response.text.split("__version__ = ")[1].split('"')[1]
|
17
|
+
return version_line
|
18
|
+
except Exception as e:
|
19
|
+
return e
|
20
|
+
|
21
|
+
|
22
|
+
def get_latest_version() -> Future[str | Exception]:
|
23
|
+
"""
|
24
|
+
Fetch the latest version from the GitHub repository.
|
25
|
+
Returns a future that will resolve with the version string or an exception.
|
26
|
+
"""
|
27
|
+
executor = ThreadPoolExecutor()
|
28
|
+
return executor.submit(_fetch_version)
|
29
|
+
|
30
|
+
|
31
|
+
def unit_test() -> None:
|
32
|
+
future = get_latest_version()
|
33
|
+
latest_version = future.result() # Wait for the future to complete
|
34
|
+
if isinstance(latest_version, Exception):
|
35
|
+
print(f"Error fetching latest version: {latest_version}")
|
36
|
+
else:
|
37
|
+
print(f"Latest version: {latest_version}")
|
38
|
+
|
39
|
+
|
40
|
+
if __name__ == "__main__":
|
41
|
+
unit_test()
|
fastled/web_compile.py
CHANGED
@@ -437,21 +437,23 @@ def web_compile(
|
|
437
437
|
response, zip_result, start_time, zip_time, libfastled_time, sketch_time
|
438
438
|
)
|
439
439
|
|
440
|
-
except
|
441
|
-
|
440
|
+
except KeyboardInterrupt:
|
441
|
+
print("Keyboard interrupt")
|
442
|
+
raise
|
443
|
+
except (ConnectionError, httpx.RemoteProtocolError, httpx.RequestError) as e:
|
444
|
+
# Handle connection and server disconnection issues
|
445
|
+
error_msg = f"Server connection error: {e}"
|
446
|
+
_print_banner(error_msg)
|
442
447
|
return CompileResult(
|
443
448
|
success=False,
|
444
|
-
stdout=
|
449
|
+
stdout=error_msg,
|
445
450
|
hash_value=None,
|
446
451
|
zip_bytes=b"",
|
447
452
|
zip_time=zip_time,
|
448
453
|
libfastled_time=libfastled_time,
|
449
454
|
sketch_time=sketch_time,
|
450
|
-
response_processing_time=0.0,
|
455
|
+
response_processing_time=0.0,
|
451
456
|
)
|
452
|
-
except KeyboardInterrupt:
|
453
|
-
print("Keyboard interrupt")
|
454
|
-
raise
|
455
457
|
except httpx.HTTPError as e:
|
456
458
|
print(f"Error: {e}")
|
457
459
|
return CompileResult(
|