fastled 1.4.13__py3-none-any.whl → 1.4.15__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/docker_manager.py +21 -1
- fastled/print_filter.py +52 -52
- fastled/settings.py +45 -3
- fastled/string_diff.py +165 -165
- fastled/version.py +41 -41
- fastled/web_compile.py +16 -6
- {fastled-1.4.13.dist-info → fastled-1.4.15.dist-info}/METADATA +531 -531
- {fastled-1.4.13.dist-info → fastled-1.4.15.dist-info}/RECORD +13 -13
- {fastled-1.4.13.dist-info → fastled-1.4.15.dist-info}/WHEEL +0 -0
- {fastled-1.4.13.dist-info → fastled-1.4.15.dist-info}/entry_points.txt +0 -0
- {fastled-1.4.13.dist-info → fastled-1.4.15.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.4.13.dist-info → fastled-1.4.15.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.15"
|
5
5
|
|
6
6
|
__version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
|
fastled/docker_manager.py
CHANGED
@@ -980,6 +980,7 @@ class DockerManager:
|
|
980
980
|
def purge(self, image_name: str) -> None:
|
981
981
|
"""
|
982
982
|
Remove all containers and images associated with the given image name.
|
983
|
+
Also removes FastLED containers by name pattern (including test containers).
|
983
984
|
|
984
985
|
Args:
|
985
986
|
image_name: The name of the image to purge (without tag)
|
@@ -990,8 +991,27 @@ class DockerManager:
|
|
990
991
|
try:
|
991
992
|
containers = self.client.containers.list(all=True)
|
992
993
|
for container in containers:
|
994
|
+
should_remove = False
|
995
|
+
|
996
|
+
# Check if container uses the specified image
|
993
997
|
if any(image_name in tag for tag in container.image.tags):
|
994
|
-
|
998
|
+
should_remove = True
|
999
|
+
print(
|
1000
|
+
f"Removing container {container.name} (uses image {image_name})"
|
1001
|
+
)
|
1002
|
+
|
1003
|
+
# Also check for FastLED container name patterns (including test containers)
|
1004
|
+
elif any(
|
1005
|
+
pattern in container.name
|
1006
|
+
for pattern in [
|
1007
|
+
"fastled-wasm-container",
|
1008
|
+
"fastled-wasm-container-test",
|
1009
|
+
]
|
1010
|
+
):
|
1011
|
+
should_remove = True
|
1012
|
+
print(f"Removing FastLED container {container.name}")
|
1013
|
+
|
1014
|
+
if should_remove:
|
995
1015
|
container.remove(force=True)
|
996
1016
|
|
997
1017
|
except Exception as e:
|
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"
|
fastled/settings.py
CHANGED
@@ -1,16 +1,58 @@
|
|
1
1
|
import os
|
2
2
|
import platform
|
3
|
+
import sys
|
3
4
|
|
4
5
|
FILE_CHANGED_DEBOUNCE_SECONDS = 2.0
|
5
6
|
MACHINE = platform.machine().lower()
|
6
7
|
IS_ARM: bool = "arm" in MACHINE or "aarch64" in MACHINE
|
7
8
|
PLATFORM_TAG: str = "-arm64" if IS_ARM else ""
|
8
|
-
|
9
|
+
|
10
|
+
|
11
|
+
def _is_running_under_pytest() -> bool:
|
12
|
+
"""Detect if we're running under pytest."""
|
13
|
+
# Check if pytest is in the loaded modules
|
14
|
+
if "pytest" in sys.modules:
|
15
|
+
return True
|
16
|
+
|
17
|
+
# Check for pytest environment variables
|
18
|
+
if "PYTEST_CURRENT_TEST" in os.environ:
|
19
|
+
return True
|
20
|
+
|
21
|
+
return False
|
22
|
+
|
23
|
+
|
24
|
+
def _get_container_name() -> str:
|
25
|
+
"""Get the appropriate container name based on runtime context."""
|
26
|
+
base_name = "fastled-wasm-container"
|
27
|
+
|
28
|
+
if _is_running_under_pytest():
|
29
|
+
# Use test container name when running under pytest
|
30
|
+
return f"{base_name}-test{PLATFORM_TAG}"
|
31
|
+
else:
|
32
|
+
# Use regular container name
|
33
|
+
return f"{base_name}{PLATFORM_TAG}"
|
34
|
+
|
35
|
+
|
36
|
+
def _get_server_port() -> int:
|
37
|
+
"""Get the appropriate server port based on runtime context."""
|
38
|
+
if _is_running_under_pytest():
|
39
|
+
# Use test port when running under pytest to avoid conflicts
|
40
|
+
return 9022
|
41
|
+
else:
|
42
|
+
# Use regular port
|
43
|
+
return 9021
|
44
|
+
|
45
|
+
|
46
|
+
CONTAINER_NAME = _get_container_name()
|
9
47
|
DEFAULT_URL = str(os.environ.get("FASTLED_URL", "https://fastled.onrender.com"))
|
10
|
-
SERVER_PORT =
|
48
|
+
SERVER_PORT = _get_server_port()
|
11
49
|
|
12
50
|
IMAGE_NAME = "niteris/fastled-wasm"
|
13
|
-
DEFAULT_CONTAINER_NAME =
|
51
|
+
DEFAULT_CONTAINER_NAME = (
|
52
|
+
"fastled-wasm-container-test"
|
53
|
+
if _is_running_under_pytest()
|
54
|
+
else "fastled-wasm-container"
|
55
|
+
)
|
14
56
|
# IMAGE_TAG = "latest"
|
15
57
|
|
16
58
|
DOCKER_FILE = (
|
fastled/string_diff.py
CHANGED
@@ -1,165 +1,165 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
|
3
|
-
from rapidfuzz import fuzz
|
4
|
-
|
5
|
-
|
6
|
-
def _filter_out_obvious_bad_choices(
|
7
|
-
input_str: str, string_list: list[str]
|
8
|
-
) -> list[str]:
|
9
|
-
"""
|
10
|
-
Filter out strings that are too different from the input string.
|
11
|
-
This is a heuristic and may not be perfect.
|
12
|
-
"""
|
13
|
-
if not input_str.strip(): # Handle empty input
|
14
|
-
return string_list
|
15
|
-
|
16
|
-
input_chars = set(input_str.lower())
|
17
|
-
filtered_list = []
|
18
|
-
for s in string_list:
|
19
|
-
# Check if at least half of the input characters are in the string
|
20
|
-
s_chars = set(s.lower())
|
21
|
-
common_chars = input_chars.intersection(s_chars)
|
22
|
-
if len(common_chars) >= len(input_chars) / 2:
|
23
|
-
filtered_list.append(s)
|
24
|
-
return filtered_list
|
25
|
-
|
26
|
-
|
27
|
-
def is_in_order_match(input_str: str, other: str) -> bool:
|
28
|
-
"""
|
29
|
-
Check if the input string is an in-order match for any string in the list.
|
30
|
-
An in-order match means that the characters of the input string appear
|
31
|
-
in the same order in the string from the list, ignoring spaces in the input.
|
32
|
-
"""
|
33
|
-
|
34
|
-
# Remove spaces from input string for matching
|
35
|
-
input_chars = [c.lower() for c in input_str if c != " "]
|
36
|
-
other_chars = [c.lower() for c in other]
|
37
|
-
input_index = 0
|
38
|
-
other_index = 0
|
39
|
-
while input_index < len(input_chars) and other_index < len(other_chars):
|
40
|
-
if input_chars[input_index] == other_chars[other_index]:
|
41
|
-
input_index += 1
|
42
|
-
other_index += 1
|
43
|
-
# If we reached the end of the input string, it means all characters were found in order
|
44
|
-
if input_index == len(input_chars):
|
45
|
-
return True
|
46
|
-
return False
|
47
|
-
|
48
|
-
|
49
|
-
# Returns the min distance strings. If there is a tie, it returns
|
50
|
-
# all the strings that have the same min distance.
|
51
|
-
# Returns a tuple of index and string.
|
52
|
-
def string_diff(
|
53
|
-
input_string: str, string_list: list[str], ignore_case=True
|
54
|
-
) -> list[tuple[float, str]]:
|
55
|
-
|
56
|
-
def normalize(s: str) -> str:
|
57
|
-
return s.lower() if ignore_case else s
|
58
|
-
|
59
|
-
# Handle empty input or empty list
|
60
|
-
if not input_string.strip():
|
61
|
-
# Return all strings with equal distance for empty input
|
62
|
-
return [(i, s) for i, s in enumerate(string_list)]
|
63
|
-
|
64
|
-
if not string_list:
|
65
|
-
return []
|
66
|
-
|
67
|
-
map_string: dict[str, str] = {}
|
68
|
-
|
69
|
-
if ignore_case:
|
70
|
-
map_string = {s.lower(): s for s in string_list}
|
71
|
-
else:
|
72
|
-
map_string = {s: s for s in string_list}
|
73
|
-
|
74
|
-
original_string_list = string_list.copy()
|
75
|
-
if ignore_case:
|
76
|
-
string_list = [s.lower() for s in string_list]
|
77
|
-
input_string = input_string.lower()
|
78
|
-
|
79
|
-
# Check for exact matches, but also check if there are other substring matches
|
80
|
-
exact_matches = [s for s in string_list if s == input_string]
|
81
|
-
substring_matches = [s for s in string_list if input_string in s]
|
82
|
-
|
83
|
-
# If there's an exact match AND other substring matches, return all substring matches
|
84
|
-
# This provides better user experience for partial matching
|
85
|
-
if exact_matches and len(substring_matches) > 1:
|
86
|
-
out: list[tuple[float, str]] = []
|
87
|
-
for i, s in enumerate(substring_matches):
|
88
|
-
s_mapped = map_string.get(s, s)
|
89
|
-
out.append((i, s_mapped))
|
90
|
-
return out
|
91
|
-
|
92
|
-
# If there's only an exact match and no other substring matches, return just the exact match
|
93
|
-
if exact_matches and len(substring_matches) == 1:
|
94
|
-
out: list[tuple[float, str]] = []
|
95
|
-
for i, s in enumerate(exact_matches):
|
96
|
-
s_mapped = map_string.get(s, s)
|
97
|
-
out.append((i, s_mapped))
|
98
|
-
return out
|
99
|
-
|
100
|
-
# Apply set membership filtering for queries with 3+ characters
|
101
|
-
if len(input_string.strip()) >= 3:
|
102
|
-
filtered = _filter_out_obvious_bad_choices(input_string, string_list)
|
103
|
-
if filtered: # Only apply filter if it doesn't eliminate everything
|
104
|
-
string_list = filtered
|
105
|
-
|
106
|
-
# Second filter: exact substring filtering if applicable
|
107
|
-
if substring_matches:
|
108
|
-
string_list = substring_matches
|
109
|
-
# Return all substring matches
|
110
|
-
out: list[tuple[float, str]] = []
|
111
|
-
for i, s in enumerate(string_list):
|
112
|
-
s_mapped = map_string.get(s, s)
|
113
|
-
out.append((i, s_mapped))
|
114
|
-
return out
|
115
|
-
|
116
|
-
# Third filter: in order exact match filtering if applicable.
|
117
|
-
in_order_matches = [s for s in string_list if is_in_order_match(input_string, s)]
|
118
|
-
if in_order_matches:
|
119
|
-
string_list = in_order_matches
|
120
|
-
|
121
|
-
# Calculate distances
|
122
|
-
distances: list[float] = []
|
123
|
-
for s in string_list:
|
124
|
-
dist = fuzz.token_sort_ratio(normalize(input_string), normalize(s))
|
125
|
-
distances.append(1.0 / (dist + 1.0))
|
126
|
-
|
127
|
-
# Handle case where no strings remain after filtering
|
128
|
-
if not distances:
|
129
|
-
# Fall back to original list and calculate distances
|
130
|
-
string_list = original_string_list.copy()
|
131
|
-
if ignore_case:
|
132
|
-
string_list = [s.lower() for s in string_list]
|
133
|
-
|
134
|
-
distances = []
|
135
|
-
for s in string_list:
|
136
|
-
dist = fuzz.token_sort_ratio(normalize(input_string), normalize(s))
|
137
|
-
distances.append(1.0 / (dist + 1.0))
|
138
|
-
|
139
|
-
min_distance = min(distances)
|
140
|
-
out: list[tuple[float, str]] = []
|
141
|
-
for i, d in enumerate(distances):
|
142
|
-
if d == min_distance:
|
143
|
-
s = string_list[i]
|
144
|
-
s_mapped = map_string.get(s, s)
|
145
|
-
out.append((i, s_mapped))
|
146
|
-
|
147
|
-
return out
|
148
|
-
|
149
|
-
|
150
|
-
def string_diff_paths(
|
151
|
-
input_string: str | Path, path_list: list[Path], ignore_case=True
|
152
|
-
) -> list[tuple[float, Path]]:
|
153
|
-
# Normalize path separators to forward slashes for consistent comparison
|
154
|
-
string_list = [str(p).replace("\\", "/") for p in path_list]
|
155
|
-
input_str = str(input_string).replace("\\", "/")
|
156
|
-
|
157
|
-
tmp = string_diff(input_str, string_list, ignore_case)
|
158
|
-
out: list[tuple[float, Path]] = []
|
159
|
-
for i, j in tmp:
|
160
|
-
# Find the original path that matches the normalized result
|
161
|
-
for idx, orig_path in enumerate(path_list):
|
162
|
-
if str(orig_path).replace("\\", "/") == j:
|
163
|
-
out.append((i, orig_path))
|
164
|
-
break
|
165
|
-
return out
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from rapidfuzz import fuzz
|
4
|
+
|
5
|
+
|
6
|
+
def _filter_out_obvious_bad_choices(
|
7
|
+
input_str: str, string_list: list[str]
|
8
|
+
) -> list[str]:
|
9
|
+
"""
|
10
|
+
Filter out strings that are too different from the input string.
|
11
|
+
This is a heuristic and may not be perfect.
|
12
|
+
"""
|
13
|
+
if not input_str.strip(): # Handle empty input
|
14
|
+
return string_list
|
15
|
+
|
16
|
+
input_chars = set(input_str.lower())
|
17
|
+
filtered_list = []
|
18
|
+
for s in string_list:
|
19
|
+
# Check if at least half of the input characters are in the string
|
20
|
+
s_chars = set(s.lower())
|
21
|
+
common_chars = input_chars.intersection(s_chars)
|
22
|
+
if len(common_chars) >= len(input_chars) / 2:
|
23
|
+
filtered_list.append(s)
|
24
|
+
return filtered_list
|
25
|
+
|
26
|
+
|
27
|
+
def is_in_order_match(input_str: str, other: str) -> bool:
|
28
|
+
"""
|
29
|
+
Check if the input string is an in-order match for any string in the list.
|
30
|
+
An in-order match means that the characters of the input string appear
|
31
|
+
in the same order in the string from the list, ignoring spaces in the input.
|
32
|
+
"""
|
33
|
+
|
34
|
+
# Remove spaces from input string for matching
|
35
|
+
input_chars = [c.lower() for c in input_str if c != " "]
|
36
|
+
other_chars = [c.lower() for c in other]
|
37
|
+
input_index = 0
|
38
|
+
other_index = 0
|
39
|
+
while input_index < len(input_chars) and other_index < len(other_chars):
|
40
|
+
if input_chars[input_index] == other_chars[other_index]:
|
41
|
+
input_index += 1
|
42
|
+
other_index += 1
|
43
|
+
# If we reached the end of the input string, it means all characters were found in order
|
44
|
+
if input_index == len(input_chars):
|
45
|
+
return True
|
46
|
+
return False
|
47
|
+
|
48
|
+
|
49
|
+
# Returns the min distance strings. If there is a tie, it returns
|
50
|
+
# all the strings that have the same min distance.
|
51
|
+
# Returns a tuple of index and string.
|
52
|
+
def string_diff(
|
53
|
+
input_string: str, string_list: list[str], ignore_case=True
|
54
|
+
) -> list[tuple[float, str]]:
|
55
|
+
|
56
|
+
def normalize(s: str) -> str:
|
57
|
+
return s.lower() if ignore_case else s
|
58
|
+
|
59
|
+
# Handle empty input or empty list
|
60
|
+
if not input_string.strip():
|
61
|
+
# Return all strings with equal distance for empty input
|
62
|
+
return [(i, s) for i, s in enumerate(string_list)]
|
63
|
+
|
64
|
+
if not string_list:
|
65
|
+
return []
|
66
|
+
|
67
|
+
map_string: dict[str, str] = {}
|
68
|
+
|
69
|
+
if ignore_case:
|
70
|
+
map_string = {s.lower(): s for s in string_list}
|
71
|
+
else:
|
72
|
+
map_string = {s: s for s in string_list}
|
73
|
+
|
74
|
+
original_string_list = string_list.copy()
|
75
|
+
if ignore_case:
|
76
|
+
string_list = [s.lower() for s in string_list]
|
77
|
+
input_string = input_string.lower()
|
78
|
+
|
79
|
+
# Check for exact matches, but also check if there are other substring matches
|
80
|
+
exact_matches = [s for s in string_list if s == input_string]
|
81
|
+
substring_matches = [s for s in string_list if input_string in s]
|
82
|
+
|
83
|
+
# If there's an exact match AND other substring matches, return all substring matches
|
84
|
+
# This provides better user experience for partial matching
|
85
|
+
if exact_matches and len(substring_matches) > 1:
|
86
|
+
out: list[tuple[float, str]] = []
|
87
|
+
for i, s in enumerate(substring_matches):
|
88
|
+
s_mapped = map_string.get(s, s)
|
89
|
+
out.append((i, s_mapped))
|
90
|
+
return out
|
91
|
+
|
92
|
+
# If there's only an exact match and no other substring matches, return just the exact match
|
93
|
+
if exact_matches and len(substring_matches) == 1:
|
94
|
+
out: list[tuple[float, str]] = []
|
95
|
+
for i, s in enumerate(exact_matches):
|
96
|
+
s_mapped = map_string.get(s, s)
|
97
|
+
out.append((i, s_mapped))
|
98
|
+
return out
|
99
|
+
|
100
|
+
# Apply set membership filtering for queries with 3+ characters
|
101
|
+
if len(input_string.strip()) >= 3:
|
102
|
+
filtered = _filter_out_obvious_bad_choices(input_string, string_list)
|
103
|
+
if filtered: # Only apply filter if it doesn't eliminate everything
|
104
|
+
string_list = filtered
|
105
|
+
|
106
|
+
# Second filter: exact substring filtering if applicable
|
107
|
+
if substring_matches:
|
108
|
+
string_list = substring_matches
|
109
|
+
# Return all substring matches
|
110
|
+
out: list[tuple[float, str]] = []
|
111
|
+
for i, s in enumerate(string_list):
|
112
|
+
s_mapped = map_string.get(s, s)
|
113
|
+
out.append((i, s_mapped))
|
114
|
+
return out
|
115
|
+
|
116
|
+
# Third filter: in order exact match filtering if applicable.
|
117
|
+
in_order_matches = [s for s in string_list if is_in_order_match(input_string, s)]
|
118
|
+
if in_order_matches:
|
119
|
+
string_list = in_order_matches
|
120
|
+
|
121
|
+
# Calculate distances
|
122
|
+
distances: list[float] = []
|
123
|
+
for s in string_list:
|
124
|
+
dist = fuzz.token_sort_ratio(normalize(input_string), normalize(s))
|
125
|
+
distances.append(1.0 / (dist + 1.0))
|
126
|
+
|
127
|
+
# Handle case where no strings remain after filtering
|
128
|
+
if not distances:
|
129
|
+
# Fall back to original list and calculate distances
|
130
|
+
string_list = original_string_list.copy()
|
131
|
+
if ignore_case:
|
132
|
+
string_list = [s.lower() for s in string_list]
|
133
|
+
|
134
|
+
distances = []
|
135
|
+
for s in string_list:
|
136
|
+
dist = fuzz.token_sort_ratio(normalize(input_string), normalize(s))
|
137
|
+
distances.append(1.0 / (dist + 1.0))
|
138
|
+
|
139
|
+
min_distance = min(distances)
|
140
|
+
out: list[tuple[float, str]] = []
|
141
|
+
for i, d in enumerate(distances):
|
142
|
+
if d == min_distance:
|
143
|
+
s = string_list[i]
|
144
|
+
s_mapped = map_string.get(s, s)
|
145
|
+
out.append((i, s_mapped))
|
146
|
+
|
147
|
+
return out
|
148
|
+
|
149
|
+
|
150
|
+
def string_diff_paths(
|
151
|
+
input_string: str | Path, path_list: list[Path], ignore_case=True
|
152
|
+
) -> list[tuple[float, Path]]:
|
153
|
+
# Normalize path separators to forward slashes for consistent comparison
|
154
|
+
string_list = [str(p).replace("\\", "/") for p in path_list]
|
155
|
+
input_str = str(input_string).replace("\\", "/")
|
156
|
+
|
157
|
+
tmp = string_diff(input_str, string_list, ignore_case)
|
158
|
+
out: list[tuple[float, Path]] = []
|
159
|
+
for i, j in tmp:
|
160
|
+
# Find the original path that matches the normalized result
|
161
|
+
for idx, orig_path in enumerate(path_list):
|
162
|
+
if str(orig_path).replace("\\", "/") == j:
|
163
|
+
out.append((i, orig_path))
|
164
|
+
break
|
165
|
+
return out
|
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
@@ -47,10 +47,10 @@ def _check_embedded_http_status(response_content: bytes) -> tuple[bool, int | No
|
|
47
47
|
lines = content_str.strip().split("\n")
|
48
48
|
if lines:
|
49
49
|
last_line = lines[-1].strip()
|
50
|
-
if
|
51
|
-
# Extract the status code
|
50
|
+
if "HTTP_STATUS:" in last_line:
|
52
51
|
try:
|
53
|
-
|
52
|
+
right = last_line.split("HTTP_STATUS:")[-1].strip()
|
53
|
+
status_code = int(right)
|
54
54
|
return True, status_code
|
55
55
|
except (ValueError, IndexError):
|
56
56
|
# Malformed status line
|
@@ -296,10 +296,20 @@ def web_compile(
|
|
296
296
|
|
297
297
|
# Check HTTP response status first
|
298
298
|
if libfastled_response.status_code != 200:
|
299
|
-
|
300
|
-
|
299
|
+
msg = f"Error: libfastled compilation failed with HTTP status {libfastled_response.status_code}"
|
300
|
+
|
301
|
+
# Error out here, this is a critical error
|
302
|
+
stdout = libfastled_response.content
|
303
|
+
if stdout is not None:
|
304
|
+
stdout = stdout.decode("utf-8", errors="replace") + "\n" + msg
|
305
|
+
else:
|
306
|
+
stdout = msg
|
307
|
+
return CompileResult(
|
308
|
+
success=False,
|
309
|
+
stdout=stdout,
|
310
|
+
hash_value=None,
|
311
|
+
zip_bytes=b"",
|
301
312
|
)
|
302
|
-
# Continue with sketch compilation even if libfastled fails
|
303
313
|
else:
|
304
314
|
# Check for embedded HTTP status in response content
|
305
315
|
has_embedded_status, embedded_status = _check_embedded_http_status(
|