fastled 1.3.15__py3-none-any.whl → 1.3.17__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/print_filter.py CHANGED
@@ -1,247 +1,52 @@
1
- import re
2
- import zlib
3
- from abc import ABC, abstractmethod
4
- from dataclasses import dataclass
5
- from enum import Enum
6
-
7
-
8
- class PrintFilter(ABC):
9
- """Abstract base class for filtering text output."""
10
-
11
- def __init__(self, echo: bool = True) -> None:
12
- self.echo = echo
13
-
14
- @abstractmethod
15
- def filter(self, text: str) -> str:
16
- """Filter the text according to implementation-specific rules."""
17
- pass
18
-
19
- def print(self, text: str | bytes) -> str:
20
- """Prints the text to the console after filtering."""
21
- if isinstance(text, bytes):
22
- text = text.decode("utf-8")
23
- text = self.filter(text)
24
- if self.echo:
25
- print(text, end="")
26
- return text
27
-
28
-
29
- def _handle_ino_cpp(line: str) -> str:
30
- if ".ino.cpp" in line[0:30]:
31
- # Extract the filename without path and extension
32
- match = re.search(r"src/([^/]+)\.ino\.cpp", line)
33
- if match:
34
- filename = match.group(1)
35
- # Replace with examples/Filename/Filename.ino format
36
- line = line.replace(
37
- f"src/{filename}.ino.cpp", f"examples/{filename}/{filename}.ino"
38
- )
39
- else:
40
- # Fall back to simple extension replacement if regex doesn't match
41
- line = line.replace(".ino.cpp", ".ino")
42
- return line
43
-
44
-
45
- def _handle_fastled_src(line: str) -> str:
46
- return line.replace("fastled/src", "src")
47
-
48
-
49
- class PrintFilterDefault(PrintFilter):
50
- """Provides default filtering for FastLED output."""
51
-
52
- def filter(self, text: str) -> str:
53
- return text
54
-
55
-
56
- class PrintFilterFastled(PrintFilter):
57
- """Provides filtering for FastLED output so that source files match up with local names."""
58
-
59
- def __init__(self, echo: bool = True) -> None:
60
- super().__init__(echo)
61
- self.build_started = False
62
- # self.compile_link_active = False
63
- # self.compile_link_filter:
64
-
65
- def filter(self, text: str) -> str:
66
- lines = text.splitlines()
67
- out: list[str] = []
68
- for line in lines:
69
- ## DEBUG DO NOT SUBMIT
70
- # print(line)
71
- if "# WASM is building" in line:
72
- self.build_started = True
73
- line = _handle_fastled_src(
74
- line
75
- ) # Always convert fastled/src to src for file matchups.
76
- if self.build_started or " error: " in line:
77
- line = _handle_ino_cpp(line)
78
- out.append(line)
79
- text = "\n".join(out)
80
- return text
81
-
82
-
83
- class CompileOrLink(Enum):
84
- COMPILE = "compile"
85
- LINK = "link"
86
-
87
-
88
- @dataclass
89
- class BuildArtifact:
90
- timestamp: float
91
- input_artifact: str | None
92
- output_artifact: str | None
93
- build_flags: str
94
- compile_or_link: CompileOrLink
95
- hash: int
96
-
97
- def flags_pretty(self) -> str:
98
- """
99
- Returns the flags in a pretty format.
100
- This is used for printing the flags to the console.
101
- """
102
- flags = self.build_flags
103
- flags = flags.replace(" -I", "\n-I")
104
- flags = flags.replace(" -D", "\n-D")
105
- flags = flags.replace(" -l", "\n-l")
106
- flags = flags.replace(" -L", "\n-L")
107
- flags = flags.replace(" -o", "\n-o")
108
- flags = flags.replace(" -W", "\n-W")
109
- flags = flags.replace(" -f", "\n-f")
110
- flags = flags.replace(" -g", "\n-g")
111
-
112
- # break into lines and sort
113
- lines = flags.splitlines()
114
- first_line = lines[0]
115
- lines.pop(0) # remove first line
116
- lines = sorted(lines)
117
- # remove duplicates
118
- lines = list(dict.fromkeys(lines))
119
- # remove empty lines
120
- lines = [line for line in lines if line.strip() != ""]
121
- # remove leading and trailing whitespace
122
- lines = [line.strip() for line in lines]
123
- lines = sorted(lines)
124
- lines = [first_line] + lines # add first line back to the beginning
125
- # stringify
126
- flags = "\n".join(lines)
127
- return flags
128
-
129
- def __str__(self) -> str:
130
- return f"{self.brief()} {self.build_flags} {self.compile_or_link} {self.hash}"
131
-
132
- def brief(self) -> str:
133
- return f"{self.timestamp:.2f} {self.output_artifact}"
134
-
135
- def begin_flags(self) -> str:
136
- """
137
- Returns the flags that are used to begin a build.
138
- This is the flags that are used for the first compile or link.
139
- """
140
-
141
- out: str = (
142
- "\n################ NEW COMPILE/LINK FLAG GROUP #####################\n\n"
143
- )
144
- out += f"{self.flags_pretty()}\n"
145
- return out
146
-
147
- def end_flags(self) -> str:
148
- """
149
- Returns the flags that are used to end a build.
150
- This is the flags that are used for the last compile or link.
151
- """
152
- out: str = (
153
- "\n################ END COMPILE/LINK FLAG GROUP #####################\n"
154
- )
155
- return out
156
-
157
- @staticmethod
158
- def parse(input_str: str) -> "BuildArtifact | None":
159
- """
160
- Parse a single build-log line of the form:
161
- "<timestamp> ... <some .cpp or .h file> ... <flags>"
162
-
163
- Returns a BuildArtifact, or None if parsing failed.
164
- """
165
- return _parse(input_str)
166
-
167
-
168
- class TokenFilter(ABC):
169
- @abstractmethod
170
- def extract(self, tokens: list[str]) -> str | None:
171
- """
172
- Scan `tokens`, remove any tokens this filter is responsible for,
173
- and return the extracted string (or None if not found/invalid).
174
- """
175
- ...
176
-
177
-
178
- class TimestampFilter(TokenFilter):
179
- def extract(self, tokens: list[str]) -> str | None:
180
- if not tokens:
181
- return None
182
- candidate = tokens[0]
183
- try:
184
- _ = float(candidate)
185
- return tokens.pop(0)
186
- except ValueError:
187
- return None
188
-
189
-
190
- class InputArtifactFilter(TokenFilter):
191
- def extract(self, tokens: list[str]) -> str | None:
192
- for i, tok in enumerate(tokens):
193
- if tok.endswith(".cpp") or tok.endswith(".h"):
194
- return tokens.pop(i)
195
- return None
196
-
197
-
198
- class OutputArtifactFilter(TokenFilter):
199
- def extract(self, tokens: list[str]) -> str | None:
200
- for i, tok in enumerate(tokens):
201
- if tok == "-o" and i + 1 < len(tokens):
202
- tokens.pop(i) # drop '-o'
203
- return tokens.pop(i) # drop & return artifact
204
- return None
205
-
206
-
207
- class ActionFilter(TokenFilter):
208
- def extract(self, tokens: list[str]) -> str | None:
209
- if "-c" in tokens:
210
- return CompileOrLink.COMPILE.value
211
- return CompileOrLink.LINK.value
212
-
213
-
214
- def _parse(line: str) -> BuildArtifact | None:
215
- tokens = line.strip().split()
216
- if not tokens:
217
- return None
218
-
219
- # instantiate in the order we need them
220
- filters: list[TokenFilter] = [
221
- TimestampFilter(),
222
- InputArtifactFilter(),
223
- OutputArtifactFilter(),
224
- ActionFilter(),
225
- ]
226
-
227
- # apply each filter
228
- raw_ts = filters[0].extract(tokens)
229
- raw_in = filters[1].extract(tokens)
230
- raw_out = filters[2].extract(tokens)
231
- raw_act = filters[3].extract(tokens)
232
-
233
- if raw_ts is None or raw_in is None or raw_act is None:
234
- return None
235
-
236
- # the rest of `tokens` are the flags
237
- flags_str = " ".join(tokens)
238
- h = zlib.adler32(flags_str.encode("utf-8"))
239
-
240
- return BuildArtifact(
241
- timestamp=float(raw_ts),
242
- input_artifact=raw_in,
243
- output_artifact=raw_out,
244
- build_flags=flags_str,
245
- compile_or_link=CompileOrLink(raw_act),
246
- hash=h,
247
- )
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/project_init.py CHANGED
@@ -1,129 +1,129 @@
1
- import _thread
2
- import threading
3
- import time
4
- import zipfile
5
- from pathlib import Path
6
-
7
- import httpx
8
-
9
- from fastled.settings import DEFAULT_URL
10
- from fastled.spinner import Spinner
11
-
12
- DEFAULT_EXAMPLE = "wasm"
13
-
14
-
15
- def get_examples(host: str | None = None) -> list[str]:
16
- host = host or DEFAULT_URL
17
- url_info = f"{host}/info"
18
- response = httpx.get(url_info, timeout=4)
19
- response.raise_for_status()
20
- examples: list[str] = response.json()["examples"]
21
- return sorted(examples)
22
-
23
-
24
- def _prompt_for_example() -> str:
25
- examples = get_examples()
26
- while True:
27
- print("Available examples:")
28
- for i, example in enumerate(examples):
29
- print(f" [{i+1}]: {example}")
30
- answer = input("Enter the example number or name: ").strip()
31
- if answer.isdigit():
32
- example_num = int(answer) - 1
33
- if example_num < 0 or example_num >= len(examples):
34
- print("Invalid example number")
35
- continue
36
- return examples[example_num]
37
- elif answer in examples:
38
- return answer
39
-
40
-
41
- class DownloadThread(threading.Thread):
42
- def __init__(self, url: str, json: str):
43
- super().__init__(daemon=True)
44
- self.url = url
45
- self.json = json
46
- self.bytes_downloaded = 0
47
- self.content: bytes | None = None
48
- self.error: Exception | None = None
49
- self.success = False
50
-
51
- def run(self) -> None:
52
- timeout = httpx.Timeout(5.0, connect=5.0, read=120.0, write=30.0)
53
- try:
54
- with httpx.Client(timeout=timeout) as client:
55
- with client.stream("POST", self.url, json=self.json) as response:
56
- response.raise_for_status()
57
- content = b""
58
- for chunk in response.iter_bytes():
59
- content += chunk
60
- self.bytes_downloaded += len(chunk)
61
- self.content = content
62
- self.success = True
63
- except KeyboardInterrupt:
64
- self.error = RuntimeError("Download cancelled")
65
- _thread.interrupt_main()
66
- except Exception as e:
67
- self.error = e
68
-
69
-
70
- def project_init(
71
- example: str | None = "PROMPT", # prompt for example
72
- outputdir: Path | None = None,
73
- host: str | None = None,
74
- ) -> Path:
75
- """
76
- Initialize a new FastLED project.
77
- """
78
- host = host or DEFAULT_URL
79
- outputdir = Path(outputdir) if outputdir is not None else Path("fastled")
80
- outputdir.mkdir(exist_ok=True, parents=True)
81
- if example == "PROMPT" or example is None:
82
- try:
83
- example = _prompt_for_example()
84
- except httpx.HTTPStatusError:
85
- print(
86
- f"Failed to fetch examples, using default example '{DEFAULT_EXAMPLE}'"
87
- )
88
- example = DEFAULT_EXAMPLE
89
- assert example is not None
90
- endpoint_url = f"{host}/project/init"
91
- json = example
92
- print(f"Initializing project with example '{example}', url={endpoint_url}")
93
-
94
- # Start download thread
95
- download_thread = DownloadThread(endpoint_url, json)
96
- # spinner = Spinner("Downloading project...")
97
- with Spinner(f"Downloading project {example}..."):
98
- download_thread.start()
99
- while download_thread.is_alive():
100
- time.sleep(0.1)
101
-
102
- print() # New line after progress
103
- download_thread.join()
104
-
105
- # Check for errors
106
- if not download_thread.success:
107
- assert download_thread.error is not None
108
- raise download_thread.error
109
-
110
- content = download_thread.content
111
- assert content is not None
112
- tmpzip = outputdir / "fastled.zip"
113
- outputdir.mkdir(exist_ok=True)
114
- tmpzip.write_bytes(content)
115
- with zipfile.ZipFile(tmpzip, "r") as zip_ref:
116
- zip_ref.extractall(outputdir)
117
- tmpzip.unlink()
118
- out = outputdir / example
119
- print(f"Project initialized at {out}")
120
- assert out.exists()
121
- return out
122
-
123
-
124
- def unit_test() -> None:
125
- project_init()
126
-
127
-
128
- if __name__ == "__main__":
129
- unit_test()
1
+ import _thread
2
+ import threading
3
+ import time
4
+ import zipfile
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+
9
+ from fastled.settings import DEFAULT_URL
10
+ from fastled.spinner import Spinner
11
+
12
+ DEFAULT_EXAMPLE = "wasm"
13
+
14
+
15
+ def get_examples(host: str | None = None) -> list[str]:
16
+ host = host or DEFAULT_URL
17
+ url_info = f"{host}/info"
18
+ response = httpx.get(url_info, timeout=4)
19
+ response.raise_for_status()
20
+ examples: list[str] = response.json()["examples"]
21
+ return sorted(examples)
22
+
23
+
24
+ def _prompt_for_example() -> str:
25
+ examples = get_examples()
26
+ while True:
27
+ print("Available examples:")
28
+ for i, example in enumerate(examples):
29
+ print(f" [{i+1}]: {example}")
30
+ answer = input("Enter the example number or name: ").strip()
31
+ if answer.isdigit():
32
+ example_num = int(answer) - 1
33
+ if example_num < 0 or example_num >= len(examples):
34
+ print("Invalid example number")
35
+ continue
36
+ return examples[example_num]
37
+ elif answer in examples:
38
+ return answer
39
+
40
+
41
+ class DownloadThread(threading.Thread):
42
+ def __init__(self, url: str, json: str):
43
+ super().__init__(daemon=True)
44
+ self.url = url
45
+ self.json = json
46
+ self.bytes_downloaded = 0
47
+ self.content: bytes | None = None
48
+ self.error: Exception | None = None
49
+ self.success = False
50
+
51
+ def run(self) -> None:
52
+ timeout = httpx.Timeout(5.0, connect=5.0, read=120.0, write=30.0)
53
+ try:
54
+ with httpx.Client(timeout=timeout) as client:
55
+ with client.stream("POST", self.url, json=self.json) as response:
56
+ response.raise_for_status()
57
+ content = b""
58
+ for chunk in response.iter_bytes():
59
+ content += chunk
60
+ self.bytes_downloaded += len(chunk)
61
+ self.content = content
62
+ self.success = True
63
+ except KeyboardInterrupt:
64
+ self.error = RuntimeError("Download cancelled")
65
+ _thread.interrupt_main()
66
+ except Exception as e:
67
+ self.error = e
68
+
69
+
70
+ def project_init(
71
+ example: str | None = "PROMPT", # prompt for example
72
+ outputdir: Path | None = None,
73
+ host: str | None = None,
74
+ ) -> Path:
75
+ """
76
+ Initialize a new FastLED project.
77
+ """
78
+ host = host or DEFAULT_URL
79
+ outputdir = Path(outputdir) if outputdir is not None else Path("fastled")
80
+ outputdir.mkdir(exist_ok=True, parents=True)
81
+ if example == "PROMPT" or example is None:
82
+ try:
83
+ example = _prompt_for_example()
84
+ except httpx.HTTPStatusError:
85
+ print(
86
+ f"Failed to fetch examples, using default example '{DEFAULT_EXAMPLE}'"
87
+ )
88
+ example = DEFAULT_EXAMPLE
89
+ assert example is not None
90
+ endpoint_url = f"{host}/project/init"
91
+ json = example
92
+ print(f"Initializing project with example '{example}', url={endpoint_url}")
93
+
94
+ # Start download thread
95
+ download_thread = DownloadThread(endpoint_url, json)
96
+ # spinner = Spinner("Downloading project...")
97
+ with Spinner(f"Downloading project {example}..."):
98
+ download_thread.start()
99
+ while download_thread.is_alive():
100
+ time.sleep(0.1)
101
+
102
+ print() # New line after progress
103
+ download_thread.join()
104
+
105
+ # Check for errors
106
+ if not download_thread.success:
107
+ assert download_thread.error is not None
108
+ raise download_thread.error
109
+
110
+ content = download_thread.content
111
+ assert content is not None
112
+ tmpzip = outputdir / "fastled.zip"
113
+ outputdir.mkdir(exist_ok=True)
114
+ tmpzip.write_bytes(content)
115
+ with zipfile.ZipFile(tmpzip, "r") as zip_ref:
116
+ zip_ref.extractall(outputdir)
117
+ tmpzip.unlink()
118
+ out = outputdir / example
119
+ print(f"Project initialized at {out}")
120
+ assert out.exists()
121
+ return out
122
+
123
+
124
+ def unit_test() -> None:
125
+ project_init()
126
+
127
+
128
+ if __name__ == "__main__":
129
+ unit_test()
fastled/server_flask.py CHANGED
@@ -32,7 +32,8 @@ def _is_dwarf_source(path: str) -> bool:
32
32
  return (
33
33
  path.startswith("fastledsource/")
34
34
  or path.startswith("sketchsource/")
35
- or path.startswith("dwarfsource")
35
+ or path.startswith("/dwarfsource/")
36
+ or path.startswith("dwarfsource/")
36
37
  )
37
38
 
38
39