fastled 1.2.67__py3-none-any.whl → 1.2.68__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 +4 -2
- fastled/compile_server.py +2 -1
- fastled/compile_server_impl.py +17 -6
- fastled/docker_manager.py +5 -5
- fastled/project_init.py +129 -129
- fastled/server_flask.py +152 -152
- fastled/site/build.py +449 -449
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/METADATA +400 -400
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/RECORD +13 -13
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/WHEEL +0 -0
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.2.67.dist-info → fastled-1.2.68.dist-info}/top_level.txt +0 -0
fastled/__init__.py
CHANGED
@@ -13,7 +13,7 @@ from .types import BuildMode, CompileResult, CompileServerError
|
|
13
13
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
14
14
|
# that has any other change in the repo. Please bump the version as the
|
15
15
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
16
|
-
__version__ = "1.2.
|
16
|
+
__version__ = "1.2.68"
|
17
17
|
|
18
18
|
DOCKER_FILE = (
|
19
19
|
"https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
|
@@ -137,7 +137,9 @@ class Docker:
|
|
137
137
|
def is_running() -> bool:
|
138
138
|
from fastled.docker_manager import DockerManager
|
139
139
|
|
140
|
-
|
140
|
+
ok: bool
|
141
|
+
ok, _ = DockerManager.is_running()
|
142
|
+
return ok
|
141
143
|
|
142
144
|
@staticmethod
|
143
145
|
def is_container_running(container_name: str | None = None) -> bool:
|
fastled/compile_server.py
CHANGED
fastled/compile_server_impl.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import subprocess
|
2
2
|
import sys
|
3
3
|
import time
|
4
|
+
import traceback
|
4
5
|
import warnings
|
5
6
|
from datetime import datetime, timezone
|
6
7
|
from pathlib import Path
|
@@ -111,14 +112,24 @@ class CompileServerImpl:
|
|
111
112
|
project_init(example=example, outputdir=outputdir)
|
112
113
|
|
113
114
|
@property
|
114
|
-
def running(self) -> bool:
|
115
|
+
def running(self) -> tuple[bool, Exception | None]:
|
115
116
|
if not self._port:
|
116
|
-
return False
|
117
|
+
return False, Exception("Docker hasn't been initialzed with a port yet")
|
117
118
|
if not DockerManager.is_docker_installed():
|
118
|
-
return False
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
return False, Exception("Docker is not installed")
|
120
|
+
docker_running, err = self.docker.is_running()
|
121
|
+
if not docker_running:
|
122
|
+
IS_MAC = sys.platform == "darwin"
|
123
|
+
if IS_MAC:
|
124
|
+
if "FileNotFoundError" in str(err):
|
125
|
+
traceback.print_exc()
|
126
|
+
print("\n\nNone fatal debug print for MacOS\n")
|
127
|
+
return False, err
|
128
|
+
ok: bool = self.docker.is_container_running(self.container_name)
|
129
|
+
if ok:
|
130
|
+
return True, None
|
131
|
+
else:
|
132
|
+
return False, Exception("Docker is not running")
|
122
133
|
|
123
134
|
def using_fastled_src_dir_volume(self) -> bool:
|
124
135
|
out = self.fastled_src_dir is not None
|
fastled/docker_manager.py
CHANGED
@@ -201,24 +201,24 @@ class DockerManager:
|
|
201
201
|
return False
|
202
202
|
|
203
203
|
@staticmethod
|
204
|
-
def is_running() -> bool:
|
204
|
+
def is_running() -> tuple[bool, Exception | None]:
|
205
205
|
"""Check if Docker is running by pinging the Docker daemon."""
|
206
206
|
|
207
207
|
if not DockerManager.is_docker_installed():
|
208
208
|
print("Docker is not installed.")
|
209
|
-
return False
|
209
|
+
return False, Exception("Docker is not installed.")
|
210
210
|
try:
|
211
211
|
# self.client.ping()
|
212
212
|
client = docker.from_env()
|
213
213
|
client.ping()
|
214
214
|
print("Docker is running.")
|
215
|
-
return True
|
215
|
+
return True, None
|
216
216
|
except DockerException as e:
|
217
217
|
print(f"Docker is not running: {str(e)}")
|
218
|
-
return False
|
218
|
+
return False, e
|
219
219
|
except Exception as e:
|
220
220
|
print(f"Error pinging Docker daemon: {str(e)}")
|
221
|
-
return False
|
221
|
+
return False, e
|
222
222
|
|
223
223
|
def start(self) -> bool:
|
224
224
|
"""Attempt to start Docker Desktop (or the Docker daemon) automatically."""
|
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
@@ -1,152 +1,152 @@
|
|
1
|
-
import argparse
|
2
|
-
from multiprocessing import Process
|
3
|
-
from pathlib import Path
|
4
|
-
|
5
|
-
from livereload import Server
|
6
|
-
|
7
|
-
|
8
|
-
def _run_flask_server(
|
9
|
-
fastled_js: Path,
|
10
|
-
port: int,
|
11
|
-
certfile: Path | None = None,
|
12
|
-
keyfile: Path | None = None,
|
13
|
-
) -> None:
|
14
|
-
"""Run Flask server with live reload in a subprocess
|
15
|
-
|
16
|
-
Args:
|
17
|
-
fastled_js: Path to the fastled_js directory
|
18
|
-
port: Port to run the server on
|
19
|
-
certfile: Path to the SSL certificate file
|
20
|
-
keyfile: Path to the SSL key file
|
21
|
-
"""
|
22
|
-
try:
|
23
|
-
from flask import Flask, send_from_directory
|
24
|
-
|
25
|
-
app = Flask(__name__)
|
26
|
-
|
27
|
-
# Must be a full path or flask will fail to find the file.
|
28
|
-
fastled_js = fastled_js.resolve()
|
29
|
-
|
30
|
-
@app.route("/")
|
31
|
-
def serve_index():
|
32
|
-
return send_from_directory(fastled_js, "index.html")
|
33
|
-
|
34
|
-
@app.route("/<path:path>")
|
35
|
-
def serve_files(path):
|
36
|
-
response = send_from_directory(fastled_js, path)
|
37
|
-
# Some servers don't set the Content-Type header for a bunch of files.
|
38
|
-
if path.endswith(".js"):
|
39
|
-
response.headers["Content-Type"] = "application/javascript"
|
40
|
-
if path.endswith(".css"):
|
41
|
-
response.headers["Content-Type"] = "text/css"
|
42
|
-
if path.endswith(".wasm"):
|
43
|
-
response.headers["Content-Type"] = "application/wasm"
|
44
|
-
if path.endswith(".json"):
|
45
|
-
response.headers["Content-Type"] = "application/json"
|
46
|
-
if path.endswith(".png"):
|
47
|
-
response.headers["Content-Type"] = "image/png"
|
48
|
-
if path.endswith(".jpg"):
|
49
|
-
response.headers["Content-Type"] = "image/jpeg"
|
50
|
-
if path.endswith(".jpeg"):
|
51
|
-
response.headers["Content-Type"] = "image/jpeg"
|
52
|
-
if path.endswith(".gif"):
|
53
|
-
response.headers["Content-Type"] = "image/gif"
|
54
|
-
if path.endswith(".svg"):
|
55
|
-
response.headers["Content-Type"] = "image/svg+xml"
|
56
|
-
if path.endswith(".ico"):
|
57
|
-
response.headers["Content-Type"] = "image/x-icon"
|
58
|
-
if path.endswith(".html"):
|
59
|
-
response.headers["Content-Type"] = "text/html"
|
60
|
-
|
61
|
-
# now also add headers to force no caching
|
62
|
-
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
63
|
-
response.headers["Pragma"] = "no-cache"
|
64
|
-
response.headers["Expires"] = "0"
|
65
|
-
return response
|
66
|
-
|
67
|
-
server = Server(app.wsgi_app)
|
68
|
-
# Watch index.html for changes
|
69
|
-
server.watch(str(fastled_js / "index.html"))
|
70
|
-
# server.watch(str(fastled_js / "index.js"))
|
71
|
-
# server.watch(str(fastled_js / "index.css"))
|
72
|
-
# Start the server
|
73
|
-
server.serve(port=port, debug=True)
|
74
|
-
except KeyboardInterrupt:
|
75
|
-
import _thread
|
76
|
-
|
77
|
-
_thread.interrupt_main()
|
78
|
-
except Exception as e:
|
79
|
-
print(f"Failed to run Flask server: {e}")
|
80
|
-
import _thread
|
81
|
-
|
82
|
-
_thread.interrupt_main()
|
83
|
-
|
84
|
-
|
85
|
-
def run(
|
86
|
-
port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
|
87
|
-
) -> None:
|
88
|
-
"""Run the Flask server."""
|
89
|
-
try:
|
90
|
-
_run_flask_server(cwd, port, certfile, keyfile)
|
91
|
-
import warnings
|
92
|
-
|
93
|
-
warnings.warn("Flask server has stopped")
|
94
|
-
except KeyboardInterrupt:
|
95
|
-
import _thread
|
96
|
-
|
97
|
-
_thread.interrupt_main()
|
98
|
-
pass
|
99
|
-
|
100
|
-
|
101
|
-
def parse_args() -> argparse.Namespace:
|
102
|
-
"""Parse the command line arguments."""
|
103
|
-
parser = argparse.ArgumentParser(
|
104
|
-
description="Open a browser to the fastled_js directory"
|
105
|
-
)
|
106
|
-
parser.add_argument(
|
107
|
-
"fastled_js", type=Path, help="Path to the fastled_js directory"
|
108
|
-
)
|
109
|
-
parser.add_argument(
|
110
|
-
"--port",
|
111
|
-
"-p",
|
112
|
-
type=int,
|
113
|
-
required=True,
|
114
|
-
help="Port to run the server on (default: %(default)s)",
|
115
|
-
)
|
116
|
-
parser.add_argument(
|
117
|
-
"--certfile",
|
118
|
-
type=Path,
|
119
|
-
help="Path to the SSL certificate file for HTTPS",
|
120
|
-
)
|
121
|
-
parser.add_argument(
|
122
|
-
"--keyfile",
|
123
|
-
type=Path,
|
124
|
-
help="Path to the SSL key file for HTTPS",
|
125
|
-
)
|
126
|
-
return parser.parse_args()
|
127
|
-
|
128
|
-
|
129
|
-
def run_flask_server_process(
|
130
|
-
port: int,
|
131
|
-
cwd: Path | None = None,
|
132
|
-
certfile: Path | None = None,
|
133
|
-
keyfile: Path | None = None,
|
134
|
-
) -> Process:
|
135
|
-
"""Run the Flask server in a separate process."""
|
136
|
-
cwd = cwd or Path(".")
|
137
|
-
process = Process(
|
138
|
-
target=run,
|
139
|
-
args=(port, cwd, certfile, keyfile),
|
140
|
-
)
|
141
|
-
process.start()
|
142
|
-
return process
|
143
|
-
|
144
|
-
|
145
|
-
def main() -> None:
|
146
|
-
"""Main function."""
|
147
|
-
args = parse_args()
|
148
|
-
run(args.port, args.fastled_js, args.certfile, args.keyfile)
|
149
|
-
|
150
|
-
|
151
|
-
if __name__ == "__main__":
|
152
|
-
main()
|
1
|
+
import argparse
|
2
|
+
from multiprocessing import Process
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from livereload import Server
|
6
|
+
|
7
|
+
|
8
|
+
def _run_flask_server(
|
9
|
+
fastled_js: Path,
|
10
|
+
port: int,
|
11
|
+
certfile: Path | None = None,
|
12
|
+
keyfile: Path | None = None,
|
13
|
+
) -> None:
|
14
|
+
"""Run Flask server with live reload in a subprocess
|
15
|
+
|
16
|
+
Args:
|
17
|
+
fastled_js: Path to the fastled_js directory
|
18
|
+
port: Port to run the server on
|
19
|
+
certfile: Path to the SSL certificate file
|
20
|
+
keyfile: Path to the SSL key file
|
21
|
+
"""
|
22
|
+
try:
|
23
|
+
from flask import Flask, send_from_directory
|
24
|
+
|
25
|
+
app = Flask(__name__)
|
26
|
+
|
27
|
+
# Must be a full path or flask will fail to find the file.
|
28
|
+
fastled_js = fastled_js.resolve()
|
29
|
+
|
30
|
+
@app.route("/")
|
31
|
+
def serve_index():
|
32
|
+
return send_from_directory(fastled_js, "index.html")
|
33
|
+
|
34
|
+
@app.route("/<path:path>")
|
35
|
+
def serve_files(path):
|
36
|
+
response = send_from_directory(fastled_js, path)
|
37
|
+
# Some servers don't set the Content-Type header for a bunch of files.
|
38
|
+
if path.endswith(".js"):
|
39
|
+
response.headers["Content-Type"] = "application/javascript"
|
40
|
+
if path.endswith(".css"):
|
41
|
+
response.headers["Content-Type"] = "text/css"
|
42
|
+
if path.endswith(".wasm"):
|
43
|
+
response.headers["Content-Type"] = "application/wasm"
|
44
|
+
if path.endswith(".json"):
|
45
|
+
response.headers["Content-Type"] = "application/json"
|
46
|
+
if path.endswith(".png"):
|
47
|
+
response.headers["Content-Type"] = "image/png"
|
48
|
+
if path.endswith(".jpg"):
|
49
|
+
response.headers["Content-Type"] = "image/jpeg"
|
50
|
+
if path.endswith(".jpeg"):
|
51
|
+
response.headers["Content-Type"] = "image/jpeg"
|
52
|
+
if path.endswith(".gif"):
|
53
|
+
response.headers["Content-Type"] = "image/gif"
|
54
|
+
if path.endswith(".svg"):
|
55
|
+
response.headers["Content-Type"] = "image/svg+xml"
|
56
|
+
if path.endswith(".ico"):
|
57
|
+
response.headers["Content-Type"] = "image/x-icon"
|
58
|
+
if path.endswith(".html"):
|
59
|
+
response.headers["Content-Type"] = "text/html"
|
60
|
+
|
61
|
+
# now also add headers to force no caching
|
62
|
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
63
|
+
response.headers["Pragma"] = "no-cache"
|
64
|
+
response.headers["Expires"] = "0"
|
65
|
+
return response
|
66
|
+
|
67
|
+
server = Server(app.wsgi_app)
|
68
|
+
# Watch index.html for changes
|
69
|
+
server.watch(str(fastled_js / "index.html"))
|
70
|
+
# server.watch(str(fastled_js / "index.js"))
|
71
|
+
# server.watch(str(fastled_js / "index.css"))
|
72
|
+
# Start the server
|
73
|
+
server.serve(port=port, debug=True)
|
74
|
+
except KeyboardInterrupt:
|
75
|
+
import _thread
|
76
|
+
|
77
|
+
_thread.interrupt_main()
|
78
|
+
except Exception as e:
|
79
|
+
print(f"Failed to run Flask server: {e}")
|
80
|
+
import _thread
|
81
|
+
|
82
|
+
_thread.interrupt_main()
|
83
|
+
|
84
|
+
|
85
|
+
def run(
|
86
|
+
port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
|
87
|
+
) -> None:
|
88
|
+
"""Run the Flask server."""
|
89
|
+
try:
|
90
|
+
_run_flask_server(cwd, port, certfile, keyfile)
|
91
|
+
import warnings
|
92
|
+
|
93
|
+
warnings.warn("Flask server has stopped")
|
94
|
+
except KeyboardInterrupt:
|
95
|
+
import _thread
|
96
|
+
|
97
|
+
_thread.interrupt_main()
|
98
|
+
pass
|
99
|
+
|
100
|
+
|
101
|
+
def parse_args() -> argparse.Namespace:
|
102
|
+
"""Parse the command line arguments."""
|
103
|
+
parser = argparse.ArgumentParser(
|
104
|
+
description="Open a browser to the fastled_js directory"
|
105
|
+
)
|
106
|
+
parser.add_argument(
|
107
|
+
"fastled_js", type=Path, help="Path to the fastled_js directory"
|
108
|
+
)
|
109
|
+
parser.add_argument(
|
110
|
+
"--port",
|
111
|
+
"-p",
|
112
|
+
type=int,
|
113
|
+
required=True,
|
114
|
+
help="Port to run the server on (default: %(default)s)",
|
115
|
+
)
|
116
|
+
parser.add_argument(
|
117
|
+
"--certfile",
|
118
|
+
type=Path,
|
119
|
+
help="Path to the SSL certificate file for HTTPS",
|
120
|
+
)
|
121
|
+
parser.add_argument(
|
122
|
+
"--keyfile",
|
123
|
+
type=Path,
|
124
|
+
help="Path to the SSL key file for HTTPS",
|
125
|
+
)
|
126
|
+
return parser.parse_args()
|
127
|
+
|
128
|
+
|
129
|
+
def run_flask_server_process(
|
130
|
+
port: int,
|
131
|
+
cwd: Path | None = None,
|
132
|
+
certfile: Path | None = None,
|
133
|
+
keyfile: Path | None = None,
|
134
|
+
) -> Process:
|
135
|
+
"""Run the Flask server in a separate process."""
|
136
|
+
cwd = cwd or Path(".")
|
137
|
+
process = Process(
|
138
|
+
target=run,
|
139
|
+
args=(port, cwd, certfile, keyfile),
|
140
|
+
)
|
141
|
+
process.start()
|
142
|
+
return process
|
143
|
+
|
144
|
+
|
145
|
+
def main() -> None:
|
146
|
+
"""Main function."""
|
147
|
+
args = parse_args()
|
148
|
+
run(args.port, args.fastled_js, args.certfile, args.keyfile)
|
149
|
+
|
150
|
+
|
151
|
+
if __name__ == "__main__":
|
152
|
+
main()
|