fastled 1.2.84__tar.gz → 1.2.87__tar.gz
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-1.2.84 → fastled-1.2.87}/PKG-INFO +1 -1
- fastled-1.2.87/build_local_docker.py +47 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/compile.py +9 -2
- {fastled-1.2.84 → fastled-1.2.87}/compiler/server.py +27 -1
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/__init__.py +1 -1
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/compile_server_impl.py +6 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/docker_manager.py +6 -0
- fastled-1.2.87/src/fastled/print_filter.py +190 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/server_flask.py +17 -6
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/PKG-INFO +1 -1
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/SOURCES.txt +1 -0
- {fastled-1.2.84 → fastled-1.2.87}/test +8 -2
- fastled-1.2.87/tests/unit/test_experimental_cpp_filter.py +167 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_fetch_source_files.py +13 -3
- fastled-1.2.84/build_local_docker.py +0 -4
- fastled-1.2.84/src/fastled/print_filter.py +0 -75
- {fastled-1.2.84 → fastled-1.2.87}/.aiderignore +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.dockerignore +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/build_multi_docker_image.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/build_webpage.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/lint.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/publish_release.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/template_build_docker_image.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_build_exe.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_macos.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_ubuntu.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_win.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.gitignore +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.pylintrc +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.vscode/launch.json +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.vscode/settings.json +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/.vscode/tasks.json +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/Dockerfile +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/LICENSE +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/MANIFEST.in +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/README.md +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/RELEASE.md +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/TODO.md +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/build_exe.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/build_site.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/clean +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/CMakeLists.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/__init__.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/arduino-pre-process.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/build.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/build_archive.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/build_fast.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/code_sync.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/compile_lock.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/debug.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/entrypoint.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/100dots.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/demo_threejs.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/micdemo.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/mp3upload.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/final_prewarm.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/init_runtime.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/install-arduino-cli.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/libcompile/CMakeLists.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/paths.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/pre-process.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/prewarm.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/process-ino.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/process_extended.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/pyproject.toml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/run.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/sketch_hasher.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/compiler/wasm_compiler_flags.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/docker-compose.yml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/install +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/install_linux.sh +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/lint +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/pyproject.toml +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/requirements.testing.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/setup.cfg +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/setup.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/app.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/example.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/localhost-key.pem +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/localhost.pem +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli_test.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli_test_interactive.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/client_server.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/compile_server.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/filewatcher.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/keyboard.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/keyz.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/live_client.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/open_browser.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/parse_args.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/paths.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/project_init.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/select_sketch_directory.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/server_start.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/settings.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/site/build.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/site/examples.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/sketch.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/spinner.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/string_diff.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/test/can_run_local_docker_tests.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/test/examples.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/types.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/util.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled/web_compile.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/dependency_links.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/entry_points.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/requires.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/top_level.txt +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/integration/test_build_examples.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/integration/test_examples.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/html/index.html +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_api.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_bad_ino.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_cli.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_compile_server.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_docker_linux_on_windows.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_embedded_data.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_filechanger.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_http_server.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad/bad.ino +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/embedded/wasm.ino +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/wasm/wasm.ino +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_print_filter.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_project_init.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_server_and_client_seperatly.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_string_diff.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_version.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_webcompile.py +0 -0
- {fastled-1.2.84 → fastled-1.2.87}/upload_package.sh +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env -S uv run
|
2
|
+
|
3
|
+
# Requirements:
|
4
|
+
# - python-dateutil
|
5
|
+
|
6
|
+
# pyproject.toml
|
7
|
+
# [project]
|
8
|
+
# dependencies = [
|
9
|
+
# "python-dateutil"
|
10
|
+
# ]
|
11
|
+
|
12
|
+
import os
|
13
|
+
import shutil
|
14
|
+
import subprocess
|
15
|
+
|
16
|
+
def _exec(cmd: str) -> int:
|
17
|
+
print(f"$ {cmd}")
|
18
|
+
return subprocess.call(cmd, shell=True)
|
19
|
+
|
20
|
+
# Step 1: Stop containers and remove images
|
21
|
+
_exec("docker compose down --remove-orphans --rmi all")
|
22
|
+
|
23
|
+
# Step 2: Remove all dangling images
|
24
|
+
_exec("docker image prune -f")
|
25
|
+
|
26
|
+
# Step 3: Remove all images matching niteris/fastled-wasm
|
27
|
+
images = subprocess.check_output(
|
28
|
+
"docker images --format '{{.Repository}} {{.ID}}'",
|
29
|
+
shell=True, text=True
|
30
|
+
)
|
31
|
+
|
32
|
+
for line in images.strip().splitlines():
|
33
|
+
repo, image_id = line.split()
|
34
|
+
if repo == "niteris/fastled-wasm":
|
35
|
+
_exec(f"docker rmi {image_id}")
|
36
|
+
|
37
|
+
# Step 4: Optionally remove volumes
|
38
|
+
# _exec("docker volume prune -f") # Uncomment if you want to purge volumes too
|
39
|
+
|
40
|
+
# Step 5: Remove local build artifacts (e.g., PlatformIO)
|
41
|
+
if os.path.exists("build"):
|
42
|
+
print("Removing local build/ directory")
|
43
|
+
shutil.rmtree("build")
|
44
|
+
|
45
|
+
|
46
|
+
# Step 6: Rebuild images
|
47
|
+
_exec("docker compose build")
|
@@ -116,6 +116,11 @@ def _banner(msg: str) -> str:
|
|
116
116
|
return banner
|
117
117
|
|
118
118
|
|
119
|
+
def _print_banner(msg: str) -> None:
|
120
|
+
"""Prints a banner with the given message."""
|
121
|
+
print(_banner(msg))
|
122
|
+
|
123
|
+
|
119
124
|
def _chunked_print(text: str, lines_per_print: int = 10) -> None:
|
120
125
|
"""Prints the text in chunks of the specified size."""
|
121
126
|
lines = text.splitlines()
|
@@ -189,13 +194,15 @@ def compile(
|
|
189
194
|
|
190
195
|
process.wait()
|
191
196
|
|
197
|
+
print(_banner("Compilation process Finsished."))
|
198
|
+
|
192
199
|
if process.returncode == 0:
|
193
|
-
print(
|
200
|
+
print("\nCompilation successful.\n")
|
194
201
|
return 0
|
195
202
|
else:
|
196
203
|
raise subprocess.CalledProcessError(process.returncode, ["pio", "run"])
|
197
204
|
except subprocess.CalledProcessError:
|
198
|
-
print(f"Compilation failed on attempt {attempt}")
|
205
|
+
print(_banner(f"Compilation failed on attempt {attempt}"))
|
199
206
|
if attempt == max_attempts:
|
200
207
|
print("Max attempts reached. Compilation failed.")
|
201
208
|
return 1
|
@@ -77,6 +77,12 @@ _LIVE_GIT_UPDATES_INTERVAL = int(
|
|
77
77
|
_ALLOW_SHUTDOWN = os.environ.get("ALLOW_SHUTDOWN", "false").lower() in ["true", "1"]
|
78
78
|
_NO_SKETCH_CACHE = os.environ.get("NO_SKETCH_CACHE", "false").lower() in ["true", "1"]
|
79
79
|
|
80
|
+
# debug is a 20mb payload for the symbol information.
|
81
|
+
_ONLY_QUICK_BUILDS = os.environ.get("ONLY_QUICK_BUILDS", "false").lower() in [
|
82
|
+
"true",
|
83
|
+
"0",
|
84
|
+
]
|
85
|
+
|
80
86
|
|
81
87
|
# TODO - cleanup
|
82
88
|
_NO_AUTO_UPDATE = (
|
@@ -255,6 +261,12 @@ def compile_source(
|
|
255
261
|
diff = time.time() - epoch
|
256
262
|
print(f" = SERVER {diff:.2f}s = {msg}")
|
257
263
|
|
264
|
+
if build_mode != "quick" and _ONLY_QUICK_BUILDS:
|
265
|
+
raise HTTPException(
|
266
|
+
status_code=400,
|
267
|
+
detail="Only quick builds are allowed in this version.",
|
268
|
+
)
|
269
|
+
|
258
270
|
_print("Starting compile_source")
|
259
271
|
global COMPILE_COUNT
|
260
272
|
global COMPILE_FAILURES
|
@@ -439,7 +451,9 @@ def fetch_file(full_path: Path) -> tuple[bytes, str] | HTTPException:
|
|
439
451
|
raise HTTPException(status_code=404, detail="File not found.")
|
440
452
|
if not full_path.is_file():
|
441
453
|
raise HTTPException(status_code=400, detail="Not a file.")
|
442
|
-
if not full_path.is_relative_to(FASTLED_SRC)
|
454
|
+
if not full_path.is_relative_to(FASTLED_SRC) and not full_path.is_relative_to(
|
455
|
+
"/emsdk"
|
456
|
+
):
|
443
457
|
raise HTTPException(status_code=400, detail="Invalid file path.")
|
444
458
|
|
445
459
|
content = full_path.read_bytes()
|
@@ -664,6 +678,18 @@ async def static_files(file_path: str) -> Response:
|
|
664
678
|
)
|
665
679
|
content, media_type = result
|
666
680
|
return Response(content=content, media_type=media_type)
|
681
|
+
elif file_path.startswith("js/drawfsource/emsdk/"):
|
682
|
+
relative_path = file_path[len("js/drawfsource/emsdk/") :]
|
683
|
+
full_path = Path("/") / "emsdk" / relative_path
|
684
|
+
result: tuple[bytes, str] | HTTPException = fetch_file(full_path=full_path)
|
685
|
+
if isinstance(result, HTTPException):
|
686
|
+
return Response(
|
687
|
+
content=result.detail, # type: ignore
|
688
|
+
media_type="text/plain",
|
689
|
+
status_code=result.status_code, # type: ignore
|
690
|
+
)
|
691
|
+
content, media_type = result
|
692
|
+
return Response(content=content, media_type=media_type)
|
667
693
|
|
668
694
|
# If file not found or path doesn't match expected format
|
669
695
|
return Response(
|
@@ -14,7 +14,7 @@ from .types import BuildMode, CompileResult, CompileServerError, FileResponse
|
|
14
14
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
15
15
|
# that has any other change in the repo. Please bump the version as the
|
16
16
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
17
|
-
__version__ = "1.2.
|
17
|
+
__version__ = "1.2.87"
|
18
18
|
|
19
19
|
|
20
20
|
class Api:
|
@@ -25,6 +25,10 @@ SERVER_OPTIONS = [
|
|
25
25
|
"--no-auto-update", # Don't auto live updates from the git repo.
|
26
26
|
]
|
27
27
|
|
28
|
+
LOCAL_DOCKER_ENV = {
|
29
|
+
"ONLY_QUICK_BUILDS": "0" # When running docker always allow release and debug.
|
30
|
+
}
|
31
|
+
|
28
32
|
|
29
33
|
def _try_get_fastled_src(path: Path) -> Path | None:
|
30
34
|
fastled_src_dir: Path | None = None
|
@@ -310,6 +314,7 @@ class CompileServerImpl:
|
|
310
314
|
ports=ports,
|
311
315
|
volumes=volumes,
|
312
316
|
remove_previous=self.interactive or self.remove_previous or updated,
|
317
|
+
environment=LOCAL_DOCKER_ENV,
|
313
318
|
)
|
314
319
|
self.running_container = self.docker.attach_and_run(container)
|
315
320
|
assert self.running_container is not None, "Container should be running"
|
@@ -330,6 +335,7 @@ class CompileServerImpl:
|
|
330
335
|
command=cmd_str,
|
331
336
|
ports=ports,
|
332
337
|
volumes=volumes,
|
338
|
+
environment=LOCAL_DOCKER_ENV,
|
333
339
|
)
|
334
340
|
|
335
341
|
print("Exiting interactive mode")
|
@@ -533,6 +533,7 @@ class DockerManager:
|
|
533
533
|
volumes: list[Volume] | None = None,
|
534
534
|
ports: dict[int, int] | None = None,
|
535
535
|
remove_previous: bool = False,
|
536
|
+
environment: dict[str, str] | None = None,
|
536
537
|
) -> Container:
|
537
538
|
"""
|
538
539
|
Run a container from an image. If it already exists with matching config, start it.
|
@@ -617,6 +618,7 @@ class DockerManager:
|
|
617
618
|
tty=True,
|
618
619
|
volumes=volumes_dict,
|
619
620
|
ports=ports, # type: ignore
|
621
|
+
environment=environment,
|
620
622
|
remove=True,
|
621
623
|
)
|
622
624
|
return container
|
@@ -629,6 +631,7 @@ class DockerManager:
|
|
629
631
|
command: str | None = None,
|
630
632
|
volumes: list[Volume] | None = None,
|
631
633
|
ports: dict[int, int] | None = None,
|
634
|
+
environment: dict[str, str] | None = None,
|
632
635
|
) -> None:
|
633
636
|
# Convert volumes to the format expected by Docker API
|
634
637
|
volumes = _hack_to_fix_mac(volumes)
|
@@ -663,6 +666,9 @@ class DockerManager:
|
|
663
666
|
if ports:
|
664
667
|
for host_port, container_port in ports.items():
|
665
668
|
docker_command.extend(["-p", f"{host_port}:{container_port}"])
|
669
|
+
if environment:
|
670
|
+
for env_name, env_value in environment.items():
|
671
|
+
docker_command.extend(["-e", f"{env_name}={env_value}"])
|
666
672
|
docker_command.append(f"{image_name}:{tag}")
|
667
673
|
if command:
|
668
674
|
docker_command.append(command)
|
@@ -0,0 +1,190 @@
|
|
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 __str__(self) -> str:
|
98
|
+
return f"{self.timestamp} {self.output_artifact} {self.build_flags} {self.compile_or_link} {self.hash}"
|
99
|
+
|
100
|
+
@staticmethod
|
101
|
+
def parse(input_str: str) -> "BuildArtifact | None":
|
102
|
+
"""
|
103
|
+
Parse a single build-log line of the form:
|
104
|
+
"<timestamp> ... <some .cpp or .h file> ... <flags>"
|
105
|
+
|
106
|
+
Returns a BuildArtifact, or None if parsing failed.
|
107
|
+
"""
|
108
|
+
return _parse(input_str)
|
109
|
+
|
110
|
+
|
111
|
+
class TokenFilter(ABC):
|
112
|
+
@abstractmethod
|
113
|
+
def extract(self, tokens: list[str]) -> str | None:
|
114
|
+
"""
|
115
|
+
Scan `tokens`, remove any tokens this filter is responsible for,
|
116
|
+
and return the extracted string (or None if not found/invalid).
|
117
|
+
"""
|
118
|
+
...
|
119
|
+
|
120
|
+
|
121
|
+
class TimestampFilter(TokenFilter):
|
122
|
+
def extract(self, tokens: list[str]) -> str | None:
|
123
|
+
if not tokens:
|
124
|
+
return None
|
125
|
+
candidate = tokens[0]
|
126
|
+
try:
|
127
|
+
_ = float(candidate)
|
128
|
+
return tokens.pop(0)
|
129
|
+
except ValueError:
|
130
|
+
return None
|
131
|
+
|
132
|
+
|
133
|
+
class InputArtifactFilter(TokenFilter):
|
134
|
+
def extract(self, tokens: list[str]) -> str | None:
|
135
|
+
for i, tok in enumerate(tokens):
|
136
|
+
if tok.endswith(".cpp") or tok.endswith(".h"):
|
137
|
+
return tokens.pop(i)
|
138
|
+
return None
|
139
|
+
|
140
|
+
|
141
|
+
class OutputArtifactFilter(TokenFilter):
|
142
|
+
def extract(self, tokens: list[str]) -> str | None:
|
143
|
+
for i, tok in enumerate(tokens):
|
144
|
+
if tok == "-o" and i + 1 < len(tokens):
|
145
|
+
tokens.pop(i) # drop '-o'
|
146
|
+
return tokens.pop(i) # drop & return artifact
|
147
|
+
return None
|
148
|
+
|
149
|
+
|
150
|
+
class ActionFilter(TokenFilter):
|
151
|
+
def extract(self, tokens: list[str]) -> str | None:
|
152
|
+
if "-c" in tokens:
|
153
|
+
return CompileOrLink.COMPILE.value
|
154
|
+
return CompileOrLink.LINK.value
|
155
|
+
|
156
|
+
|
157
|
+
def _parse(line: str) -> BuildArtifact | None:
|
158
|
+
tokens = line.strip().split()
|
159
|
+
if not tokens:
|
160
|
+
return None
|
161
|
+
|
162
|
+
# instantiate in the order we need them
|
163
|
+
filters: list[TokenFilter] = [
|
164
|
+
TimestampFilter(),
|
165
|
+
InputArtifactFilter(),
|
166
|
+
OutputArtifactFilter(),
|
167
|
+
ActionFilter(),
|
168
|
+
]
|
169
|
+
|
170
|
+
# apply each filter
|
171
|
+
raw_ts = filters[0].extract(tokens)
|
172
|
+
raw_in = filters[1].extract(tokens)
|
173
|
+
raw_out = filters[2].extract(tokens)
|
174
|
+
raw_act = filters[3].extract(tokens)
|
175
|
+
|
176
|
+
if raw_ts is None or raw_in is None or raw_act is None:
|
177
|
+
return None
|
178
|
+
|
179
|
+
# the rest of `tokens` are the flags
|
180
|
+
flags_str = " ".join(tokens)
|
181
|
+
h = zlib.adler32(flags_str.encode("utf-8"))
|
182
|
+
|
183
|
+
return BuildArtifact(
|
184
|
+
timestamp=float(raw_ts),
|
185
|
+
input_artifact=raw_in,
|
186
|
+
output_artifact=raw_out,
|
187
|
+
build_flags=flags_str,
|
188
|
+
compile_or_link=CompileOrLink(raw_act),
|
189
|
+
hash=h,
|
190
|
+
)
|
@@ -23,7 +23,8 @@ else:
|
|
23
23
|
logger = logging.getLogger("flask_server")
|
24
24
|
logger.disabled = True
|
25
25
|
|
26
|
-
|
26
|
+
_DRAWF_SOURCE_FASTLED = "drawfsource/js/fastled/src/"
|
27
|
+
_DRAWF_SOURCE_EMSDK = "drawfsource/js/drawfsource/emsdk/"
|
27
28
|
|
28
29
|
|
29
30
|
def _run_flask_server(
|
@@ -126,24 +127,34 @@ def _run_flask_server(
|
|
126
127
|
return Response(f"Error: {str(e)}", status=500)
|
127
128
|
|
128
129
|
def handle_dwarfsource(path: str) -> Response:
|
129
|
-
"""Handle requests to /drawfsource/js/fastled/src/
|
130
|
+
"""Handle requests to /drawfsource/js/fastled/src/
|
131
|
+
or /drawfsource/js/drawfsource/emsdk/*"""
|
130
132
|
from flask import request
|
131
133
|
|
132
134
|
start_time = time.time()
|
133
135
|
logger.info(f"Processing request: {request.method} {request.url}")
|
134
136
|
|
135
|
-
if
|
137
|
+
if "../" in path:
|
138
|
+
# Prevent directory traversal attacks
|
139
|
+
error_msg = "Directory traversal attack detected"
|
140
|
+
logger.error(error_msg)
|
141
|
+
return Response(error_msg, status=400)
|
142
|
+
|
143
|
+
if not path.startswith(_DRAWF_SOURCE_FASTLED) and not path.startswith(
|
144
|
+
_DRAWF_SOURCE_EMSDK
|
145
|
+
):
|
136
146
|
# unexpected
|
137
147
|
error_msg = f"Unexpected path: {path}"
|
138
148
|
logger.error(error_msg)
|
139
149
|
# Logging disabled
|
140
150
|
return Response("Malformed path", status=400)
|
141
151
|
|
142
|
-
|
143
|
-
|
152
|
+
# Weird magic being played with these paths, it's beyond me.
|
153
|
+
if path.startswith(_DRAWF_SOURCE_FASTLED):
|
154
|
+
path = path[len("/drawfsource") :]
|
144
155
|
|
145
156
|
# Forward the request to the compile server
|
146
|
-
target_url = f"http://localhost:{compile_server_port}/
|
157
|
+
target_url = f"http://localhost:{compile_server_port}/drawfsource/{path}"
|
147
158
|
logger.info(f"Requesting: {target_url}")
|
148
159
|
logger.info(f"Processing dwarfsource request for {path}")
|
149
160
|
|
@@ -112,6 +112,7 @@ tests/unit/test_cli.py
|
|
112
112
|
tests/unit/test_compile_server.py
|
113
113
|
tests/unit/test_docker_linux_on_windows.py
|
114
114
|
tests/unit/test_embedded_data.py
|
115
|
+
tests/unit/test_experimental_cpp_filter.py
|
115
116
|
tests/unit/test_fetch_source_files.py
|
116
117
|
tests/unit/test_filechanger.py
|
117
118
|
tests/unit/test_http_server.py
|
@@ -12,8 +12,14 @@ fi
|
|
12
12
|
|
13
13
|
. ./activate
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# --full in args, then rebuild the docker image
|
16
|
+
if [[ "$*" == *"--full"* ]]; then
|
17
|
+
echo "Rebuilding docker image"
|
18
|
+
echo "Rebuilding from local image"
|
19
|
+
uv run build_local_docker.py
|
20
|
+
fi
|
21
|
+
|
22
|
+
|
17
23
|
|
18
24
|
pytest -x -v -s --durations=0 tests/unit "$@"
|
19
25
|
# uv run pytest -n auto tests -v
|