diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bda1efa..de0515a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,6 @@ name: Tests on: push: - branches: - - main - - feature/observable-callback-coverage pull_request: branches: - main @@ -14,6 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Verify core Command remains Event-free + run: | + if grep -R -nE '#[[:space:]]*include[[:space:]]*[<\"]ESPressio_(Event|CommandEvents|CommandRegistryEventBridge)' src/ESPressio_Command.hpp src/ESPressio_Commands.hpp; then + echo 'Core Command umbrella must not acquire Event integration.' + exit 1 + fi - name: Configure run: cmake -S tests -B build - name: Build @@ -21,40 +24,69 @@ jobs: - name: Test run: ctest --test-dir build --output-on-failure - esp32-example: + esp32-event-integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + path: project/ESPressio-Command - uses: actions/checkout@v4 with: repository: Flowduino/ESPressio-Observable ref: 3.0.1 - path: deps/ESPressio-Observable + path: project/dependencies/ESPressio-Observable + - uses: actions/checkout@v4 + with: + repository: Flowduino/ESPressio-Units + ref: 0.2.3 + path: project/dependencies/ESPressio-Units + - uses: actions/checkout@v4 + with: + repository: Flowduino/ESPressio-Timing + ref: 2.2.4 + path: project/dependencies/ESPressio-Timing + - uses: actions/checkout@v4 + with: + repository: Flowduino/ESPressio-Threads + ref: 3.1.4 + path: project/dependencies/ESPressio-Threads + - uses: actions/checkout@v4 + with: + repository: Flowduino/ESPressio-Event + ref: 6.0.0 + path: project/dependencies/ESPressio-Event + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Install PlatformIO run: pip install platformio - - name: Create PlatformIO consumer project + - name: Create Event integration compile project shell: bash run: | - mkdir -p "$RUNNER_TEMP/espressio-command-ci/src" "$RUNNER_TEMP/espressio-command-ci/lib" - rsync -a --exclude='.git' --exclude='deps' --exclude='build' ./ "$RUNNER_TEMP/espressio-command-ci/lib/ESPressio-Command/" - cp -R deps/ESPressio-Observable "$RUNNER_TEMP/espressio-command-ci/lib/ESPressio-Observable" - cat > "$RUNNER_TEMP/espressio-command-ci/platformio.ini" <<'EOF' - [env:esp32dev] + mkdir -p project/compile/src + cat > project/compile/platformio.ini <<'EOF' + [env:esp32] platform = espressif32 board = esp32dev framework = arduino - build_flags = - -std=gnu++17 - -frtti - build_unflags = - -std=gnu++11 - -fno-rtti + build_flags = -std=gnu++17 -frtti + build_unflags = -std=gnu++11 -fno-rtti + lib_ldf_mode = deep+ lib_deps = - ESPressio-Command - ESPressio-Observable + ../dependencies/ESPressio-Observable + ../dependencies/ESPressio-Units + ../dependencies/ESPressio-Timing + ../dependencies/ESPressio-Threads + ../dependencies/ESPressio-Event + ../ESPressio-Command EOF - - name: Compile BasicCommand - shell: bash - run: | - cp examples/BasicCommand/BasicCommand.ino "$RUNNER_TEMP/espressio-command-ci/src/main.cpp" - pio run -d "$RUNNER_TEMP/espressio-command-ci" + cat > project/compile/src/main.cpp <<'EOF' + #include + #include + #include + #include + void setup() { ESPressio::Event::CommandRegistryEventBridge::GetInstance().Initialize(); } + void loop() {} + EOF + - name: Compile Command Event integration + run: pio run -d project/compile diff --git a/CHANGELOG.md b/CHANGELOG.md index 0128572..8ea2157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 0.4.0 — 2026-08-21 + +### Added + +- Moved Command registry Event types and `CommandRegistryEventBridge` ownership into ESPressio Command. +- Added an opt-in Command -> Event integration targeting ESPressio Event 6.0.0 while keeping the normal Command core independent of Event. + +### Changed + +- Preserved the existing `ESPressio_CommandEvents.hpp` and `ESPressio_CommandRegistryEventBridge.hpp` public names in their new owning package. +- Updated package metadata, documentation, dependency charts, and CI for the 0.4.0 architecture. +- The normal Command umbrella remains Event-free; Event is required only when the Event bridge headers are selected. + +### Compatibility + +- Core Command APIs and registry behavior are unchanged. +- Applications using the Event bridge must obtain the bridge headers from ESPressio Command 0.4.0 rather than ESPressio Event 6.0.0. + +### Tracking + +- Implements #3. +- Coordinated with Flowduino/ESPressio-Event#35. + ## 0.3.0 - Added `ICommandRegistryObserver` and observer registration on `CommandRegistry`. diff --git a/DEPENDENCY_BOUNDARIES.md b/DEPENDENCY_BOUNDARIES.md new file mode 100644 index 0000000..8b2277c --- /dev/null +++ b/DEPENDENCY_BOUNDARIES.md @@ -0,0 +1,7 @@ +# ESPressio Command Dependency Boundaries + +ESPressio Command owns Command-specific lifecycle semantics and their optional Event representation. + +The core Command mechanism depends on Observable and remains independent of ESPressio Event. `CommandRegistryEventBridge` and the Command Event family are optional integration headers owned by Command and may depend on Event only when explicitly selected. + +The normal Command umbrella must remain free of Event includes. diff --git a/ESPRESSIO_DEPENDENCY_CHART.md b/ESPRESSIO_DEPENDENCY_CHART.md index 272a0b4..1257c92 100644 --- a/ESPRESSIO_DEPENDENCY_CHART.md +++ b/ESPRESSIO_DEPENDENCY_CHART.md @@ -1,38 +1,32 @@ -# ESPressio Dependency Chart +# ESPressio Dependency Chart — Command 0.4.0 -## Purpose +![ESPressio Library Dependency Chart](ESPRESSIO_DEPENDENCY_CHART.svg) -This document describes ESPressio Command's place within the ESPressio library ecosystem. - -- **Solid arrow** — required ESPressio dependency. -- **Dashed arrow** — opt-in dependency activated only by the associated feature/header. -- Arrows point from the dependent library to the library it consumes. - -## ESPressio Command 0.2.0 - -ESPressio Command has **no required ESPressio dependencies**. - -The core remains transport-neutral and independently usable: +## Command 0.4.0 ```text -ESPressio Command 0.2.0 - | - +-- no required ESPressio dependencies +Command 0.4.0 + -> Observable >= 3.0.1 < 4.0.0 + - - -> Event >= 6.0.0 < 7.0.0 + Command Event types / CommandRegistryEventBridge only ``` -Higher-level libraries may optionally consume Command: +The Event relationship is opt-in. Normal Command use remains Event-free. + +## Final coordinated ecosystem ```text -ESPressio Serial 0.4.0 - - - -> ESPressio Command >= 0.2.0 < 1.0.0 +Observable 3.0.1 +Serializable 0.10.2 +Units 0.2.3 +Timing 2.2.4 +Threads 3.1.4 +Command 0.4.0 +Security 0.3.0 +Event 6.0.0 +Sockets 0.6.0 +ESP-Now 0.6.0 +Serial 0.6.0 ``` -Serial's `CommandConsole` adapts Stream/Console input to the shared Command registry, while its Command-backed EventConsole registers an `event` subtree using ownership-safe registration handles. - -The dependency direction is intentionally one-way: Command does not depend on Serial, Event, Serializable, Sockets, networking transports, or Arduino `Stream`/`Print`. - -## Design rule - -Future transport/protocol integrations should depend on ESPressio Command rather than adding those transports as dependencies of the Command core. - -Examples include future Serial, USB CDC, TCP, WebSocket, BLE, HTTP/RPC, Serializable, or Event bridge integrations. +Command owns its Command-specific Event bridge. Event 6.0.0 does not depend back on Command, so no reciprocal edge remains. diff --git a/ESPRESSIO_DEPENDENCY_CHART.svg b/ESPRESSIO_DEPENDENCY_CHART.svg new file mode 100644 index 0000000..d35a747 --- /dev/null +++ b/ESPRESSIO_DEPENDENCY_CHART.svg @@ -0,0 +1 @@ +ESPressio Dependency Chart — Command 0.4.0Observable 3.0.1Command 0.4.0Event 6.0.0optional Event bridgeCore Command depends only on Observable. Event is selected only by Command-owned Event integration headers. \ No newline at end of file diff --git a/README.md b/README.md index 6f67732..b66d2fd 100644 --- a/README.md +++ b/README.md @@ -1,499 +1,91 @@ # ESPressio Command -Transport-neutral, strongly typed Command definition, parsing, routing, -validation and invocation for the Flowduino ESPressio Development Platform. +Transport-neutral typed command definition, parsing, routing and invocation for the Flowduino ESPressio Development Platform. -ESPressio Command provides a common Command layer that separates **what an -application is being asked to do** from **how that request arrived**. Serial, -USB CDC, TCP, WebSocket, BLE, HTTP, test harnesses and programmatic callers can -therefore share the same Command tree, parameter definitions, validation and -callbacks without coupling application logic to a transport. +## Current Version — 0.4.0 -## 0.3.0 Development Update — Observable Callback Coverage +Command 0.4.0 owns the optional Event representation of its own registry lifecycle. The Command core remains independent of ESPressio Event; applications acquire Event only when they explicitly select the Event bridge headers. -The `feature/observable-callback-coverage` branch targets **ESPressio Command 0.3.0**. The stable/pre-release information below remains the 0.2.0 documentation until 0.3.0 is released. - -Command 0.3.0 adds a required dependency on **ESPressio Observable >= 3.0.1 and < 4.0.0** and introduces `ICommandRegistryObserver`. `CommandRegistry` now reports root command registration and successful unregistration, including scoped `CommandRegistrationHandle` cleanup. Command invocation itself deliberately remains on the existing callbacks, middleware, `Before(...)` and `After(...)` hooks rather than being duplicated as Observable traffic. - -ESPressio Event remains **optional**. ESPressio Event 5.8.0 provides `CommandRegistryEventBridge`, which converts registry lifecycle observations into asynchronous `CommandRegisteredEvent` and `CommandUnregisteredEvent` instances without making Event a Command dependency. - -Development-branch PlatformIO dependencies are: - -```ini -lib_deps = - https://github.com/Flowduino/ESPressio-Command.git#feature/observable-callback-coverage - flowduino/ESPressio-Observable@^3.0.1 -``` - -The host tests include dedicated registry-observer lifecycle coverage. See [CHANGELOG.md](CHANGELOG.md) for the complete 0.3.0 change list. - -## Latest Stable Version - -ESPressio Command is currently **0.2.0 (pre-release)**. - -This is the initial pre-release of the library. For release-by-release history, -see [CHANGELOG.md](CHANGELOG.md). - -## Compatibility - -ESPressio Command targets **C++17** and is designed primarily for the **ESP32 -family under Arduino-ESP32** as part of the ESPressio Development Platform. - -The Command core is deliberately transport-neutral and does not directly depend -on Arduino `Stream`, `Print`, ESPressio Serial, ESPressio Event, a network -stack, or any other ESPressio component library. Beginning with the 0.3.0 development generation it does require ESPressio Observable 3.x for its registry lifecycle surface. - -Host-side tests are also provided so that the transport-neutral core can be -validated with a conventional C++17 toolchain. - -Compatibility should still be verified against the exact compiler, -Arduino-ESP32 version and ESP32 target used by the consuming application. - -## ESPressio Development Platform - -The **ESPressio Development Platform** is a collection of discrete, composable -component libraries developed around a common design ethos. - -The principal objectives are: - -- **Light-weight** — components should strive to minimise memory consumption - and operational overhead without sacrificing clarity or correctness. -- **Ease of Use** — ESPressio components provide developer-friendly, strongly - typed abstractions over lower-level procedural facilities. -- **Object-Oriented** — a type for everything, and everything in a type. -- **SOLID** — to the maximum extent practical within C++, Arduino, FreeRTOS and - microcontroller constraints: - - **Single Responsibility Principle (SRP)** — keep components small and - focused. - - **Open/Closed Principle (OCP)** — prefer extension without modification. - - **Liskov Substitution Principle (LSP)** — derived implementations should - remain substitutable for their abstractions. - - **Interface Segregation Principle (ISP)** — prefer focused, - client-specific interfaces. - - **Dependency Inversion Principle (DIP)** — depend upon abstractions rather - than concrete implementations. - -ESPressio Command follows these principles by treating a Command invocation as -a transport-independent application contract. Transport adapters depend on the -Command abstraction; the Command core does not depend on the adapters. - -## License - -ESPressio and its component libraries are licensed under the **Apache License -2.0**. - -See [LICENSE](LICENSE) for details. - -## ESPressio Library Dependencies - -ESPressio is designed as a modular ecosystem of independently useful libraries, -with required dependencies kept explicit and optional integrations introduced -only when the corresponding functionality is selected. - -For a complete overview of required and opt-in relationships, see: - -**[ESPressio Library Dependency Chart](ESPRESSIO_DEPENDENCY_CHART.md)** - -In the dependency chart: - -- **Solid relationships** represent required ESPressio dependencies. -- **Dashed relationships** represent opt-in dependencies introduced only when - the corresponding feature, integration, type, or header is used. - -### Required ESPressio dependencies - -The stable 0.2.0 pre-release has no ESPressio dependency. **The 0.3.0 development branch requires ESPressio Observable >= 3.0.1 and < 4.0.0.** - -Serial, Event, networking, Serializable and other integrations should depend on Command or be provided as opt-in adapters; they must not become mandatory dependencies of the Command core. Event remains opt-in even though 5.8.0 provides a Command registry Event bridge. - -## Namespace - -The Command API resides beneath: - -```cpp -ESPressio::Command -``` - -The principal public types are: - -- `CommandRegistry` — owns and resolves the Command tree. -- `CommandNode` — describes a Command or Command group. -- `CommandParameter` — describes and validates a parameter. -- `CommandContext` — exposes resolved values to a Command callback. -- `CommandInvocation` — transport-neutral structured invocation. -- `CommandResult` — success/error result returned by Command execution. -- `TextCommandParser` — converts textual Command lines into tokens. -- `CommandLine` — incrementally consumes character/buffer input. -- `CommandFactory` — convenient facade for Command registration. -- `CommandRegistrationHandle` — ownership-safe scoped dynamic registration. -- `ICommandRegistryObserver` — 0.3.0 registry lifecycle observer. - -## PlatformIO - -For the stable/pre-release 0.2.0 generation: - -```ini -lib_deps = - flowduino/ESPressio-Command@^0.2.0 -``` - -For 0.3.0, consume ESPressio Observable 3.x as shown in the development update above. - -Until a release/tag is published, or when deliberately consuming the latest -integration sources, use: - -```ini -lib_deps = - https://github.com/Flowduino/ESPressio-Command.git -``` - -The Git source tracks the latest commits on the repository and may therefore be -more volatile than a tagged release. - -## Why a separate Command library? - -A **Command** expresses intent: **do something**. - -An **Event** expresses a fact: **something happened**. - -Keeping these concepts separate allows application code to expose operations -without embedding Serial, networking, protocol or UI concerns into those -operations. +## Core dependency ```text -Serial / USB CDC ----+ -TCP / WebSocket -----+ -BLE / HTTP ----------+--> CommandInvocation --> CommandRegistry --> callback -Programmatic --------+ -Test harness --------+ +Command 0.4.0 + -> Observable >= 3.0.1 < 4.0.0 ``` -Text input is therefore only one possible adapter. The same registered Command -can be invoked from a parsed line, a structured request or another application -component. +There is no mandatory dependency on Event, Serial, Sockets, ESP-Now, Security, or a particular input transport. -## Command Trees +## Optional Event integration -Commands are organised hierarchically. A parent can represent a namespace or -operation group while child nodes provide increasingly specific actions. +Command 0.4.0 provides: ```cpp -#include - -using namespace ESPressio::Command; - -auto& commands = CommandRegistry::GetInstance(); - -auto& write = commands.Command("gpio") - .Description("GPIO operations") - .Command("write") - .Description("Set a GPIO output value"); - -write.Parameter("pin") - .Description("GPIO pin") - .Range(0, 48); - -write.Parameter("state") - .Description("Desired pin state"); - -write.OnExecute([](const CommandContext& context) { - const int pin = context.Get("pin"); - const bool state = context.Get("state"); - - // digitalWrite(pin, state ? HIGH : LOW); - - return CommandResult::Ok("GPIO updated"); -}); +#include +#include ``` -All of the following resolve to the same callback: +The integration requires: ```text -gpio write 2 high -gpio write --pin 2 --state high -gpio write --pin=2 --state=high -``` - -This makes the Command definition the authoritative contract rather than any -particular textual syntax. - -## Parameters - -Parameters can be: - -- strongly typed as string, boolean, signed integer, unsigned integer or - floating point; -- positional, named-only, or supplied by name; -- required or optional; -- assigned default values; -- given aliases; -- range constrained; -- constrained to a set of permitted values; and -- checked by a custom validator. - -For example: - -```cpp -auto& mode = commands.Command("gpio").Command("mode"); - -mode.Parameter("pin") - .Range(0, 48); - -mode.Parameter("mode", ParameterKind::Enumeration) - .OneOf({"in", "out", "pullup", "pulldown"}); -``` - -Resolved values are exposed through `CommandContext` and can be requested in -the desired C++ type: - -```cpp -const int pin = context.Get("pin"); -const bool state = context.Get("state"); +ESPressio Event >= 6.0.0 < 7.0.0 ``` -Validation occurs before the Command callback is executed, keeping parsing and -input validation out of application logic. - -## Automatic Help - -Help is generated from the same metadata used to define and resolve Commands: +and converts the existing synchronous `ICommandRegistryObserver` lifecycle into asynchronous Events: ```text -help -help gpio -help gpio write -``` - -Help can also be generated programmatically: - -```cpp -auto text = commands.Help({"gpio", "write"}); -``` - -Descriptions, parameters, required/optional state and defaults therefore remain -aligned with the executable Command definition. - -## Completion and Typo Suggestions - -Registered Command metadata can also be used for completion: - -```cpp -auto matches = commands.Complete("gpio w"); -``` - -Unknown Command names are compared with registered siblings and a nearby -Command is suggested where appropriate. Hidden Commands are omitted from -completion results. - -## Structured Invocation - -Text parsing is an adapter rather than the core Command contract. Other input -mechanisms can invoke the registry directly without manufacturing a textual -Command line: - -```cpp -CommandInvocation invocation; -invocation.path = {"gpio", "write"}; -invocation.named["pin"] = "2"; -invocation.named["state"] = "high"; - -auto result = commands.Invoke(invocation); +CommandRegisteredEvent +CommandUnregisteredEvent ``` -This is the intended integration point for Serial adapters, HTTP endpoints, -WebSocket messages, BLE services, RPC mechanisms, automated tests and other -structured callers. - -## Middleware and Interception - -Cross-cutting behaviour can wrap every invocation: - -```cpp -commands.Use([](const CommandInvocation& invocation, const auto& next) { - // Authorization, audit, rate limiting, tracing, etc. - return next(); -}); -``` - -Individual Commands can also register `Before(...)` and `After(...)` callbacks. - -These extension points allow policy, diagnostics and integration behaviour to -be layered around Command execution without coupling those concerns to the -Command callback itself. - -## Observable Registry Lifecycle (0.3.0) - -Registry topology changes can now be observed without changing command execution semantics: - -```cpp -class RegistryObserver final : - public ESPressio::Command::ICommandRegistryObserver { -public: - void OnCommandRegistered(const std::vector& path) override { - // Passive diagnostics / discovery refresh. - } - - void OnCommandUnregistered(const std::vector& path) override { - // Owned registration lifetime ended. - } -}; - -RegistryObserver observer; -auto observerHandle = commands.RegisterObserver(&observer); -``` - -New root creation and successful root removal emit notifications. Duplicate registration attempts that do not change the tree do not emit. `CommandRegistrationHandle::Reset()` and handle destruction flow through the same successful-unregistration path. - -With ESPressio Event 5.8.0 selected, `CommandRegistryEventBridge` can convert these facts into asynchronous Events. Event remains an optional downstream adapter. - -## Incremental Text Input - -`CommandLine` accepts characters or buffers and submits complete lines to a -registry. It deliberately knows nothing about Serial itself: +The bridge/header names are preserved from their previous location in ESPressio Event, but ownership now matches the represented domain: Command. -```cpp -CommandLine input(commands); +## Dependency direction -input.OnResult([](const CommandResult& result) { - // Send result.message to whichever output transport owns this input. -}); +```text +Command core + -> Observable -input.Feed(receivedCharacter); +Command Event integration + - - -> Event ``` -A Serial or USB CDC integration can therefore feed received bytes into the -Command layer while retaining complete ownership of the underlying stream, -connection and output formatting. - -## Aliases, Visibility and Deprecation +Event does not depend back on Command in Event 6.0.0. This removes the misplaced Event -> Command adapter relationship and keeps the dependency graph one-way. -Commands can expose aliases without duplicating callbacks: +The normal Command umbrella remains Event-free: ```cpp -commands.Command("diagnostics") - .Alias("diag") - .Description("Diagnostic commands"); +#include ``` -Commands can also be marked as deprecated: +## Example Event bridge ```cpp -commands.Command("old-command") - .Deprecated("Use 'new-command' instead"); -``` - -Hidden Commands remain resolvable but are omitted from generated help and -completion. +#include +#include -## Quoting and Escaping - -The text parser supports whitespace-separated arguments, single-quoted values, -double-quoted values and backslash escaping: - -```text -system label "Main Controller" -system label 'Bench Unit' +void setup() { + ESPressio::Event::CommandRegistryEventBridge::GetInstance().Initialize(); +} ``` -This keeps ordinary console usage convenient without making textual parsing a -requirement for structured callers. - -## Command Results - -Command callbacks return `CommandResult`, providing a transport-neutral success -state, numeric result code and optional message: - -```cpp -return CommandResult::Ok("GPIO updated"); -``` - -or: - -```cpp -return CommandResult::Error("GPIO update failed", 5); -``` - -The caller or adapter decides how that result is represented to its consumer. -A Serial console may print the message, while an HTTP adapter might map the -result into a structured response. - -## Design Principles - -ESPressio Command is intentionally built around a small number of architectural -rules: - -1. **Commands describe intent, not transport.** -2. **Command definitions are the authoritative source of metadata and - validation.** -3. **Application callbacks receive validated, typed values.** -4. **Text parsing is an adapter, not the invocation model.** -5. **Transport and protocol integrations belong outside the core.** -6. **Cross-cutting behaviour should be implemented through middleware or - focused hooks rather than embedded in application callbacks.** -7. **The core remains independently useful; from 0.3.0 its only required ESPressio dependency is Observable.** - -## Examples - -The repository includes examples beneath [`examples/`](examples/) demonstrating -Command registration and invocation in an Arduino/ESP32 application. - -A typical application defines its Command tree during initialization and then -feeds invocations from whichever transport or application surface owns the -interaction. - -## Testing - -Host-side tests are provided beneath [`tests/`](tests/). - -They exercise the transport-neutral Command implementation independently of -Arduino hardware. This keeps parsing, resolution, validation and invocation -behaviour testable with a conventional C++17 toolchain while embedded examples -validate intended ESP32 integration usage. The 0.3.0 generation also validates registry-observer registration lifetime and notification semantics. - -## Future Integration Direction - -ESPressio Command is intended to become the common invocation layer for: - -- Serial and USB consoles; -- TCP, WebSocket and BLE Command surfaces; -- HTTP/RPC gateways; -- structured programmatic invocation; -- Command discovery and schemas; -- authorization and permissions; -- auditing and diagnostics; -- cancellation/progress for asynchronous operations; -- remote Command invocation; -- JSON/Serializable argument adapters; and -- Event bridges for Command lifecycle/completion/result Events where those asynchronous representations are justified. - -These integrations should remain **opt-in**. The dependency direction is -important: +## Final coordinated generation ```text -Serial adapter --------+ -Network adapter -------+ -Serializable adapter --+--> ESPressio Command --> ESPressio Observable -Event bridge ----------+ +Observable 3.0.1 +Serializable 0.10.2 +Units 0.2.3 +Timing 2.2.4 +Threads 3.1.4 +Command 0.4.0 +Security 0.3.0 +Event 6.0.0 +Sockets 0.6.0 +ESP-Now 0.6.0 +Serial 0.6.0 ``` -The Command core must remain transport-neutral and independently usable. - -## Contributing - -Issues and contributions are welcome through the ESPressio Command GitHub -repository. Changes should preserve the library's transport-neutral core, -C++17 compatibility and ESPressio design principles. - -Where practical, behavioural changes should include corresponding tests and -examples or documentation updates. - -## Changelog - -See [CHANGELOG.md](CHANGELOG.md) for release history and notable changes. +See [ESPRESSIO_DEPENDENCY_CHART.md](ESPRESSIO_DEPENDENCY_CHART.md) for the dependency graph and [CHANGELOG.md](CHANGELOG.md) for release history. ## License -ESPressio and its component libraries are licensed under the **Apache License -2.0**. - -See [LICENSE](LICENSE) for details. +Apache License 2.0. See [LICENSE](LICENSE). diff --git a/component.mk b/component.mk index bed2c5a..c1cc92d 100644 --- a/component.mk +++ b/component.mk @@ -3,7 +3,7 @@ COMPONENT_SRCDIRS := src CXXFLAGS += -DESPRESSIO_COMMAND CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_MAJOR=0 -CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_MINOR=3 +CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_MINOR=4 CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_PATCH=0 -CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_STRING=\"0.3.0\" +CXXFLAGS += -DESPRESSIO_COMMAND_VERSION_STRING=\"0.4.0\" CXXFLAGS += -std=gnu++17 diff --git a/library.json b/library.json index 456815c..531ed55 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "ESPressio-Command", "description": "Transport-neutral typed command definition, parsing, routing and invocation framework for ESP32 and C++17", - "keywords": "command,commands,cli,console,parser,router,dispatcher,observable,espressio", + "keywords": "command,commands,cli,console,parser,router,dispatcher,observable,event,espressio", "authors": { "name": "Flowduino", "maintainer": true, @@ -17,7 +17,7 @@ "type": "git", "url": "https://github.com/Flowduino/ESPressio-Command.git" }, - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "frameworks": "*", "platforms": "*", diff --git a/library.properties b/library.properties index 00fb19d..54a78d9 100644 --- a/library.properties +++ b/library.properties @@ -1,11 +1,11 @@ name=Flowduino ESPressio-Command -version=0.3.0 +version=0.4.0 author=Simon J. Stuart maintainer=Flowduino.com sentence=Transport-neutral typed command routing and invocation framework -paragraph=Defines hierarchical commands, typed positional and named parameters, validation, help, completion, middleware, callbacks and observable command-registry lifecycle notifications independently of Serial, TCP, WebSocket or other input transports. +paragraph=Defines hierarchical commands, typed positional and named parameters, validation, help, completion, middleware, callbacks and observable command-registry lifecycle notifications, with an opt-in Event 6.x bridge owned by Command. category=Other url=https://github.com/Flowduino/ESPressio-Command architectures=* includes=ESPressio_Command.hpp,ESPressio_CommandFactory.hpp,ESPressio_CommandLine.hpp,ESPressio_Commands.hpp -depends=Flowduino ESPressio-Observable (>=3.0.1) +depends=Flowduino ESPressio-Observable (>=3.0.1 && <4.0.0) diff --git a/src/ESPressio_CommandEvents.hpp b/src/ESPressio_CommandEvents.hpp new file mode 100644 index 0000000..e8af725 --- /dev/null +++ b/src/ESPressio_CommandEvents.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +#include + +namespace ESPressio::Event { + +class CommandRegisteredEvent final : public Event<> { +public: + const std::vector Path; + explicit CommandRegisteredEvent(const std::vector& path) : Path(path) {} +}; + +class CommandUnregisteredEvent final : public Event<> { +public: + const std::vector Path; + explicit CommandUnregisteredEvent(const std::vector& path) : Path(path) {} +}; + +} // namespace ESPressio::Event diff --git a/src/ESPressio_CommandRegistryEventBridge.hpp b/src/ESPressio_CommandRegistryEventBridge.hpp new file mode 100644 index 0000000..a3cfa80 --- /dev/null +++ b/src/ESPressio_CommandRegistryEventBridge.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include "ESPressio_CommandEvents.hpp" + +namespace ESPressio::Event { + +class CommandRegistryEventBridge final : + public Command::ICommandRegistryObserver { +private: + Observable::ObserverHandlePtr _observerHandle; + bool _initialized = false; + + CommandRegistryEventBridge() = default; + +public: + CommandRegistryEventBridge(const CommandRegistryEventBridge&) = delete; + CommandRegistryEventBridge& operator=(const CommandRegistryEventBridge&) = delete; + + static CommandRegistryEventBridge& GetInstance() { + static CommandRegistryEventBridge instance; + return instance; + } + + bool Initialize( + Command::CommandRegistry& registry = Command::CommandRegistry::GetInstance() + ) { + if (_initialized) return true; + _observerHandle = registry.RegisterObserver(this); + _initialized = static_cast(_observerHandle); + return _initialized; + } + + void Shutdown() { + _observerHandle.reset(); + _initialized = false; + } + + bool IsInitialized() const { return _initialized; } + + void OnCommandRegistered(const std::vector& path) override { + (new CommandRegisteredEvent(path))->Queue(); + } + + void OnCommandUnregistered(const std::vector& path) override { + (new CommandUnregisteredEvent(path))->Queue(); + } +}; + +} // namespace ESPressio::Event