chore: initialize project infrastructure and Phred IO adapters

This commit is contained in:
Funky Waddle 2026-02-22 01:00:52 -06:00
commit eead487714
6 changed files with 497 additions and 0 deletions

27
MILESTONES.md Normal file
View file

@ -0,0 +1,27 @@
# TaskerBridges Milestones
This document defines the implementation milestones for the `getphred/tasker-bridges` package.
## 1. Bridge Infrastructure
- [ ] Initialize repository with `getphred/console-contracts` dependency.
- [ ] Set up testing environment for multi-toolkit validation (mocking Symfony/Laravel).
## 2. Symfony Bridge
- [ ] Implement `SymfonyCommandAdapter`.
- [ ] Implement `SymfonyInputAdapter` and `SymfonyOutputAdapter`.
- [ ] Implement helper adapters (Interaction, ProgressBar, Table).
- [ ] Implement fixed markup translation (mapping to Symfony Formatter).
## 3. Laravel Bridge
- [ ] Implement `LaravelCommandAdapter` and `LaravelServiceProvider`.
- [ ] Integrate Symfony IO adapters within the Laravel context.
- [ ] Implement native Laravel interaction helper mappings.
## 4. Phred (Native) Bridge
- [ ] Implement ANSI-based `OutputInterface` with full markup support.
- [ ] Implement lightweight interaction and progress helpers.
- [ ] Implement regex-based Markdown-to-Markup converter.
## 5. Universal Integration
- [ ] Implement universal exit code translation logic.
- [ ] Implement global flag mapping (Verbosity, Decoration, Interactivity).

174
NOTES.md Normal file
View file

@ -0,0 +1,174 @@
# TaskerBridges: Phred CLI Interoperability Adapters
TaskerBridges is the adapter layer that allows commands written against `Phred\ConsoleContracts` to run inside multiple CLI runtimes with a native experience. Bridges translate Phred's runner-agnostic contracts into the APIs of specific console toolkits.
Project Phase: Idea (NOTES.md)
## 1. Core Vision
- Interoperability first: Write a command once, run it anywhere (Tasker native runner, Symfony Console, Laravel Artisan, etc.).
- Contracts-driven: Bridges never change command code; they only adapt `ConsoleContracts` to the target runtime.
- Minimal footprint: Optional, per-bridge dependencies and lazy wiring. No heavy transitive dependencies for users who don't need a given bridge.
- Native UX: When running under a given toolkit, output, helpers, and ergonomics should feel native to that toolkit.
## 2. Scope & Responsibilities
Bridges provide a cohesive set of adapters:
- Command adapter: Wraps `CommandInterface` and exposes a native command entrypoint.
- IO adapters: Implement `InputInterface`/`OutputInterface` on top of the target toolkits input/output.
- Helper adapters: Implement optional helper contracts (`InteractionInterface`, `ProgressBarInterface`, `TableInterface`, `MarkdownConverterInterface`).
- Markup translator: Converts Phreds fixed markup tags (`<success>`, `<info>`, `<error>`, `<warning>`, `<comment>`, `<b>`, `<i>`, `<u>`) to the target toolkits styles or to raw ANSI.
- Policy alignment: Honor `Verbosity` levels, non-interactive mode, TTY/color detection, and `sysexits.h` exit code semantics.
Non-goals:
- Defining new business logic or command semantics.
- Extending the fixed markup set (bridges must support the core set only).
## 3. Supported Bridges (Initial Set)
- SymfonyBridge: Adapter for `symfony/console` (targeting Symfony 6/7 lines).
- PhredBridge (Native): Minimal adapters used by the Tasker core runner.
- LaravelBridge: Adapter for Laravel's Artisan console (`illuminate/console`).
### 3.1 Future / Potential Bridges (Roadmap)
The following bridges are identified as high-value for future development or upon community request:
- **LaminasBridge**: Adapter for `laminas/laminas-cli`.
- **CakePHPBridge**: Adapter for the CakePHP Console/Shell ecosystem.
- **YiiBridge**: Adapter for Yii 2.0 console commands (Note: Yii 3.0 uses `symfony/console` and may be covered by `SymfonyBridge`).
- **AuraCliBridge**: Adapter for `Aura.Cli` for users of the Aura library suite.
- **RichOutputBridge**: A bridge leveraging a library like `CLImate` for advanced terminal UI features (colors, progress bars) when the native Phred runner is used.
Each bridge lives under `Phred\TaskerBridges\{BridgeName}\...` and can be installed/used independently via Composer suggests.
## 4. SymfonyBridge — Design
### 4.1 High-Level Mapping
- Command metadata
- `CommandInterface::getName()` -> `Symfony\Component\Console\Command\Command::setName()`
- `getDescription()` -> `setDescription()`
- `getArguments()` -> loop `addArgument($name, InputArgument::OPTIONAL|REQUIRED, $description, $default)`
- `getOptions()` -> loop `addOption($name, $shortcut, InputOption::VALUE_NONE|VALUE_OPTIONAL|VALUE_REQUIRED, $description, $default)`
- Note: `ConsoleContracts` currently models arguments/options as `name => description` (DX-minimal). Bridge MAY enhance by honoring defaults/modes if/when added to contracts in future specs.
- Execution
- The adapter class (e.g., `SymfonyCommandAdapter`) extends `Symfony\Component\Console\Command\Command` and wraps a `CommandInterface` instance.
- In `execute(InputInterface $in, OutputInterface $out)`, it:
1) Adapts Symfony input/output to Phred `InputInterface`/`OutputInterface` via IO adapters.
2) Invokes `$wrapped->execute($phredInput, $phredOutput)`.
3) Returns the integer exit code from the wrapped command.
### 4.2 IO Adapters
- `SymfonyInputAdapter` implements `GetPhred\ConsoleContracts\InputInterface` by delegating to `Symfony\Component\Console\Input\InputInterface` methods (`getArgument`, `getOption`, `hasOption`).
- `SymfonyOutputAdapter` implements `GetPhred\ConsoleContracts\OutputInterface` by delegating to `Symfony\Component\Console\Output\OutputInterface`/`Style` and applying markup translation.
Verbosity mapping:
- Phred `Verbosity::QUIET|NORMAL|VERBOSE|VERY_VERBOSE|DEBUG` <-> Symfony `OutputInterface::VERBOSITY_*`.
Non-interactive:
- Respect a runner-provided flag; adapters should avoid prompts and ensure helpers also respect non-interactive mode.
### 4.3 Markup Translation (Fixed Set)
- Translate Phred tags into Symfony formatter equivalents or raw ANSI where Symfony lacks a native tag.
- `<success>` -> Symfony style (green, bold) using `SymfonyStyle` or custom formatter style.
- `<info>` -> Symfony `<info>` (blue where applicable) or custom style to match Phreds blue guideline.
- `<error>` -> white on red background.
- `<warning>` -> black on yellow background.
- `<comment>` -> yellow.
- `<b>` -> bold; `<i>` -> italic; `<u>` -> underline (use ANSI sequences if formatter lacks direct styles).
- If output is not decorated (no TTY or colors disabled), strip tags to plain text.
### 4.4 Helper Adapters
- `InteractionInterface` -> wrap `QuestionHelper` (ask, confirm, secret, choice), ensuring non-interactive fallback behavior (return defaults, never block).
- `ProgressBarInterface` -> wrap `Symfony\Component\Console\Helper\ProgressBar`.
- `TableInterface` -> wrap `Symfony\Component\Console\Helper\Table`.
- `MarkdownConverterInterface` -> use a lightweight adapter strategy:
- Prefer Symfony-native formatting if available; otherwise convert Markdown to Phred markup (headings -> `<b>`, emphasis -> `<i>`/`<b>`, links -> underline/text), then pass through normal markup translation.
### 4.5 Error & Exit Codes
- Return integer exit codes from wrapped command directly.
- Exceptions: Allow the runner to handle globally; optionally map known `ConsoleExceptionInterface` to `ExitCode::USAGE|CONFIG|UNAVAILABLE|SOFTWARE` as appropriate.
## 5. PhredBridge (Native) — Design
- Provide minimal in-house adapters usable by the Tasker runner.
- `PhredOutputAdapter`: translates fixed markup to ANSI escape sequences:
- `<success>` -> green (e.g., `\e[32m`), `<info>` -> blue (`\e[34m`), `<error>` -> white on red (`\e[97;41m`), `<warning>` -> black on yellow (`\e[30;43m`), `<comment>` -> yellow (`\e[33m`), `<b>` -> bold (`\e[1m`), `<i>` -> italic (`\e[3m`), `<u>` -> underline (`\e[4m`). Reset with `\e[0m`.
- TTY detection and `--no-interaction`/`--no-ansi` handling (strip tags when colors disabled or piping to files).
- Provide simple `InteractionInterface`, `ProgressBarInterface`, `TableInterface`, and `MarkdownConverterInterface` implementations with zero external dependencies (basic, but reliable).
## 6. LaravelBridge — Design
### 6.1 Registration & Discovery
- Provide a `TaskerServiceProvider` for Laravel applications.
- Leverage Laravel Package Auto-Discovery via `composer.json` to register the provider automatically.
- The provider will:
- Discover Phred commands via `extra.phred-tasker` or explicit container binding.
- Register them with the Artisan application.
- Provide configuration for non-interactive defaults and formatting.
### 6.2 Container & Execution
- Resolve `CommandInterface` instances via the Laravel Service Container (`app()`), favoring constructor injection.
- Wrap commands in a `LaravelCommandAdapter` that extends `Illuminate\Console\Command`.
- Delegate execution and return integer exit codes.
### 6.3 IO & Markup
- Reuse `SymfonyBridge` IO adapters internally (Artisan's IO is built on Symfony).
- Ensure Phreds fixed markup set (`<success>`, `<info>`, `<error>`, `<warning>`, `<comment>`, `<b>`, `<i>`, `<u>`) renders natively in Artisan.
### 6.4 Helper Adapters
- `InteractionInterface` -> Map to Laravel's native console methods (`ask`, `confirm`, `secret`, `choice`).
- `ProgressBarInterface` / `TableInterface` -> Reuse SymfonyBridge adapters via Artisan.
- `MarkdownConverterInterface` -> Standard Phred implementation (lightweight regex/mapping).
## 7. Dependency & Packaging Strategy
- Composer `suggest` dependencies per bridge:
- `symfony/console` for SymfonyBridge.
- `illuminate/console` for LaravelBridge.
- Hard requirements: only `php:^8.2` and `getphred/console-contracts`.
- Conditional wiring: Bridge factory/services check for class existence before enabling a bridge.
- Namespacing: `Phred\TaskerBridges\Symfony\...`, `Phred\TaskerBridges\Phred\...`, `Phred\TaskerBridges\Laravel\...`.
## 8. Registration & Discovery
- Consumers may register bridges explicitly in DI (preferred for clarity).
- For Laravel, auto-discovery via `TaskerServiceProvider` is the primary path.
- For Symfony apps, provide a small integration guide to register `SymfonyCommandAdapter` instances in the application.
- For Tasker, PhredBridge is used by default; other bridges are opt-in and instantiated only if their dependencies are present.
## 9. Middleware Compatibility
- Bridges must compose with `ConsoleMiddlewareInterface` stacks managed by the runner. The adapter boundary is after middleware resolution so that middlewares see Phred `InputInterface`/`OutputInterface` consistently.
## 10. Performance & Lazy Loading
- Metadata access should be O(1) with minimal reflection at runtime. If attributes are used (`#[Cmd]`, `#[Arg]`, `#[Opt]`) and surfaced via `HasAttributes`, bridges continue to consume only the `CommandInterface` methods — no extra reflection needed in bridges.
- For toolkits that support lazy command discovery, prefer deferring heavy initialization until execution.
### 11. Testing Strategy (for later phases)
- Unit tests: Markup translation, verbosity mapping, non-interactive behavior, helpers integration.
- Integration tests: Execute a sample Phred command through SymfonyBridge, LaravelBridge, and PhredBridge, verify identical behavior and exit codes.
## 12. Architectural Decisions (Resolved)
### 12.1 Global Options & Flags
- Bridges will map toolkit-specific flags (e.g., Symfonys `--ansi`, `--no-interaction`) to the Phred `InputInterface` and `OutputInterface` state.
- **Purity**: Commands must NOT depend on toolkit-specific options. Instead, they should rely on the standardized Phred flags:
- `-v`, `-vv`, `-vvv`, `-q` (Verbosity)
- `-n` or `--no-interaction` (Non-interactive)
- `--no-ansi` (Color/Decoration)
- **Passthrough**: Toolkit-specific global options remain at the application layer and are consumed only by the bridge to configure the IO adapters.
### 12.2 Error Mapping Policy
- Bridges are responsible for a "Standard Translation" of common exceptions into `sysexits.h` exit codes.
- **Mapping Strategy**:
- `InvalidArgumentException` -> `ExitCode::USAGE` (64)
- `RuntimeException` -> `ExitCode::SOFTWARE` (70)
- `ConsoleExceptionInterface` (from Contracts) -> Mapped according to category (e.g., `UNAVAILABLE`, `CONFIG`).
- **Global Handler**: The primary runner (Tasker) or Bridge wrapper provides the top-level `try/catch` to ensure these codes are returned to the shell.
### 12.3 Helper Feature Parity
- **Guaranteed Minimum API**: Bridges must implement the full `ProgressBarInterface` and `TableInterface` as defined in `ConsoleContracts`.
- **Progressive Enhancement**: Bridges MAY support advanced features (e.g., table styling, progress bar steps) if the underlying toolkit supports it, but commands should avoid depending on these for core functionality.
- **Fallback**: In non-TTY or non-supported environments, bridges must provide a "silent" or "degraded" implementation (e.g., printing table as a list, silencing progress bars) to prevent command failure.
## Brainstorming / Open Questions
(All current open questions have been resolved and moved to Architectural Decisions.)

89
SPECS.md Normal file
View file

@ -0,0 +1,89 @@
# TaskerBridges Specification
This document defines the technical specifications for the `getphred/tasker-bridges` package, which adapts `Phred\ConsoleContracts` for various CLI runtimes.
## 1. Bridge Strategy
Bridges act as adapters between the standardized Phred CLI contracts and the native APIs of target console toolkits.
### 1.1 Universal Bridge Requirements
All bridges must:
- Adapt command metadata (name, description, arguments, options) to the native format.
- Wrap execution to translate native input/output to Phred `InputInterface` and `OutputInterface`.
- Implement fixed markup translation.
- Map native verbosity and interaction states to Phred's standardized levels/flags.
- Handle common exceptions and map them to `sysexits.h` exit codes.
## 2. SymfonyBridge
**Target**: `symfony/console` (v6.x and v7.x)
**Namespace**: `Phred\TaskerBridges\Symfony`
### 2.1 SymfonyCommandAdapter
- Extends `Symfony\Component\Console\Command\Command`.
- Wraps a `Phred\ConsoleContracts\CommandInterface`.
- Maps Phred metadata to Symfony's `setName`, `setDescription`, `addArgument`, and `addOption`.
### 2.2 Symfony IO Adapters
- `SymfonyInputAdapter`: Wraps `Symfony\Input\InputInterface` and implements `Phred\ConsoleContracts\InputInterface`.
- `SymfonyOutputAdapter`: Wraps `Symfony\Output\OutputInterface` and implements `Phred\ConsoleContracts\OutputInterface`.
### 2.3 Symfony Helper Adapters
- `SymfonyInteractionAdapter`: Wraps `Symfony\Helper\QuestionHelper` to implement `Phred\ConsoleContracts\InteractionInterface`.
- `SymfonyProgressBarAdapter`: Wraps `Symfony\Helper\ProgressBar`.
- `SymfonyTableAdapter`: Wraps `Symfony\Helper\Table`.
## 3. LaravelBridge
**Target**: `illuminate/console` (v10.x and v11.x)
**Namespace**: `Phred\TaskerBridges\Laravel`
### 3.1 LaravelCommandAdapter
- Extends `Illuminate\Console\Command`.
- Wraps a `Phred\ConsoleContracts\CommandInterface`.
- Internally uses Symfony adapters for IO and helpers where applicable (as Artisan is built on Symfony).
### 3.2 LaravelServiceProvider
- Handles auto-discovery of Phred commands from `composer.json` (`extra.phred-tasker`).
- Registers commands within the Laravel container and Artisan application.
## 4. PhredBridge (Native)
**Target**: Tasker Core Runner
**Namespace**: `Phred\TaskerBridges\Phred`
### 4.1 Native Adapters
- Provides ANSI-based implementations for `OutputInterface`.
- Provides lightweight implementations for `InteractionInterface`, `ProgressBarInterface`, and `TableInterface` with zero external dependencies.
### 4.2 MarkdownConverterInterface (Native)
- **Implementation**: Provides a regex-based parser to translate standard Markdown (headings, emphasis, links) into the fixed Phred markup tag set (`<b>`, `<i>`, `<u>`, etc.).
## 5. Markup Translation Mapping
| Phred Tag | Phred Color/Style | Symfony/Laravel Implementation | Native Implementation (ANSI) |
|:---|:---|:---|:---|
| `<success>` | Green | Custom `SymfonyStyle` or Formatter style | `\e[32m` |
| `<info>` | Blue | Custom Formatter style | `\e[34m` |
| `<error>` | White on Red | `<error>` tag | `\e[97;41m` |
| `<warning>` | Black on Yellow | Custom Formatter style | `\e[30;43m` |
| `<comment>` | Yellow | `<comment>` tag | `\e[33m` |
| `<b>` | Bold | `\e[1m` (if not native) | `\e[1m` |
| `<i>` | Italic | `\e[3m` (if not native) | `\e[3m` |
| `<u>` | Underline | `\e[4m` (if not native) | `\e[4m` |
## 6. Exit Code Translation
Bridges should map the following standard exceptions:
- `InvalidArgumentException` -> `ExitCode::USAGE` (64)
- `RuntimeException` -> `ExitCode::SOFTWARE` (70)
- `LogicException` -> `ExitCode::SOFTWARE` (70)
- `Phred\ConsoleContracts\ConsoleExceptionInterface` implementations -> mapped per category.
## 7. Global Options & Flags
Bridges are responsible for mapping toolkit-specific global options to the Phred state:
- Symfony `--ansi` / `--no-ansi` -> `OutputInterface::setDecorated()`
- Symfony `-v` / `-vv` / `-vvv` -> `OutputInterface::setVerbosity()`
- Symfony `--no-interaction` -> `InteractionInterface` behavior (non-blocking).
- Laravel specific variants of the above.

20
composer.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "getphred/tasker-bridges",
"description": "Optional bridges for integrating Phred CLI commands into various PHP console tools.",
"license": "MIT",
"type": "library",
"require": {
"php": "^8.2",
"getphred/console-contracts": "dev-master"
},
"suggest": {
"getphred/tasker": "To use the PhredBridge with the Tasker runner",
"symfony/console": "To use the SymfonyBridge",
"laminas/laminas-console": "To use the LaminasBridge"
},
"autoload": {
"psr-4": {
"Phred\\TaskerBridges\\": "src/"
}
}
}

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Phred\TaskerBridges\Phred;
use Phred\ConsoleContracts\InputInterface;
/**
* Native Phred adapter for InputInterface.
*/
class InputAdapter implements InputInterface
{
/**
* @param array<string, mixed> $arguments
* @param array<string, mixed> $options
*/
public function __construct(
private readonly array $arguments = [],
private readonly array $options = []
) {
}
/**
* @inheritDoc
*/
public function getArgument(string $name, mixed $default = null): mixed
{
return $this->arguments[$name] ?? $default;
}
/**
* @inheritDoc
*/
public function getOption(string $name, mixed $default = null): mixed
{
return $this->options[$name] ?? $default;
}
/**
* @inheritDoc
*/
public function hasOption(string $name): bool
{
return array_key_exists($name, $this->options);
}
}

140
src/Phred/OutputAdapter.php Normal file
View file

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Phred\TaskerBridges\Phred;
use Phred\ConsoleContracts\OutputInterface;
/**
* Native Phred adapter for OutputInterface with ANSI support.
*/
class OutputAdapter implements OutputInterface
{
private int $verbosity = 1;
private bool $decorated = true;
/**
* @var array<string, string> ANSI color mapping.
*/
private const TAGS = [
'info' => '34', // Blue
'success' => '32', // Green
'error' => '31', // Red
'warning' => '33', // Yellow
'comment' => '36', // Cyan
'bold' => '1',
];
/**
* @param bool $decorated Whether to use ANSI decoration.
*/
public function __construct(bool $decorated = true)
{
$this->decorated = $decorated;
}
/**
* @inheritDoc
*/
public function write(string $message): void
{
echo $this->format($message);
}
/**
* @inheritDoc
*/
public function writeln(string $message): void
{
echo $this->format($message) . PHP_EOL;
}
/**
* @inheritDoc
*/
public function success(string $message): void
{
$this->writeln("<success>$message</success>");
}
/**
* @inheritDoc
*/
public function info(string $message): void
{
$this->writeln("<info>$message</info>");
}
/**
* @inheritDoc
*/
public function error(string $message): void
{
$this->writeln("<error>$message</error>");
}
/**
* @inheritDoc
*/
public function warning(string $message): void
{
$this->writeln("<warning>$message</warning>");
}
/**
* @inheritDoc
*/
public function comment(string $message): void
{
$this->writeln("<comment>$message</comment>");
}
/**
* @inheritDoc
*/
public function setVerbosity(int $level): void
{
$this->verbosity = $level;
}
/**
* @inheritDoc
*/
public function getVerbosity(): int
{
return $this->verbosity;
}
/**
* Sets whether the output should be decorated.
*
* @param bool $decorated
* @return void
*/
public function setDecorated(bool $decorated): void
{
$this->decorated = $decorated;
}
/**
* Formats the message by replacing tags with ANSI codes or stripping them.
*
* @param string $message
* @return string
*/
private function format(string $message): string
{
return preg_replace_callback('/<([a-z]+)>(.*?)<\/\\1>/i', function (array $matches): string {
$tag = strtolower($matches[1]);
$content = $matches[2];
if (!$this->decorated || !isset(self::TAGS[$tag])) {
return $content;
}
return sprintf("\033[%sm%s\033[0m", self::TAGS[$tag], $content);
}, $message) ?? $message;
}
}