Skip to content

Commit e880153

Browse files
committed
iterate
1 parent 8d95693 commit e880153

File tree

12 files changed

+118
-61
lines changed

12 files changed

+118
-61
lines changed

src/Toolkit/bin/ux-toolkit-kit-debug

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ if (!class_exists(Application::class)) {
4747
}
4848

4949
$filesystem = new Filesystem();
50-
$kitSynchronizer = new KitSynchronizer(new RecipeSynchronizer($filesystem));
50+
$kitSynchronizer = new KitSynchronizer($filesystem, new RecipeSynchronizer($filesystem));
5151
$kitFactory = new KitFactory($filesystem, $kitSynchronizer);
5252

5353
(new Application())->add($command = new DebugKitCommand($kitFactory))

src/Toolkit/config/services.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575

7676
->set('.ux_toolkit.kit.kit_synchronizer', KitSynchronizer::class)
7777
->args([
78+
service('filesystem'),
7879
service('.ux_toolkit.recipe.recipe_synchronizer'),
7980
])
8081

src/Toolkit/src/Command/InstallComponentCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
107107
}
108108
// If more than one kit is available, we ask the user which one to use
109109
if (($availableKitsCount = \count($availableKits)) > 1) {
110-
$kitName = $io->choice(null === $componentName ? 'Which kit do you want to use?' : \sprintf('The component "%s" exists in multiple kits. Which one do you want to use?', $componentName), array_map(fn (Kit $kit) => $kit->name, $availableKits));
110+
$kitName = $io->choice(null === $componentName ? 'Which kit do you want to use?' : \sprintf('The component "%s" exists in multiple kits. Which one do you want to use?', $componentName), array_map(fn (Kit $kit) => $kit->manifest->name, $availableKits));
111111

112112
foreach ($availableKits as $availableKit) {
113113
if ($availableKit->manifest->name === $kitName) {

src/Toolkit/src/Kit/KitManifest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function __construct(
2323
public readonly string $description,
2424
public readonly string $license,
2525
public readonly string $homepage,
26+
public string|null $installAsMarkdown = null,
2627
) {
2728
Assert::kitName($this->name);
2829

src/Toolkit/src/Kit/KitSynchronizer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\Toolkit\Kit;
1313

1414
use Symfony\Component\Filesystem\Filesystem;
15+
use Symfony\Component\Filesystem\Path;
1516
use Symfony\Component\Finder\Finder;
1617
use Symfony\UX\Toolkit\Recipe\RecipeSynchronizer;
1718

@@ -23,12 +24,17 @@
2324
final class KitSynchronizer
2425
{
2526
public function __construct(
27+
private readonly Filesystem $filesystem,
2628
private readonly RecipeSynchronizer $recipeSynchronizer,
2729
) {
2830
}
2931

3032
public function synchronize(Kit $kit): void
3133
{
34+
if ($this->filesystem->exists($installMd = Path::join($kit->absolutePath, 'INSTALL.md'))) {
35+
$kit->installAsMarkdown = $this->filesystem->readFile($installMd);
36+
}
37+
3238
$this->synchronizeRecipes($kit);
3339
}
3440

ux.symfony.com/src/Controller/Toolkit/ComponentsController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\HttpKernel\Profiler\Profiler;
2525
use Symfony\Component\Routing\Attribute\Route;
2626
use Symfony\UX\Toolkit\Kit\KitContextRunner;
27+
use Symfony\UX\Toolkit\Recipe\RecipeType;
2728

2829
class ComponentsController extends AbstractController
2930
{
@@ -47,7 +48,7 @@ public function listComponents(ToolkitKitId $kit): Response
4748
public function showComponent(ToolkitKitId $kitId, string $componentName): Response
4849
{
4950
$kit = $this->toolkitService->getKit($kitId);
50-
if (null === $component = $kit->getComponent($componentName)) {
51+
if (null === $component = $kit->getRecipe($componentName, type: RecipeType::Component)) {
5152
throw $this->createNotFoundException(\sprintf('Component "%s" not found', $componentName));
5253
}
5354

ux.symfony.com/src/Service/Toolkit/ToolkitService.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
use Symfony\Component\HttpFoundation\UriSigner;
1919
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2020
use Symfony\UX\Toolkit\Asset\Component;
21+
use Symfony\UX\Toolkit\Dependency\PhpPackageDependency;
2122
use Symfony\UX\Toolkit\Installer\PoolResolver;
2223
use Symfony\UX\Toolkit\Kit\Kit;
24+
use Symfony\UX\Toolkit\Recipe\Recipe;
25+
use Symfony\UX\Toolkit\Recipe\RecipeType;
2326
use Symfony\UX\Toolkit\Registry\RegistryFactory;
2427

2528
class ToolkitService
@@ -59,7 +62,7 @@ public function getKits(): array
5962
*/
6063
public function getDocumentableComponents(Kit $kit): array
6164
{
62-
return array_filter($kit->getComponents(), fn (Component $component) => $component->doc);
65+
return array_filter($kit->getRecipes(RecipeType::Component), fn (Recipe $recipe) => file_exists(Path::join($recipe->absolutePath, 'EXAMPLES.md')));
6366
}
6467

6568
public function renderComponentPreviewCodeTabs(ToolkitKitId $kitId, string $code, string $highlightedCode, string $height): string
@@ -79,26 +82,30 @@ public function renderComponentPreviewCodeTabs(ToolkitKitId $kitId, string $code
7982
]);
8083
}
8184

82-
public function renderInstallationSteps(ToolkitKitId $kitId, Component $component): string
85+
public function renderInstallationSteps(ToolkitKitId $kitId, Recipe $component): string
8386
{
8487
$kit = $this->getKit($kitId);
8588
$pool = (new PoolResolver())->resolveForRecipe($kit, $component);
8689

8790
$manual = '<p>The UX Toolkit is not mandatory to install a component. You can install it manually by following the next steps:</p>';
8891
$manual .= '<ol style="display: grid; gap: 1rem;">';
8992
$manual .= '<li><strong>Copy the following file(s) into your Symfony app:</strong>';
90-
foreach ($pool->getFiles() as $file) {
91-
$manual .= \sprintf(
92-
"<details><summary><code>%s</code></summary>\n%s\n</details>",
93-
$file->relativePathName,
94-
\sprintf("\n```%s\n%s\n```", pathinfo($file->relativePathName, \PATHINFO_EXTENSION), trim(file_get_contents(Path::join($kit->absolutePath, $file->relativePathName))))
95-
);
93+
foreach ($pool->getFiles() as $recipeFullPath => $files) {
94+
foreach ($files as $file) {
95+
$manual .= \sprintf(
96+
"<details><summary><code>%s</code></summary>\n%s\n</details>",
97+
$file->sourceRelativePathName,
98+
\sprintf("\n```%s\n%s\n```", pathinfo($file->sourceRelativePathName, \PATHINFO_EXTENSION), trim(file_get_contents(Path::join($recipeFullPath, $file->sourceRelativePathName))))
99+
);
100+
}
96101
}
97102
$manual .= '</li>';
98103

99104
if ($phpPackageDependencies = $pool->getPhpPackageDependencies()) {
100105
$manual .= '<li><strong>If necessary, install the following Composer dependencies:</strong>';
101-
$manual .= CodeBlockRenderer::highlightCode('shell', '$ composer require '.implode(' ', $phpPackageDependencies), 'margin-bottom: 0');
106+
$manual .= CodeBlockRenderer::highlightCode('shell', '$ composer require '.implode(' ',
107+
array_map(fn(PhpPackageDependency $dependency) => $dependency->name.($dependency->constraintVersion ? ':'.$dependency->constraintVersion : ''), $phpPackageDependencies),
108+
), 'margin-bottom: 0');
102109
$manual .= '</li>';
103110
}
104111

@@ -109,7 +116,7 @@ public function renderInstallationSteps(ToolkitKitId $kitId, Component $componen
109116
'Automatic' => \sprintf(
110117
'<p>Ensure the Symfony UX Toolkit is installed in your Symfony app:</p>%s<p>Then, run the following command to install the component and its dependencies:</p>%s',
111118
CodeBlockRenderer::highlightCode('shell', '$ composer require --dev symfony/ux-toolkit'),
112-
CodeBlockRenderer::highlightCode('shell', "$ bin/console ux:install {$component->name} --kit {$kitId->value}"),
119+
CodeBlockRenderer::highlightCode('shell', "$ bin/console ux:install {$component->manifest->name} --kit {$kitId->value}"),
113120
),
114121
'Manual' => $manual,
115122
]);

ux.symfony.com/src/Twig/Components/Toolkit/ComponentDoc.php

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use App\Enum\ToolkitKitId;
1515
use App\Service\Toolkit\ToolkitService;
16+
use Symfony\Component\Filesystem\Filesystem;
17+
use Symfony\Component\Filesystem\Path;
1618
use Symfony\Component\String\AbstractString;
17-
use Symfony\UX\Toolkit\Asset\Component;
19+
use Symfony\UX\Toolkit\Recipe\Recipe;
1820
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
1921

2022
use function Symfony\Component\String\s;
@@ -23,61 +25,100 @@
2325
class ComponentDoc
2426
{
2527
public ToolkitKitId $kitId;
26-
public Component $component;
28+
public Recipe $component;
2729

28-
public function __construct(private readonly ToolkitService $toolkitService)
29-
{
30+
public function __construct(
31+
private readonly Filesystem $filesystem,
32+
private readonly ToolkitService $toolkitService
33+
) {
3034
}
3135

3236
public function getContent(): string
3337
{
34-
return $this->formatContent($this->component->doc->markdownContent);
35-
}
38+
$examples = $this->getExamples();
3639

37-
private function formatContent(string $markdownContent): string
38-
{
39-
$markdownContent = s($markdownContent);
40+
return $this->adaptPreviewableCodeBlocks(sprintf(<<<MARKDOWN
41+
# %s
4042
41-
$markdownContent = $this->insertInstallation($markdownContent);
42-
$markdownContent = $this->insertUsage($markdownContent);
43-
$markdownContent = $this->adaptPreviewableCodeBlocks($markdownContent);
43+
%s
4444
45-
return $markdownContent;
46-
}
45+
%s
4746
48-
private function insertInstallation(AbstractString $markdownContent): AbstractString
49-
{
50-
return $markdownContent->replace(
51-
'<!-- Placeholder: Installation -->',
52-
$this->toolkitService->renderInstallationSteps($this->kitId, $this->component)
53-
);
47+
## Installation
48+
49+
%s
50+
51+
## Usage
52+
53+
%s
54+
55+
## Examples
56+
57+
%s
58+
MARKDOWN,
59+
$this->component->manifest->name,
60+
$this->component->manifest->description,
61+
current($examples),
62+
$this->toolkitService->renderInstallationSteps($this->kitId, $this->component),
63+
dump(preg_replace('/^```twig.*\n/', '```twig'.PHP_EOL, current($examples))),
64+
array_reduce(array_keys($examples), function (string $acc, string $exampleTitle) use ($examples) {
65+
$acc .= '### '.$exampleTitle.PHP_EOL.$examples[$exampleTitle].PHP_EOL;
66+
67+
return $acc;
68+
}, '')
69+
));
5470
}
5571

56-
private function insertUsage(AbstractString $markdownContent): AbstractString
72+
/**
73+
* @return array<string, string>
74+
*/
75+
private function getExamples(): array
5776
{
58-
$firstTwigPreviewBlock = $markdownContent->match('/```twig.*?\n(.+?)```/s');
59-
$firstTwigPreviewBlock = $firstTwigPreviewBlock ? trim($firstTwigPreviewBlock[1]) : '';
77+
$examplesMdPath = Path::join($this->component->absolutePath, 'EXAMPLES.md');
78+
79+
$markdown = s($this->filesystem->readFile($examplesMdPath));
80+
81+
// Remove "# Examples" header
82+
$markdown = $markdown->replace('# Examples', '');
83+
84+
// Split the markdown for each title and content
85+
$examples = [];
86+
foreach (explode(PHP_EOL, $markdown) as $line) {
87+
if (str_starts_with($line, '## ')) {
88+
// This is a new example title
89+
$title = trim(substr($line, 2));
90+
$examples[$title] = '';
91+
} elseif (isset($title)) {
92+
// This line belongs to the last example
93+
$examples[$title] .= $line.PHP_EOL;
94+
}
95+
}
96+
97+
if ([] === $examples) {
98+
throw new \LogicException(sprintf('No examples found in "%s".', $examplesMdPath));
99+
}
100+
101+
foreach ($examples as $title => &$example) {
102+
$example = trim($example);
103+
}
60104

61-
return $markdownContent->replace(
62-
'<!-- Placeholder: Usage -->',
63-
'```twig'."\n".$firstTwigPreviewBlock."\n".'```'
64-
);
105+
return $examples;
65106
}
66107

67108
/**
68109
* Iterate over code blocks, and add the option "kit" if the option "preview" exists.
69110
*/
70-
private function adaptPreviewableCodeBlocks(AbstractString $markdownContent): AbstractString
111+
private function adaptPreviewableCodeBlocks(string $markdownContent): string
71112
{
72-
return $markdownContent->replaceMatches('/```(?P<lang>[a-z]+) +(?P<options>\{.+?\})\n/', function (array $matches) {
113+
return s($markdownContent)->replaceMatches('/```(?P<lang>[a-z]+) +(?P<options>\{.+?\})\n/', function (array $matches) {
73114
$lang = $matches['lang'];
74115
$options = json_decode($matches['options'], true, flags: \JSON_THROW_ON_ERROR);
75116

76117
if ($options['preview'] ?? false) {
77118
$options['kit'] = $this->kitId->value;
78119
}
79120

80-
return \sprintf('```%s %s'."\n", $lang, json_encode($options, \JSON_THROW_ON_ERROR));
81-
});
121+
return \sprintf('```%s %s'.PHP_EOL, $lang, json_encode($options, \JSON_THROW_ON_ERROR));
122+
})->toString();
82123
}
83124
}

ux.symfony.com/templates/toolkit/_kit_aside.html.twig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
<div class="SidebarNav_Heading">Components</div>
1313
<ul class="list-unstyled">
1414
{% for component in components %}
15-
<li class="SidebarNav_Item {{ app.current_route == 'app_toolkit_component' and app.current_route_parameters.componentName == component.name ? 'active' : '' }}">
16-
<a class="SidebarNav_Link" href="{{ path('app_toolkit_component', {kitId: kit_id.value, componentName: component.name}) }}">
17-
{{ component.name }}
15+
<li class="SidebarNav_Item {{ app.current_route == 'app_toolkit_component' and app.current_route_parameters.componentName == component.manifest.name ? 'active' : '' }}">
16+
<a class="SidebarNav_Link" href="{{ path('app_toolkit_component', {kitId: kit_id.value, componentName: component.manifest.name}) }}">
17+
{{ component.manifest.name }}
1818
</a>
1919
</li>
2020
{% endfor %}

ux.symfony.com/templates/toolkit/component.html.twig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% extends 'base.html.twig' %}
22

3-
{% set title = 'Component ' ~ component.name ~ ' - ' ~ kit.name ~ ' Kit' %}
4-
{% set description = "Documentation of component #{component.name}, from the UX Toolkit #{kit.name}, #{kit.description}." %}
3+
{% set title = 'Component ' ~ component.manifest.name ~ ' - ' ~ kit.manifest.name ~ ' Kit' %}
4+
{% set description = "Documentation of component #{component.manifest.name}, from the UX Toolkit #{kit.manifest.name}, #{kit.manifest.description}." %}
55
{% set meta = {
66
title,
77
description,
@@ -14,7 +14,7 @@
1414
type: 'image/png',
1515
width: 1200,
1616
height: 675,
17-
alt: package.humanName ~ ' - Kit ' ~ kit.name,
17+
alt: package.humanName ~ ' - Kit ' ~ kit.manifest.name,
1818
},
1919
}
2020
} %}
@@ -30,8 +30,8 @@
3030
<ol class="breadcrumb">
3131
<li class="breadcrumb-item"><a href="{{ path('app_toolkit') }}">UX Toolkit</a></li>
3232
<li class="breadcrumb-item"><a href="{{ path('app_toolkit', {'_fragment': 'kits'}) }}">Kits</a></li>
33-
<li class="breadcrumb-item"><a href="{{ path('app_toolkit_kit', {kitId: kit_id.value}) }}">{{ kit.name }}</a></li>
34-
<li class="breadcrumb-item active" aria-current="page">{{ component.name }}</li>
33+
<li class="breadcrumb-item"><a href="{{ path('app_toolkit_kit', {kitId: kit_id.value}) }}">{{ kit.manifest.name }}</a></li>
34+
<li class="breadcrumb-item active" aria-current="page">{{ component.manifest.name }}</li>
3535
</ol>
3636
</nav>
3737

0 commit comments

Comments
 (0)