Skip to content

Commit 1663716

Browse files
authored
Merge pull request #482 from wouterj/config
Add support for extension configuration
2 parents d53ddf1 + cb5ea6a commit 1663716

File tree

18 files changed

+234
-22
lines changed

18 files changed

+234
-22
lines changed

docs/configuration.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,54 @@
44
Configuration
55
=============
66

7+
This library can be configured in two ways:
8+
9+
1. ``guides.xml`` in the current directory. This file configures the Guides
10+
library (which extensions to load) and can configure default values for
11+
the project specific settings.
12+
2. ``settings.php`` in the source files directory. This file can contain
13+
project/manual specific settings.
14+
15+
In most cases, you should do everything in the ``guides.xml`` file.
16+
Documentations that compile the docs for a collection of projects might
17+
want to use both config options. For instance, the ``guides.xml`` can
18+
configure the documentation theme, whereas the ``settings.php`` configures
19+
the title and version of each specific project.
20+
721
Global configuration
822
====================
923

1024
Settings that you want to have regardless of the documentation you are
1125
building should live in ``guides.xml`` in the current working directory.
1226

27+
.. code-block:: xml
28+
29+
<?xml version="1.0" encoding="UTF-8" ?>
30+
<guides xmlns="https://www.phpdoc.org/guides"
31+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
32+
xsi:schemaLocation="https://www.phpdoc.org/guides vendor/phpdocumentor/guides-cli/resources/schema/guides.xsd">
33+
34+
<project title="phpDocumentor Guides"/>
35+
36+
<extension class="phpDocumentor\Guides\Bootstrap"/>
37+
</guides>
38+
39+
The XML file can also be used to enable custom extensions. For instance, if
40+
you want to use the Bootstrap HTML theme, use this configuration:
41+
42+
.. code-block:: xml
43+
44+
<?xml version="1.0" encoding="UTF-8" ?>
45+
<guides xmlns="https://www.phpdoc.org/guides"
46+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
47+
xsi:schemaLocation="https://www.phpdoc.org/guides vendor/phpdocumentor/guides-cli/resources/schema/guides.xsd"
48+
49+
html-theme="bootstrap"
50+
>
51+
<extension class="phpDocumentor\Guides\Bootstrap"/>
52+
</guides>
53+
54+
See the ``guides.xsd`` file for all available config options.
1355

1456
Per-manual configuration
1557
========================

guides.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
2-
<guides>
2+
<guides xmlns="https://www.phpdoc.org/guides"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="https://www.phpdoc.org/guides packages/guides-cli/resources/schema/guides.xsd">
5+
<project title="phpDocumentor Guides"/>
6+
37
<extension class="phpDocumentor\Guides\Bootstrap"/>
48
</guides>

packages/guides-cli/bin/guides

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ declare(strict_types=1);
55

66
namespace phpDocumentor\Guides;
77

8+
use Symfony\Component\Console\Input\ArgvInput;
89
use phpDocumentor\Guides\Cli\Application;
910
use phpDocumentor\Guides\Cli\DependencyInjection\ApplicationExtension;
1011
use phpDocumentor\Guides\Cli\DependencyInjection\ContainerFactory;
@@ -29,8 +30,10 @@ if (file_exists($autoloadDirectory)){
2930
require $vendorDir . '/autoload.php';
3031
}
3132

33+
$input = new ArgvInput();
34+
3235
$containerFactory = new ContainerFactory([new ApplicationExtension()]);
33-
if (is_file(getcwd().'/guides.xml')) {
36+
if (is_file($input->getParameterOption('--config', getcwd(), true).'/guides.xml')) {
3437
$containerFactory->addConfigFile('guides.xml');
3538
}
3639
$container = $containerFactory->create($vendorDir);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xsd:schema
3+
targetNamespace="https://www.phpdoc.org/guides"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5+
version="3.0"
6+
>
7+
<xsd:element name="guides" type="guides"/>
8+
9+
<xsd:complexType name="guides">
10+
<xsd:choice maxOccurs="unbounded">
11+
<xsd:element name="project" type="project" minOccurs="0" maxOccurs="1"/>
12+
<xsd:element name="base-template-path" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
13+
<xsd:element name="theme" type="theme" minOccurs="0" maxOccurs="unbounded"/>
14+
<xsd:element name="extension" type="extension" minOccurs="0" maxOccurs="unbounded"/>
15+
</xsd:choice>
16+
17+
<xsd:attribute name="html-theme" type="xsd:string"/>
18+
</xsd:complexType>
19+
20+
<xsd:complexType name="extension">
21+
<xsd:choice maxOccurs="unbounded">
22+
<!-- allow extensions to use dynamic elements -->
23+
<xsd:any processContents="lax" minOccurs="0" maxOccurs="unbounded" />
24+
</xsd:choice>
25+
26+
<xsd:attribute name="class" type="xsd:string" use="required"/>
27+
<!-- allow extensions to use dynamic elements -->
28+
<xsd:anyAttribute processContents="lax" />
29+
</xsd:complexType>
30+
31+
<xsd:complexType name="project">
32+
<xsd:attribute name="title" type="xsd:string"/>
33+
<xsd:attribute name="version" type="xsd:string"/>
34+
</xsd:complexType>
35+
36+
<xsd:complexType name="theme">
37+
<xsd:sequence>
38+
<xsd:element name="template" type="xsd:string" minOccurs="1" maxOccurs="unbounded"/>
39+
</xsd:sequence>
40+
41+
<xsd:attribute name="extends" type="xsd:string"/>
42+
</xsd:complexType>
43+
</xsd:schema>

packages/guides-cli/src/Application.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
use Symfony\Component\Console\Application as BaseApplication;
88
use Symfony\Component\Console\Command\Command;
9+
use Symfony\Component\Console\Input\InputDefinition;
10+
use Symfony\Component\Console\Input\InputOption;
11+
12+
use function getcwd;
913

1014
final class Application extends BaseApplication
1115
{
@@ -20,4 +24,18 @@ public function __construct(iterable $commands, string $defaultCommand = 'run')
2024

2125
$this->setDefaultCommand($defaultCommand, true);
2226
}
27+
28+
protected function getDefaultInputDefinition(): InputDefinition
29+
{
30+
$definition = parent::getDefaultInputDefinition();
31+
$definition->addOption(new InputOption(
32+
'config',
33+
'c',
34+
InputOption::VALUE_REQUIRED,
35+
'The path to a "guides.xml" config file, if needed',
36+
getcwd(),
37+
));
38+
39+
return $definition;
40+
}
2341
}

packages/guides-cli/src/Command/Run.php

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use function is_countable;
3939
use function is_dir;
4040
use function is_file;
41+
use function realpath;
4142
use function sprintf;
4243
use function str_starts_with;
4344
use function strtoupper;
@@ -69,21 +70,21 @@ public function __construct(
6970
$this->addOption(
7071
'input-format',
7172
null,
72-
InputOption::VALUE_OPTIONAL,
73+
InputOption::VALUE_REQUIRED,
7374
'Format of the input can be RST, or Markdown',
7475
'rst',
7576
);
7677
$this->addOption(
7778
'output-format',
7879
null,
79-
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
80+
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
8081
'Format of the input can be html',
8182
['html'],
8283
);
8384
$this->addOption(
8485
'log-path',
8586
null,
86-
InputOption::VALUE_OPTIONAL,
87+
InputOption::VALUE_REQUIRED,
8788
'Write log to this path',
8889
'php://stder',
8990
);
@@ -97,7 +98,7 @@ public function __construct(
9798
$this->addOption(
9899
'theme',
99100
null,
100-
InputOption::VALUE_OPTIONAL,
101+
InputOption::VALUE_REQUIRED,
101102
'The theme used for rendering.',
102103
'default',
103104
);
@@ -127,16 +128,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
127128

128129
$settings = new ProjectSettings($settingsArray);
129130
$this->settingsManager->setProjectSettings($settings);
130-
$projectNode = new ProjectNode(
131-
$settings->getTitle(),
132-
$settings->getVersion(),
133-
);
134-
$this->inventoryRepository->initialize($settings->getInventories());
135131
} else {
136-
$this->settingsManager->setProjectSettings(new ProjectSettings([]));
137-
$projectNode = new ProjectNode();
132+
$settings = $this->settingsManager->getProjectSettings();
138133
}
139134

135+
$projectNode = new ProjectNode(
136+
$settings->getTitle() === '' ? null : $settings->getTitle(),
137+
$settings->getVersion() === '' ? null : $settings->getVersion(),
138+
);
139+
$this->inventoryRepository->initialize($settings->getInventories());
140+
140141
$outputDir = $this->getAbsolutePath((string) ($input->getArgument('output') ?? ''));
141142
$sourceFileSystem = new Filesystem(new Local($input->getArgument('input')));
142143
$sourceFileSystem->addPlugin(new Finder());
@@ -164,8 +165,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
164165
),
165166
);
166167

167-
if ($input->hasOption('theme')) {
168-
$this->themeManager->useTheme($input->getOption('theme') ?? 'default');
168+
$theme = $input->getOption('theme');
169+
if ($theme) {
170+
$this->themeManager->useTheme($theme);
169171
}
170172

171173
$documents = $this->commandBus->handle(new CompileDocumentsCommand($documents, new CompilerContext($projectNode)));
@@ -213,14 +215,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
213215

214216
private function getAbsolutePath(string $path): string
215217
{
216-
if (!str_starts_with($path, '/')) {
218+
$absolutePath = $path;
219+
if (!str_starts_with($absolutePath, '/')) {
217220
if (getcwd() === false) {
218221
throw new RuntimeException('Cannot find current working directory, use absolute paths.');
219222
}
220223

221-
$path = getcwd() . '/' . $path;
224+
$absolutePath = realpath(getcwd() . '/' . $absolutePath);
225+
if ($absolutePath === false) {
226+
throw new RuntimeException('Cannot find path "' . $path . '".');
227+
}
222228
}
223229

224-
return $path;
230+
return $absolutePath;
225231
}
226232
}

packages/guides-cli/src/Config/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ public function getConfigTreeBuilder(): TreeBuilder
2020

2121
$rootNode
2222
->fixXmlConfig('extension')
23+
->ignoreExtraKeys(false)
2324
->children()
2425
->arrayNode('extensions')
2526
->prototype('array')
2627
->beforeNormalization()
2728
->ifString()
2829
->then(static fn ($n) => ['class' => $n])
2930
->end()
31+
->ignoreExtraKeys(false)
3032
->children()
3133
->scalarNode('class')
3234
->isRequired()

packages/guides-cli/src/DependencyInjection/ContainerFactory.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,23 @@ private function processConfig(): void
112112
$processor = new Processor();
113113
$config = $processor->processConfiguration(new Configuration(), $this->configs);
114114

115+
$guidesConfig = [];
116+
foreach ($config as $key => $value) {
117+
if ($key === 'extensions') {
118+
continue;
119+
}
120+
121+
$guidesConfig[$key] = $value;
122+
unset($config[$key]);
123+
}
124+
125+
$config['extensions'][] = ['class' => GuidesExtension::class] + $guidesConfig;
126+
115127
foreach ($config['extensions'] as $extension) {
116128
$extensionFqcn = $this->resolveExtensionClass($extension['class']);
129+
unset($extension['class']);
117130

118-
$this->registerExtension(new $extensionFqcn());
131+
$this->loadExtensionConfig($extensionFqcn, $extension);
119132
}
120133
}
121134
}

packages/guides/src/DependencyInjection/GuidesExtension.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use phpDocumentor\Guides\DependencyInjection\Compiler\NodeRendererPass;
88
use phpDocumentor\Guides\DependencyInjection\Compiler\ParserRulesPass;
9+
use phpDocumentor\Guides\Settings\ProjectSettings;
10+
use phpDocumentor\Guides\Settings\SettingsManager;
911
use phpDocumentor\Guides\Twig\Theme\ThemeConfig;
1012
use phpDocumentor\Guides\Twig\Theme\ThemeManager;
1113
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
@@ -31,6 +33,13 @@ public function getConfigTreeBuilder(): TreeBuilder
3133

3234
$rootNode
3335
->children()
36+
->arrayNode('project')
37+
->children()
38+
->scalarNode('title')->end()
39+
->scalarNode('version')->end()
40+
->end()
41+
->end()
42+
->scalarNode('html_theme')->end()
3443
->arrayNode('base_template_paths')
3544
->defaultValue([])
3645
->scalarPrototype()->end()
@@ -70,6 +79,20 @@ public function load(array $configs, ContainerBuilder $container): void
7079
$loader->load('command_bus.php');
7180
$loader->load('guides.php');
7281

82+
if (isset($config['project'])) {
83+
if (isset($config['project']['version'])) {
84+
$config['project']['version'] = (string) $config['project']['version'];
85+
}
86+
87+
$container->getDefinition(SettingsManager::class)
88+
->addMethodCall('setProjectSettings', [new ProjectSettings($config['project'])]);
89+
}
90+
91+
if (isset($config['html_theme'])) {
92+
$container->getDefinition(ThemeManager::class)
93+
->addMethodCall('useTheme', [$config['html_theme']]);
94+
}
95+
7396
$config['base_template_paths'][] = dirname(__DIR__, 2) . '/resources/template/html';
7497
$container->setParameter('phpdoc.guides.base_template_paths', $config['base_template_paths']);
7598

phpstan-baseline.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ parameters:
1919
message: "#^Using nullsafe property access on non\\-nullable type Doctrine\\\\Common\\\\Lexer\\\\Token\\<int, string\\>\\. Use \\-\\> instead\\.$#"
2020
count: 1
2121
path: packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/NamedPhraseRule.php
22+
23+
-
24+
message: "#^Cannot call method scalarNode\\(\\) on Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\|null\\.$#"
25+
count: 1
26+
path: packages/guides/src/DependencyInjection/GuidesExtension.php

0 commit comments

Comments
 (0)