fastled 1.3.17__py3-none-any.whl → 1.3.19__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,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/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
@@ -28,13 +28,19 @@ else:
28
28
 
29
29
  def _is_dwarf_source(path: str) -> bool:
30
30
  """Check if the path is a dwarf source file."""
31
- # Check if the path starts with "fastledsource/" or "sketchsource/"
32
- return (
33
- path.startswith("fastledsource/")
34
- or path.startswith("sketchsource/")
35
- or path.startswith("/dwarfsource/")
36
- or path.startswith("dwarfsource/")
37
- )
31
+ logger.debug(f"_is_dwarf_source called with path: {path}")
32
+ if "dwarfsource" in path:
33
+ logger.debug(f"Path '{path}' contains 'dwarfsource'")
34
+ return True
35
+ _is_dwarf_source = False
36
+ for p in ["fastledsource", "sketchsource", "dwarfsource", "drawfsource"]:
37
+ if p in path:
38
+ _is_dwarf_source = True
39
+ logger.debug(f"Path '{p}' contains '{path}'")
40
+ break
41
+ if not _is_dwarf_source:
42
+ logger.debug(f"Path '{path}' is not a dwarf source file")
43
+ return _is_dwarf_source
38
44
 
39
45
 
40
46
  def _run_flask_server(
@@ -136,70 +142,15 @@ def _run_flask_server(
136
142
  logger.error(f"Error forwarding request: {e}", exc_info=True)
137
143
  return Response(f"Error: {str(e)}", status=500)
138
144
 
139
- def handle_fastledsource(path: str) -> Response:
140
- """Handle requests to
141
- /fastledsource/js/fastledsource/git/fastled/src/
142
- or
143
- /sketchsource/js/src/Blink.ino
144
-
145
- The names are a bit mangled due to the way C++ prefixing works near the root directory.
146
- """
147
- from flask import request
148
-
149
- start_time = time.time()
150
- logger.info(f"Processing request: {request.method} {request.url}")
151
- # Forward the request to the compile server
152
- target_url = f"http://localhost:{compile_server_port}/dwarfsource/{path}"
153
- logger.info(f"Requesting: {target_url}")
154
- logger.info(f"Processing dwarfsource request for {path}")
155
-
156
- # Log request headers
157
- request_headers = {
158
- key: value for key, value in request.headers if key != "Host"
159
- }
160
- logger.debug(f"Request headers: {request_headers}")
161
-
162
- try:
163
- # Forward the request with the same method, headers, and body
164
- with httpx.Client() as client:
165
- resp = client.request(
166
- method=request.method,
167
- url=target_url,
168
- headers=request_headers,
169
- content=request.get_data(),
170
- cookies=request.cookies,
171
- follow_redirects=True,
172
- )
173
-
174
- logger.info(f"Response status: {resp.status_code}")
175
- logger.debug(f"Response headers: {dict(resp.headers)}")
176
-
177
- # Create a Flask Response object from the httpx response
178
- payload = resp.content
179
- assert isinstance(payload, bytes)
180
-
181
- # Check if the payload is empty
182
- if len(payload) == 0:
183
- logger.error("Empty payload received from compile server")
184
- return Response("Empty payload", status=400)
185
-
186
- response = Response(
187
- payload, status=resp.status_code, headers=dict(resp.headers)
188
- )
189
-
190
- elapsed_time = time.time() - start_time
191
- logger.info(f"Request completed in {elapsed_time:.3f} seconds")
192
-
193
- return response
194
-
195
- except Exception as e:
196
- logger.error(f"Error handling dwarfsource request: {e}", exc_info=True)
197
- return Response(f"Error: {str(e)}", status=500)
198
-
199
- def handle_sourcefile(path: str) -> Response:
145
+ def handle_dwarfsource(path: str) -> Response:
200
146
  """Handle requests to /sourcefiles/*"""
201
147
  from flask import Response, request
202
148
 
149
+ # Request body should be
150
+ # {
151
+ # "path": "string"
152
+ # }
153
+
203
154
  start_time = time.time()
204
155
  logger.info("\n##################################")
205
156
  logger.info(f"# Serving source file /sourcefiles/ {path}")
@@ -208,7 +159,7 @@ def _run_flask_server(
208
159
  logger.info(f"Processing sourcefile request for {path}")
209
160
 
210
161
  # Forward the request to the compile server
211
- target_url = f"http://localhost:{compile_server_port}/{path}"
162
+ target_url = f"http://localhost:{compile_server_port}/dwarfsource"
212
163
  logger.info(f"Forwarding to: {target_url}")
213
164
 
214
165
  # Log request headers
@@ -217,14 +168,18 @@ def _run_flask_server(
217
168
  }
218
169
  logger.debug(f"Request headers: {request_headers}")
219
170
 
171
+ body: dict[str, str] = {
172
+ "path": path,
173
+ }
174
+
220
175
  try:
221
176
  # Forward the request with the same method, headers, and body
222
177
  with httpx.Client() as client:
223
178
  resp = client.request(
224
- method=request.method,
179
+ method="POST",
225
180
  url=target_url,
226
181
  headers=request_headers,
227
- content=request.get_data(),
182
+ json=body,
228
183
  cookies=request.cookies,
229
184
  follow_redirects=True,
230
185
  )
@@ -336,12 +291,10 @@ def _run_flask_server(
336
291
 
337
292
  try:
338
293
  is_debug_src_code_request = _is_dwarf_source(path)
294
+ logger.info(f"is debug_src_code_request: {is_debug_src_code_request}")
339
295
  if is_debug_src_code_request:
340
- logger.info(f"Handling as drawfsource: {path}")
341
- return handle_fastledsource(path)
342
- elif path.startswith("sourcefiles/"):
343
- logger.info(f"Handling as sourcefiles: {path}")
344
- return handle_sourcefile(path)
296
+ logger.info(f"Handling as dwarf source file request: {path}")
297
+ return handle_dwarfsource(path)
345
298
  else:
346
299
  logger.info(f"Handling as local file: {path}")
347
300
  return handle_local_file_fetch(path)