Skip to content

Commit

Permalink
New console abstraction for commands. PluginInterface replaced by abs…
Browse files Browse the repository at this point in the history
…tract Plugin class
  • Loading branch information
luzrain committed Sep 17, 2024
1 parent a395b1d commit 6ad15ae
Show file tree
Hide file tree
Showing 25 changed files with 475 additions and 329 deletions.
33 changes: 0 additions & 33 deletions src/Command/ReloadCommand.php

This file was deleted.

33 changes: 0 additions & 33 deletions src/Command/StopCommand.php

This file was deleted.

157 changes: 119 additions & 38 deletions src/Console/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,151 @@

final class App
{
private array $commands;
/**
* @var array<Command>
*/
private array $commands = [];

private string|null $command = null;
private array $parsedOptions = [];

public function __construct(Command ...$commands)
private Options $options;

public function __construct()
{
$this->commands = $commands;
$this->options = new Options(
parsedOptions: $this->parsedOptions,
defaultOptionDefinitions: [
new OptionDefinition('help', 'h', 'Show help'),
new OptionDefinition('quiet', 'q', 'Do not output any message'),
new OptionDefinition('no-color', null, 'Disable color output'),
]
);
}

public function run(string $cmd = ''): int
private function getArgvs(): array
{
[$option, $arguments] = $this->parseCommand($cmd);
$argv = $_SERVER['argv'] ?? [];
unset($argv[0]);
return \array_values($argv);
}

// Supress any output
if (\in_array('-q', $arguments, true) || \in_array('--quiet', $arguments, true)) {
StdoutHandler::disableStdout();
private function parseArgvs(array $arguments): array
{
$options = [];
for ($i = 0; $i < \count($arguments); $i++) {
if (\str_starts_with($arguments[$i], '--')) {
$optionParts = \explode('=', \substr($arguments[$i], 2), 2);
$options[$optionParts[0]] = $optionParts[1] ?? true;
} elseif (\str_starts_with($arguments[$i], '-')) {
$splitOtions = \str_split(\substr($arguments[$i], 1));
foreach ($splitOtions as $option) {
$options[$option] = true;
if (isset($arguments[$i + 1]) && !\str_starts_with($arguments[$i + 1], '-') && \count($splitOtions) === 1) {
$options[$option] = $arguments[++$i];
}
}
}
}
return $options;
}

public function register(Command $command): void
{
$this->commands[] = $command;
}

// Force show help
if (\in_array('-h', $arguments, true) || \in_array('--help', $arguments, true)) {
return $this->showHelp();
public function run(array|null $argv = null): int
{
$argv ??= $this->getArgvs();
$this->parsedOptions = $this->parseArgvs($argv);
$command = $argv[0] ?? null;
if ($command !== null && !\str_starts_with($command, '-')) {
$this->command = $command;
}

foreach ($this->commands as $command) {
if ($command->getCommand() === $option) {
return $command->run($arguments);
if ($command::getCommand() === $this->command) {
$command->configure($this->options);
$this->configureStdoutHandler();

if ($this->options->hasOption('help')) {
$this->showHelpForCommand($command);
return 0;
}

return $command->execute($this->options);
}
}

// Show help by default
return $this->showHelp();
$this->configureStdoutHandler();
$this->options->addOptionDefinition('version', null, 'Show version');

if ($this->command !== null) {
echo \sprintf("<color;bg=red>✘ Command \"%s\" does not exist</>\n", $this->command);
return 1;
}

if ($this->options->hasOption('version')) {
echo \sprintf("%s\n", Server::VERSION);
return 0;
}

$this->showHelp();
return 0;
}

/**
* @return array{string, array<string>}
*/
private function parseCommand(string $cmd): array
private function configureStdoutHandler(): void
{
if ($cmd === '') {
/** @psalm-suppress PossiblyUndefinedArrayOffset */
$argv = $_SERVER['argv'];
unset($argv[0]);
$cmd = \implode(' ', $argv);
}
StdoutHandler::register();

$parts = \explode(' ', $cmd, 2);
if ($this->options->hasOption('quiet')) {
StdoutHandler::disableStdout();
}

return [$parts[0] ?? '', \array_filter(\explode(' ', $parts[1] ?? ''))];
if ($this->options->hasOption('no-color')) {
StdoutHandler::disableColor();
}
}

private function showHelp(): int
private function showHelp(): void
{
echo \sprintf("%s (%s)\n", Server::TITLE, Server::VERSION);
echo "<color;fg=yellow>Usage:</>\n";
echo \sprintf(" %s <command> [options]\n", \basename(Functions::getStartFile()));
echo "<color;fg=yellow>Options:</>\n";
echo (new Table(indent: 1))->addRows([
['<color;fg=green>-h, --help</>', 'Show help'],
['<color;fg=green>-q, --quiet</>', 'Do not output any message'],
['<color;fg=green>-d, --daemon</>', 'Run in daemon mode'],
]);
echo "<color;fg=yellow>Commands:</>\n";
echo (new Table(indent: 1))->addRows(\array_map(array: $this->commands, callback: static function (Command $command) {
return ["<color;fg=green>{$command->getCommand()}</>", $command->getHelp()];
}));
echo (new Table(indent: 1))->addRows(\array_map(
array: $this->commands,
callback: static function (Command $command) {
return ["<color;fg=green>{$command::getCommand()}</>", $command::getDescription()];
},
));
echo "<color;fg=yellow>Options:</>\n";
echo (new Table(indent: 1))->addRows($this->createOptionsTableRows());
}

return 0;
private function showHelpForCommand(Command $command): void
{
echo \sprintf("%s (%s)\n", Server::TITLE, Server::VERSION);
echo "<color;fg=yellow>Description:</>\n";
echo \sprintf(" %s\n", $command::getDescription());
echo "<color;fg=yellow>Usage:</>\n";
echo \sprintf(" %s %s [options]\n", \basename(Functions::getStartFile()), $command::getCommand());
echo "<color;fg=yellow>Options:</>\n";
echo (new Table(indent: 1))->addRows($this->createOptionsTableRows());
}

private function createOptionsTableRows(): array
{
$definitions = $this->options->getOptionDefinitions();

$options = [];
foreach ($definitions as $option) {
$options[] = [
\sprintf('<color;fg=green>%s--%s</>', $option->shortName !== null ? '-' . $option->shortName . ', ' : ' ', $option->name),
$option->description,
];
}
return $options;
}
}
2 changes: 1 addition & 1 deletion src/Console/Colorizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static function colorize(string $string): string
if (isset(self::COLORMAP[$bg][1])) {
$colors[] = self::COLORMAP[$bg][1];
}
if (empty($colors)) {
if ($colors === []) {
continue;
}
$formattedString = \sprintf("\e[%sm%s\e[0m", \implode(';', $colors), $text);
Expand Down
27 changes: 23 additions & 4 deletions src/Console/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,30 @@

namespace Luzrain\PHPStreamServer\Console;

interface Command
use Luzrain\PHPStreamServer\Internal\MasterProcess;

abstract class Command
{
public function getCommand(): string;
protected const COMMAND = '';
protected const DESCRIPTION = '';

public function __construct(protected readonly MasterProcess $masterProcess)
{
}

public static function getCommand(): string
{
return static::COMMAND;
}

public static function getDescription(): string
{
return static::DESCRIPTION;
}

public function getHelp(): string;
public function configure(Options $options): void
{
}

public function run(array $arguments): int;
abstract public function execute(Options $options): int;
}
16 changes: 16 additions & 0 deletions src/Console/OptionDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\Console;

final readonly class OptionDefinition
{
public function __construct(
public string $name,
public string|null $shortName = null,
public string $description = '',
public string|null $default = null
) {
}
}
61 changes: 61 additions & 0 deletions src/Console/Options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Luzrain\PHPStreamServer\Console;

final class Options
{
private array $parsedOptions;

/**
* @var array<OptionDefinition>
*/
private array $defaultOptionDefinitions = [];

/**
* @var array<OptionDefinition>
*/
private array $optionDefinitions = [];

/**
* @param array<string, string|true> $parsedOptions
* @param array<OptionDefinition> $defaultOptionDefinitions
*/
public function __construct(array &$parsedOptions, array $defaultOptionDefinitions = [])
{
$this->parsedOptions = &$parsedOptions;
foreach ($defaultOptionDefinitions as $defaultOptionDefinition) {
$this->defaultOptionDefinitions[$defaultOptionDefinition->name] = $defaultOptionDefinition;
}
}

public function addOptionDefinition(string $name, string|null $shortcut = null, string $description = '', string|null $default = null): void
{
$this->optionDefinitions[$name] = new OptionDefinition($name, $shortcut, $description, $default);
}

public function getOptionDefinitions(): array
{
return [...$this->optionDefinitions, ...$this->defaultOptionDefinitions];
}

public function hasOption(string $name): bool
{
$definition = $this->getOptionDefinitions()[$name] ?? null;
$fullName = $definition?->name;
$shortName = $definition?->shortName;

return \array_key_exists($fullName, $this->parsedOptions) || ($shortName !== null && \array_key_exists($shortName, $this->parsedOptions));
}

public function getOption(string $name): string|true|null
{
$definition = $this->getOptionDefinitions()[$name] ?? null;
$fullName = $definition?->name;
$shortName = $definition?->shortName;
$default = $definition?->default;

return $this->parsedOptions[$fullName] ?? $this->parsedOptions[$shortName] ?? $default;
}
}
Loading

0 comments on commit 6ad15ae

Please sign in to comment.