fastled 1.2.23__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/__init__.py +352 -0
- fastled/app.py +107 -0
- fastled/assets/example.txt +1 -0
- fastled/cli.py +19 -0
- fastled/client_server.py +401 -0
- fastled/compile_server.py +92 -0
- fastled/compile_server_impl.py +247 -0
- fastled/docker_manager.py +784 -0
- fastled/filewatcher.py +202 -0
- fastled/keyboard.py +116 -0
- fastled/live_client.py +86 -0
- fastled/open_browser.py +161 -0
- fastled/open_browser2.py +111 -0
- fastled/parse_args.py +195 -0
- fastled/paths.py +4 -0
- fastled/project_init.py +129 -0
- fastled/select_sketch_directory.py +35 -0
- fastled/settings.py +13 -0
- fastled/site/build.py +457 -0
- fastled/sketch.py +97 -0
- fastled/spinner.py +34 -0
- fastled/string_diff.py +42 -0
- fastled/test/can_run_local_docker_tests.py +13 -0
- fastled/test/examples.py +49 -0
- fastled/types.py +61 -0
- fastled/util.py +10 -0
- fastled/web_compile.py +285 -0
- fastled-1.2.23.dist-info/LICENSE +21 -0
- fastled-1.2.23.dist-info/METADATA +382 -0
- fastled-1.2.23.dist-info/RECORD +33 -0
- fastled-1.2.23.dist-info/WHEEL +5 -0
- fastled-1.2.23.dist-info/entry_points.txt +4 -0
- fastled-1.2.23.dist-info/top_level.txt +1 -0
fastled/parse_args.py
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
import argparse
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from fastled import __version__
|
7
|
+
from fastled.project_init import project_init
|
8
|
+
from fastled.select_sketch_directory import select_sketch_directory
|
9
|
+
from fastled.settings import DEFAULT_URL, IMAGE_NAME
|
10
|
+
from fastled.sketch import (
|
11
|
+
find_sketch_directories,
|
12
|
+
looks_like_fastled_repo,
|
13
|
+
looks_like_sketch_directory,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
def parse_args() -> argparse.Namespace:
|
18
|
+
"""Parse command-line arguments."""
|
19
|
+
parser = argparse.ArgumentParser(description=f"FastLED WASM Compiler {__version__}")
|
20
|
+
parser.add_argument("--version", action="version", version=f"{__version__}")
|
21
|
+
parser.add_argument(
|
22
|
+
"directory",
|
23
|
+
type=str,
|
24
|
+
nargs="?",
|
25
|
+
default=None,
|
26
|
+
help="Directory containing the FastLED sketch to compile",
|
27
|
+
)
|
28
|
+
parser.add_argument(
|
29
|
+
"--init",
|
30
|
+
nargs="?",
|
31
|
+
const=True,
|
32
|
+
help="Initialize the FastLED sketch in the current directory. Optional name can be provided",
|
33
|
+
)
|
34
|
+
parser.add_argument(
|
35
|
+
"--just-compile",
|
36
|
+
action="store_true",
|
37
|
+
help="Just compile, skip opening the browser and watching for changes.",
|
38
|
+
)
|
39
|
+
parser.add_argument(
|
40
|
+
"--web",
|
41
|
+
"-w",
|
42
|
+
type=str,
|
43
|
+
nargs="?",
|
44
|
+
# const does not seem to be working as expected
|
45
|
+
const=DEFAULT_URL, # Default value when --web is specified without value
|
46
|
+
help="Use web compiler. Optional URL can be provided (default: https://fastled.onrender.com)",
|
47
|
+
)
|
48
|
+
parser.add_argument(
|
49
|
+
"-i",
|
50
|
+
"--interactive",
|
51
|
+
action="store_true",
|
52
|
+
help="Run in interactive mode (Not available with --web)",
|
53
|
+
)
|
54
|
+
parser.add_argument(
|
55
|
+
"--profile",
|
56
|
+
action="store_true",
|
57
|
+
help="Enable profiling for web compilation",
|
58
|
+
)
|
59
|
+
parser.add_argument(
|
60
|
+
"--force-compile",
|
61
|
+
action="store_true",
|
62
|
+
help="Skips the test to see if the current directory is a valid FastLED sketch directory",
|
63
|
+
)
|
64
|
+
parser.add_argument(
|
65
|
+
"--no-auto-updates",
|
66
|
+
action="store_true",
|
67
|
+
help="Disable automatic updates of the wasm compiler image when using docker.",
|
68
|
+
)
|
69
|
+
parser.add_argument(
|
70
|
+
"--update",
|
71
|
+
"--upgrade",
|
72
|
+
action="store_true",
|
73
|
+
help="Update the wasm compiler (if necessary) before running",
|
74
|
+
)
|
75
|
+
parser.add_argument(
|
76
|
+
"--localhost",
|
77
|
+
"--local",
|
78
|
+
"-l",
|
79
|
+
action="store_true",
|
80
|
+
help="Use localhost for web compilation from an instance of fastled --server, creating it if necessary",
|
81
|
+
)
|
82
|
+
parser.add_argument(
|
83
|
+
"--build",
|
84
|
+
"-b",
|
85
|
+
action="store_true",
|
86
|
+
help="Build the wasm compiler image from the FastLED repo",
|
87
|
+
)
|
88
|
+
parser.add_argument(
|
89
|
+
"--server",
|
90
|
+
"-s",
|
91
|
+
action="store_true",
|
92
|
+
help="Run the server in the current directory, volume mapping fastled if we are in the repo",
|
93
|
+
)
|
94
|
+
parser.add_argument(
|
95
|
+
"--purge",
|
96
|
+
action="store_true",
|
97
|
+
help="Remove all FastLED containers and images",
|
98
|
+
)
|
99
|
+
|
100
|
+
build_mode = parser.add_mutually_exclusive_group()
|
101
|
+
build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
|
102
|
+
build_mode.add_argument(
|
103
|
+
"--quick",
|
104
|
+
action="store_true",
|
105
|
+
default=True,
|
106
|
+
help="Build in quick mode (default)",
|
107
|
+
)
|
108
|
+
build_mode.add_argument(
|
109
|
+
"--release", action="store_true", help="Build in release mode"
|
110
|
+
)
|
111
|
+
|
112
|
+
cwd_is_fastled = looks_like_fastled_repo(Path(os.getcwd()))
|
113
|
+
|
114
|
+
args = parser.parse_args()
|
115
|
+
|
116
|
+
if args.purge:
|
117
|
+
from fastled.docker_manager import DockerManager
|
118
|
+
|
119
|
+
docker = DockerManager()
|
120
|
+
docker.purge(IMAGE_NAME)
|
121
|
+
sys.exit(0)
|
122
|
+
|
123
|
+
if args.init:
|
124
|
+
example = args.init if args.init is not True else None
|
125
|
+
try:
|
126
|
+
args.directory = project_init(example, args.directory)
|
127
|
+
except Exception as e:
|
128
|
+
print(f"Failed to initialize project: {e}")
|
129
|
+
sys.exit(1)
|
130
|
+
print("\nInitialized FastLED project in", args.directory)
|
131
|
+
print(f"Use 'fastled {args.directory}' to compile the project.")
|
132
|
+
sys.exit(0)
|
133
|
+
|
134
|
+
if not args.update:
|
135
|
+
if args.no_auto_updates:
|
136
|
+
args.auto_update = False
|
137
|
+
else:
|
138
|
+
args.auto_update = None
|
139
|
+
|
140
|
+
if (
|
141
|
+
not cwd_is_fastled
|
142
|
+
and not args.localhost
|
143
|
+
and not args.web
|
144
|
+
and not args.server
|
145
|
+
):
|
146
|
+
from fastled.docker_manager import DockerManager
|
147
|
+
|
148
|
+
if DockerManager.is_docker_installed():
|
149
|
+
if not DockerManager.ensure_linux_containers_for_windows():
|
150
|
+
print(
|
151
|
+
f"Windows must be in linux containers mode, but is in Windows container mode, Using web compiler at {DEFAULT_URL}."
|
152
|
+
)
|
153
|
+
args.web = DEFAULT_URL
|
154
|
+
else:
|
155
|
+
print(
|
156
|
+
"Docker is installed. Defaulting to --local mode, use --web to override and use the web compiler instead."
|
157
|
+
)
|
158
|
+
args.localhost = True
|
159
|
+
else:
|
160
|
+
print(f"Docker is not installed. Using web compiler at {DEFAULT_URL}.")
|
161
|
+
args.web = DEFAULT_URL
|
162
|
+
if cwd_is_fastled and not args.web and not args.server:
|
163
|
+
print("Forcing --local mode because we are in the FastLED repo")
|
164
|
+
args.localhost = True
|
165
|
+
if args.localhost:
|
166
|
+
args.web = "localhost"
|
167
|
+
if args.interactive and not args.server:
|
168
|
+
print("--interactive forces --server mode")
|
169
|
+
args.server = True
|
170
|
+
if args.directory is None and not args.server:
|
171
|
+
# does current directory look like a sketch?
|
172
|
+
maybe_sketch_dir = Path(os.getcwd())
|
173
|
+
if looks_like_sketch_directory(maybe_sketch_dir):
|
174
|
+
args.directory = str(maybe_sketch_dir)
|
175
|
+
else:
|
176
|
+
print("Searching for sketch directories...")
|
177
|
+
sketch_directories = find_sketch_directories(maybe_sketch_dir)
|
178
|
+
selected_dir = select_sketch_directory(
|
179
|
+
sketch_directories, cwd_is_fastled
|
180
|
+
)
|
181
|
+
if selected_dir:
|
182
|
+
print(f"Using sketch directory: {selected_dir}")
|
183
|
+
args.directory = selected_dir
|
184
|
+
else:
|
185
|
+
print(
|
186
|
+
"\nYou either need to specify a sketch directory or run in --server mode."
|
187
|
+
)
|
188
|
+
sys.exit(1)
|
189
|
+
elif args.directory is not None and os.path.isfile(args.directory):
|
190
|
+
dir_path = Path(args.directory).parent
|
191
|
+
if looks_like_sketch_directory(dir_path):
|
192
|
+
print(f"Using sketch directory: {dir_path}")
|
193
|
+
args.directory = str(dir_path)
|
194
|
+
|
195
|
+
return args
|
fastled/paths.py
ADDED
fastled/project_init.py
ADDED
@@ -0,0 +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()
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from fastled.string_diff import string_diff_paths
|
4
|
+
|
5
|
+
|
6
|
+
def select_sketch_directory(
|
7
|
+
sketch_directories: list[Path], cwd_is_fastled: bool
|
8
|
+
) -> str | None:
|
9
|
+
if cwd_is_fastled:
|
10
|
+
exclude = ["src", "dev", "tests"]
|
11
|
+
for ex in exclude:
|
12
|
+
p = Path(ex)
|
13
|
+
if p in sketch_directories:
|
14
|
+
sketch_directories.remove(p)
|
15
|
+
|
16
|
+
if len(sketch_directories) == 1:
|
17
|
+
print(f"\nUsing sketch directory: {sketch_directories[0]}")
|
18
|
+
return str(sketch_directories[0])
|
19
|
+
elif len(sketch_directories) > 1:
|
20
|
+
print("\nMultiple Directories found, choose one:")
|
21
|
+
for i, sketch_dir in enumerate(sketch_directories):
|
22
|
+
print(f" [{i+1}]: {sketch_dir}")
|
23
|
+
which = input("\nPlease specify a sketch directory: ").strip()
|
24
|
+
try:
|
25
|
+
index = int(which) - 1
|
26
|
+
return str(sketch_directories[index])
|
27
|
+
except (ValueError, IndexError):
|
28
|
+
inputs = [p for p in sketch_directories]
|
29
|
+
top_hits: list[tuple[float, Path]] = string_diff_paths(which, inputs)
|
30
|
+
if len(top_hits) == 1:
|
31
|
+
example = top_hits[0][1]
|
32
|
+
return str(example)
|
33
|
+
else:
|
34
|
+
return select_sketch_directory([p for _, p in top_hits], cwd_is_fastled)
|
35
|
+
return None
|
fastled/settings.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
|
4
|
+
MACHINE = platform.machine().lower()
|
5
|
+
IS_ARM: bool = "arm" in MACHINE or "aarch64" in MACHINE
|
6
|
+
PLATFORM_TAG: str = "-arm64" if IS_ARM else ""
|
7
|
+
CONTAINER_NAME = f"fastled-wasm-compiler{PLATFORM_TAG}"
|
8
|
+
DEFAULT_URL = str(os.environ.get("FASTLED_URL", "https://fastled.onrender.com"))
|
9
|
+
SERVER_PORT = 9021
|
10
|
+
|
11
|
+
IMAGE_NAME = "niteris/fastled-wasm"
|
12
|
+
DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
|
13
|
+
# IMAGE_TAG = "main"
|