mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-04 04:28:10 +10:00
feat: prepare DFTE 1.0.2
This commit is contained in:
@@ -2,22 +2,32 @@ name: Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
documentation:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./scripts/check-docs.sh
|
||||||
compile-tests:
|
compile-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
environment: ["test_template_engine_8266", "test_template_engine_esp32"]
|
platform: [esp8266, esp32]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
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: pio test -e ${{ matrix.environment }} --without-uploading --without-testing
|
- run: ./scripts/test.sh compile --platform ${{ matrix.platform }}
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
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 esp32
|
||||||
|
- run: ./scripts/check-docs.sh
|
||||||
- run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME"
|
- run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME"
|
||||||
- run: gh release create "$GITHUB_REF_NAME" --generate-notes --title "$GITHUB_REF_NAME"
|
- run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md"
|
||||||
|
- run: gh release create "$GITHUB_REF_NAME" --title "DeviceFrameworkTemplateEngine $GITHUB_REF_NAME" --notes-file "$RUNNER_TEMP/release-notes.md"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
@@ -1,333 +1,70 @@
|
|||||||
# Device Framework Template Engine (DFTE)
|
# Device Framework Template Engine
|
||||||
|
|
||||||
DFTE is a lightweight C++ template engine for ESP8266 and ESP32 Arduino
|
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.
|
||||||
projects that need to render HTML dashboards or textual feeds without allocating
|
|
||||||
giant buffers. It streams HTML over chunked HTTP, stitches together nested
|
|
||||||
layouts from PROGMEM, and injects live device data from RAM getters while
|
|
||||||
keeping the microcontroller responsive.
|
|
||||||
|
|
||||||
The published package and CI support ESP8266 and ESP32. The code may be useful
|
## Why use it
|
||||||
on other Arduino-compatible targets, but those targets are not currently part
|
|
||||||
of the supported release matrix and should be validated by the consuming
|
|
||||||
project.
|
|
||||||
|
|
||||||
## Quickstart
|
- **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.
|
||||||
|
|
||||||
1. Define your root template in PROGMEM (or RAM if you prefer):
|
## Try it
|
||||||
|
|
||||||
```
|
|
||||||
static const char ROOT_TEMPLATE_PROGMEM[] PROGMEM = R"DFTE(
|
|
||||||
<html>
|
|
||||||
<head><title>%APP_TITLE%</title></head>
|
|
||||||
<body>
|
|
||||||
<h1>%APP_TITLE%</h1>
|
|
||||||
<p>Uptime: %UPTIME%</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)DFTE";
|
|
||||||
```
|
|
||||||
|
|
||||||
2. In your sketch, register placeholders and stream them out:
|
|
||||||
|
|
||||||
```
|
|
||||||
#include <TemplateEngine.h>
|
|
||||||
|
|
||||||
PlaceholderRegistry registry;
|
|
||||||
TemplateContext ctx;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
registry.registerProgmemData(PSTR("%APP_TITLE%"), PSTR("DFTE Quickstart"));
|
|
||||||
registry.registerRamData(PSTR("%UPTIME%"), []() -> const char* {
|
|
||||||
static char buffer[16];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%lus", millis() / 1000);
|
|
||||||
return buffer;
|
|
||||||
});
|
|
||||||
registry.registerProgmemTemplate(PSTR("%ROOT%"), ROOT_TEMPLATE_PROGMEM);
|
|
||||||
|
|
||||||
ctx.setRegistry(®istry);
|
|
||||||
TemplateRenderer::initializeContext(ctx, PSTR("%ROOT%"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
static uint8_t buffer[128];
|
|
||||||
if (!TemplateRenderer::isComplete(ctx) && !TemplateRenderer::hasError(ctx)) {
|
|
||||||
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
|
||||||
Serial.write(buffer, written);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Open the serial monitor (or send chunks to `AsyncWebServer`) to watch the template stream without ever allocating the full HTML in RAM.
|
|
||||||
|
|
||||||
## Why DFTE vs Alternatives?
|
|
||||||
|
|
||||||
| Approach | Streaming | Dynamic data | Template reuse | Notes |
|
|
||||||
|----------|-----------|--------------|---------------|-------|
|
|
||||||
| DFTE (this library) | ✅ Streams in 128–512 byte chunks | ✅ Registry handles placeholders, conditionals, iterators | ✅ PROGMEM templates shared across requests | Built for async HTTP; keeps RAM usage predictable |
|
|
||||||
| Static SPIFFS/LittleFS files | ❌ Full file send only | ⚠️ Requires pre-generated HTML | ✅ Stored once on flash filesystem | Great for static assets, not ideal for live telemetry |
|
|
||||||
| Arduino `String` concatenation | ❌ Concatenates into one RAM buffer | ✅ Manual `String` inserts | ❌ Template duplicated per build | Fast to prototype but fragments heap on longer sketches |
|
|
||||||
| AsyncWebServer template callback | ⚠️ Streams callback output but copies per placeholder | ✅ Values supplied in callback | ❌ No shared layout or nesting support | Fine for small pages; scales poorly with complex UIs |
|
|
||||||
| Server-side proxy (external backend) | ✅ Offloaded to external service | ✅ Managed by backend | ❌ Device ships only proxy stub | Requires constant connectivity and extra infrastructure |
|
|
||||||
|
|
||||||
## Core Types & API
|
|
||||||
- `PlaceholderRegistry`
|
|
||||||
- `registerProgmemData(const char*, const char*)` – link `%TOKEN%` to flash-resident data.
|
|
||||||
- `registerRamData(const char*, PlaceholderDataGetter)` – provide dynamic strings from getters.
|
|
||||||
- `registerProgmemTemplate(const char*, const char*)` – nest other templates.
|
|
||||||
- `registerDynamicTemplate(const char*, const DynamicTemplateDescriptor*)` – compute template fragments at render time.
|
|
||||||
- `registerConditional(const char*, const ConditionalDescriptor*)` – choose between delegates (`TRUE_BRANCH`, `FALSE_BRANCH`, `SKIP`).
|
|
||||||
- `registerIterator(const char*, const IteratorDescriptor*)` – stream repeated sections item-by-item.
|
|
||||||
- `getPlaceholder`, `getCount`, `clear` – inspection/utilities used throughout the tests.
|
|
||||||
|
|
||||||
- `TemplateContext`
|
|
||||||
- Holds the render stack, buffers, and statistics.
|
|
||||||
- `setRegistry(PlaceholderRegistry*)` – inject the registry you populated.
|
|
||||||
- `reset()` – reuse the context without re-allocating buffers.
|
|
||||||
- `isComplete()`, `hasError()`, `getStateString()` – status helpers.
|
|
||||||
|
|
||||||
- `TemplateRenderer`
|
|
||||||
- `initializeContext(TemplateContext&, const char*, bool templateInProgmem = true)` – prime the context with the root template.
|
|
||||||
- `renderNextChunk(TemplateContext&, uint8_t* buf, size_t len)` – stream out the next chunk; returns written bytes.
|
|
||||||
- `isComplete(const TemplateContext&)`, `hasError(const TemplateContext&)` – convenience checks.
|
|
||||||
|
|
||||||
- `DeviceFrameworkTemplateEngineDebug`
|
|
||||||
- Optional logging interface; create a `DeviceFrameworkTemplateEngineLogger` subclass and call `deviceFrameworkTemplateEngineEnableLogging(&logger)` (or the two-argument overload with an owner tag, e.g. `this`, for `deviceFrameworkTemplateEngineDisableLoggingForOwner`).
|
|
||||||
|
|
||||||
All public headers are re-exported from `TemplateEngine.h`, so typical sketches only include that file.
|
|
||||||
|
|
||||||
## Usage Overview
|
|
||||||
1. Include `TemplateEngine.h`.
|
|
||||||
|
|
||||||
```
|
|
||||||
#include <TemplateEngine.h>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Register placeholders on a `PlaceholderRegistry`.
|
|
||||||
|
|
||||||
```
|
|
||||||
PlaceholderRegistry registry;
|
|
||||||
registry.registerProgmemData(PSTR("%APP_TITLE%"), PSTR("DFTE Dashboard"));
|
|
||||||
registry.registerRamData(PSTR("%UPTIME%"), []() -> const char* {
|
|
||||||
static char buffer[16];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%lus", millis() / 1000);
|
|
||||||
return buffer;
|
|
||||||
});
|
|
||||||
registry.registerProgmemTemplate(PSTR("%ROOT%"), ROOT_TEMPLATE_PROGMEM);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Attach the registry to a `TemplateContext`.
|
|
||||||
|
|
||||||
```
|
|
||||||
TemplateContext ctx;
|
|
||||||
ctx.setRegistry(®istry);
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Feed the root template to `TemplateRenderer::initializeContext`.
|
|
||||||
|
|
||||||
```
|
|
||||||
TemplateRenderer::initializeContext(ctx, PSTR("%ROOT%"));
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Loop on `renderNextChunk` until `TemplateRenderer::isComplete(ctx)` is `true`.
|
|
||||||
|
|
||||||
```
|
|
||||||
uint8_t buffer[128];
|
|
||||||
while (!TemplateRenderer::isComplete(ctx) && !TemplateRenderer::hasError(ctx)) {
|
|
||||||
size_t written = TemplateRenderer::renderNextChunk(ctx, buffer, sizeof(buffer));
|
|
||||||
Serial.write(buffer, written);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Optionally reuse the same context for additional templates by calling `ctx.reset()`.
|
|
||||||
|
|
||||||
```
|
|
||||||
ctx.reset();
|
|
||||||
TemplateRenderer::initializeContext(ctx, PSTR("%DETAIL_PANEL%"));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Async Streaming Pattern
|
|
||||||
|
|
||||||
When serving requests with ESPAsyncWebServer, include `TemplateEngineAsyncWeb.h` and give every request its own `TemplateContext` so chunked rendering cannot be corrupted by overlapping clients. Build and cache your `PlaceholderRegistry` once during setup, then share it across handlers. The same pattern powers the DeviceFramework web UI and the DFTE examples:
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <TemplateEngine.h>
|
#include <TemplateEngine.h>
|
||||||
#include <TemplateEngineAsyncWeb.h>
|
|
||||||
|
|
||||||
/** Global registry prepared during setup() */
|
static const char PAGE[] PROGMEM = "<h1>%TITLE%</h1>";
|
||||||
std::shared_ptr<PlaceholderRegistry> registry;
|
Serial.begin(115200);
|
||||||
|
PlaceholderRegistry registry;
|
||||||
void setupRegistry() {
|
TemplateContext context;
|
||||||
registry = std::make_shared<PlaceholderRegistry>();
|
|
||||||
registry->registerProgmemData(PSTR("%APP_TITLE%"), PSTR("DFTE Async Portal"));
|
|
||||||
registry->registerRamData(PSTR("%UPTIME%"), []() -> const char* {
|
|
||||||
static char buffer[16];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%lus", millis() / 1000);
|
|
||||||
return buffer;
|
|
||||||
});
|
|
||||||
registry->registerProgmemTemplate(PSTR("%ROOT%"), ROOT_TEMPLATE_PROGMEM);
|
|
||||||
}
|
|
||||||
|
|
||||||
void streamTemplate(AsyncWebServerRequest* request, const char* rootTemplate) {
|
|
||||||
auto ctx = std::make_shared<TemplateContext>();
|
|
||||||
ctx->setRegistry(registry.get());
|
|
||||||
TemplateRenderer::initializeContext(*ctx, rootTemplate);
|
|
||||||
|
|
||||||
request->onDisconnect([ctx]() mutable { ctx.reset(); });
|
|
||||||
|
|
||||||
AsyncWebServerResponse* response =
|
|
||||||
TemplateEngineAsyncWeb::beginSafeTemplateResponse(
|
|
||||||
request, "text/html; charset=utf-8", ctx, 128);
|
|
||||||
|
|
||||||
response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
||||||
response->addHeader("Pragma", "no-cache");
|
|
||||||
response->addHeader("Expires", "-1");
|
|
||||||
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
setupRegistry();
|
registry.registerProgmemData(PSTR("%TITLE%"), PSTR("Hello DFTE"));
|
||||||
server.on("/", [](AsyncWebServerRequest* request) {
|
registry.registerProgmemTemplate(PSTR("%PAGE%"), PAGE);
|
||||||
streamTemplate(request, PSTR("%ROOT%"));
|
context.setRegistry(®istry);
|
||||||
});
|
TemplateRenderer::initializeContext(context, PSTR("%PAGE%"));
|
||||||
server.begin();
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
uint8_t chunk[128];
|
||||||
|
if (!TemplateRenderer::isComplete(context) && !TemplateRenderer::hasError(context)) {
|
||||||
|
Serial.write(chunk, TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Build and cache the `PlaceholderRegistry` during startup so heavy PROGMEM registration runs once.
|
## Featured examples
|
||||||
2. Allocate a request-scoped `TemplateContext`, initialise it with the shared registry, and render inside the chunked callback.
|
|
||||||
3. Tear everything down on completion or disconnect to avoid state bleed between clients.
|
|
||||||
|
|
||||||
### Template Syntax
|
| Goal | Example |
|
||||||
|
| --- | --- |
|
||||||
|
| 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/) |
|
||||||
|
|
||||||
Every placeholder in a template uses `%NAME%`. DFTE looks up `NAME` in the registry and decides how to render it based on the registered type. Templates can be nested arbitrarily (up to `DFTE_MAX_STACK_DEPTH_DEFAULT` unless you raise it).
|
## Install
|
||||||
|
|
||||||
### Placeholder Types
|
|
||||||
|
|
||||||
- **Static data** – `registerProgmemData("%CSS%", PROGMEM_BLOCK)` streams literal content from flash or RAM.
|
|
||||||
- **Nested template** – `registerProgmemTemplate("%HEADER%", HEADER_TEMPLATE)` injects another template that can contain its own placeholders.
|
|
||||||
- **Dynamic value** – `registerRamData("%UPTIME%", getter)` calls a function that returns the current value as a `const char*`.
|
|
||||||
- **Dynamic template** – `registerDynamicTemplate("%CONTENT%", &DynamicTemplateDescriptor{getter, getLength, userData})` asks your getter to return template text at render time.
|
|
||||||
- **Conditional** – `registerConditional("%IS_ONLINE%", &ConditionalDescriptor{evaluate, "%ONLINE%", "%OFFLINE%", userData})` chooses which delegate placeholder to render based on the evaluator result.
|
|
||||||
- **Iterator** – `registerIterator("%SENSORS%", &IteratorDescriptor{open, next, close, userData})` opens a handle, streams each item template through `IteratorItemView`, and finalises with `close`.
|
|
||||||
|
|
||||||
### Buildable Examples
|
|
||||||
|
|
||||||
All demos under `examples/` are standalone PlatformIO projects that use the library via `lib_extra_dirs`. Each contains a `platformio.ini` with ready-to-build environments, so you can compile and upload without touching your primary application.
|
|
||||||
|
|
||||||
- `examples/AsyncDashboardDemo/` – Full SoftAP dashboard with iterators, conditionals, and runtime telemetry.
|
|
||||||
- `examples/StreamingAsync/` – Minimal captive portal that streams the template directly to the HTTP response.
|
|
||||||
- `examples/NestedLayouts/` – Demonstrates layout stacking, partials, and conditional fragments.
|
|
||||||
- `examples/HelloPlaceholder/` – Smallest possible sketch that renders a single placeholder.
|
|
||||||
|
|
||||||
Typical workflow (replace the path/env as needed):
|
|
||||||
|
|
||||||
```
|
|
||||||
# Build
|
|
||||||
pio run -d examples/StreamingAsync -e example_esp32
|
|
||||||
|
|
||||||
# Flash
|
|
||||||
pio run -d examples/StreamingAsync -e example_esp32 -t upload
|
|
||||||
|
|
||||||
# Monitor (optional)
|
|
||||||
pio run -d examples/StreamingAsync -e example_esp32 -t monitor
|
|
||||||
```
|
|
||||||
|
|
||||||
ESP8266 variants use the `example_esp8266` or `dashboard_esp8266` environments, while ESP32 boards use `example_esp32` or `dashboard_esp32`. Update Wi-Fi credentials inside each example’s `src/main.cpp`, then connect to the serial monitor or SoftAP as documented in the per-example README.
|
|
||||||
|
|
||||||
Refer to the Unity tests in `test/test_template_engine/tests` for exhaustive combinations of placeholders, nested templates, conditionals, and iterators.
|
|
||||||
|
|
||||||
### Memory & Configuration Knobs
|
|
||||||
|
|
||||||
DFTE has two kinds of limits. Set build-wide limits with PlatformIO `build_flags`, so the library and every consuming translation unit use the exact same object layout. Do not set these only in a sketch header.
|
|
||||||
|
|
||||||
- `DFTE_BUFFER_SIZE` (default 512) – streaming buffer inside `TemplateContext`.
|
|
||||||
- `DFTE_MAX_STACK_DEPTH` (default 16) – rendering stack frames. A nested template generally uses two frames, so set this to at least twice the intended nesting plus one.
|
|
||||||
- `DFTE_PLACEHOLDER_NAME_SIZE` (default 24) – maximum placeholder-token length including `%` characters.
|
|
||||||
- `DFTE_PROGMEM_CHUNK_SIZE` (default 512) and `DFTE_RAM_CHUNK_SIZE` (default 128) – source copy windows.
|
|
||||||
- `DFTE_MAX_ITERATIONS` (default 50) – safety cap for an individual render call.
|
|
||||||
- `DFTE_MAX_PLACEHOLDERS_DEFAULT` (default 16) – constructor default only; alternatively pass an explicit capacity to `PlaceholderRegistry`.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
; platformio.ini
|
|
||||||
build_flags =
|
|
||||||
-DDFTE_BUFFER_SIZE=768
|
|
||||||
-DDFTE_MAX_STACK_DEPTH=24
|
|
||||||
-DDFTE_PLACEHOLDER_NAME_SIZE=32
|
|
||||||
-DDFTE_MAX_ITERATIONS=80
|
|
||||||
```
|
|
||||||
|
|
||||||
DFTE is deliberately independent of DeviceFramework's runtime `CONFIG_template*` values. DeviceFramework may choose a registry capacity at runtime, but any fixed DFTE layout must be configured by the shared `DFTE_*` build flags above. This prevents an application header and the compiled library from disagreeing about object size.
|
|
||||||
|
|
||||||
## PlatformIO Usage
|
|
||||||
|
|
||||||
### Consume as a dependency
|
|
||||||
|
|
||||||
Pin a released Git tag in the application's `platformio.ini`:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
lib_deps =
|
lib_deps =
|
||||||
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.0.2
|
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.0.2
|
||||||
```
|
```
|
||||||
|
|
||||||
The suffix after `#` is a Git ref. PlatformIO clones the repository and checks
|
PlatformIO checks out the Git ref after `#`; GitHub Release assets are unrelated. DFTE’s supported release targets are ESP8266 and ESP32.
|
||||||
out that tag; GitHub Release assets are unrelated. A tag is reproducible,
|
|
||||||
whereas `#main` deliberately follows moving development. Use a local
|
|
||||||
`symlink://` or `file://` dependency only while working on DFTE itself.
|
|
||||||
|
|
||||||
### Test without hardware
|
## Documentation
|
||||||
|
|
||||||
The test environments compile the full Unity suites without uploading or
|
Read the [documentation index](docs/README.md) for template syntax, async web responses, ABI-safe build flags, examples, tests, and releases.
|
||||||
executing them, so no board is required:
|
|
||||||
|
|
||||||
```bash
|
## Development and releases
|
||||||
pio test -e test_template_engine_8266 --without-uploading --without-testing
|
|
||||||
pio test -e test_template_engine_esp32 --without-uploading --without-testing
|
|
||||||
```
|
|
||||||
|
|
||||||
Both environments set `test_build_src = yes`, so the library sources are
|
|
||||||
included. CI runs the two commands above on every push and pull request.
|
|
||||||
|
|
||||||
### Publish a release
|
|
||||||
|
|
||||||
Update `library.json`, `CHANGELOG.md`, and the public documentation, run
|
|
||||||
both compile checks, then create the annotated tag:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
./scripts/test.sh compile --platform esp8266
|
||||||
|
./scripts/test.sh compile --platform esp32
|
||||||
|
./scripts/check-docs.sh
|
||||||
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
|
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
|
||||||
```
|
```
|
||||||
|
|
||||||
Push the branch and tag. The tag workflow validates the PlatformIO package
|
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.
|
||||||
again and creates the GitHub Release.
|
|
||||||
|
|
||||||
### Debug Logging
|
See the [changelog](CHANGELOG.md) and [licence](LICENSE).
|
||||||
|
|
||||||
DFTE’s logger is opt-in and costs nothing until you enable it. Implement `DeviceFrameworkTemplateEngineLogger`, register it once, and all internal `DFTE_LOG_*` calls stream through your logger.
|
|
||||||
|
|
||||||
```
|
|
||||||
#include <TemplateEngine.h>
|
|
||||||
#include <DeviceFrameworkTemplateEngineDebug.h>
|
|
||||||
|
|
||||||
class SerialLogger : public DeviceFrameworkTemplateEngineLogger {
|
|
||||||
public:
|
|
||||||
void error(const String& msg) override { Serial.println("[DFTE][E] " + msg); }
|
|
||||||
void warn(const String& msg) override { Serial.println("[DFTE][W] " + msg); }
|
|
||||||
void info(const String& msg) override { Serial.println("[DFTE][I] " + msg); }
|
|
||||||
void debug(const String& msg) override { Serial.println("[DFTE][D] " + msg); }
|
|
||||||
};
|
|
||||||
|
|
||||||
SerialLogger logger;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
deviceFrameworkTemplateEngineEnableLogging(&logger, static_cast<const void*>(&logger));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `deviceFrameworkTemplateEngineDisableLogging()` to silence output, `deviceFrameworkTemplateEngineDisableLoggingForOwner(tag)` to tear down only the registration made with that owner (for example WiFiManager’s server instance), or `deviceFrameworkTemplateEngineIsLoggingEnabled()` to inspect the current state.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is released under the MIT License. See `LICENSE` for details.
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Async web responses
|
||||||
|
|
||||||
|
Build a `PlaceholderRegistry` once during setup, but allocate a fresh `TemplateContext` for each request. A shared context would mix rendering state when clients overlap.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <TemplateEngine.h>
|
||||||
|
#include <TemplateEngineAsyncWeb.h>
|
||||||
|
|
||||||
|
std::shared_ptr<PlaceholderRegistry> registry;
|
||||||
|
|
||||||
|
void sendTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||||
|
auto context = std::make_shared<TemplateContext>();
|
||||||
|
context->setRegistry(registry.get());
|
||||||
|
TemplateRenderer::initializeContext(*context, root);
|
||||||
|
request->onDisconnect([context]() mutable { context.reset(); });
|
||||||
|
|
||||||
|
AsyncWebServerResponse* response =
|
||||||
|
TemplateEngineAsyncWeb::beginSafeTemplateResponse(
|
||||||
|
request, "text/html; charset=utf-8", context, 128
|
||||||
|
);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The response retains the context while it streams. Releasing the request-owned `shared_ptr` on disconnect prevents state from leaking into later requests.
|
||||||
|
|
||||||
|
Use [StreamingAsync](../examples/StreamingAsync/) for a complete SoftAP/captive-portal project.
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Configuration and memory limits
|
||||||
|
|
||||||
|
DFTE object layouts depend on fixed `DFTE_*` compile-time limits. Define any changes through shared PlatformIO `build_flags` so the library and every consuming translation unit agree on the same layout.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
build_flags =
|
||||||
|
-DDFTE_BUFFER_SIZE=768
|
||||||
|
-DDFTE_MAX_STACK_DEPTH=24
|
||||||
|
-DDFTE_PLACEHOLDER_NAME_SIZE=32
|
||||||
|
-DDFTE_MAX_ITERATIONS=80
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DFTE_BUFFER_SIZE` | 512 | Streaming buffer size in `TemplateContext` |
|
||||||
|
| `DFTE_MAX_STACK_DEPTH` | 16 | Render stack frames; nested templates usually need two frames each |
|
||||||
|
| `DFTE_PLACEHOLDER_NAME_SIZE` | 24 | Maximum token length including `%` characters |
|
||||||
|
| `DFTE_PROGMEM_CHUNK_SIZE` | 512 | PROGMEM source copy window |
|
||||||
|
| `DFTE_RAM_CHUNK_SIZE` | 128 | RAM source copy window |
|
||||||
|
| `DFTE_MAX_ITERATIONS` | 50 | Safety cap for one render call |
|
||||||
|
| `DFTE_MAX_PLACEHOLDERS_DEFAULT` | 16 | Default registry constructor capacity |
|
||||||
|
|
||||||
|
`DFTE_MAX_PLACEHOLDERS_DEFAULT` is only a constructor default; pass an explicit registry capacity where a device needs more. DeviceFramework runtime template parameters do not change DFTE’s compile-time object layout.
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Development and releases
|
||||||
|
|
||||||
|
Released applications should use a public Git tag. While changing DFTE with a sibling library, select a local `symlink://` or `file://` dependency from an ignored PlatformIO override rather than changing tracked application dependencies.
|
||||||
|
|
||||||
|
Before a release, update `library.json`, `CHANGELOG.md`, and the relevant guides, then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/check-docs.sh
|
||||||
|
./scripts/test.sh compile --platform esp8266
|
||||||
|
./scripts/test.sh compile --platform esp32
|
||||||
|
./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag
|
||||||
|
```
|
||||||
|
|
||||||
|
Push the branch and annotated tag. GitHub Actions repeats the board-free compile checks, validates the package, and creates a GitHub Release from that version’s changelog section. It does not publish to the PlatformIO Registry.
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Getting started
|
||||||
|
|
||||||
|
DFTE renders a named root template through a `TemplateContext`. Register templates and placeholder values once, then ask the renderer for fixed-size chunks until completion.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <TemplateEngine.h>
|
||||||
|
|
||||||
|
static const char ROOT[] PROGMEM = R"DFTE(
|
||||||
|
<h1>%TITLE%</h1><p>Uptime: %UPTIME%</p>
|
||||||
|
)DFTE";
|
||||||
|
|
||||||
|
PlaceholderRegistry registry;
|
||||||
|
TemplateContext context;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
registry.registerProgmemData(PSTR("%TITLE%"), PSTR("DFTE Quickstart"));
|
||||||
|
registry.registerRamData(PSTR("%UPTIME%"), []() -> const char* {
|
||||||
|
static char value[16];
|
||||||
|
snprintf(value, sizeof(value), "%lus", millis() / 1000);
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
registry.registerProgmemTemplate(PSTR("%ROOT%"), ROOT);
|
||||||
|
context.setRegistry(®istry);
|
||||||
|
TemplateRenderer::initializeContext(context, PSTR("%ROOT%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
uint8_t chunk[128];
|
||||||
|
if (!TemplateRenderer::isComplete(context) && !TemplateRenderer::hasError(context)) {
|
||||||
|
const size_t written = TemplateRenderer::renderNextChunk(context, chunk, sizeof(chunk));
|
||||||
|
Serial.write(chunk, written);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`registerProgmemData()` is for static flash data. `registerRamData()` takes a getter for a value that can change as chunks are rendered. `registerProgmemTemplate()` lets a placeholder expand to another template.
|
||||||
|
|
||||||
|
For a buildable project, start with [HelloPlaceholder](../examples/HelloPlaceholder/). Next: [template language](TEMPLATE_LANGUAGE.md).
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# 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) |
|
||||||
|
| 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).
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Template language and lifecycle
|
||||||
|
|
||||||
|
Every token has the form `%NAME%`. DFTE resolves it from a `PlaceholderRegistry` according to its registered type.
|
||||||
|
|
||||||
|
| Registered type | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `registerProgmemData()` | Static flash-resident text |
|
||||||
|
| `registerRamData()` | Getter returning current `const char*` data |
|
||||||
|
| `registerProgmemTemplate()` | Nested PROGMEM template |
|
||||||
|
| `registerDynamicTemplate()` | Template fragment supplied at render time |
|
||||||
|
| `registerConditional()` | Choose a true, false, or skipped delegate |
|
||||||
|
| `registerIterator()` | Stream repeated item templates through an iterator handle |
|
||||||
|
|
||||||
|
## Context lifecycle
|
||||||
|
|
||||||
|
1. Populate a registry during setup.
|
||||||
|
2. Attach it to a `TemplateContext`.
|
||||||
|
3. Initialise the context with the root placeholder.
|
||||||
|
4. Call `renderNextChunk()` until complete or error.
|
||||||
|
5. Call `reset()` before reusing a completed context for another root.
|
||||||
|
|
||||||
|
Do not treat `isComplete()` as a success result by itself; check `hasError()` as well.
|
||||||
|
|
||||||
|
## Iterators
|
||||||
|
|
||||||
|
An iterator opens a handle, produces `IteratorItemView` values, then closes the handle. Handles are closed when rendering finishes, resets, stalls, or fails. The `open`, `next`, and `close` callbacks must therefore tolerate early termination.
|
||||||
|
|
||||||
|
Use [AsyncDashboardDemo](../examples/AsyncDashboardDemo/) for a complete iterator example. Use [NestedLayouts](../examples/NestedLayouts/) for partials and conditions.
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
The two PlatformIO Unity commands compile the complete DFTE test suites without uploading or executing them, so they require no attached board.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/test.sh compile --platform esp8266
|
||||||
|
./scripts/test.sh compile --platform esp32
|
||||||
|
```
|
||||||
|
|
||||||
|
The test environments include the library sources with `test_build_src = yes`. CI runs both target checks on the maintained branch and pull requests.
|
||||||
|
|
||||||
|
The standalone examples are buildable PlatformIO projects; see [examples](../examples/README.md).
|
||||||
|
|
||||||
|
Back to [documentation](README.md) · [project overview](../README.md).
|
||||||
@@ -17,6 +17,8 @@ pio run -d examples/AsyncDashboardDemo -e dashboard_esp32
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
See the shared [examples guide](../README.md) and [async web guidance](../../docs/ASYNC_WEB.md).
|
||||||
|
|
||||||
1. Flash your target board (`pio run -d examples/AsyncDashboardDemo -e <env> -t upload --upload-port <port>`).
|
1. Flash your target board (`pio run -d examples/AsyncDashboardDemo -e <env> -t upload --upload-port <port>`).
|
||||||
2. Open the serial monitor at 115200 baud to confirm the SoftAP credentials.
|
2. Open the serial monitor at 115200 baud to confirm the SoftAP credentials.
|
||||||
3. Connect to the advertised network and browse to `http://192.168.4.1/`. The captive portal should automatically redirect; if not, navigate manually.
|
3. Connect to the advertised network and browse to `http://192.168.4.1/`. The captive portal should automatically redirect; if not, navigate manually.
|
||||||
|
|||||||
@@ -13,5 +13,7 @@ pio run -d examples/HelloPlaceholder -e example_esp8266
|
|||||||
pio run -d examples/HelloPlaceholder -e example_esp32
|
pio run -d examples/HelloPlaceholder -e example_esp32
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the shared [examples guide](../README.md) and [quickstart](../../docs/GETTING_STARTED.md).
|
||||||
|
|
||||||
Flash the firmware and open the serial monitor at 115200 baud to see the rendered text.
|
Flash the firmware and open the serial monitor at 115200 baud to see the rendered text.
|
||||||
|
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ pio run -d examples/NestedLayouts -e example_esp8266
|
|||||||
pio run -d examples/NestedLayouts -e example_esp32
|
pio run -d examples/NestedLayouts -e example_esp32
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the shared [examples guide](../README.md) and [template-language guide](../../docs/TEMPLATE_LANGUAGE.md).
|
||||||
|
|
||||||
Open the serial monitor at 115200 baud to inspect the rendered output.
|
Open the serial monitor at 115200 baud to inspect the rendered output.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# DFTE examples
|
||||||
|
|
||||||
|
Every example is a standalone PlatformIO project. Build, upload, and monitor from its directory:
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose the ESP32 environment where provided.
|
||||||
|
|
||||||
|
| Example | What it demonstrates |
|
||||||
|
| --- | --- |
|
||||||
|
| [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 |
|
||||||
|
|
||||||
|
The SoftAP examples print their network names and passwords to serial. They are demonstrations, not production provisioning implementations.
|
||||||
|
|
||||||
|
Back to [DFTE documentation](../docs/README.md) · [project overview](../README.md).
|
||||||
@@ -11,6 +11,8 @@ Small ESPAsyncWebServer demo that exposes a captive portal over a SoftAP and ren
|
|||||||
|
|
||||||
```
|
```
|
||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -34,5 +34,16 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/alexhopeoconnor/DFTE.git"
|
"url": "https://github.com/alexhopeoconnor/DFTE.git"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"include": [
|
||||||
|
"include",
|
||||||
|
"src",
|
||||||
|
"examples",
|
||||||
|
"README.md",
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"LICENSE",
|
||||||
|
"library.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
required=(
|
||||||
|
README.md CHANGELOG.md
|
||||||
|
docs/README.md docs/GETTING_STARTED.md docs/TEMPLATE_LANGUAGE.md
|
||||||
|
docs/ASYNC_WEB.md docs/CONFIGURATION.md docs/TESTING.md docs/DEVELOPMENT.md
|
||||||
|
examples/README.md
|
||||||
|
)
|
||||||
|
|
||||||
|
for path in "${required[@]}"; do
|
||||||
|
[[ -f "$root/$path" ]] || { echo "Missing required documentation: $path" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
while IFS= read -r -d '' markdown; do
|
||||||
|
while IFS= read -r target; do
|
||||||
|
[[ -z "$target" || "$target" == \#* || "$target" == http://* || "$target" == https://* || "$target" == mailto:* ]] && continue
|
||||||
|
target="${target%%#*}"
|
||||||
|
case "$target" in
|
||||||
|
/*) candidate="$root/${target#/}" ;;
|
||||||
|
*) candidate="$(dirname "$markdown")/$target" ;;
|
||||||
|
esac
|
||||||
|
[[ -e "$candidate" ]] || { echo "Broken relative link in ${markdown#$root/}: $target" >&2; exit 1; }
|
||||||
|
done < <(sed -nE 's/.*\]\(([^ )]+)( "[^"]*")?\).*/\1/p' "$markdown")
|
||||||
|
done < <(find "$root" -path "$root/.git" -prune -o -path '*/.pio' -prune -o -name '*.md' -type f -print0)
|
||||||
|
|
||||||
|
echo "Documentation links and required files passed"
|
||||||
@@ -27,6 +27,13 @@ if [[ -f "$root/library.properties" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^## ${version}$" "$root/CHANGELOG.md"; then
|
||||||
|
echo "CHANGELOG.md is missing a ## ${version} section" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git -C "$root" diff --check
|
||||||
|
|
||||||
package_dir="$(mktemp -d)"
|
package_dir="$(mktemp -d)"
|
||||||
trap 'rm -rf "$package_dir"' EXIT
|
trap 'rm -rf "$package_dir"' EXIT
|
||||||
pio pkg pack "$root" --output "$package_dir/package.tar.gz" >/dev/null
|
pio pkg pack "$root" --output "$package_dir/package.tar.gz" >/dev/null
|
||||||
|
|||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 vMAJOR.MINOR.PATCH" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
tag="${1:-}"
|
||||||
|
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || usage
|
||||||
|
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
version="${tag#v}"
|
||||||
|
changelog="$root/CHANGELOG.md"
|
||||||
|
output="$(mktemp)"
|
||||||
|
trap 'rm -f "$output"' EXIT
|
||||||
|
|
||||||
|
awk -v version="$version" '
|
||||||
|
$0 == "## " version { capture = 1; next }
|
||||||
|
capture && /^## / { exit }
|
||||||
|
capture { print }
|
||||||
|
' "$changelog" > "$output"
|
||||||
|
|
||||||
|
if [[ ! -s "$output" ]]; then
|
||||||
|
echo "No release notes found for $tag in CHANGELOG.md" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n\n' "# DeviceFrameworkTemplateEngine $tag"
|
||||||
|
cat "$output"
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 compile --platform esp8266|esp32" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "${1:-}" == "compile" && "${2:-}" == "--platform" && $# -eq 3 ]] || usage
|
||||||
|
|
||||||
|
case "${3:-}" in
|
||||||
|
esp8266) environment="test_template_engine_8266" ;;
|
||||||
|
esp32) environment="test_template_engine_esp32" ;;
|
||||||
|
*) 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}"
|
||||||
@@ -154,7 +154,7 @@ RenderingContextType DeviceFrameworkTemplateContext::getCurrentContextType() con
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceFrameworkTemplateContext::isComplete() const {
|
bool DeviceFrameworkTemplateContext::isComplete() const {
|
||||||
return state == TemplateRenderState::COMPLETE || state == TemplateRenderState::ERROR;
|
return state == TemplateRenderState::COMPLETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceFrameworkTemplateContext::hasError() const {
|
bool DeviceFrameworkTemplateContext::hasError() const {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ void test_template_context_initialization() {
|
|||||||
ctx.state = TemplateRenderState::COMPLETE;
|
ctx.state = TemplateRenderState::COMPLETE;
|
||||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "Should be complete when state is COMPLETE");
|
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "Should be complete when state is COMPLETE");
|
||||||
ctx.state = TemplateRenderState::ERROR;
|
ctx.state = TemplateRenderState::ERROR;
|
||||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "Should be complete when state is ERROR");
|
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "An error must not report successful completion");
|
||||||
|
|
||||||
// Test hasError
|
// Test hasError
|
||||||
ctx.state = TemplateRenderState::TEXT;
|
ctx.state = TemplateRenderState::TEXT;
|
||||||
@@ -310,7 +310,7 @@ void test_template_context_state() {
|
|||||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "COMPLETE should be complete");
|
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "COMPLETE should be complete");
|
||||||
|
|
||||||
ctx.state = TemplateRenderState::ERROR;
|
ctx.state = TemplateRenderState::ERROR;
|
||||||
TEST_ASSERT_TRUE_MESSAGE(ctx.isComplete(), "ERROR should be complete");
|
TEST_ASSERT_FALSE_MESSAGE(ctx.isComplete(), "ERROR should not be complete");
|
||||||
|
|
||||||
// Test hasError for all states
|
// Test hasError for all states
|
||||||
ctx.state = TemplateRenderState::TEXT;
|
ctx.state = TemplateRenderState::TEXT;
|
||||||
|
|||||||
Reference in New Issue
Block a user