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.
Files changed (135) hide show
  1. {fastled-1.2.84 → fastled-1.2.87}/PKG-INFO +1 -1
  2. fastled-1.2.87/build_local_docker.py +47 -0
  3. {fastled-1.2.84 → fastled-1.2.87}/compiler/compile.py +9 -2
  4. {fastled-1.2.84 → fastled-1.2.87}/compiler/server.py +27 -1
  5. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/__init__.py +1 -1
  6. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/compile_server_impl.py +6 -0
  7. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/docker_manager.py +6 -0
  8. fastled-1.2.87/src/fastled/print_filter.py +190 -0
  9. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/server_flask.py +17 -6
  10. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/PKG-INFO +1 -1
  11. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/SOURCES.txt +1 -0
  12. {fastled-1.2.84 → fastled-1.2.87}/test +8 -2
  13. fastled-1.2.87/tests/unit/test_experimental_cpp_filter.py +167 -0
  14. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_fetch_source_files.py +13 -3
  15. fastled-1.2.84/build_local_docker.py +0 -4
  16. fastled-1.2.84/src/fastled/print_filter.py +0 -75
  17. {fastled-1.2.84 → fastled-1.2.87}/.aiderignore +0 -0
  18. {fastled-1.2.84 → fastled-1.2.87}/.dockerignore +0 -0
  19. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/build_multi_docker_image.yml +0 -0
  20. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/build_webpage.yml +0 -0
  21. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/lint.yml +0 -0
  22. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/publish_release.yml +0 -0
  23. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/template_build_docker_image.yml +0 -0
  24. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_build_exe.yml +0 -0
  25. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_macos.yml +0 -0
  26. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_ubuntu.yml +0 -0
  27. {fastled-1.2.84 → fastled-1.2.87}/.github/workflows/test_win.yml +0 -0
  28. {fastled-1.2.84 → fastled-1.2.87}/.gitignore +0 -0
  29. {fastled-1.2.84 → fastled-1.2.87}/.pylintrc +0 -0
  30. {fastled-1.2.84 → fastled-1.2.87}/.vscode/launch.json +0 -0
  31. {fastled-1.2.84 → fastled-1.2.87}/.vscode/settings.json +0 -0
  32. {fastled-1.2.84 → fastled-1.2.87}/.vscode/tasks.json +0 -0
  33. {fastled-1.2.84 → fastled-1.2.87}/Dockerfile +0 -0
  34. {fastled-1.2.84 → fastled-1.2.87}/LICENSE +0 -0
  35. {fastled-1.2.84 → fastled-1.2.87}/MANIFEST.in +0 -0
  36. {fastled-1.2.84 → fastled-1.2.87}/README.md +0 -0
  37. {fastled-1.2.84 → fastled-1.2.87}/RELEASE.md +0 -0
  38. {fastled-1.2.84 → fastled-1.2.87}/TODO.md +0 -0
  39. {fastled-1.2.84 → fastled-1.2.87}/build_exe.py +0 -0
  40. {fastled-1.2.84 → fastled-1.2.87}/build_site.py +0 -0
  41. {fastled-1.2.84 → fastled-1.2.87}/clean +0 -0
  42. {fastled-1.2.84 → fastled-1.2.87}/compiler/CMakeLists.txt +0 -0
  43. {fastled-1.2.84 → fastled-1.2.87}/compiler/__init__.py +0 -0
  44. {fastled-1.2.84 → fastled-1.2.87}/compiler/arduino-pre-process.sh +0 -0
  45. {fastled-1.2.84 → fastled-1.2.87}/compiler/build.sh +0 -0
  46. {fastled-1.2.84 → fastled-1.2.87}/compiler/build_archive.sh +0 -0
  47. {fastled-1.2.84 → fastled-1.2.87}/compiler/build_fast.sh +0 -0
  48. {fastled-1.2.84 → fastled-1.2.87}/compiler/code_sync.py +0 -0
  49. {fastled-1.2.84 → fastled-1.2.87}/compiler/compile_lock.py +0 -0
  50. {fastled-1.2.84 → fastled-1.2.87}/compiler/debug.sh +0 -0
  51. {fastled-1.2.84 → fastled-1.2.87}/compiler/entrypoint.sh +0 -0
  52. {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/100dots.html +0 -0
  53. {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/demo_threejs.html +0 -0
  54. {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/micdemo.html +0 -0
  55. {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/mp3upload.html +0 -0
  56. {fastled-1.2.84 → fastled-1.2.87}/compiler/extra/webgl_postprocessing_unreal_bloom.html +0 -0
  57. {fastled-1.2.84 → fastled-1.2.87}/compiler/final_prewarm.sh +0 -0
  58. {fastled-1.2.84 → fastled-1.2.87}/compiler/init_runtime.py +0 -0
  59. {fastled-1.2.84 → fastled-1.2.87}/compiler/install-arduino-cli.sh +0 -0
  60. {fastled-1.2.84 → fastled-1.2.87}/compiler/libcompile/CMakeLists.txt +0 -0
  61. {fastled-1.2.84 → fastled-1.2.87}/compiler/paths.py +0 -0
  62. {fastled-1.2.84 → fastled-1.2.87}/compiler/pre-process.sh +0 -0
  63. {fastled-1.2.84 → fastled-1.2.87}/compiler/prewarm.sh +0 -0
  64. {fastled-1.2.84 → fastled-1.2.87}/compiler/process-ino.py +0 -0
  65. {fastled-1.2.84 → fastled-1.2.87}/compiler/process_extended.py +0 -0
  66. {fastled-1.2.84 → fastled-1.2.87}/compiler/pyproject.toml +0 -0
  67. {fastled-1.2.84 → fastled-1.2.87}/compiler/run.py +0 -0
  68. {fastled-1.2.84 → fastled-1.2.87}/compiler/sketch_hasher.py +0 -0
  69. {fastled-1.2.84 → fastled-1.2.87}/compiler/wasm_compiler_flags.py +0 -0
  70. {fastled-1.2.84 → fastled-1.2.87}/docker-compose.yml +0 -0
  71. {fastled-1.2.84 → fastled-1.2.87}/install +0 -0
  72. {fastled-1.2.84 → fastled-1.2.87}/install_linux.sh +0 -0
  73. {fastled-1.2.84 → fastled-1.2.87}/lint +0 -0
  74. {fastled-1.2.84 → fastled-1.2.87}/pyproject.toml +0 -0
  75. {fastled-1.2.84 → fastled-1.2.87}/requirements.testing.txt +0 -0
  76. {fastled-1.2.84 → fastled-1.2.87}/setup.cfg +0 -0
  77. {fastled-1.2.84 → fastled-1.2.87}/setup.py +0 -0
  78. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/app.py +0 -0
  79. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/example.txt +0 -0
  80. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/localhost-key.pem +0 -0
  81. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/assets/localhost.pem +0 -0
  82. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli.py +0 -0
  83. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli_test.py +0 -0
  84. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/cli_test_interactive.py +0 -0
  85. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/client_server.py +0 -0
  86. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/compile_server.py +0 -0
  87. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/filewatcher.py +0 -0
  88. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/keyboard.py +0 -0
  89. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/keyz.py +0 -0
  90. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/live_client.py +0 -0
  91. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/open_browser.py +0 -0
  92. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/parse_args.py +0 -0
  93. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/paths.py +0 -0
  94. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/project_init.py +0 -0
  95. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/select_sketch_directory.py +0 -0
  96. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/server_start.py +0 -0
  97. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/settings.py +0 -0
  98. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/site/build.py +0 -0
  99. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/site/examples.py +0 -0
  100. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/sketch.py +0 -0
  101. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/spinner.py +0 -0
  102. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/string_diff.py +0 -0
  103. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/test/can_run_local_docker_tests.py +0 -0
  104. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/test/examples.py +0 -0
  105. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/types.py +0 -0
  106. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/util.py +0 -0
  107. {fastled-1.2.84 → fastled-1.2.87}/src/fastled/web_compile.py +0 -0
  108. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/dependency_links.txt +0 -0
  109. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/entry_points.txt +0 -0
  110. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/requires.txt +0 -0
  111. {fastled-1.2.84 → fastled-1.2.87}/src/fastled.egg-info/top_level.txt +0 -0
  112. {fastled-1.2.84 → fastled-1.2.87}/tests/integration/test_build_examples.py +0 -0
  113. {fastled-1.2.84 → fastled-1.2.87}/tests/integration/test_examples.py +0 -0
  114. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/html/index.html +0 -0
  115. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_api.py +0 -0
  116. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_bad_ino.py +0 -0
  117. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_cli.py +0 -0
  118. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_compile_server.py +0 -0
  119. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_docker_linux_on_windows.py +0 -0
  120. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_embedded_data.py +0 -0
  121. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_filechanger.py +0 -0
  122. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_http_server.py +0 -0
  123. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad/bad.ino +0 -0
  124. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
  125. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
  126. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
  127. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/embedded/wasm.ino +0 -0
  128. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_ino/wasm/wasm.ino +0 -0
  129. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_print_filter.py +0 -0
  130. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_project_init.py +0 -0
  131. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_server_and_client_seperatly.py +0 -0
  132. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_string_diff.py +0 -0
  133. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_version.py +0 -0
  134. {fastled-1.2.84 → fastled-1.2.87}/tests/unit/test_webcompile.py +0 -0
  135. {fastled-1.2.84 → fastled-1.2.87}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.84
3
+ Version: 1.2.87
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -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(_banner(f"Compilation successful on attempt {attempt}"))
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.84"
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
- _DRAWF_SOURCE_PREFIX = "drawfsource/js/fastled/src/"
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 not path.startswith(_DRAWF_SOURCE_PREFIX):
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
- path = path.replace("drawfsource/js/fastled/src/", "")
143
- logger.info(f"Transformed path: {path}")
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}/sourcefiles/{path}"
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.2.84
3
+ Version: 1.2.87
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -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
- echo "Rebuilding from local image"
16
- uv run build_local_docker.py
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