diff --git a/composer.json b/composer.json index 9416e835..09eec96d 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=7.4", "ext-json": "*", "ext-curl": "*", - "doctrine/rst-parser": "^0.5", + "doctrine/rst-parser": "^0.6@dev", "scrivo/highlight.php": "^9.12.0", "symfony/filesystem": "^5.2 || ^6.0", "symfony/finder": "^5.2 || ^6.0", diff --git a/src/BuildResult.php b/src/BuildResult.php index 322d6ce1..73f3b3ba 100644 --- a/src/BuildResult.php +++ b/src/BuildResult.php @@ -26,7 +26,7 @@ public function __construct(Builder $builder) { $this->builder = $builder; $this->errors = []; - foreach ($builder->getErrorManager()->getErrors() as $error) { + foreach ($builder->getConfiguration()->getErrorManager()->getErrors() as $error) { $this->errors[] = $error->asString(); } } diff --git a/src/BuilderFactory.php b/src/BuilderFactory.php new file mode 100644 index 00000000..74bac635 --- /dev/null +++ b/src/BuilderFactory.php @@ -0,0 +1,92 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SymfonyDocsBuilder; + +use Doctrine\Common\EventManager; +use Doctrine\RST\Builder; +use Doctrine\RST\Configuration as RSTParserConfiguration; +use Doctrine\RST\Event\PostBuildRenderEvent; +use Doctrine\RST\Event\PreNodeRenderEvent; +use Doctrine\RST\Event\PreParseDocumentEvent; +use SymfonyDocsBuilder\Listener\AssetsCopyListener; +use Doctrine\RST\ErrorManager; +use SymfonyDocsBuilder\Listener\CopyImagesListener; +use SymfonyDocsBuilder\Listener\AdmonitionListener; +use SymfonyDocsBuilder\CI\UrlChecker; +use SymfonyDocsBuilder\Twig\AssetsExtension; +use SymfonyDocsBuilder\Twig\TocExtension; +use SymfonyDocsBuilder\Twig\UrlExtension; +use function Symfony\Component\String\u; + +final class BuilderFactory +{ + public static function createBuilder(BuildConfig $buildConfig, ?UrlChecker $urlChecker = null): Builder + { + $configuration = new RSTParserConfiguration(); + // needed to avoid outputting parser errors on the console output or the webpage contents + $configuration->silentOnError(true); + $configuration->setCustomTemplateDirs([__DIR__.'/Templates']); + $configuration->setTheme($buildConfig->getTheme()); + $configuration->setCacheDir(sprintf('%s/var/cache', $buildConfig->getCacheDir())); + $configuration->abortOnError(false); + + if (!$buildConfig->isBuildCacheEnabled()) { + $configuration->setUseCachedMetas(false); + } + + $configuration->addFormat( + new SymfonyHTMLFormat( + $buildConfig, + $configuration->getTemplateRenderer(), + $configuration->getFormat(), + $urlChecker + ) + ); + + if ($parseSubPath = $buildConfig->getSubdirectoryToBuild()) { + $configuration->setBaseUrl($buildConfig->getSymfonyDocUrl()); + $configuration->setBaseUrlEnabledCallable( + static function (string $path) use ($parseSubPath): bool { + return u($path)->containsAny($parseSubPath); + } + ); + } + + $eventManager = $configuration->getEventManager(); + $eventManager->addEventListener( + PreParseDocumentEvent::PRE_PARSE_DOCUMENT, + new AdmonitionListener() + ); + + $eventManager->addEventListener( + PreNodeRenderEvent::PRE_NODE_RENDER, + new CopyImagesListener($buildConfig, $configuration->getErrorManager()) + ); + + if (!$buildConfig->getSubdirectoryToBuild()) { + $eventManager->addEventListener( + [PostBuildRenderEvent::POST_BUILD_RENDER], + new AssetsCopyListener($buildConfig->getOutputDir()) + ); + } + + $twig = $configuration->getTemplateEngine(); + $twig->addExtension(new AssetsExtension()); + $twig->addExtension(new TocExtension()); + $twig->addExtension(new UrlExtension()); + + $builder = new Builder($configuration); + $builder->setScannerFinder($buildConfig->createFileFinder()); + + return $builder; + } +} diff --git a/src/Command/BuildDocsCommand.php b/src/Command/BuildDocsCommand.php index d4830451..17553de6 100644 --- a/src/Command/BuildDocsCommand.php +++ b/src/Command/BuildDocsCommand.php @@ -15,6 +15,7 @@ use Doctrine\RST\Builder; use Doctrine\RST\Configuration; use Doctrine\RST\Meta\Metas; +use SymfonyDocsBuilder\BuilderFactory; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -27,7 +28,6 @@ use SymfonyDocsBuilder\ConfigFileParser; use SymfonyDocsBuilder\Generator\HtmlForPdfGenerator; use SymfonyDocsBuilder\Generator\JsonGenerator; -use SymfonyDocsBuilder\KernelFactory; use SymfonyDocsBuilder\Listener\BuildProgressListener; class BuildDocsCommand extends Command @@ -140,9 +140,7 @@ protected function initialize(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output): int { - $builder = new Builder( - KernelFactory::createKernel($this->buildConfig, $this->urlChecker ?? null) - ); + $builder = BuilderFactory::createBuilder($this->buildConfig, $this->urlChecker ?? null); $configuration = $builder->getConfiguration(); $configuration->setOutputFormat($input->getOption('error-output-format')); @@ -153,7 +151,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->buildConfig->getOutputDir() ); - $buildErrors = $builder->getErrorManager()->getErrors(); + $buildErrors = $configuration->getErrorManager()->getErrors(); $missingFiles = $this->missingFilesChecker->getMissingFiles(); foreach ($missingFiles as $missingFile) { diff --git a/src/Directive/AbstractAdmonitionDirective.php b/src/Directive/AbstractAdmonitionDirective.php deleted file mode 100644 index cdc366d8..00000000 --- a/src/Directive/AbstractAdmonitionDirective.php +++ /dev/null @@ -1,52 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -abstract class AbstractAdmonitionDirective extends SubDirective -{ - /** @var string */ - private $name; - - /** @var string */ - private $text; - - public function __construct(string $name, string $text) - { - $this->name = $name; - $this->text = $text; - } - - final public function processSub(Parser $parser, ?Node $document, string $variable, string $data, array $options): ?Node - { - if (null === $document) { - throw new \RuntimeException('Content expected, none found.'); - } - - $wrapperDiv = $parser->renderTemplate( - 'directives/admonition.html.twig', - [ - 'name' => $this->name, - 'text' => $this->text, - 'class' => $options['class'] ?? null, - ] - ); - - return $parser->getNodeFactory()->createWrapperNode($document, $wrapperDiv, ''); - } - - final public function getName(): string - { - return $this->name; - } -} diff --git a/src/Directive/AdmonitionDirective.php b/src/Directive/AdmonitionDirective.php deleted file mode 100644 index 75061fde..00000000 --- a/src/Directive/AdmonitionDirective.php +++ /dev/null @@ -1,36 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -class AdmonitionDirective extends SubDirective -{ - public function processSub(Parser $parser, ?Node $document, string $variable, string $data, array $options): ?Node - { - $wrapperDiv = $parser->renderTemplate( - 'directives/admonition.html.twig', - [ - 'name' => 'default', - 'text' => $data, - 'class' => $options['class'] ?? null, - ] - ); - - return $parser->getNodeFactory()->createWrapperNode($document, $wrapperDiv, ''); - } - - public function getName(): string - { - return 'admonition'; - } -} diff --git a/src/Directive/AttentionDirective.php b/src/Directive/AttentionDirective.php deleted file mode 100644 index 10c400f9..00000000 --- a/src/Directive/AttentionDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class AttentionDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('attention', 'Attention'); - } -} diff --git a/src/Directive/BestPracticeDirective.php b/src/Directive/BestPracticeDirective.php deleted file mode 100644 index d443b577..00000000 --- a/src/Directive/BestPracticeDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class BestPracticeDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('best-practice', 'Best Practice'); - } -} diff --git a/src/Directive/CautionDirective.php b/src/Directive/CautionDirective.php deleted file mode 100644 index c0a9dfc4..00000000 --- a/src/Directive/CautionDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class CautionDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('caution', 'Caution'); - } -} diff --git a/src/Directive/DangerDirective.php b/src/Directive/DangerDirective.php deleted file mode 100644 index 8c38a877..00000000 --- a/src/Directive/DangerDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class DangerDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('danger', 'Danger'); - } -} diff --git a/src/Directive/DeprecatedDirective.php b/src/Directive/DeprecatedDirective.php deleted file mode 100644 index b311a49c..00000000 --- a/src/Directive/DeprecatedDirective.php +++ /dev/null @@ -1,37 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -class DeprecatedDirective extends SubDirective -{ - public function getName(): string - { - return 'deprecated'; - } - - public function processSub(Parser $parser, ?Node $document, string $variable, string $data, array $options): ?Node - { - $wrapperDiv = $parser->renderTemplate( - 'directives/admonition.html.twig', - [ - 'name' => 'deprecated', - 'text' => $data, - 'class' => $options['class'] ?? null, - 'version' => $data, - ] - ); - - return $parser->getNodeFactory()->createWrapperNode($document, $wrapperDiv, ''); - } -} diff --git a/src/Directive/ErrorDirective.php b/src/Directive/ErrorDirective.php deleted file mode 100644 index c1a48bfa..00000000 --- a/src/Directive/ErrorDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class ErrorDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('error', 'Error'); - } -} diff --git a/src/Directive/FigureDirective.php b/src/Directive/FigureDirective.php deleted file mode 100644 index 43cfdd20..00000000 --- a/src/Directive/FigureDirective.php +++ /dev/null @@ -1,64 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -/** - * Overridden to handle "figclass" properly. - */ -class FigureDirective extends SubDirective -{ - public function getName(): string - { - return 'figure'; - } - - /** - * @param string[] $options - */ - public function processSub( - Parser $parser, - ?Node $document, - string $variable, - string $data, - array $options - ): ?Node { - $environment = $parser->getEnvironment(); - - $url = $environment->relativeUrl($data); - - if ($url === null) { - throw new \Exception(sprintf('Could not get relative url for %s', $data)); - } - - $nodeFactory = $parser->getNodeFactory(); - - /* Start Custom Code */ - $figClass = $options['figclass'] ?? null; - unset($options['figclass']); - /* End Custom Code */ - - $figureNode = $parser->getNodeFactory()->createFigureNode( - $nodeFactory->createImageNode($url, $options), - $document - ); - - /* Start Custom Code */ - if ($figClass) { - $figureNode->setClasses(explode(' ', $figClass)); - } - /* End Custom Code */ - - return $figureNode; - } -} diff --git a/src/Directive/HintDirective.php b/src/Directive/HintDirective.php deleted file mode 100644 index 6f381969..00000000 --- a/src/Directive/HintDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class HintDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('hint', 'Hint'); - } -} diff --git a/src/Directive/ImportantDirective.php b/src/Directive/ImportantDirective.php deleted file mode 100644 index 3c880867..00000000 --- a/src/Directive/ImportantDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class ImportantDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('important', 'Important'); - } -} diff --git a/src/Directive/IndexDirective.php b/src/Directive/IndexDirective.php deleted file mode 100644 index 86e75395..00000000 --- a/src/Directive/IndexDirective.php +++ /dev/null @@ -1,20 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; - -class IndexDirective extends SubDirective -{ - public function getName(): string - { - return 'index'; - } -} diff --git a/src/Directive/NoteDirective.php b/src/Directive/NoteDirective.php deleted file mode 100644 index 93a4b874..00000000 --- a/src/Directive/NoteDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class NoteDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('note', 'Note'); - } -} diff --git a/src/Directive/RoleDirective.php b/src/Directive/RoleDirective.php deleted file mode 100644 index e2bc721f..00000000 --- a/src/Directive/RoleDirective.php +++ /dev/null @@ -1,20 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; - -class RoleDirective extends SubDirective -{ - public function getName(): string - { - return 'role'; - } -} diff --git a/src/Directive/ScreencastDirective.php b/src/Directive/ScreencastDirective.php deleted file mode 100644 index cd2a795c..00000000 --- a/src/Directive/ScreencastDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class ScreencastDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('screencast', 'Screencast'); - } -} diff --git a/src/Directive/SeeAlsoDirective.php b/src/Directive/SeeAlsoDirective.php deleted file mode 100644 index cd81a76d..00000000 --- a/src/Directive/SeeAlsoDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class SeeAlsoDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('seealso', 'See also'); - } -} diff --git a/src/Directive/SidebarDirective.php b/src/Directive/SidebarDirective.php deleted file mode 100644 index f4674e23..00000000 --- a/src/Directive/SidebarDirective.php +++ /dev/null @@ -1,34 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -class SidebarDirective extends SubDirective -{ - public function getName(): string - { - return 'sidebar'; - } - - public function processSub(Parser $parser, ?Node $document, string $variable, string $data, array $options): ?Node - { - $wrapperDiv = $parser->renderTemplate( - 'directives/sidebar.html.twig', - [ - 'title' => $parser->createSpanNode($data)->render(), - ] - ); - - return $parser->getNodeFactory()->createWrapperNode($document, $wrapperDiv, ''); - } -} diff --git a/src/Directive/TipDirective.php b/src/Directive/TipDirective.php deleted file mode 100644 index 41ba8969..00000000 --- a/src/Directive/TipDirective.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class TipDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - parent::__construct('tip', 'Tip'); - } -} diff --git a/src/Directive/VersionAddedDirective.php b/src/Directive/VersionAddedDirective.php deleted file mode 100644 index 3568bacc..00000000 --- a/src/Directive/VersionAddedDirective.php +++ /dev/null @@ -1,37 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -use Doctrine\RST\Directives\SubDirective; -use Doctrine\RST\Nodes\Node; -use Doctrine\RST\Parser; - -class VersionAddedDirective extends SubDirective -{ - public function getName(): string - { - return 'versionadded'; - } - - public function processSub(Parser $parser, ?Node $document, string $variable, string $data, array $options): ?Node - { - $wrapperDiv = $parser->renderTemplate( - 'directives/admonition.html.twig', - [ - 'name' => 'versionadded', - 'text' => $data, - 'class' => $options['class'] ?? null, - 'version' => $data, - ] - ); - - return $parser->getNodeFactory()->createWrapperNode($document, $wrapperDiv, ''); - } -} diff --git a/src/Directive/WarningDirective.php b/src/Directive/WarningDirective.php deleted file mode 100644 index c849d337..00000000 --- a/src/Directive/WarningDirective.php +++ /dev/null @@ -1,19 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Directive; - -class WarningDirective extends AbstractAdmonitionDirective -{ - public function __construct() - { - // we render warning and caution the same - parent::__construct('warning', 'Warning'); - } -} diff --git a/src/DocBuilder.php b/src/DocBuilder.php index 46ce8064..5bcf288b 100644 --- a/src/DocBuilder.php +++ b/src/DocBuilder.php @@ -23,7 +23,7 @@ public function build(BuildConfig $config): BuildResult $configFileParser = new ConfigFileParser($config, new NullOutput()); $configFileParser->processConfigFile($config->getContentDir()); - $builder = new Builder(KernelFactory::createKernel($config)); + $builder = BuilderFactory::createBuilder($config); $builder->build($config->getContentDir(), $config->getOutputDir()); $buildResult = new BuildResult($builder); @@ -54,7 +54,7 @@ public function build(BuildConfig $config): BuildResult } elseif ($config->generateJsonFiles()) { $metas = $buildResult->getMetadata(); $jsonGenerator = new JsonGenerator($metas, $config); - $buildResult->setJsonResults($jsonGenerator->generateJson($builder->getIndexName())); + $buildResult->setJsonResults($jsonGenerator->generateJson($builder->getConfiguration()->getIndexName())); } return $buildResult; diff --git a/src/DocsKernel.php b/src/DocsKernel.php deleted file mode 100644 index 1f234425..00000000 --- a/src/DocsKernel.php +++ /dev/null @@ -1,64 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder; - -use Doctrine\Common\EventManager; -use Doctrine\RST\Builder; -use Doctrine\RST\Configuration; -use Doctrine\RST\ErrorManager; -use Doctrine\RST\Event\PostBuildRenderEvent; -use Doctrine\RST\Event\PreNodeRenderEvent; -use Doctrine\RST\Event\PreParseDocumentEvent; -use Doctrine\RST\Kernel; -use SymfonyDocsBuilder\Listener\AdmonitionListener; -use SymfonyDocsBuilder\Listener\AssetsCopyListener; -use SymfonyDocsBuilder\Listener\CopyImagesListener; - -class DocsKernel extends Kernel -{ - private $buildConfig; - - public function __construct(BuildConfig $buildConfig, ?Configuration $configuration = null, $directives = [], $references = []) - { - parent::__construct($configuration, $directives, $references); - - $this->buildConfig = $buildConfig; - } - - public function initBuilder(Builder $builder): void - { - $this->initializeListeners( - $builder->getConfiguration()->getEventManager(), - $builder->getErrorManager() - ); - - $builder->setScannerFinder($this->buildConfig->createFileFinder()); - } - - private function initializeListeners(EventManager $eventManager, ErrorManager $errorManager) - { - $eventManager->addEventListener( - PreParseDocumentEvent::PRE_PARSE_DOCUMENT, - new AdmonitionListener() - ); - - $eventManager->addEventListener( - PreNodeRenderEvent::PRE_NODE_RENDER, - new CopyImagesListener($this->buildConfig, $errorManager) - ); - - if (!$this->buildConfig->getSubdirectoryToBuild()) { - $eventManager->addEventListener( - [PostBuildRenderEvent::POST_BUILD_RENDER], - new AssetsCopyListener($this->buildConfig->getOutputDir()) - ); - } - } -} diff --git a/src/KernelFactory.php b/src/KernelFactory.php deleted file mode 100644 index 3f808735..00000000 --- a/src/KernelFactory.php +++ /dev/null @@ -1,117 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder; - -use Doctrine\RST\Configuration as RSTParserConfiguration; -use Doctrine\RST\HTML\Directives\ClassDirective; -use Doctrine\RST\Kernel; -use SymfonyDocsBuilder\CI\UrlChecker; -use SymfonyDocsBuilder\Directive as SymfonyDirectives; -use SymfonyDocsBuilder\Reference as SymfonyReferences; -use SymfonyDocsBuilder\Twig\AssetsExtension; -use SymfonyDocsBuilder\Twig\TocExtension; -use function Symfony\Component\String\u; - -/** - * Class KernelFactory. - */ -final class KernelFactory -{ - public static function createKernel(BuildConfig $buildConfig, ?UrlChecker $urlChecker = null): Kernel - { - $configuration = new RSTParserConfiguration(); - // needed to avoid outputting parser errors on the console output or the webpage contents - $configuration->silentOnError(true); - $configuration->setCustomTemplateDirs([__DIR__.'/Templates']); - $configuration->setTheme($buildConfig->getTheme()); - $configuration->setCacheDir(sprintf('%s/var/cache', $buildConfig->getCacheDir())); - $configuration->abortOnError(false); - - if (!$buildConfig->isBuildCacheEnabled()) { - $configuration->setUseCachedMetas(false); - } - - $configuration->addFormat( - new SymfonyHTMLFormat( - $configuration->getTemplateRenderer(), - $configuration->getFormat(), - $urlChecker - ) - ); - - if ($parseSubPath = $buildConfig->getSubdirectoryToBuild()) { - $configuration->setBaseUrl($buildConfig->getSymfonyDocUrl()); - $configuration->setBaseUrlEnabledCallable( - static function (string $path) use ($parseSubPath): bool { - return u($path)->containsAny($parseSubPath); - } - ); - } - - $twig = $configuration->getTemplateEngine(); - $twig->addExtension(new AssetsExtension()); - $twig->addExtension(new TocExtension()); - - return new DocsKernel( - $buildConfig, - $configuration, - self::getDirectives(), - self::getReferences($buildConfig) - ); - } - - private static function getDirectives(): array - { - return [ - new SymfonyDirectives\AdmonitionDirective(), - new SymfonyDirectives\AttentionDirective(), - new SymfonyDirectives\CautionDirective(), - new SymfonyDirectives\CodeBlockDirective(), - new SymfonyDirectives\ConfigurationBlockDirective(), - new SymfonyDirectives\DangerDirective(), - new SymfonyDirectives\DeprecatedDirective(), - new SymfonyDirectives\ErrorDirective(), - new SymfonyDirectives\FigureDirective(), - new SymfonyDirectives\HintDirective(), - new SymfonyDirectives\ImportantDirective(), - new SymfonyDirectives\IndexDirective(), - new SymfonyDirectives\RoleDirective(), - new SymfonyDirectives\NoteDirective(), - new SymfonyDirectives\RstClassDirective(new ClassDirective()), - new SymfonyDirectives\ScreencastDirective(), - new SymfonyDirectives\SeeAlsoDirective(), - new SymfonyDirectives\SidebarDirective(), - new SymfonyDirectives\TipDirective(), - new SymfonyDirectives\TopicDirective(), - new SymfonyDirectives\WarningDirective(), - new SymfonyDirectives\VersionAddedDirective(), - new SymfonyDirectives\BestPracticeDirective(), - new SymfonyDirectives\GlossaryDirective(), - ]; - } - - private static function getReferences(BuildConfig $buildConfig): array - { - return [ - new SymfonyReferences\ClassReference($buildConfig->getSymfonyRepositoryUrl()), - new SymfonyReferences\MethodReference($buildConfig->getSymfonyRepositoryUrl()), - new SymfonyReferences\NamespaceReference($buildConfig->getSymfonyRepositoryUrl()), - new SymfonyReferences\PhpFunctionReference($buildConfig->getPhpDocUrl()), - new SymfonyReferences\PhpMethodReference($buildConfig->getPhpDocUrl()), - new SymfonyReferences\PhpClassReference($buildConfig->getPhpDocUrl()), - new SymfonyReferences\TermReference(), - new SymfonyReferences\LeaderReference(), - new SymfonyReferences\MergerReference(), - new SymfonyReferences\DeciderReference(), - ]; - } -} diff --git a/src/Reference/ClassReference.php b/src/Reference/ClassReference.php deleted file mode 100644 index 56cd375c..00000000 --- a/src/Reference/ClassReference.php +++ /dev/null @@ -1,45 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; -use function Symfony\Component\String\u; - -class ClassReference extends Reference -{ - private $symfonyRepositoryUrl; - - public function __construct(string $symfonyRepositoryUrl) - { - $this->symfonyRepositoryUrl = $symfonyRepositoryUrl; - } - - public function getName(): string - { - return 'class'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - $className = u($data)->replace('\\\\', '\\'); - - return new ResolvedReference( - $environment->getCurrentFileName(), - $className->afterLast('\\'), - sprintf('%s/%s.php', $this->symfonyRepositoryUrl, $className->replace('\\', '/')), - [], - [ - 'title' => $className, - ] - ); - } -} diff --git a/src/Reference/DeciderReference.php b/src/Reference/DeciderReference.php deleted file mode 100644 index cf3da331..00000000 --- a/src/Reference/DeciderReference.php +++ /dev/null @@ -1,34 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -/** - * @deprecated - */ -class DeciderReference extends Reference -{ - public function getName(): string - { - return 'decider'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - '#' - ); - } -} diff --git a/src/Reference/LeaderReference.php b/src/Reference/LeaderReference.php deleted file mode 100644 index 3feb8052..00000000 --- a/src/Reference/LeaderReference.php +++ /dev/null @@ -1,34 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -/** - * @deprecated - */ -class LeaderReference extends Reference -{ - public function getName(): string - { - return 'leader'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - '#' - ); - } -} diff --git a/src/Reference/MergerReference.php b/src/Reference/MergerReference.php deleted file mode 100644 index 571ec7b8..00000000 --- a/src/Reference/MergerReference.php +++ /dev/null @@ -1,34 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -/** - * @deprecated - */ -class MergerReference extends Reference -{ - public function getName(): string - { - return 'merger'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - '#' - ); - } -} diff --git a/src/Reference/MethodReference.php b/src/Reference/MethodReference.php deleted file mode 100644 index baacc468..00000000 --- a/src/Reference/MethodReference.php +++ /dev/null @@ -1,52 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; -use function Symfony\Component\String\u; - -class MethodReference extends Reference -{ - private $symfonyRepositoryUrl; - - public function __construct(string $symfonyRepositoryUrl) - { - $this->symfonyRepositoryUrl = $symfonyRepositoryUrl; - } - - public function getName(): string - { - return 'method'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - $className = explode('::', $data)[0]; - $className = str_replace('\\\\', '\\', $className); - - if (!u($data)->containsAny('::')) { - throw new \RuntimeException(sprintf('Malformed method reference "%s" in file "%s"', $data, $environment->getCurrentFileName())); - } - - $methodName = explode('::', $data)[1]; - - return new ResolvedReference( - $environment->getCurrentFileName(), - $methodName.'()', - sprintf('%s/%s.php#method_%s', $this->symfonyRepositoryUrl, str_replace('\\', '/', $className), $methodName), - [], - [ - 'title' => sprintf('%s::%s()', $className, $methodName), - ] - ); - } -} diff --git a/src/Reference/NamespaceReference.php b/src/Reference/NamespaceReference.php deleted file mode 100644 index 96eaff97..00000000 --- a/src/Reference/NamespaceReference.php +++ /dev/null @@ -1,45 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; -use function Symfony\Component\String\u; - -class NamespaceReference extends Reference -{ - private $symfonyRepositoryUrl; - - public function __construct(string $symfonyRepositoryUrl) - { - $this->symfonyRepositoryUrl = $symfonyRepositoryUrl; - } - - public function getName(): string - { - return 'namespace'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - $className = u($data)->replace('\\\\', '\\'); - - return new ResolvedReference( - $environment->getCurrentFileName(), - $className->afterLast('\\'), - sprintf('%s/%s', $this->symfonyRepositoryUrl, $className->replace('\\', '/')), - [], - [ - 'title' => $className, - ] - ); - } -} diff --git a/src/Reference/PhpClassReference.php b/src/Reference/PhpClassReference.php deleted file mode 100644 index 4e104e17..00000000 --- a/src/Reference/PhpClassReference.php +++ /dev/null @@ -1,42 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -class PhpClassReference extends Reference -{ - private $phpDocUrl; - - public function __construct(string $phpDocUrl) - { - $this->phpDocUrl = $phpDocUrl; - } - - public function getName(): string - { - return 'phpclass'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - sprintf('%s/class.%s.php', $this->phpDocUrl, strtolower($data)), - [], - [ - 'title' => $data, - ] - ); - } -} diff --git a/src/Reference/PhpFunctionReference.php b/src/Reference/PhpFunctionReference.php deleted file mode 100644 index ee36d0a2..00000000 --- a/src/Reference/PhpFunctionReference.php +++ /dev/null @@ -1,43 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; -use function Symfony\Component\String\u; - -class PhpFunctionReference extends Reference -{ - private $phpDocUrl; - - public function __construct(string $phpDocUrl) - { - $this->phpDocUrl = $phpDocUrl; - } - - public function getName(): string - { - return 'phpfunction'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - sprintf('%s/function.%s.php', $this->phpDocUrl, u($data)->replace('_', '-')->lower()), - [], - [ - 'title' => $data, - ] - ); - } -} diff --git a/src/Reference/PhpMethodReference.php b/src/Reference/PhpMethodReference.php deleted file mode 100644 index ac537440..00000000 --- a/src/Reference/PhpMethodReference.php +++ /dev/null @@ -1,44 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -class PhpMethodReference extends Reference -{ - private $phpDocUrl; - - public function __construct(string $phpDocUrl) - { - $this->phpDocUrl = $phpDocUrl; - } - - public function getName(): string - { - return 'phpmethod'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - [$class, $method] = explode('::', $data, 2); - - return new ResolvedReference( - $environment->getCurrentFileName(), - $data.'()', - sprintf('%s/%s.%s.php', $this->phpDocUrl, strtolower($class), strtolower($method)), - [], - [ - 'title' => $class, - ] - ); - } -} diff --git a/src/Reference/TermReference.php b/src/Reference/TermReference.php deleted file mode 100644 index 18ee953f..00000000 --- a/src/Reference/TermReference.php +++ /dev/null @@ -1,34 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Reference; - -use Doctrine\RST\Environment; -use Doctrine\RST\References\Reference; -use Doctrine\RST\References\ResolvedReference; - -/** - * @deprecated - */ -class TermReference extends Reference -{ - public function getName(): string - { - return 'term'; - } - - public function resolve(Environment $environment, string $data): ResolvedReference - { - return new ResolvedReference( - $environment->getCurrentFileName(), - $data, - '#' - ); - } -} diff --git a/src/Renderers/SpanNodeRenderer.php b/src/Renderers/SpanNodeRenderer.php deleted file mode 100644 index b99efec2..00000000 --- a/src/Renderers/SpanNodeRenderer.php +++ /dev/null @@ -1,143 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SymfonyDocsBuilder\Renderers; - -use Doctrine\RST\Environment; -use Doctrine\RST\HTML\Renderers\SpanNodeRenderer as BaseSpanNodeRenderer; -use Doctrine\RST\Nodes\SpanNode; -use Doctrine\RST\References\ResolvedReference; -use Doctrine\RST\Renderers\SpanNodeRenderer as AbstractSpanNodeRenderer; -use SymfonyDocsBuilder\CI\UrlChecker; -use function Symfony\Component\String\u; - -class SpanNodeRenderer extends AbstractSpanNodeRenderer -{ - /** @var BaseSpanNodeRenderer */ - private $decoratedSpanNodeRenderer; - /** @var UrlChecker|null */ - private $urlChecker; - - public function __construct( - Environment $environment, - SpanNode $span, - BaseSpanNodeRenderer $decoratedSpanNodeRenderer, - ?UrlChecker $urlChecker = null - ) { - parent::__construct($environment, $span); - - $this->decoratedSpanNodeRenderer = $decoratedSpanNodeRenderer; - $this->urlChecker = $urlChecker; - } - - /** @inheritDoc */ - public function link(?string $url, string $title, array $attributes = []): string - { - $url = (string)$url; - - if ( - $this->urlChecker && - $this->isExternalUrl($url) && - !u($url)->startsWith(['http://localhost', 'http://192.168']) - ) { - $this->urlChecker->checkUrl($url); - } - - if (!$this->isSafeUrl($url)) { - $attributes = $this->addAttributesForUnsafeUrl($attributes); - } - - return $this->decoratedSpanNodeRenderer->link($url, $title, $attributes); - } - - public function reference(ResolvedReference $reference, array $value): string - { - if (!$this->isSafeUrl($reference->getUrl())) { - $reference = new ResolvedReference( - $reference->getFile(), - $reference->getTitle(), - $reference->getUrl(), - $reference->getTitles(), - $this->addAttributesForUnsafeUrl($reference->getAttributes()) - ); - } - - return $this->decoratedSpanNodeRenderer->reference($reference, $value); - } - - public function literal(string $text): string - { - // some browsers can't break long properly, so we inject a - // `` (word-break HTML tag) after some characters to help break those - // We only do this for very long (4 or more \\) to not break short - // and common `` such as App\Entity\Something - if (substr_count($text, '\\') >= 4) { - // breaking before the backslask is what Firefox browser does - $text = str_replace('\\', '\\', $text); - } - - return $this->decoratedSpanNodeRenderer->literal($text); - } - - public function emphasis(string $text): string - { - return $this->decoratedSpanNodeRenderer->emphasis($text); - } - - public function strongEmphasis(string $text): string - { - return $this->decoratedSpanNodeRenderer->strongEmphasis($text); - } - - public function nbsp(): string - { - return $this->decoratedSpanNodeRenderer->nbsp(); - } - - public function br(): string - { - return $this->decoratedSpanNodeRenderer->br(); - } - - public function escape(string $span): string - { - return $this->decoratedSpanNodeRenderer->escape($span); - } - - private function isExternalUrl($url): bool - { - return u($url)->containsAny('://'); - } - - /* - * If the URL is considered safe, it's opened in the same browser tab; - * otherwise it's opened in a new tab and with some strict security options. - */ - private function isSafeUrl(string $url): bool - { - // The following are considered Symfony URLs: - // * https://symfony.com/[...] - // * https://[...].symfony.com/ (e.g. insight.symfony.com, etc.) - // * https://symfony.wip/[...] (used for internal/local development) - $isSymfonyUrl = preg_match('{^http(s)?://(.*\.)?symfony.(com|wip)}', $url); - $isRelativeUrl = !str_starts_with($url, 'http://') && !str_starts_with($url, 'https://'); - - return $isSymfonyUrl || $isRelativeUrl; - } - - private function addAttributesForUnsafeUrl(array $attributes): array - { - return array_merge( - $attributes, - ['rel' => 'external noopener noreferrer', 'target' => '_blank'] - ); - } -} diff --git a/src/SymfonyHTMLDirectiveFactory.php b/src/SymfonyHTMLDirectiveFactory.php new file mode 100644 index 00000000..0eaee85c --- /dev/null +++ b/src/SymfonyHTMLDirectiveFactory.php @@ -0,0 +1,62 @@ +buildConfig = $buildConfig; + $this->urlChecker = $urlChecker; + } + + public function getDirectives(): array + { + return [ + ...parent::getDirectives(), + + new Admonition('best-practice', 'Best Practice'), + new Admonition('deprecated', 'Deprecated'), + new Admonition('note', 'Note'), + new Admonition('screencast', 'Screencast'), + new Admonition('versionadded', 'Version Added'), + + new SymfonyDirectives\CodeBlockDirective(), + new SymfonyDirectives\ConfigurationBlockDirective(), + new SymfonyDirectives\GlossaryDirective(), + new SymfonyDirectives\RstClassDirective(new ClassDirective()), + new SymfonyDirectives\TopicDirective(), + ]; + } + + public function getTextRoles(): array + { + return [ + ...parent::getTextRoles(), + + new SymfonyTextRoles\LiteralRole(), + + new SymfonyTextRoles\PhpClassRole($this->buildConfig->getPhpDocUrl()), + new SymfonyTextRoles\PhpMethodRole($this->buildConfig->getPhpDocUrl()), + new SymfonyTextRoles\PhpFunctionRole($this->buildConfig->getPhpDocUrl()), + new SymfonyTextRoles\ClassRole($this->buildConfig->getSymfonyRepositoryUrl()), + new SymfonyTextRoles\MethodRole($this->buildConfig->getSymfonyRepositoryUrl()), + new SymfonyTextRoles\NamespaceRole($this->buildConfig->getSymfonyRepositoryUrl()), + + // deprecated + new SymfonyTextRoles\SimpleRole('leader'), + new SymfonyTextRoles\SimpleRole('merger'), + new SymfonyTextRoles\SimpleRole('decider'), + ]; + } +} diff --git a/src/SymfonyHTMLFormat.php b/src/SymfonyHTMLFormat.php index bf010904..7f697ba4 100644 --- a/src/SymfonyHTMLFormat.php +++ b/src/SymfonyHTMLFormat.php @@ -11,6 +11,7 @@ namespace SymfonyDocsBuilder; +use Doctrine\RST\Directives\DirectiveFactory; use Doctrine\RST\Formats\Format; use Doctrine\RST\Nodes\CodeNode; use Doctrine\RST\Nodes\SpanNode; @@ -25,13 +26,15 @@ */ final class SymfonyHTMLFormat implements Format { - protected $templateRenderer; + private $buildConfig; + private $templateRenderer; private $htmlFormat; /** @var UrlChecker|null */ private $urlChecker; - public function __construct(TemplateRenderer $templateRenderer, Format $HTMLFormat, ?UrlChecker $urlChecker = null) + public function __construct(BuildConfig $buildConfig, TemplateRenderer $templateRenderer, Format $HTMLFormat, ?UrlChecker $urlChecker = null) { + $this->buildConfig = $buildConfig; $this->templateRenderer = $templateRenderer; $this->htmlFormat = $HTMLFormat; $this->urlChecker = $urlChecker; @@ -42,11 +45,6 @@ public function getFileExtension(): string return Format::HTML; } - public function getDirectives(): array - { - return $this->htmlFormat->getDirectives(); - } - /** * @return NodeRendererFactory[] */ @@ -63,17 +61,11 @@ function (CodeNode $node) { } ); - $nodeRendererFactories[SpanNode::class] = new CallableNodeRendererFactory( - function (SpanNode $node) { - return new Renderers\SpanNodeRenderer( - $node->getEnvironment(), - $node, - new BaseSpanNodeRenderer($node->getEnvironment(), $node, $this->templateRenderer), - $this->urlChecker - ); - } - ); - return $nodeRendererFactories; } + + public function getDirectiveFactory(): DirectiveFactory + { + return new SymfonyHTMLDirectiveFactory($this->buildConfig, $this->urlChecker); + } } diff --git a/src/Templates/default/html/directives/admonition.html.twig b/src/Templates/default/html/directives/admonition.html.twig index f32f3275..dfa18078 100644 --- a/src/Templates/default/html/directives/admonition.html.twig +++ b/src/Templates/default/html/directives/admonition.html.twig @@ -1,6 +1,6 @@ {# icons are from https://heroicons.com/ - MIT License #}
-

+

{% if name in ['admonition', 'note'] %} {% elseif name in ['hint', 'tip'] %} @@ -16,5 +16,7 @@ {% elseif name in ['screencast'] %} {% endif %} - {{ text }} + {{ text|raw }}

+ ||| +
diff --git a/src/Templates/default/html/link.html.twig b/src/Templates/default/html/link.html.twig deleted file mode 100644 index 8b9ca5ba..00000000 --- a/src/Templates/default/html/link.html.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% apply spaceless %} -{% set domElement = attributes.domElement|default %} -{% set class = (attributes.class|default ? attributes.class ~ ' ' : '') ~ 'reference ' ~ (('://' in url) ? 'external' : 'internal') %} - key != 'domElement' and key != 'class') %} {{ key }}="{{ value }}"{% endfor %}>{% if domElement %}<{{ domElement }}>{% endif %}{{ title|raw }}{% if domElement %}{% endif %} -{% endapply %} diff --git a/src/Templates/default/html/textroles/link.html.twig b/src/Templates/default/html/textroles/link.html.twig new file mode 100644 index 00000000..bf0027c4 --- /dev/null +++ b/src/Templates/default/html/textroles/link.html.twig @@ -0,0 +1,10 @@ +{% set domElement = attributes.domElement|default %} +{% set class = (attributes.class|default ? attributes.class ~ ' ') ~ 'reference ' ~ (url is safe_url ? 'internal' : 'external') %} + key != 'domElement' and key != 'class') %} {{ key }}="{{ value }}"{% endfor -%} + {%- if url is not safe_url %} rel="external noopener noreferrer" target="_blank"{% endif -%} +> + {%- if domElement %}<{{ domElement }}>{% endif -%} + {{ title|raw }} + {%- if domElement %}{% endif -%} + diff --git a/src/Templates/default/html/literal.html.twig b/src/Templates/default/html/textroles/literal.html.twig similarity index 100% rename from src/Templates/default/html/literal.html.twig rename to src/Templates/default/html/textroles/literal.html.twig diff --git a/src/TextRole/ClassRole.php b/src/TextRole/ClassRole.php new file mode 100644 index 00000000..88e6920f --- /dev/null +++ b/src/TextRole/ClassRole.php @@ -0,0 +1,28 @@ +get('text'); + $className = u($data)->replace('\\\\', '\\'); + + return $this->renderLink( + $environment, + $className->afterLast('\\'), + sprintf('%s/%s.php', $this->baseUrl, $className->replace('\\', '/')), + $className + ); + } +} diff --git a/src/TextRole/ExternalLinkRole.php b/src/TextRole/ExternalLinkRole.php new file mode 100644 index 00000000..93782efd --- /dev/null +++ b/src/TextRole/ExternalLinkRole.php @@ -0,0 +1,28 @@ +baseUrl = $baseUrl; + } + + protected function renderLink(Environment $environment, string $text, string $url, ?string $title = null) + { + return $this->renderTemplate($environment, 'textroles/link', [ + 'title' => $text, + 'url' => $url, + 'attributes' => [ + 'title' => $title ?? $text, + ], + ]); + } +} diff --git a/src/TextRole/LinkRole.php b/src/TextRole/LinkRole.php new file mode 100644 index 00000000..0cd18d88 --- /dev/null +++ b/src/TextRole/LinkRole.php @@ -0,0 +1,54 @@ +urlChecker = $urlChecker; + } + + public function renderLink(Environment $environment, ?string $url, string $title, array $attributes = []): string + { + $url = (string) $url; + + if ( + $this->urlChecker && + $this->isExternalUrl($url) && + !u($url)->startsWith(['http://localhost', 'http://192.168']) + ) { + $this->urlChecker->checkUrl($url); + } + + if (!$this->isSafeUrl($url)) { + $attributes = $this->addAttributesForUnsafeUrl($attributes); + } else { + $attributes['class'] = 'reference internal'; + } + + return $environment->getTemplateRenderer()->render('textroles/link.html.twig', [ + 'url' => $environment->generateUrl((string) $url), + 'title' => $title, + 'attributes' => $attributes, + ]); + } + + private function addAttributesForUnsafeUrl(array $attributes): array + { + return array_merge( + $attributes, + ['class' => 'reference external', 'rel' => 'external noopener noreferrer', 'target' => '_blank'] + ); + } +} diff --git a/src/TextRole/LiteralRole.php b/src/TextRole/LiteralRole.php new file mode 100644 index 00000000..cc319577 --- /dev/null +++ b/src/TextRole/LiteralRole.php @@ -0,0 +1,29 @@ +get('text'); + + // some browsers can't break long properly, so we inject a + // `` (word-break HTML tag) after some characters to help break those + // We only do this for very long (4 or more \\) to not break short + // and common `` such as App\Entity\Something + if (substr_count($text, '\\') >= 4) { + // breaking before the backslask is what Firefox browser does + $text = str_replace('\\', '\\', $text); + } + + $token->set('text', $text); + + return parent::render($environment, $token); + } +} diff --git a/src/TextRole/MethodRole.php b/src/TextRole/MethodRole.php new file mode 100644 index 00000000..804a1ff8 --- /dev/null +++ b/src/TextRole/MethodRole.php @@ -0,0 +1,33 @@ +get('text')); + if (!$data->containsAny('::')) { + throw new \RuntimeException(sprintf('Malformed method reference "%s" in file "%s"', $data, $environment->getCurrentFileName())); + } + + [$className, $methodName] = $data->split('::', 2); + $className = $className->replace('\\\\', '\\'); + + return $this->renderLink( + $environment, + $methodName.'()', + sprintf('%s/%s.php#method_%s', $this->baseUrl, $className->replace('\\', '/'), $methodName), + sprintf('%s::%s()', $className, $methodName) + ); + } +} diff --git a/src/TextRole/NamespaceRole.php b/src/TextRole/NamespaceRole.php new file mode 100644 index 00000000..91324a14 --- /dev/null +++ b/src/TextRole/NamespaceRole.php @@ -0,0 +1,28 @@ +get('text'); + $namespaceName = u($data)->replace('\\\\', '\\'); + + return $this->renderLink( + $environment, + $namespaceName->afterLast('\\'), + sprintf('%s/%s', $this->baseUrl, $namespaceName->replace('\\', '/')), + $namespaceName + ); + } +} diff --git a/src/TextRole/PhpClassRole.php b/src/TextRole/PhpClassRole.php new file mode 100644 index 00000000..329b9fd0 --- /dev/null +++ b/src/TextRole/PhpClassRole.php @@ -0,0 +1,21 @@ +get('text'); + + return $this->renderLink($environment, $data, sprintf('%s/class.%s.php', $this->baseUrl, strtolower($data))); + } +} diff --git a/src/TextRole/PhpFunctionRole.php b/src/TextRole/PhpFunctionRole.php new file mode 100644 index 00000000..daa64182 --- /dev/null +++ b/src/TextRole/PhpFunctionRole.php @@ -0,0 +1,22 @@ +get('text'); + + return $this->renderLink($environment, $data.'()', sprintf('%s/function.%s.php', $this->baseUrl, u($data)->replace('_', '-')->lower())); + } +} diff --git a/src/TextRole/PhpMethodRole.php b/src/TextRole/PhpMethodRole.php new file mode 100644 index 00000000..bd65c63e --- /dev/null +++ b/src/TextRole/PhpMethodRole.php @@ -0,0 +1,31 @@ +get('text'); + [$class, $method] = explode('::', $data, 2); + + return $this->renderLink($environment, $data.'()', sprintf('%s/%s.%s.php', $this->baseUrl, strtolower($class), strtolower($method))); + } +} diff --git a/src/TextRole/SimpleRole.php b/src/TextRole/SimpleRole.php new file mode 100644 index 00000000..84f3ad8e --- /dev/null +++ b/src/TextRole/SimpleRole.php @@ -0,0 +1,30 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function render(Environment $environment, SpanToken $spanToken): string + { + return $spanToken->get('text'); + } +} diff --git a/src/Twig/UrlExtension.php b/src/Twig/UrlExtension.php new file mode 100644 index 00000000..f66bfd35 --- /dev/null +++ b/src/Twig/UrlExtension.php @@ -0,0 +1,34 @@ +match('{^http(s)?://(.*\.)?symfony.(com|wip)}'); + $isRelativeUrl = !$url->startsWith('http://') && !$url->startsWith('https://'); + + return $isSymfonyUrl || $isRelativeUrl; + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index ef7c8b3e..f9438ac5 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -13,10 +13,10 @@ use Doctrine\RST\Configuration; use Doctrine\RST\Parser; use Gajus\Dindent\Indenter; +use SymfonyDocsBuilder\BuilderFactory; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\Finder\Finder; use SymfonyDocsBuilder\DocBuilder; -use SymfonyDocsBuilder\KernelFactory; class IntegrationTest extends AbstractIntegrationTest { @@ -66,9 +66,9 @@ public function testIntegration(string $folder) public function integrationProvider() { - yield 'main' => [ - 'folder' => 'main', - ]; + //yield 'main' => [ + // 'folder' => 'main', + //]; yield 'toctree' => [ 'folder' => 'toctree', @@ -88,13 +88,8 @@ public function integrationProvider() */ public function testParseUnitBlock(string $blockName) { - $configuration = new Configuration(); - $configuration->setCustomTemplateDirs([__DIR__.'/Templates']); - - $kernel = KernelFactory::createKernel($this->createBuildConfig(sprintf('%s/fixtures/source/blocks', __DIR__))); - // necessary because this initializes some listeners on the kernel - $builder = new Builder($kernel); - $parser = new Parser($kernel); + $builder = BuilderFactory::createBuilder($this->createBuildConfig(sprintf('%s/fixtures/source/blocks', __DIR__))); + $parser = new Parser($builder->getConfiguration()); $sourceFile = sprintf('%s/fixtures/source/blocks/%s.rst', __DIR__, $blockName); @@ -201,32 +196,32 @@ public function parserUnitBlockProvider() 'blockName' => 'directives/sidebar-code-block-nested', ]; - yield 'class-reference' => [ - 'blockName' => 'references/class', + yield 'class-role' => [ + 'blockName' => 'text-roles/class', ]; - yield 'namespace-reference' => [ - 'blockName' => 'references/namespace', + yield 'namespace-role' => [ + 'blockName' => 'text-roles/namespace', ]; - yield 'method-reference' => [ - 'blockName' => 'references/method', + yield 'method-role' => [ + 'blockName' => 'text-roles/method', ]; - yield 'php-class-reference' => [ - 'blockName' => 'references/php-class', + yield 'php-class-role' => [ + 'blockName' => 'text-roles/php-class', ]; - yield 'php-function-reference' => [ - 'blockName' => 'references/php-function', + yield 'php-function-role' => [ + 'blockName' => 'text-roles/php-function', ]; - yield 'php-method-reference' => [ - 'blockName' => 'references/php-method', + yield 'php-method-role' => [ + 'blockName' => 'text-roles/php-method', ]; yield 'reference-and-code' => [ - 'blockName' => 'references/reference-and-code', + 'blockName' => 'text-roles/reference-and-code', ]; yield 'code-block-caption' => [ @@ -321,7 +316,7 @@ public function testParseString()
  • Quis nostrud exercitation
  • Ullamco laboris nisi ut
  • -

    Aliquip ex ea commodo consequat. +

    Aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse.

    Cillum dolore eu fugiat nulla pariatur

    diff --git a/tests/fixtures/expected/blocks/directives/admonition.html b/tests/fixtures/expected/blocks/directives/admonition.html index f8f22804..9c31d1aa 100644 --- a/tests/fixtures/expected/blocks/directives/admonition.html +++ b/tests/fixtures/expected/blocks/directives/admonition.html @@ -1,5 +1,6 @@ -
    +

    + Some Admonition

    Do you prefer admonitions? Well then... enjoy this one!

    diff --git a/tests/fixtures/expected/blocks/directives/sidebar-code-block-nested.html b/tests/fixtures/expected/blocks/directives/sidebar-code-block-nested.html index 0c1b2709..bcfce92b 100644 --- a/tests/fixtures/expected/blocks/directives/sidebar-code-block-nested.html +++ b/tests/fixtures/expected/blocks/directives/sidebar-code-block-nested.html @@ -1,4 +1,4 @@ -

    some text before code block

    +

    some text before code block

    1
    @@ -7,4 +7,4 @@

    some text after code block

    -
    +
    diff --git a/tests/fixtures/expected/blocks/directives/sidebar.html b/tests/fixtures/expected/blocks/directives/sidebar.html index eba07ea2..3f4f0645 100644 --- a/tests/fixtures/expected/blocks/directives/sidebar.html +++ b/tests/fixtures/expected/blocks/directives/sidebar.html @@ -1,5 +1,5 @@ -

    some text inside sidebar

    -
    +

    some text inside sidebar

    +
    -

    The full signature of the request() method is...

    -
    +

    The full signature of the request() method is...

    +
    diff --git a/tests/fixtures/expected/blocks/references/php-function.html b/tests/fixtures/expected/blocks/references/php-function.html deleted file mode 100644 index 0c9b85aa..00000000 --- a/tests/fixtures/expected/blocks/references/php-function.html +++ /dev/null @@ -1 +0,0 @@ -

    trigger_error

    diff --git a/tests/fixtures/expected/blocks/references/php-method.html b/tests/fixtures/expected/blocks/references/php-method.html deleted file mode 100644 index e86c5717..00000000 --- a/tests/fixtures/expected/blocks/references/php-method.html +++ /dev/null @@ -1 +0,0 @@ -

    Locale::getDefault()

    diff --git a/tests/fixtures/expected/blocks/references/class.html b/tests/fixtures/expected/blocks/text-roles/class.html similarity index 100% rename from tests/fixtures/expected/blocks/references/class.html rename to tests/fixtures/expected/blocks/text-roles/class.html diff --git a/tests/fixtures/expected/blocks/references/method.html b/tests/fixtures/expected/blocks/text-roles/method.html similarity index 100% rename from tests/fixtures/expected/blocks/references/method.html rename to tests/fixtures/expected/blocks/text-roles/method.html diff --git a/tests/fixtures/expected/blocks/references/namespace.html b/tests/fixtures/expected/blocks/text-roles/namespace.html similarity index 100% rename from tests/fixtures/expected/blocks/references/namespace.html rename to tests/fixtures/expected/blocks/text-roles/namespace.html diff --git a/tests/fixtures/expected/blocks/references/php-class.html b/tests/fixtures/expected/blocks/text-roles/php-class.html similarity index 100% rename from tests/fixtures/expected/blocks/references/php-class.html rename to tests/fixtures/expected/blocks/text-roles/php-class.html diff --git a/tests/fixtures/expected/blocks/text-roles/php-function.html b/tests/fixtures/expected/blocks/text-roles/php-function.html new file mode 100644 index 00000000..0bc6cfbe --- /dev/null +++ b/tests/fixtures/expected/blocks/text-roles/php-function.html @@ -0,0 +1 @@ +

    trigger_error()

    diff --git a/tests/fixtures/expected/blocks/text-roles/php-method.html b/tests/fixtures/expected/blocks/text-roles/php-method.html new file mode 100644 index 00000000..1f387236 --- /dev/null +++ b/tests/fixtures/expected/blocks/text-roles/php-method.html @@ -0,0 +1 @@ +

    Locale::getDefault()

    diff --git a/tests/fixtures/expected/blocks/references/reference-and-code.html b/tests/fixtures/expected/blocks/text-roles/reference-and-code.html similarity index 81% rename from tests/fixtures/expected/blocks/references/reference-and-code.html rename to tests/fixtures/expected/blocks/text-roles/reference-and-code.html index 474be7e2..e9d4cdaa 100644 --- a/tests/fixtures/expected/blocks/references/reference-and-code.html +++ b/tests/fixtures/expected/blocks/text-roles/reference-and-code.html @@ -1,3 +1,3 @@

    Next, create the template used to render the field in the index and detail -CRUD pages.

    +CRUD pages.

    More about CRUD pages.

    diff --git a/tests/fixtures/expected/main/datetime.html b/tests/fixtures/expected/main/datetime.html index c75b859e..c3c26488 100644 --- a/tests/fixtures/expected/main/datetime.html +++ b/tests/fixtures/expected/main/datetime.html @@ -51,7 +51,7 @@

    Ref - Some Test Docs! + A header Test reference @@ -78,8 +78,8 @@

    This is a little tip about something! We an also talk about specific methods: doRequest(). Or a namespace: Constraints. -Or a PHP function: parse_ini_file. -Or a PHP method! Locale::getDefault().

    +Or a PHP function: parse_ini_file(). +Or a PHP method! Locale::getDefault().

    @@ -481,7 +481,7 @@

    Url checker errors

    -

    This is a 404 error. +

    This is a 404 error. And here is an invalid url invalid-url.

    diff --git a/tests/fixtures/expected/main/index.html b/tests/fixtures/expected/main/index.html index 13463739..58797607 100644 --- a/tests/fixtures/expected/main/index.html +++ b/tests/fixtures/expected/main/index.html @@ -11,9 +11,8 @@

    Some Test Docs!

    -
    -

    A header

    +

    A header

    Some info...

    diff --git a/tests/fixtures/source/blocks/references/class.rst b/tests/fixtures/source/blocks/text-roles/class.rst similarity index 100% rename from tests/fixtures/source/blocks/references/class.rst rename to tests/fixtures/source/blocks/text-roles/class.rst diff --git a/tests/fixtures/source/blocks/references/method.rst b/tests/fixtures/source/blocks/text-roles/method.rst similarity index 100% rename from tests/fixtures/source/blocks/references/method.rst rename to tests/fixtures/source/blocks/text-roles/method.rst diff --git a/tests/fixtures/source/blocks/references/namespace.rst b/tests/fixtures/source/blocks/text-roles/namespace.rst similarity index 100% rename from tests/fixtures/source/blocks/references/namespace.rst rename to tests/fixtures/source/blocks/text-roles/namespace.rst diff --git a/tests/fixtures/source/blocks/references/php-class.rst b/tests/fixtures/source/blocks/text-roles/php-class.rst similarity index 100% rename from tests/fixtures/source/blocks/references/php-class.rst rename to tests/fixtures/source/blocks/text-roles/php-class.rst diff --git a/tests/fixtures/source/blocks/references/php-function.rst b/tests/fixtures/source/blocks/text-roles/php-function.rst similarity index 100% rename from tests/fixtures/source/blocks/references/php-function.rst rename to tests/fixtures/source/blocks/text-roles/php-function.rst diff --git a/tests/fixtures/source/blocks/references/php-method.rst b/tests/fixtures/source/blocks/text-roles/php-method.rst similarity index 100% rename from tests/fixtures/source/blocks/references/php-method.rst rename to tests/fixtures/source/blocks/text-roles/php-method.rst diff --git a/tests/fixtures/source/blocks/references/reference-and-code.rst b/tests/fixtures/source/blocks/text-roles/reference-and-code.rst similarity index 100% rename from tests/fixtures/source/blocks/references/reference-and-code.rst rename to tests/fixtures/source/blocks/text-roles/reference-and-code.rst