docs: refine template engine guides and validation

This commit is contained in:
2026-09-04 09:52:07 +10:00
parent ff33de6293
commit a09f9e331a
8 changed files with 88 additions and 69 deletions
+1
View File
@@ -33,3 +33,4 @@ jobs:
python-version: '3.11' python-version: '3.11'
- run: python -m pip install --upgrade platformio==6.1.19 - run: python -m pip install --upgrade platformio==6.1.19
- run: ./scripts/test.sh compile --platform ${{ matrix.platform }} - run: ./scripts/test.sh compile --platform ${{ matrix.platform }}
- run: ./scripts/test.sh examples --platform ${{ matrix.platform }}
+2
View File
@@ -18,7 +18,9 @@ jobs:
python-version: '3.11' python-version: '3.11'
- run: python -m pip install --upgrade platformio==6.1.19 - run: python -m pip install --upgrade platformio==6.1.19
- run: ./scripts/test.sh compile --platform esp8266 - run: ./scripts/test.sh compile --platform esp8266
- run: ./scripts/test.sh examples --platform esp8266
- run: ./scripts/test.sh compile --platform esp32 - run: ./scripts/test.sh compile --platform esp32
- run: ./scripts/test.sh examples --platform esp32
- run: ./scripts/check-docs.sh - run: ./scripts/check-docs.sh
- run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME" - run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME"
- run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md" - run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md"
+20 -35
View File
@@ -1,25 +1,18 @@
# Device Framework Template Engine # Device Framework Template Engine
DFTE streams HTML and text for ESP8266 and ESP32 without constructing a giant response in RAM. It combines PROGMEM templates, live getters, nested layouts, conditions, and iterators with request-safe asynchronous HTTP rendering. DFTE streams HTML and text from ESP8266 and ESP32 firmware without constructing a complete response in RAM. Store layouts in PROGMEM, resolve changing values through getters, and render fixed-size chunks directly to serial or an asynchronous HTTP response.
## Why use it ## Render a first template
- **Predictable memory:** render fixed-size chunks instead of one large `String`.
- **Reusable templates:** keep layouts, partials, CSS, and HTML in flash.
- **Live device data:** resolve values from getters only when a chunk is rendered.
- **Safe async responses:** use one render context per HTTP request while sharing one prepared registry.
## Try it
```cpp ```cpp
#include <TemplateEngine.h> #include <TemplateEngine.h>
static const char PAGE[] PROGMEM = "<h1>%TITLE%</h1>"; static const char PAGE[] PROGMEM = "<h1>%TITLE%</h1>";
Serial.begin(115200);
PlaceholderRegistry registry; PlaceholderRegistry registry;
TemplateContext context; TemplateContext context;
void setup() { void setup() {
Serial.begin(115200);
registry.registerProgmemData(PSTR("%TITLE%"), PSTR("Hello DFTE")); registry.registerProgmemData(PSTR("%TITLE%"), PSTR("Hello DFTE"));
registry.registerProgmemTemplate(PSTR("%PAGE%"), PAGE); registry.registerProgmemTemplate(PSTR("%PAGE%"), PAGE);
context.setRegistry(&registry); context.setRegistry(&registry);
@@ -34,14 +27,23 @@ void loop() {
} }
``` ```
## Featured examples Build [Hello Placeholder](examples/HelloPlaceholder/) to see the rendered result over serial.
| Goal | Example | ## Choose an example
| Example | What you will build |
| --- | --- | | --- | --- |
| Smallest placeholder render | [HelloPlaceholder](examples/HelloPlaceholder/) | | [Hello Placeholder](examples/HelloPlaceholder/) | the smallest registry, context, and serial-rendering flow |
| Layouts, partials, and conditions | [NestedLayouts](examples/NestedLayouts/) | | [Nested Layouts](examples/NestedLayouts/) | reusable partials, conditions, and iterator sections |
| Async HTTP streaming | [StreamingAsync](examples/StreamingAsync/) | | [Streaming Async](examples/StreamingAsync/) | a streamed ESPAsyncWebServer response over a SoftAP |
| Dashboard, iterators, and telemetry | [AsyncDashboardDemo](examples/AsyncDashboardDemo/) | | [Async Dashboard Demo](examples/AsyncDashboardDemo/) | a live dashboard with iterator rows and captive-portal access |
## Why DFTE
- **Bounded response memory:** render fixed-size chunks instead of allocating one large `String`.
- **Flash-resident layouts:** keep templates, CSS, and shared fragments in PROGMEM.
- **Live values:** resolve dynamic information only as the active response needs it.
- **Async-safe rendering:** each HTTP request owns its rendering context while sharing the prepared registry.
## Install ## Install
@@ -50,23 +52,6 @@ lib_deps =
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.1.0 DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.1.0
``` ```
PlatformIO checks out the Git ref after `#`; GitHub Release assets are unrelated. DFTE’s supported release targets are ESP8266 and ESP32. PlatformIO checks out the Git ref after `#`; GitHub Release assets are unrelated. DFTE supports ESP8266 and ESP32 Arduino projects.
## Documentation See [getting started](docs/GETTING_STARTED.md), the [documentation index](docs/README.md), [examples](examples/README.md), [changelog](CHANGELOG.md), and [licence](LICENSE).
Read the [documentation index](docs/README.md) for template syntax, async web responses, per-context memory sizing, examples, tests, and releases.
## Development and releases
```bash
./scripts/bump-version.sh vMAJOR.MINOR.PATCH
# Replace the generated CHANGELOG TODO with the release summary.
./scripts/test.sh compile --platform esp8266
./scripts/test.sh compile --platform esp32
./scripts/check-docs.sh
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
```
Tagging repeats the board-free compile checks, validates the package, and creates a GitHub Release from the matching changelog section. It does not publish to the PlatformIO Registry or deploy firmware.
See the [changelog](CHANGELOG.md) and [licence](LICENSE).
+1 -9
View File
@@ -1,20 +1,12 @@
# DFTE documentation # DFTE documentation
DFTE documentation is organised by the rendering problem being solved.
| I want to… | Read | | I want to… | Read |
| --- | --- | | --- | --- |
| Render a first template | [Getting started](GETTING_STARTED.md) | | Render a first template | [Getting started](GETTING_STARTED.md) |
| Use placeholders, templates, conditions, dynamic fragments, or iterators | [Template language](TEMPLATE_LANGUAGE.md) | | Use placeholders, templates, conditions, dynamic fragments, or iterators | [Template language](TEMPLATE_LANGUAGE.md) |
| Stream a response through ESPAsyncWebServer | [Async web responses](ASYNC_WEB.md) | | Stream a response through ESPAsyncWebServer | [Async web responses](ASYNC_WEB.md) |
| Change buffer, stack, or placeholder limits safely | [Configuration](CONFIGURATION.md) | | Change buffer, stack, or placeholder limits safely | [Configuration](CONFIGURATION.md) |
| Build the standalone examples | [Examples](../examples/README.md) | | Build, flash, and observe a complete example | [Examples](../examples/README.md) |
| Run checks or prepare a release | [Testing](TESTING.md) · [Development](DEVELOPMENT.md) | | Run checks or prepare a release | [Testing](TESTING.md) · [Development](DEVELOPMENT.md) |
## Documentation rules
- The template-language guide owns DFTE semantics and lifecycle contracts.
- The configuration guide owns ABI-sensitive build flags.
- The project README is an entry point and example chooser, not a full API manual.
Back to the [project overview](../README.md). Back to the [project overview](../README.md).
+9 -9
View File
@@ -1,22 +1,22 @@
# DFTE examples # DFTE examples
Every example is a standalone PlatformIO project. Build, upload, and monitor from its directory: Every example is a standalone PlatformIO project. Build, upload, and monitor from the repository root:
```bash ```bash
pio run -d examples/HelloPlaceholder -e example_esp8266 pio run -d examples/HelloPlaceholder -e example_esp8266
pio run -d examples/HelloPlaceholder -e example_esp8266 -t upload pio run -d examples/HelloPlaceholder -e example_esp8266 -t upload
pio run -d examples/HelloPlaceholder -e example_esp8266 -t monitor pio device monitor -d examples/HelloPlaceholder -e example_esp8266
``` ```
Choose the ESP32 environment where provided. Choose the corresponding ESP32 environment where provided. The examples use the checked-out DFTE source, so they double as practical integration checks for this repository.
| Example | What it demonstrates | | Example | Start here when you want to… |
| --- | --- | | --- | --- |
| [HelloPlaceholder](HelloPlaceholder/) | Smallest registry/context/chunk flow over serial | | [HelloPlaceholder](HelloPlaceholder/) | understand the smallest registry/context/chunk flow over serial |
| [NestedLayouts](NestedLayouts/) | Templates, partials, conditionals, and iterators without a web server | | [NestedLayouts](NestedLayouts/) | compose a page with partials, conditions, and repeating data |
| [StreamingAsync](StreamingAsync/) | Request-scoped streaming HTTP response through a SoftAP portal | | [StreamingAsync](StreamingAsync/) | serve one streamed page through an asynchronous web server |
| [AsyncDashboardDemo](AsyncDashboardDemo/) | Dashboard telemetry, iterator rows, and captive-portal flow | | [AsyncDashboardDemo](AsyncDashboardDemo/) | inspect a richer browser-facing dashboard without buffering the response |
The SoftAP examples print their network names and passwords to serial. They are demonstrations, not production provisioning implementations. The SoftAP examples print their network names and passwords to serial. They are self-contained browser demonstrations, not Wi-Fi provisioning implementations.
Back to [DFTE documentation](../docs/README.md) · [project overview](../README.md). Back to [DFTE documentation](../docs/README.md) · [project overview](../README.md).
+13 -9
View File
@@ -1,20 +1,24 @@
# Streaming Async # Streaming Async
Small ESPAsyncWebServer demo that exposes a captive portal over a SoftAP and renders a DFTE template directly to the HTTP response stream. It highlights: This ESPAsyncWebServer example exposes a captive portal over a SoftAP and streams a DFTE template directly to each HTTP response. It demonstrates:
- Per-request `TemplateContext` ownership to avoid concurrent request clashes - one request-scoped `TemplateContext` per response;
- PROGMEM template with shared CSS/header/footer snippets - a PROGMEM page with shared CSS/header/footer snippets;
- Runtime data from RAM getters (uptime, connected station count) - runtime getters for uptime and connected-station count;
- Captive portal DNS redirect so clients automatically receive the dashboard - a captive-portal DNS redirect for browsers that support it.
## Build ## Build
``` ```bash
pio run -d examples/StreamingAsync -e example_esp8266 pio run -d examples/StreamingAsync -e example_esp8266
See the shared [examples guide](../README.md) and [async web guidance](../../docs/ASYNC_WEB.md).
pio run -d examples/StreamingAsync -e example_esp32 pio run -d examples/StreamingAsync -e example_esp32
``` ```
Flash the board, connect to the SoftAP announced by the device (`DFTE-Portal-8266` or `DFTE-Portal-ESP32`, password `dfte-demo`), and your browser should automatically present the streamed dashboard. If it does not, navigate manually to `http://192.168.4.1/`. ## Run
1. Flash your target with `pio run -d examples/StreamingAsync -e <env> -t upload --upload-port <port>`.
2. Open the serial monitor at 115200 baud and confirm the SoftAP credentials.
3. Connect to `DFTE-Portal-8266` or `DFTE-Portal-ESP32` using password `dfte-demo`.
4. Browse to `http://192.168.4.1/` if the captive portal does not open automatically.
See the shared [examples guide](../README.md) and [async web guidance](../../docs/ASYNC_WEB.md).
+17
View File
@@ -25,4 +25,21 @@ while IFS= read -r -d '' markdown; do
done < <(sed -nE 's/.*\]\(([^ )]+)( "[^"]*")?\).*/\1/p' "$markdown") done < <(sed -nE 's/.*\]\(([^ )]+)( "[^"]*")?\).*/\1/p' "$markdown")
done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print0) done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print0)
while IFS= read -r example; do
for required in README.md platformio.ini; do
[[ -f "$example/$required" ]] || { echo "Incomplete example: ${example#$root/} is missing $required" >&2; exit 1; }
done
find "$example/src" -type f \( -name '*.ino' -o -name '*.cpp' \) -print -quit | grep -q . || {
echo "Incomplete example: ${example#$root/} has no source" >&2
exit 1
}
done < <(find "$root/examples" -mindepth 2 -maxdepth 2 -type f -name platformio.ini -printf '%h\n' | sort)
while IFS= read -r markdown; do
(( $(grep -Ec '^[[:space:]]*```' "$markdown") % 2 == 0 )) || {
echo "Unclosed code fence in ${markdown#$root/}" >&2
exit 1
}
done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print)
echo "Documentation links and required files passed" echo "Documentation links and required files passed"
+25 -7
View File
@@ -2,18 +2,36 @@
set -euo pipefail set -euo pipefail
usage() { usage() {
echo "Usage: $0 compile --platform esp8266|esp32" >&2 echo "Usage: $0 compile|examples --platform esp8266|esp32" >&2
exit 2 exit 2
} }
[[ "${1:-}" == "compile" && "${2:-}" == "--platform" && $# -eq 3 ]] || usage [[ $# -eq 3 && ( "${1:-}" == "compile" || "${1:-}" == "examples" ) && "${2:-}" == "--platform" ]] || usage
case "${3:-}" in case "${3:-}" in
esp8266) environment="test_template_engine_8266" ;; esp8266|esp32) platform="$3" ;;
esp32) environment="test_template_engine_esp32" ;;
*) usage ;; *) usage ;;
esac esac
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
pio test -d "$root" -e "$environment" --without-uploading --without-testing if [[ "$1" == "compile" ]]; then
echo "DFTE compile check passed for ${3}" case "$platform" in
esp8266) test_environment="test_template_engine_8266" ;;
esp32) test_environment="test_template_engine_esp32" ;;
esac
pio test -d "$root" -e "$test_environment" --without-uploading --without-testing
echo "DFTE compile check passed for $platform"
exit 0
fi
suffix="$platform"
mapfile -t examples < <(find "$root/examples" -mindepth 2 -maxdepth 2 -type f -name platformio.ini -printf '%h\n' | sort)
if (( ${#examples[@]} == 0 )); then
echo "No example projects found" >&2
exit 1
fi
for example in "${examples[@]}"; do
env_name="example_$suffix"
[[ "$(basename "$example")" == "AsyncDashboardDemo" ]] && env_name="dashboard_$suffix"
pio run -d "$example" -e "$env_name" </dev/null
done
echo "DFTE examples compile check passed for $platform"