ConsoleContracts/NOTES.md
Funky Waddle c06e557620
Some checks are pending
CI / console-contracts (8.2) (push) Waiting to run
CI / console-contracts (8.3) (push) Waiting to run
docs: add project milestones, specs, and notes
2026-02-21 19:13:45 -06:00

271 lines
9.9 KiB
Markdown

# ConsoleContracts: Phred Console Interfaces
ConsoleContracts provides a minimal, framework-agnostic set of interfaces for building CLI tools. It is designed to be the foundation for the Tasker ecosystem, allowing library authors to define CLI commands without depending on a heavy runner or a specific framework.
## Core Vision
- **Zero Dependencies**: The package must have zero runtime dependencies (other than PHP 8.2+).
- **Framework Agnostic**: Interfaces should not assume any specific runner (Tasker, Symfony, etc.).
- **Portable Commands**: Commands defined using these interfaces should be runnable in any environment that supports the Phred Console standard.
## Architectural Decisions
### 1. Command Interface
The `CommandInterface` is the primary contract for any CLI tool. It separates metadata (name, description, arguments, options) from execution logic.
#### CommandInterface
```php
namespace GetPhred\ConsoleContracts;
interface CommandInterface
{
/**
* The unique name of the command (e.g., 'db:migrate').
*/
public function getName(): string;
/**
* A brief description of what the command does.
*/
public function getDescription(): string;
/**
* Returns an array of arguments expected by the command.
* @return array<string, string> Name => Description
*/
public function getArguments(): array;
/**
* Returns an array of options available for the command.
* @return array<string, string> Name => Description
*/
public function getOptions(): array;
/**
* Executes the command logic.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int Exit code (see ExitCode constants)
*/
public function execute(InputInterface $input, OutputInterface $output): int;
}
```
### 2. Input & Output Interfaces
To ensure portability, commands interact with the environment only through these interfaces.
#### InputInterface
```php
namespace GetPhred\ConsoleContracts;
interface InputInterface
{
/**
* Retrieve the value of a specific argument.
*/
public function getArgument(string $name, mixed $default = null): mixed;
/**
* Retrieve the value of a specific option.
*/
public function getOption(string $name, mixed $default = null): mixed;
/**
* Check if a specific option was provided.
*/
public function hasOption(string $name): bool;
}
```
#### OutputInterface
A lean interface for writing messages to the console.
```php
namespace GetPhred\ConsoleContracts;
interface OutputInterface
{
public function write(string $message): void;
public function writeln(string $message): void;
// Semantic output methods for common use cases
public function success(string $message): void;
public function error(string $message): void;
public function warning(string $message): void;
public function info(string $message): void;
/**
* Set the verbosity level of the output.
*/
public function setVerbosity(int $level): void;
/**
* Get the current verbosity level.
*/
public function getVerbosity(): int;
}
```
### 3. Interactivity & Portability
Interactive features (asking questions, confirmation prompts) are moved to an optional `InteractionInterface`. This ensures that a command remains runnable in non-interactive environments (CI/CD) by providing a mock or fallback implementation of this interface.
#### InteractionInterface (Proposed)
```php
namespace GetPhred\ConsoleContracts;
interface InteractionInterface
{
/**
* Ask a simple question and return the answer.
*/
public function ask(string $question, string $default = null): string;
/**
* Ask for confirmation (yes/no).
*/
public function confirm(string $question, bool $default = true): bool;
/**
* Ask for a sensitive value (input hidden).
*/
public function secret(string $question): string;
/**
* Provide a list of choices and return the selected one.
*/
public function choice(string $question, array $choices, mixed $default = null): mixed;
}
```
### 4. Console Middleware
Middleware should be defined at the **contract level**. This allows for cross-cutting concerns (logging, locking, environment guards) to be implemented in a runner-agnostic way.
#### ConsoleMiddlewareInterface
```php
namespace GetPhred\ConsoleContracts;
interface ConsoleMiddlewareInterface
{
/**
* Handle the command execution lifecycle.
*
* @param CommandInterface $command
* @param InputInterface $input
* @param OutputInterface $output
* @param callable $next The next middleware or the command execution itself.
* @return int Exit code
*/
public function handle(
CommandInterface $command,
InputInterface $input,
OutputInterface $output,
callable $next
): int;
}
```
### 5. Constants & Standards
#### Verbosity Levels
Standardized verbosity levels to control output detail.
- `Verbosity::QUIET` (0)
- `Verbosity::NORMAL` (1)
- `Verbosity::VERBOSE` (2)
- `Verbosity::VERY_VERBOSE` (3)
- `Verbosity::DEBUG` (4)
#### Exit Codes
Strict adherence to `sysexits.h` via `ExitCode` constants (defined in Tasker but standardized here).
### 6. Exception Handling
A base `ConsoleExceptionInterface` should be defined to allow runners to catch and handle CLI-specific errors gracefully (e.g., CommandNotFound, InvalidArgument).
### 7. Lazy Loading Readiness
The `CommandInterface` is specifically designed to support lazy loading. By separating metadata retrieval (`getName()`, `getDescription()`, `getArguments()`, `getOptions()`) from the execution logic (`execute()`), runners can display help information or command lists without instantiating dependencies required only for the command's execution.
### 8. Metadata via Attributes (Optional)
To enhance the Developer Experience (DX), PHP 8 attributes are supported for defining command metadata. While the `CommandInterface` remains the primary contract, attributes provide a declarative way to satisfy the interface methods.
#### Attributes (Proposed)
- `#[Cmd(name: '...', description: '...')]`
- `#[Arg(name: '...', description: '...')]`
- `#[Opt(name: '...', description: '...')]`
```php
use Phred\ConsoleContracts\Attributes\Cmd;
use Phred\ConsoleContracts\Attributes\Arg;
use Phred\ConsoleContracts\Attributes\Opt;
use Phred\ConsoleContracts\CommandInterface;
#[Cmd(name: 'db:migrate', description: 'Run database migrations')]
#[Arg(name: 'step', description: 'Number of migrations to run')]
#[Opt(name: 'force', description: 'Force the operation')]
class MigrateCommand implements CommandInterface
{
use HasAttributes; // A trait that handles the Reflection logic
public function execute(InputInterface $input, OutputInterface $output): int
{
// Logic here...
return 0;
}
}
```
#### Implementation Strategy
The `ConsoleContracts` will provide an optional `HasAttributes` trait (or similar utility) that uses Reflection to read these attributes and return the appropriate values through the `getName()`, `getDescription()`, `getArguments()`, and `getOptions()` methods.
This approach is:
- **Attribute-Aware**: Modern, declarative DX.
- **Interface-Driven**: The runner only interacts with the `CommandInterface` methods.
- **Optional**: Developers can still override the methods manually without any attributes.
### 9. Output Formatting (Minimal Markup)
The `OutputInterface` should support a minimal, standardized set of markup tags to ensure consistent styling across different runners.
#### Core Tag Set (Fixed)
To ensure consistent behavior across all runners and bridges, the set of supported markup tags is **fixed**. Developers should NOT attempt to use custom tags, as bridges are only required to implement this standardized core set:
- `<success>...</success>`: Green text.
- `<info>...</info>`: Blue text.
- `<error>...</error>`: White text on a red background.
- `<warning>...</warning>`: Black text on a yellow background.
- `<comment>...</comment>`: Yellow text.
- `<b>...</b>`: Bold text.
- `<i>...</i>`: Italicized text.
- `<u>...</u>`: Underlined text.
#### Bridge Responsibility
Each bridge (e.g., `SymfonyBridge`, `LaminasBridge`) is responsible for translating this fixed set of Phred tags into the native formatting syntax of the underlying CLI tool.
- **SymfonyBridge**: Maps `<info>` to Symfony's `<info>` tag in its `Formatter`.
- **PhredBridge (Native)**: Translates tags into ANSI escape sequences (e.g., `\e[32m` for green).
This ensures that a command's output remains visually consistent whether it's running via Tasker or inside a Symfony application.
### 10. Helper Contracts (Optional)
Complex CLI components like **Progress Bars** and **Tables** are defined as separate, optional interfaces. This keeps the core `OutputInterface` lean and allows commands to only depend on the specific features they need.
#### ProgressBarInterface (Proposed)
Commands can inject this interface to display progress for long-running tasks.
- `start(int $max)`
- `advance(int $step = 1)`
- `finish()`
#### TableInterface (Proposed)
Commands can inject this interface to render tabular data.
- `setHeaders(array $headers)`
- `addRow(array $row)`
- `render()`
#### MarkdownConverterInterface (Proposed)
Commands can inject this interface to convert a string or a file containing Markdown into a string formatted with Phred's standardized markup tags.
- `convert(string $markdown): string`
- `convertFile(string $path): string`
### Implementation Strategy for Helpers
Like the `InteractionInterface`, these helpers are satisfied by the runner or bridge.
- **SymfonyBridge**: Maps these to Symfony's `ProgressBar` and `Table` helper classes. For Markdown, it could use an existing library or Symfony's native Markdown support.
- **Native (Tasker)**: Provides basic ANSI-based implementations and a lightweight regex-based Markdown parser.
## Brainstorming / Open Questions
(All current open questions have been resolved and moved to Architectural Decisions.)