ewt-gen 0.1.0__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.
- ewt_gen-0.1.0/.github/workflows/publish.yml +39 -0
- ewt_gen-0.1.0/.gitignore +5 -0
- ewt_gen-0.1.0/PKG-INFO +131 -0
- ewt_gen-0.1.0/README.md +107 -0
- ewt_gen-0.1.0/pyproject.toml +39 -0
- ewt_gen-0.1.0/src/ewt/__init__.py +3 -0
- ewt_gen-0.1.0/src/ewt/cli.py +348 -0
- ewt_gen-0.1.0/src/ewt/generator.py +74 -0
- ewt_gen-0.1.0/src/ewt/templates/__init__.py +1 -0
- ewt_gen-0.1.0/src/ewt/templates/index.html +223 -0
- ewt_gen-0.1.0/uv.lock +103 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Install uv
|
|
14
|
+
uses: astral-sh/setup-uv@v4
|
|
15
|
+
|
|
16
|
+
- name: Build package
|
|
17
|
+
run: uv build
|
|
18
|
+
|
|
19
|
+
- name: Upload dist
|
|
20
|
+
uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- name: Download dist
|
|
33
|
+
uses: actions/download-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
|
|
38
|
+
- name: Publish to PyPI
|
|
39
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
ewt_gen-0.1.0/.gitignore
ADDED
ewt_gen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ewt-gen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate static websites for ESPHome firmware distribution using ESP Web Tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/esphome/ewt-gen
|
|
6
|
+
Project-URL: Repository, https://github.com/esphome/ewt-gen
|
|
7
|
+
Project-URL: Issues, https://github.com/esphome/ewt-gen/issues
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Keywords: esp32,esp8266,esphome,firmware,web-tools
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Home Automation
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: click>=8.0
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# ewt-gen
|
|
26
|
+
|
|
27
|
+
Generate static websites for ESPHome firmware distribution using ESP Web Tools.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# From a local file
|
|
33
|
+
uvx ewt-gen config.yaml
|
|
34
|
+
|
|
35
|
+
# From a URL
|
|
36
|
+
uvx ewt-gen https://github.com/esphome/firmware/blob/main/esphome-web/esp32.factory.yaml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Run directly without installing (recommended)
|
|
43
|
+
uvx ewt-gen config.yaml
|
|
44
|
+
|
|
45
|
+
# Or install globally
|
|
46
|
+
uv tool install ewt-gen
|
|
47
|
+
ewt-gen config.yaml
|
|
48
|
+
|
|
49
|
+
# Or with pip
|
|
50
|
+
pip install ewt-gen
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# From a local file
|
|
57
|
+
uvx ewt-gen config.yaml
|
|
58
|
+
|
|
59
|
+
# From a GitHub file URL
|
|
60
|
+
uvx ewt-gen https://github.com/user/repo/blob/main/config.yaml
|
|
61
|
+
|
|
62
|
+
# From a GitHub Gist
|
|
63
|
+
uvx ewt-gen https://gist.github.com/user/abc123
|
|
64
|
+
|
|
65
|
+
# From any URL
|
|
66
|
+
uvx ewt-gen https://example.com/config.yaml
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Options
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
ewt-gen [OPTIONS] YAML_SOURCE
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--version Show version
|
|
76
|
+
--skip-compile Skip ESPHome compilation (use existing firmware)
|
|
77
|
+
-f, --firmware PATH Path to firmware binary
|
|
78
|
+
-c, --chip-family [esp32|esp32-c3|esp32-s2|esp32-s3|esp8266]
|
|
79
|
+
Chip family (auto-detected from YAML)
|
|
80
|
+
-o, --output PATH Output directory (defaults to YAML filename)
|
|
81
|
+
-t, --title TEXT Page title (defaults to name from YAML)
|
|
82
|
+
--pre-release Use pre-release ESPHome version via uvx
|
|
83
|
+
--help Show help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Examples
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Basic usage - compiles and generates site
|
|
90
|
+
ewt-gen my-device.yaml
|
|
91
|
+
|
|
92
|
+
# Custom output directory and title
|
|
93
|
+
ewt-gen my-device.yaml -o ./dist -t "My Smart Device"
|
|
94
|
+
|
|
95
|
+
# Use pre-release ESPHome
|
|
96
|
+
ewt-gen my-device.yaml --pre-release
|
|
97
|
+
|
|
98
|
+
# Skip compilation, use existing firmware
|
|
99
|
+
ewt-gen my-device.yaml --skip-compile -f firmware.bin
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Generated Site
|
|
103
|
+
|
|
104
|
+
The tool generates a static website containing:
|
|
105
|
+
|
|
106
|
+
- **ESP Web Tools install button** - One-click firmware installation (requires HTTPS)
|
|
107
|
+
- **Firmware download** - Direct download of the compiled binary
|
|
108
|
+
- **YAML download** - Original ESPHome configuration
|
|
109
|
+
- **Manual installation instructions** - For non-HTTPS contexts, with link to web.esphome.io
|
|
110
|
+
|
|
111
|
+
### HTTPS Requirement
|
|
112
|
+
|
|
113
|
+
Browser-based installation using ESP Web Tools requires a secure context (HTTPS or localhost). When served over HTTP, the page automatically shows manual installation instructions instead.
|
|
114
|
+
|
|
115
|
+
## ESPHome Detection
|
|
116
|
+
|
|
117
|
+
The tool automatically:
|
|
118
|
+
|
|
119
|
+
- Detects chip family from the YAML configuration
|
|
120
|
+
- Finds compiled firmware in `.esphome/build/` directory
|
|
121
|
+
- Uses local `esphome` if available, falls back to `uvx esphome`
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Apache 2.0
|
|
126
|
+
|
|
127
|
+
## Credits
|
|
128
|
+
|
|
129
|
+
- [ESP Web Tools](https://esphome.github.io/esp-web-tools/) - Browser-based firmware installation
|
|
130
|
+
- [ESPHome](https://esphome.io) - Easy ESP8266/ESP32 firmware configuration
|
|
131
|
+
- [Open Home Foundation](https://www.openhomefoundation.org/)
|
ewt_gen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# ewt-gen
|
|
2
|
+
|
|
3
|
+
Generate static websites for ESPHome firmware distribution using ESP Web Tools.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# From a local file
|
|
9
|
+
uvx ewt-gen config.yaml
|
|
10
|
+
|
|
11
|
+
# From a URL
|
|
12
|
+
uvx ewt-gen https://github.com/esphome/firmware/blob/main/esphome-web/esp32.factory.yaml
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Run directly without installing (recommended)
|
|
19
|
+
uvx ewt-gen config.yaml
|
|
20
|
+
|
|
21
|
+
# Or install globally
|
|
22
|
+
uv tool install ewt-gen
|
|
23
|
+
ewt-gen config.yaml
|
|
24
|
+
|
|
25
|
+
# Or with pip
|
|
26
|
+
pip install ewt-gen
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# From a local file
|
|
33
|
+
uvx ewt-gen config.yaml
|
|
34
|
+
|
|
35
|
+
# From a GitHub file URL
|
|
36
|
+
uvx ewt-gen https://github.com/user/repo/blob/main/config.yaml
|
|
37
|
+
|
|
38
|
+
# From a GitHub Gist
|
|
39
|
+
uvx ewt-gen https://gist.github.com/user/abc123
|
|
40
|
+
|
|
41
|
+
# From any URL
|
|
42
|
+
uvx ewt-gen https://example.com/config.yaml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Options
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
ewt-gen [OPTIONS] YAML_SOURCE
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
--version Show version
|
|
52
|
+
--skip-compile Skip ESPHome compilation (use existing firmware)
|
|
53
|
+
-f, --firmware PATH Path to firmware binary
|
|
54
|
+
-c, --chip-family [esp32|esp32-c3|esp32-s2|esp32-s3|esp8266]
|
|
55
|
+
Chip family (auto-detected from YAML)
|
|
56
|
+
-o, --output PATH Output directory (defaults to YAML filename)
|
|
57
|
+
-t, --title TEXT Page title (defaults to name from YAML)
|
|
58
|
+
--pre-release Use pre-release ESPHome version via uvx
|
|
59
|
+
--help Show help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Examples
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Basic usage - compiles and generates site
|
|
66
|
+
ewt-gen my-device.yaml
|
|
67
|
+
|
|
68
|
+
# Custom output directory and title
|
|
69
|
+
ewt-gen my-device.yaml -o ./dist -t "My Smart Device"
|
|
70
|
+
|
|
71
|
+
# Use pre-release ESPHome
|
|
72
|
+
ewt-gen my-device.yaml --pre-release
|
|
73
|
+
|
|
74
|
+
# Skip compilation, use existing firmware
|
|
75
|
+
ewt-gen my-device.yaml --skip-compile -f firmware.bin
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Generated Site
|
|
79
|
+
|
|
80
|
+
The tool generates a static website containing:
|
|
81
|
+
|
|
82
|
+
- **ESP Web Tools install button** - One-click firmware installation (requires HTTPS)
|
|
83
|
+
- **Firmware download** - Direct download of the compiled binary
|
|
84
|
+
- **YAML download** - Original ESPHome configuration
|
|
85
|
+
- **Manual installation instructions** - For non-HTTPS contexts, with link to web.esphome.io
|
|
86
|
+
|
|
87
|
+
### HTTPS Requirement
|
|
88
|
+
|
|
89
|
+
Browser-based installation using ESP Web Tools requires a secure context (HTTPS or localhost). When served over HTTP, the page automatically shows manual installation instructions instead.
|
|
90
|
+
|
|
91
|
+
## ESPHome Detection
|
|
92
|
+
|
|
93
|
+
The tool automatically:
|
|
94
|
+
|
|
95
|
+
- Detects chip family from the YAML configuration
|
|
96
|
+
- Finds compiled firmware in `.esphome/build/` directory
|
|
97
|
+
- Uses local `esphome` if available, falls back to `uvx esphome`
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
Apache 2.0
|
|
102
|
+
|
|
103
|
+
## Credits
|
|
104
|
+
|
|
105
|
+
- [ESP Web Tools](https://esphome.github.io/esp-web-tools/) - Browser-based firmware installation
|
|
106
|
+
- [ESPHome](https://esphome.io) - Easy ESP8266/ESP32 firmware configuration
|
|
107
|
+
- [Open Home Foundation](https://www.openhomefoundation.org/)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ewt-gen"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Generate static websites for ESPHome firmware distribution using ESP Web Tools"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
keywords = ["esphome", "esp32", "esp8266", "firmware", "web-tools"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: Apache Software License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Home Automation",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"click>=8.0",
|
|
23
|
+
"pyyaml>=6.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/esphome/ewt-gen"
|
|
28
|
+
Repository = "https://github.com/esphome/ewt-gen"
|
|
29
|
+
Issues = "https://github.com/esphome/ewt-gen/issues"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
ewt-gen = "ewt.cli:main"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["hatchling"]
|
|
36
|
+
build-backend = "hatchling.build"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/ewt"]
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""CLI interface for EWT."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import urllib.request
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from ewt.generator import generate_site
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.version_option()
|
|
19
|
+
@click.argument("yaml_source")
|
|
20
|
+
@click.option(
|
|
21
|
+
"--skip-compile",
|
|
22
|
+
is_flag=True,
|
|
23
|
+
help="Skip ESPHome compilation (use existing firmware).",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--firmware",
|
|
27
|
+
"-f",
|
|
28
|
+
type=click.Path(exists=True, path_type=Path),
|
|
29
|
+
help="Path to firmware binary. If not specified, uses ESPHome build output.",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--chip-family",
|
|
33
|
+
"-c",
|
|
34
|
+
type=click.Choice(
|
|
35
|
+
["ESP32", "ESP32-C3", "ESP32-S2", "ESP32-S3", "ESP8266"],
|
|
36
|
+
case_sensitive=False,
|
|
37
|
+
),
|
|
38
|
+
help="Chip family. Auto-detected from YAML if not specified.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--output",
|
|
42
|
+
"-o",
|
|
43
|
+
type=click.Path(path_type=Path),
|
|
44
|
+
help="Output directory. Defaults to YAML filename without extension.",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--title",
|
|
48
|
+
"-t",
|
|
49
|
+
help="Page title. Defaults to name from YAML file.",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--pre-release",
|
|
53
|
+
is_flag=True,
|
|
54
|
+
help="Use pre-release ESPHome version (uvx only, forces refresh).",
|
|
55
|
+
)
|
|
56
|
+
def main(
|
|
57
|
+
yaml_source: str,
|
|
58
|
+
skip_compile: bool,
|
|
59
|
+
firmware: Path | None,
|
|
60
|
+
chip_family: str | None,
|
|
61
|
+
output: Path | None,
|
|
62
|
+
title: str | None,
|
|
63
|
+
pre_release: bool,
|
|
64
|
+
):
|
|
65
|
+
"""Generate a static website for firmware distribution.
|
|
66
|
+
|
|
67
|
+
YAML_SOURCE is the ESPHome configuration file path or URL.
|
|
68
|
+
"""
|
|
69
|
+
yaml_file, was_downloaded = resolve_yaml_source(yaml_source)
|
|
70
|
+
|
|
71
|
+
# Load YAML to get configuration info (with ESPHome tag support)
|
|
72
|
+
with open(yaml_file) as f:
|
|
73
|
+
config = load_esphome_yaml(f)
|
|
74
|
+
|
|
75
|
+
# Get substitutions for variable expansion
|
|
76
|
+
substitutions = config.get("substitutions", {})
|
|
77
|
+
|
|
78
|
+
def expand_substitutions(value: str) -> str:
|
|
79
|
+
"""Expand ${var} substitutions in a string."""
|
|
80
|
+
if not isinstance(value, str):
|
|
81
|
+
return value
|
|
82
|
+
for key, sub_value in substitutions.items():
|
|
83
|
+
value = value.replace(f"${{{key}}}", str(sub_value))
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
# Determine project name
|
|
87
|
+
esphome_config = config.get("esphome", {})
|
|
88
|
+
project_name = expand_substitutions(esphome_config.get("name", "")) or yaml_file.stem
|
|
89
|
+
|
|
90
|
+
# Determine title
|
|
91
|
+
if not title:
|
|
92
|
+
title = expand_substitutions(esphome_config.get("friendly_name", "")) or project_name
|
|
93
|
+
|
|
94
|
+
# Compile with ESPHome if needed
|
|
95
|
+
if not skip_compile and firmware is None:
|
|
96
|
+
click.echo(f"Compiling {yaml_file.name} with ESPHome...")
|
|
97
|
+
compile_with_esphome(yaml_file, pre_release=pre_release)
|
|
98
|
+
|
|
99
|
+
# Find firmware binary
|
|
100
|
+
if firmware is None:
|
|
101
|
+
firmware = find_firmware(yaml_file, project_name)
|
|
102
|
+
|
|
103
|
+
if firmware is None:
|
|
104
|
+
raise click.ClickException(
|
|
105
|
+
f"Could not find firmware binary. Please specify with --firmware option.\n"
|
|
106
|
+
f"Looked for: {yaml_file.stem}.bin, .esphome/build/{project_name}/.pioenvs/*/firmware.bin"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
firmware = firmware.resolve()
|
|
110
|
+
|
|
111
|
+
# Determine chip family
|
|
112
|
+
if chip_family is None:
|
|
113
|
+
chip_family = detect_chip_family(config)
|
|
114
|
+
|
|
115
|
+
if chip_family is None:
|
|
116
|
+
raise click.ClickException(
|
|
117
|
+
"Could not detect chip family from YAML. Please specify with --chip-family option."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Normalize chip family
|
|
121
|
+
chip_family = normalize_chip_family(chip_family)
|
|
122
|
+
|
|
123
|
+
# Determine output directory
|
|
124
|
+
if output is None:
|
|
125
|
+
output = Path.cwd() / yaml_file.stem
|
|
126
|
+
|
|
127
|
+
output = output.resolve()
|
|
128
|
+
|
|
129
|
+
click.echo(f"Generating static site for {project_name}")
|
|
130
|
+
click.echo(f" YAML: {yaml_file}")
|
|
131
|
+
click.echo(f" Firmware: {firmware}")
|
|
132
|
+
click.echo(f" Chip: {chip_family}")
|
|
133
|
+
click.echo(f" Output: {output}")
|
|
134
|
+
|
|
135
|
+
generate_site(
|
|
136
|
+
output_dir=output,
|
|
137
|
+
yaml_file=yaml_file,
|
|
138
|
+
firmware_file=firmware,
|
|
139
|
+
chip_family=chip_family,
|
|
140
|
+
title=title,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Clean up downloaded YAML (it's already copied to output)
|
|
144
|
+
if was_downloaded:
|
|
145
|
+
yaml_file.unlink()
|
|
146
|
+
|
|
147
|
+
click.echo(f"\nStatic site generated at: {output}")
|
|
148
|
+
click.echo("Serve with any static file server (must be HTTPS for ESP Web Tools)")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def load_esphome_yaml(stream):
|
|
152
|
+
"""Load ESPHome YAML with support for custom tags like !lambda, !secret, etc."""
|
|
153
|
+
class ESPHomeLoader(yaml.SafeLoader):
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
# Handle all unknown tags by returning the value as-is
|
|
157
|
+
def constructor_undefined(loader, tag_suffix, node):
|
|
158
|
+
if isinstance(node, yaml.ScalarNode):
|
|
159
|
+
return loader.construct_scalar(node)
|
|
160
|
+
if isinstance(node, yaml.SequenceNode):
|
|
161
|
+
return loader.construct_sequence(node)
|
|
162
|
+
if isinstance(node, yaml.MappingNode):
|
|
163
|
+
return loader.construct_mapping(node)
|
|
164
|
+
|
|
165
|
+
ESPHomeLoader.add_multi_constructor("!", constructor_undefined)
|
|
166
|
+
|
|
167
|
+
return yaml.load(stream, Loader=ESPHomeLoader)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def resolve_yaml_source(source: str) -> tuple[Path, bool]:
|
|
171
|
+
"""Resolve a YAML source (file path or URL) to a local file path.
|
|
172
|
+
|
|
173
|
+
Returns (path, was_downloaded) tuple.
|
|
174
|
+
"""
|
|
175
|
+
# Check if it's a URL
|
|
176
|
+
if source.startswith(("http://", "https://")):
|
|
177
|
+
return download_yaml(source), True
|
|
178
|
+
|
|
179
|
+
# It's a local file path
|
|
180
|
+
path = Path(source)
|
|
181
|
+
if not path.exists():
|
|
182
|
+
raise click.ClickException(f"File not found: {source}")
|
|
183
|
+
return path.resolve(), False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def download_yaml(url: str) -> Path:
|
|
187
|
+
"""Download YAML from a URL and save to a temporary file."""
|
|
188
|
+
# Convert GitHub blob URLs to raw URLs
|
|
189
|
+
url = convert_to_raw_url(url)
|
|
190
|
+
|
|
191
|
+
click.echo(f"Downloading {url}...")
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ewt"})
|
|
195
|
+
with urllib.request.urlopen(req) as response:
|
|
196
|
+
content = response.read().decode("utf-8")
|
|
197
|
+
except urllib.error.URLError as e:
|
|
198
|
+
raise click.ClickException(f"Failed to download {url}: {e}")
|
|
199
|
+
|
|
200
|
+
# Extract filename from URL
|
|
201
|
+
parsed = urlparse(url)
|
|
202
|
+
filename = Path(parsed.path).name
|
|
203
|
+
if not filename.endswith((".yaml", ".yml")):
|
|
204
|
+
filename = "config.yaml"
|
|
205
|
+
|
|
206
|
+
# Save to temp file in current directory (so .esphome is created here)
|
|
207
|
+
yaml_file = Path.cwd() / filename
|
|
208
|
+
yaml_file.write_text(content)
|
|
209
|
+
|
|
210
|
+
return yaml_file
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def convert_to_raw_url(url: str) -> str:
|
|
214
|
+
"""Convert GitHub/Gist URLs to raw content URLs."""
|
|
215
|
+
# GitHub blob URL: https://github.com/user/repo/blob/branch/path/file.yaml
|
|
216
|
+
# -> https://raw.githubusercontent.com/user/repo/branch/path/file.yaml
|
|
217
|
+
github_blob = re.match(
|
|
218
|
+
r"https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+)", url
|
|
219
|
+
)
|
|
220
|
+
if github_blob:
|
|
221
|
+
user, repo, branch, path = github_blob.groups()
|
|
222
|
+
return f"https://raw.githubusercontent.com/{user}/{repo}/{branch}/{path}"
|
|
223
|
+
|
|
224
|
+
# GitHub Gist URL: https://gist.github.com/user/gist_id
|
|
225
|
+
# or https://gist.github.com/user/gist_id#file-filename-yaml
|
|
226
|
+
# -> https://gist.githubusercontent.com/user/gist_id/raw/filename.yaml
|
|
227
|
+
gist_match = re.match(
|
|
228
|
+
r"https://gist\.github\.com/([^/]+)/([^/#]+)(?:#file-(.+))?", url
|
|
229
|
+
)
|
|
230
|
+
if gist_match:
|
|
231
|
+
user, gist_id, file_fragment = gist_match.groups()
|
|
232
|
+
if file_fragment:
|
|
233
|
+
# Convert file-name-yaml to name.yaml
|
|
234
|
+
filename = file_fragment.replace("-", ".")
|
|
235
|
+
# Fix double dots from extension
|
|
236
|
+
filename = re.sub(r"\.([^.]+)$", lambda m: "." + m.group(1), filename)
|
|
237
|
+
return f"https://gist.githubusercontent.com/{user}/{gist_id}/raw/{filename}"
|
|
238
|
+
return f"https://gist.githubusercontent.com/{user}/{gist_id}/raw"
|
|
239
|
+
|
|
240
|
+
# Already a raw URL or other URL, return as-is
|
|
241
|
+
return url
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def compile_with_esphome(yaml_file: Path, *, pre_release: bool = False) -> None:
|
|
245
|
+
"""Compile the ESPHome configuration."""
|
|
246
|
+
cwd = yaml_file.parent
|
|
247
|
+
|
|
248
|
+
# If pre-release requested, must use uvx
|
|
249
|
+
if pre_release:
|
|
250
|
+
if not shutil.which("uvx"):
|
|
251
|
+
raise click.ClickException(
|
|
252
|
+
"uvx not found. Please install uv to use --pre-release."
|
|
253
|
+
)
|
|
254
|
+
cmd = ["uvx", "--prerelease", "allow", "--refresh", "esphome", "compile", str(yaml_file)]
|
|
255
|
+
else:
|
|
256
|
+
# Try local esphome first, fall back to uvx
|
|
257
|
+
if shutil.which("esphome"):
|
|
258
|
+
cmd = ["esphome", "compile", str(yaml_file)]
|
|
259
|
+
elif shutil.which("uvx"):
|
|
260
|
+
cmd = ["uvx", "esphome", "compile", str(yaml_file)]
|
|
261
|
+
else:
|
|
262
|
+
raise click.ClickException(
|
|
263
|
+
"ESPHome not found. Please install ESPHome or uv:\n"
|
|
264
|
+
" pip install esphome\n"
|
|
265
|
+
"Or use --skip-compile with --firmware to provide a pre-built binary."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
result = subprocess.run(cmd, cwd=cwd)
|
|
269
|
+
if result.returncode != 0:
|
|
270
|
+
raise click.ClickException(
|
|
271
|
+
f"ESPHome compilation failed with exit code {result.returncode}"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def find_firmware(yaml_file: Path, project_name: str) -> Path | None:
|
|
276
|
+
"""Try to find the firmware binary for the given YAML file."""
|
|
277
|
+
yaml_dir = yaml_file.parent
|
|
278
|
+
|
|
279
|
+
# Try same name with .bin extension
|
|
280
|
+
bin_file = yaml_dir / f"{yaml_file.stem}.bin"
|
|
281
|
+
if bin_file.exists():
|
|
282
|
+
return bin_file
|
|
283
|
+
|
|
284
|
+
# Try ESPHome build directory
|
|
285
|
+
esphome_build_dir = yaml_dir / ".esphome" / "build" / project_name / ".pioenvs"
|
|
286
|
+
if esphome_build_dir.exists():
|
|
287
|
+
# Look for firmware.bin in any subdirectory
|
|
288
|
+
for subdir in esphome_build_dir.iterdir():
|
|
289
|
+
if subdir.is_dir():
|
|
290
|
+
fw = subdir / "firmware.bin"
|
|
291
|
+
if fw.exists():
|
|
292
|
+
return fw
|
|
293
|
+
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def detect_chip_family(config: dict) -> str | None:
|
|
298
|
+
"""Try to detect chip family from ESPHome config."""
|
|
299
|
+
# Check for esp32 platform
|
|
300
|
+
if "esp32" in config:
|
|
301
|
+
esp32_config = config["esp32"]
|
|
302
|
+
board = esp32_config.get("board", "")
|
|
303
|
+
variant = esp32_config.get("variant", "").upper()
|
|
304
|
+
|
|
305
|
+
# Check variant first
|
|
306
|
+
if variant:
|
|
307
|
+
if variant in ("ESP32C3", "ESP32-C3"):
|
|
308
|
+
return "ESP32-C3"
|
|
309
|
+
if variant in ("ESP32S2", "ESP32-S2"):
|
|
310
|
+
return "ESP32-S2"
|
|
311
|
+
if variant in ("ESP32S3", "ESP32-S3"):
|
|
312
|
+
return "ESP32-S3"
|
|
313
|
+
|
|
314
|
+
# Check board names for variants
|
|
315
|
+
board_lower = board.lower()
|
|
316
|
+
if "c3" in board_lower:
|
|
317
|
+
return "ESP32-C3"
|
|
318
|
+
if "s2" in board_lower:
|
|
319
|
+
return "ESP32-S2"
|
|
320
|
+
if "s3" in board_lower:
|
|
321
|
+
return "ESP32-S3"
|
|
322
|
+
|
|
323
|
+
return "ESP32"
|
|
324
|
+
|
|
325
|
+
# Check for esp8266 platform
|
|
326
|
+
if "esp8266" in config:
|
|
327
|
+
return "ESP8266"
|
|
328
|
+
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def normalize_chip_family(chip_family: str) -> str:
|
|
333
|
+
"""Normalize chip family string."""
|
|
334
|
+
mapping = {
|
|
335
|
+
"esp32": "ESP32",
|
|
336
|
+
"esp32c3": "ESP32-C3",
|
|
337
|
+
"esp32-c3": "ESP32-C3",
|
|
338
|
+
"esp32s2": "ESP32-S2",
|
|
339
|
+
"esp32-s2": "ESP32-S2",
|
|
340
|
+
"esp32s3": "ESP32-S3",
|
|
341
|
+
"esp32-s3": "ESP32-S3",
|
|
342
|
+
"esp8266": "ESP8266",
|
|
343
|
+
}
|
|
344
|
+
return mapping.get(chip_family.lower(), chip_family.upper())
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if __name__ == "__main__":
|
|
348
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Static site generator for ESP Web Tools."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from importlib import resources
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_site(
|
|
12
|
+
output_dir: Path,
|
|
13
|
+
yaml_file: Path,
|
|
14
|
+
firmware_file: Path,
|
|
15
|
+
chip_family: str,
|
|
16
|
+
title: str,
|
|
17
|
+
):
|
|
18
|
+
"""Generate a static website for firmware distribution."""
|
|
19
|
+
# Create output directory
|
|
20
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
# Copy files
|
|
23
|
+
yaml_dest = output_dir / yaml_file.name
|
|
24
|
+
firmware_dest = output_dir / "firmware.bin"
|
|
25
|
+
|
|
26
|
+
shutil.copy(yaml_file, yaml_dest)
|
|
27
|
+
shutil.copy(firmware_file, firmware_dest)
|
|
28
|
+
|
|
29
|
+
# Generate manifest.json
|
|
30
|
+
manifest = generate_manifest(
|
|
31
|
+
name=title,
|
|
32
|
+
chip_family=chip_family,
|
|
33
|
+
)
|
|
34
|
+
manifest_path = output_dir / "manifest.json"
|
|
35
|
+
with open(manifest_path, "w") as f:
|
|
36
|
+
json.dump(manifest, f, indent=2)
|
|
37
|
+
|
|
38
|
+
# Generate index.html from template
|
|
39
|
+
build_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
40
|
+
html = render_template(
|
|
41
|
+
"index.html",
|
|
42
|
+
title=title,
|
|
43
|
+
yaml_filename=yaml_file.name,
|
|
44
|
+
chip_family=chip_family,
|
|
45
|
+
build_date=build_date,
|
|
46
|
+
)
|
|
47
|
+
html_path = output_dir / "index.html"
|
|
48
|
+
with open(html_path, "w") as f:
|
|
49
|
+
f.write(html)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_manifest(name: str, chip_family: str) -> dict:
|
|
53
|
+
"""Generate the ESP Web Tools manifest."""
|
|
54
|
+
return {
|
|
55
|
+
"name": name,
|
|
56
|
+
"builds": [
|
|
57
|
+
{
|
|
58
|
+
"chipFamily": chip_family,
|
|
59
|
+
"parts": [{"path": "firmware.bin", "offset": 0}],
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def render_template(template_name: str, **context) -> str:
|
|
66
|
+
"""Render a template with the given context using simple string substitution."""
|
|
67
|
+
template_content = resources.files("ewt.templates").joinpath(template_name).read_text()
|
|
68
|
+
|
|
69
|
+
# Simple template rendering: replace {{ variable }} with values
|
|
70
|
+
def replace_var(match):
|
|
71
|
+
var_name = match.group(1).strip()
|
|
72
|
+
return str(context.get(var_name, match.group(0)))
|
|
73
|
+
|
|
74
|
+
return re.sub(r"\{\{\s*(\w+)\s*\}\}", replace_var, template_content)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HTML templates for EWT."""
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}</title>
|
|
7
|
+
<script
|
|
8
|
+
type="module"
|
|
9
|
+
src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module"
|
|
10
|
+
></script>
|
|
11
|
+
<style>
|
|
12
|
+
:root {
|
|
13
|
+
--primary-color: #0366d6;
|
|
14
|
+
--bg-color: #ffffff;
|
|
15
|
+
--text-color: #24292e;
|
|
16
|
+
--border-color: #e1e4e8;
|
|
17
|
+
--card-bg: #f6f8fa;
|
|
18
|
+
--warning-bg: #fff8e6;
|
|
19
|
+
--warning-border: #f0c36d;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media (prefers-color-scheme: dark) {
|
|
23
|
+
:root {
|
|
24
|
+
--primary-color: #58a6ff;
|
|
25
|
+
--bg-color: #0d1117;
|
|
26
|
+
--text-color: #c9d1d9;
|
|
27
|
+
--border-color: #30363d;
|
|
28
|
+
--card-bg: #161b22;
|
|
29
|
+
--warning-bg: #3d2e00;
|
|
30
|
+
--warning-border: #6e5a1f;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
body {
|
|
39
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
40
|
+
max-width: 800px;
|
|
41
|
+
margin: 0 auto;
|
|
42
|
+
padding: 2rem;
|
|
43
|
+
background: var(--bg-color);
|
|
44
|
+
color: var(--text-color);
|
|
45
|
+
line-height: 1.6;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
h1 {
|
|
49
|
+
border-bottom: 1px solid var(--border-color);
|
|
50
|
+
padding-bottom: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h2 {
|
|
54
|
+
margin-top: 2rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.card {
|
|
58
|
+
background: var(--card-bg);
|
|
59
|
+
border: 1px solid var(--border-color);
|
|
60
|
+
border-radius: 8px;
|
|
61
|
+
padding: 1.5rem;
|
|
62
|
+
margin: 1rem 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.install-section {
|
|
66
|
+
text-align: center;
|
|
67
|
+
padding: 2rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
esp-web-install-button {
|
|
71
|
+
--esp-tools-button-color: var(--primary-color);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
a {
|
|
75
|
+
color: var(--primary-color);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.download-link {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
padding: 0.5rem 1rem;
|
|
81
|
+
border: 1px solid var(--border-color);
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
text-decoration: none;
|
|
84
|
+
margin: 0.5rem 0.5rem 0.5rem 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.download-link:hover {
|
|
88
|
+
background: var(--card-bg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
code {
|
|
92
|
+
background: var(--card-bg);
|
|
93
|
+
padding: 0.2rem 0.4rem;
|
|
94
|
+
border-radius: 4px;
|
|
95
|
+
font-size: 0.9em;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.chip-badge {
|
|
99
|
+
display: inline-block;
|
|
100
|
+
background: var(--primary-color);
|
|
101
|
+
color: white;
|
|
102
|
+
padding: 0.2rem 0.6rem;
|
|
103
|
+
border-radius: 4px;
|
|
104
|
+
font-size: 0.85rem;
|
|
105
|
+
margin-left: 0.5rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ol {
|
|
109
|
+
padding-left: 1.5rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
li {
|
|
113
|
+
margin: 0.5rem 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.not-supported {
|
|
117
|
+
display: none;
|
|
118
|
+
color: #b00;
|
|
119
|
+
padding: 1rem;
|
|
120
|
+
background: #fee;
|
|
121
|
+
border-radius: 4px;
|
|
122
|
+
margin-top: 1rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
esp-web-install-button[install-unsupported] + .not-supported {
|
|
126
|
+
display: block;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.insecure-context {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.insecure-context .warning {
|
|
134
|
+
background: var(--warning-bg);
|
|
135
|
+
border: 1px solid var(--warning-border);
|
|
136
|
+
border-radius: 8px;
|
|
137
|
+
padding: 1rem 1.5rem;
|
|
138
|
+
margin-bottom: 1.5rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
body.insecure .secure-only {
|
|
142
|
+
display: none;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
body.insecure .insecure-context {
|
|
146
|
+
display: block;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
footer {
|
|
150
|
+
margin-top: 3rem;
|
|
151
|
+
padding-top: 1.5rem;
|
|
152
|
+
border-top: 1px solid var(--border-color);
|
|
153
|
+
font-size: 0.85rem;
|
|
154
|
+
color: var(--text-color);
|
|
155
|
+
opacity: 0.7;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
</head>
|
|
159
|
+
<body>
|
|
160
|
+
<h1>{{ title }} <span class="chip-badge">{{ chip_family }}</span></h1>
|
|
161
|
+
|
|
162
|
+
<div class="card install-section secure-only">
|
|
163
|
+
<h2>Install Firmware</h2>
|
|
164
|
+
<p>Connect your device via USB and click the button below to install.</p>
|
|
165
|
+
|
|
166
|
+
<esp-web-install-button manifest="manifest.json"></esp-web-install-button>
|
|
167
|
+
|
|
168
|
+
<div class="not-supported">
|
|
169
|
+
<strong>Browser not supported.</strong><br>
|
|
170
|
+
ESP Web Tools requires a Chromium-based browser (Chrome, Edge) with Web Serial support.
|
|
171
|
+
Please use one of these browsers, or follow the manual installation instructions below.
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="insecure-context">
|
|
176
|
+
<div class="warning">
|
|
177
|
+
<strong>Browser-based installation requires HTTPS.</strong><br>
|
|
178
|
+
This page is not served over a secure connection, so the install button won't work.
|
|
179
|
+
Use <a href="https://web.esphome.io" target="_blank">web.esphome.io</a> to install the firmware instead.
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<h2>Manual Installation</h2>
|
|
183
|
+
<div class="card">
|
|
184
|
+
<p>Install using <a href="https://web.esphome.io" target="_blank">web.esphome.io</a>:</p>
|
|
185
|
+
<ol>
|
|
186
|
+
<li>Download the <a href="firmware.bin" download>firmware binary</a> below</li>
|
|
187
|
+
<li>Go to <a href="https://web.esphome.io" target="_blank">web.esphome.io</a></li>
|
|
188
|
+
<li>Click <strong>CONNECT</strong> and select your device</li>
|
|
189
|
+
<li>Click the three dots menu and select <strong>Install</strong></li>
|
|
190
|
+
<li>Choose the downloaded <code>firmware.bin</code> file</li>
|
|
191
|
+
<li>Wait for the installation to complete</li>
|
|
192
|
+
</ol>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<h2>Downloads</h2>
|
|
197
|
+
<p>
|
|
198
|
+
<a href="{{ yaml_filename }}" class="download-link" download>Download ESPHome YAML</a>
|
|
199
|
+
<a href="firmware.bin" class="download-link" download>Download Firmware Binary</a>
|
|
200
|
+
</p>
|
|
201
|
+
|
|
202
|
+
<h2>ESPHome Configuration</h2>
|
|
203
|
+
<p>
|
|
204
|
+
Want to customize this firmware? Download the <a href="{{ yaml_filename }}" download>YAML configuration</a>
|
|
205
|
+
and use it with <a href="https://esphome.io" target="_blank">ESPHome</a>.
|
|
206
|
+
</p>
|
|
207
|
+
|
|
208
|
+
<footer>
|
|
209
|
+
<p>
|
|
210
|
+
Generated on {{ build_date }} by <a href="https://github.com/esphome/ewt-gen" target="_blank">ewt-gen</a>.
|
|
211
|
+
Powered by <a href="https://esphome.github.io/esp-web-tools/" target="_blank">ESP Web Tools</a>
|
|
212
|
+
and <a href="https://esphome.io" target="_blank">ESPHome</a>
|
|
213
|
+
by the <a href="https://www.openhomefoundation.org/" target="_blank">Open Home Foundation</a>.
|
|
214
|
+
</p>
|
|
215
|
+
</footer>
|
|
216
|
+
|
|
217
|
+
<script>
|
|
218
|
+
if (!window.isSecureContext) {
|
|
219
|
+
document.body.classList.add('insecure');
|
|
220
|
+
}
|
|
221
|
+
</script>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
ewt_gen-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 1
|
|
3
|
+
requires-python = ">=3.10"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "click"
|
|
7
|
+
version = "8.3.1"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
11
|
+
]
|
|
12
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 }
|
|
13
|
+
wheels = [
|
|
14
|
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "colorama"
|
|
19
|
+
version = "0.4.6"
|
|
20
|
+
source = { registry = "https://pypi.org/simple" }
|
|
21
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
|
22
|
+
wheels = [
|
|
23
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "ewt-gen"
|
|
28
|
+
version = "0.1.0"
|
|
29
|
+
source = { editable = "." }
|
|
30
|
+
dependencies = [
|
|
31
|
+
{ name = "click" },
|
|
32
|
+
{ name = "pyyaml" },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[package.metadata]
|
|
36
|
+
requires-dist = [
|
|
37
|
+
{ name = "click", specifier = ">=8.0" },
|
|
38
|
+
{ name = "pyyaml", specifier = ">=6.0" },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[[package]]
|
|
42
|
+
name = "pyyaml"
|
|
43
|
+
version = "6.0.3"
|
|
44
|
+
source = { registry = "https://pypi.org/simple" }
|
|
45
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 }
|
|
46
|
+
wheels = [
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227 },
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019 },
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646 },
|
|
50
|
+
{ url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793 },
|
|
51
|
+
{ url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293 },
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872 },
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828 },
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415 },
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561 },
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 },
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 },
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 },
|
|
59
|
+
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 },
|
|
60
|
+
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 },
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 },
|
|
62
|
+
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 },
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 },
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 },
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 },
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 },
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 },
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 },
|
|
69
|
+
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 },
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 },
|
|
71
|
+
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 },
|
|
72
|
+
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 },
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 },
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 },
|
|
75
|
+
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 },
|
|
76
|
+
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 },
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 },
|
|
78
|
+
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 },
|
|
79
|
+
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 },
|
|
80
|
+
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 },
|
|
81
|
+
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 },
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 },
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 },
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 },
|
|
85
|
+
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 },
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 },
|
|
87
|
+
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 },
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 },
|
|
89
|
+
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 },
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 },
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 },
|
|
103
|
+
]
|