Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"veewee/xml": "^3.0",
"azjezz/psl": "^3.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"webmozart/assert": "^1.11"
"webmozart/assert": "^1.11",
"php-tui/php-tui": "^0.2.1"
},
"require-dev": {
"symfony/var-dumper": "^6.1 || ^7.0",
Expand Down
104 changes: 104 additions & 0 deletions src/Console/Command/InspectUICommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);

namespace Soap\WsdlReader\Console\Command;

use PhpTui\Term\Actions;
use PhpTui\Term\ClearType;
use PhpTui\Term\Terminal;
use PhpTui\Tui\Bridge\PhpTerm\PhpTermBackend;
use PhpTui\Tui\Display\Display;
use PhpTui\Tui\DisplayBuilder;
use Psl\Ref;
use Soap\Wsdl\Console\Helper\ConfiguredLoader;
use Soap\Wsdl\Loader\CallbackLoader;
use Soap\Wsdl\Loader\FlatteningLoader;
use Soap\Wsdl\Loader\WsdlLoader;
use Soap\WsdlReader\Console\UI\Components\LoadingWidget;
use Soap\WsdlReader\Console\UI\Layout;
use Soap\WsdlReader\Console\UI\UIState;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function Psl\Type\non_empty_string;

final class InspectUICommand extends Command
{
public static function getDefaultName(): string
{
return 'inspect:ui';
}

/**
* @throws InvalidArgumentException
*/
protected function configure(): void
{
$this->setDescription('Inspects WSDL file through a user interface.');
$this->addArgument('wsdl', InputArgument::REQUIRED, 'Provide the URI of the WSDL you want to validate');
$this->addOption('loader', 'l', InputOption::VALUE_REQUIRED, 'Customize the WSDL loader file that will be used');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$terminal = Terminal::new();
$terminal->execute(Actions::alternateScreenEnable());
$terminal->execute(Actions::enableMouseCapture());
$terminal->execute(Actions::cursorHide());
$terminal->enableRawMode();

$display = DisplayBuilder::default(PhpTermBackend::new($terminal))->build();
$state = $this->loadState($input, $display);

try {
while ($state->running) {
while (null !== $event = $terminal->events()->next()) {
$state->handle($event);
}

$display->draw(Layout::create($state));

usleep(50_000);
}
} finally {
$terminal->disableRawMode();
$terminal->execute(Actions::alternateScreenDisable());
$terminal->execute(Actions::disableMouseCapture());
$terminal->execute(Actions::cursorShow());
$terminal->execute(Actions::clear(ClearType::All));
}

return self::SUCCESS;
}

private function loadState(InputInterface $input, Display $display): UIState
{
$wsdl = non_empty_string()->assert($input->getArgument('wsdl'));
/** @var Ref<list<string>> $info */
$info = new Ref(['Loading WSDL ...']);
$display->draw(LoadingWidget::create($info->value));

$loader = ConfiguredLoader::createFromConfig(
$input->getOption('loader'),
static fn (WsdlLoader $loader) => new FlatteningLoader(
new CallbackLoader(static function (string $location) use ($loader, $display, $info): string {
$info->value[] = '> Loading '.$location.' ...';
$currentIndex = count($info->value) - 1;
$display->draw(LoadingWidget::create($info->value));

$result = $loader($location);

$info->value[$currentIndex] .= ' OK';
$display->draw(LoadingWidget::create($info->value));

return $result;
})
),
);

return UIState::load($wsdl, $loader);
}
}
10 changes: 10 additions & 0 deletions src/Console/UI/Component.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI;

use PhpTui\Tui\Widget\Widget;

interface Component extends EventHandler
{
public function build(): Widget;
}
24 changes: 24 additions & 0 deletions src/Console/UI/Components/Help.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\BlockWidget;
use PhpTui\Tui\Extension\Core\Widget\TabsWidget;
use PhpTui\Tui\Style\Style;
use PhpTui\Tui\Text\Line;
use PhpTui\Tui\Widget\Borders;
use PhpTui\Tui\Widget\Widget;

final class Help
{
public static function create(Line ... $lines): Widget
{
return BlockWidget::default()
->borders(Borders::ALL)->style(Style::default()->white())
->widget(
TabsWidget::fromTitles(
...$lines
)
);
}
}
29 changes: 29 additions & 0 deletions src/Console/UI/Components/LoadingWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\List\ListItem;
use PhpTui\Tui\Extension\Core\Widget\ListWidget;
use PhpTui\Tui\Style\Style;
use PhpTui\Tui\Text\Text;
use PhpTui\Tui\Widget\Widget;
use function Psl\Vec\map;

final readonly class LoadingWidget
{
/**
* @param list<string> $messages
*/
public static function create(array $messages): Widget
{
return ListWidget::default()
->items(
...map(
$messages,
static fn (string $message) => ListItem::new(Text::fromString($message))
)
)
->select(count($messages) - 1)
->highlightStyle(Style::default()->white()->onBlue());
}
}
84 changes: 84 additions & 0 deletions src/Console/UI/Components/MetaTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\Table\TableCell;
use PhpTui\Tui\Extension\Core\Widget\Table\TableRow;
use PhpTui\Tui\Extension\Core\Widget\TableWidget;
use PhpTui\Tui\Layout\Constraint;
use PhpTui\Tui\Style\Style;
use PhpTui\Tui\Text\Line;
use PhpTui\Tui\Text\Text;
use PhpTui\Tui\Widget\Widget;
use Psl\Option\Option;
use ReflectionClass;
use ReflectionProperty;
use Throwable;
use function Psl\Dict\filter_nulls;
use function Psl\Vec\map;

final readonly class MetaTable
{
public static function create(object $meta): Widget
{
$headerStyle = Style::default()->bold();

return TableWidget::default()
->widths(
Constraint::min(50),
Constraint::percentage(100),
)

->header(
TableRow::fromCells(
new TableCell(Text::fromLine(Line::fromString('Key')), $headerStyle),
new TableCell(Text::fromLine(Line::fromString('Value')), $headerStyle),
)
)
->rows(...map(
self::buildKeyPairs($meta),
static fn ($current) => TableRow::fromCells(
TableCell::fromString($current[0]),
TableCell::fromString($current[1]),
)
));
}

/**
* @return array<array{0: non-empty-string, 1: string}>
*/
private static function buildKeyPairs(object $object): array
{
$rc = new ReflectionClass($object);

return filter_nulls(
map(
$rc->getProperties(),
/** @return array{0: non-empty-string, 1: string}|null */
static function (ReflectionProperty $prop) use ($object): ?array {
$value = self::tryStringifyValue($prop->getValue($object));
if ($value === null) {
return null;
}

return [$prop->getName(), $value];
}
)
);
}

private static function tryStringifyValue(mixed $value): ?string
{
try {
return match (true) {
is_array($value) => json_encode($value, JSON_UNESCAPED_SLASHES),
is_bool($value) => $value ? 'true' : 'false',
is_scalar($value) => (string)$value,
$value instanceof Option => $value->map(self::tryStringifyValue(...))->unwrapOr(null),
default => null,
};
} catch (Throwable) {
return null;
}
}
}
34 changes: 34 additions & 0 deletions src/Console/UI/Components/Navigation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\BlockWidget;
use PhpTui\Tui\Extension\Core\Widget\TabsWidget;
use PhpTui\Tui\Style\Style;
use PhpTui\Tui\Text\Line;
use PhpTui\Tui\Widget\Borders;
use PhpTui\Tui\Widget\Widget;
use Soap\WsdlReader\Console\UI\Page;
use Soap\WsdlReader\Console\UI\UIState;
use function Psl\Vec\keys;
use function Psl\Vec\map;

final class Navigation
{
public static function create(UIState $state): Widget
{
return BlockWidget::default()
->borders(Borders::ALL)->style(Style::default()->white())
->widget(
TabsWidget::fromTitles(
Line::parse('<fg=red>[q]</>uit'),
...map(
$state->availablePages,
static fn (Page $page) => Line::parse($page->title())
)
)
->select((int)array_search(get_class($state->currentPage), keys($state->availablePages), true) + 1)
->highlightStyle(Style::default()->white()->onBlue())
);
}
}
17 changes: 17 additions & 0 deletions src/Console/UI/Components/ScrollableTextArea.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\ParagraphWidget;
use PhpTui\Tui\Widget\Widget;

final class ScrollableTextArea
{
public static function create(ScrollableTextAreaState $state): Widget
{
$paragraph = ParagraphWidget::fromString($state->value);
$paragraph->scroll = [$state->position, 0];

return $paragraph;
}
}
48 changes: 48 additions & 0 deletions src/Console/UI/Components/ScrollableTextAreaState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Term\Event;
use PhpTui\Term\MouseEventKind;
use Soap\WsdlReader\Console\UI\EventHandler;

use function json_encode;
use function Psl\Math\max;
use function Psl\Math\min;

final class ScrollableTextAreaState implements EventHandler
{
public function __construct(
public string $value,
public int $position = 0,
) {
}

public static function json(mixed $data, string $fallback): self
{
return new self(
$data !== null ? json_encode($data, JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES) : $fallback,
);
}

public function scrollUp(): void
{
$this->position = (int) max([0, $this->position - 1]);
}

public function scrollDown(): void
{
$this->position = (int) min([$this->position + 1, count(explode("\n", $this->value))]);
}

public function handle(Event $event): void
{
if ($event instanceof Event\MouseEvent) {
match ($event->kind) {
MouseEventKind::ScrollUp => $this->scrollUp(),
MouseEventKind::ScrollDown => $this->scrollDown(),
default => null,
};
}
}
}
23 changes: 23 additions & 0 deletions src/Console/UI/Components/Search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Console\UI\Components;

use PhpTui\Tui\Extension\Core\Widget\BlockWidget;
use PhpTui\Tui\Extension\Core\Widget\ParagraphWidget;
use PhpTui\Tui\Text\Line;
use PhpTui\Tui\Text\Title;
use PhpTui\Tui\Widget\Borders;
use PhpTui\Tui\Widget\Widget;

final class Search
{
public static function create(SearchState $state): Widget
{
return BlockWidget::default()
->titles(Title::fromString('Search'))
->borders(Borders::ALL)
->widget(
ParagraphWidget::fromLines(Line::fromString($state->query)),
);
}
}
Loading
Loading