From a09f9e331a1be8d7fc8a2472de87e0a60a04573d Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Fri, 4 Sep 2026 09:52:07 +1000 Subject: [PATCH] docs: refine template engine guides and validation --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 2 ++ README.md | 55 +++++++++++-------------------- docs/README.md | 10 +----- examples/README.md | 18 +++++----- examples/StreamingAsync/README.md | 22 ++++++++----- scripts/check-docs.sh | 17 ++++++++++ scripts/test.sh | 32 ++++++++++++++---- 8 files changed, 88 insertions(+), 69 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32eba91..706140f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,3 +33,4 @@ jobs: python-version: '3.11' - run: python -m pip install --upgrade platformio==6.1.19 - run: ./scripts/test.sh compile --platform ${{ matrix.platform }} + - run: ./scripts/test.sh examples --platform ${{ matrix.platform }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07debb4..2d860bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,9 @@ jobs: python-version: '3.11' - run: python -m pip install --upgrade platformio==6.1.19 - 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 examples --platform esp32 - run: ./scripts/check-docs.sh - run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME" - run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md" diff --git a/README.md b/README.md index b9da6e9..6703256 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,18 @@ # 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 - -- **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 +## Render a first template ```cpp #include static const char PAGE[] PROGMEM = "

%TITLE%

"; - Serial.begin(115200); PlaceholderRegistry registry; TemplateContext context; void setup() { + Serial.begin(115200); registry.registerProgmemData(PSTR("%TITLE%"), PSTR("Hello DFTE")); registry.registerProgmemTemplate(PSTR("%PAGE%"), PAGE); context.setRegistry(®istry); @@ -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/) | -| Layouts, partials, and conditions | [NestedLayouts](examples/NestedLayouts/) | -| Async HTTP streaming | [StreamingAsync](examples/StreamingAsync/) | -| Dashboard, iterators, and telemetry | [AsyncDashboardDemo](examples/AsyncDashboardDemo/) | +| [Hello Placeholder](examples/HelloPlaceholder/) | the smallest registry, context, and serial-rendering flow | +| [Nested Layouts](examples/NestedLayouts/) | reusable partials, conditions, and iterator sections | +| [Streaming Async](examples/StreamingAsync/) | a streamed ESPAsyncWebServer response over a SoftAP | +| [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 @@ -50,23 +52,6 @@ lib_deps = 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 - -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). +See [getting started](docs/GETTING_STARTED.md), the [documentation index](docs/README.md), [examples](examples/README.md), [changelog](CHANGELOG.md), and [licence](LICENSE). diff --git a/docs/README.md b/docs/README.md index fe6a54c..44efc89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,12 @@ # DFTE documentation -DFTE documentation is organised by the rendering problem being solved. - | I want to… | Read | | --- | --- | | Render a first template | [Getting started](GETTING_STARTED.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) | | 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) | -## 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). diff --git a/examples/README.md b/examples/README.md index da1efd9..addd439 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,22 +1,22 @@ # 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 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 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 | -| [NestedLayouts](NestedLayouts/) | Templates, partials, conditionals, and iterators without a web server | -| [StreamingAsync](StreamingAsync/) | Request-scoped streaming HTTP response through a SoftAP portal | -| [AsyncDashboardDemo](AsyncDashboardDemo/) | Dashboard telemetry, iterator rows, and captive-portal flow | +| [HelloPlaceholder](HelloPlaceholder/) | understand the smallest registry/context/chunk flow over serial | +| [NestedLayouts](NestedLayouts/) | compose a page with partials, conditions, and repeating data | +| [StreamingAsync](StreamingAsync/) | serve one streamed page through an asynchronous web server | +| [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). diff --git a/examples/StreamingAsync/README.md b/examples/StreamingAsync/README.md index ebcdcec..7db9f6c 100644 --- a/examples/StreamingAsync/README.md +++ b/examples/StreamingAsync/README.md @@ -1,20 +1,24 @@ # 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 -- PROGMEM template with shared CSS/header/footer snippets -- Runtime data from RAM getters (uptime, connected station count) -- Captive portal DNS redirect so clients automatically receive the dashboard +- one request-scoped `TemplateContext` per response; +- a PROGMEM page with shared CSS/header/footer snippets; +- runtime getters for uptime and connected-station count; +- a captive-portal DNS redirect for browsers that support it. ## Build -``` +```bash 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 ``` -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 -t upload --upload-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). diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh index 4418599..f68db6d 100755 --- a/scripts/check-docs.sh +++ b/scripts/check-docs.sh @@ -25,4 +25,21 @@ while IFS= read -r -d '' markdown; do done < <(sed -nE 's/.*\]\(([^ )]+)( "[^"]*")?\).*/\1/p' "$markdown") 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" diff --git a/scripts/test.sh b/scripts/test.sh index 7e102d2..a2f1e01 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -2,18 +2,36 @@ set -euo pipefail usage() { - echo "Usage: $0 compile --platform esp8266|esp32" >&2 + echo "Usage: $0 compile|examples --platform esp8266|esp32" >&2 exit 2 } -[[ "${1:-}" == "compile" && "${2:-}" == "--platform" && $# -eq 3 ]] || usage - +[[ $# -eq 3 && ( "${1:-}" == "compile" || "${1:-}" == "examples" ) && "${2:-}" == "--platform" ]] || usage case "${3:-}" in - esp8266) environment="test_template_engine_8266" ;; - esp32) environment="test_template_engine_esp32" ;; + esp8266|esp32) platform="$3" ;; *) usage ;; esac root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -pio test -d "$root" -e "$environment" --without-uploading --without-testing -echo "DFTE compile check passed for ${3}" +if [[ "$1" == "compile" ]]; then + 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"