Skip to content

Commit d5bb15f

Browse files
committed
Properly support config_file_path being None
* Prevent error when the config file is a stream, to support `-` as the config. * Fix type annotations and edge cases when `config_file_path` is None * Properly prevent users setting `config_file_path` in mkdocs.yml - this was not intended.
1 parent 285461a commit d5bb15f

File tree

8 files changed

+31
-30
lines changed

8 files changed

+31
-30
lines changed

docs/user-guide/configuration.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,12 @@ Therefore, defining paths in a parent file which is inherited by multiple
10211021
different sites may not work as expected. It is generally best to define
10221022
path based options in the primary configuration file only.
10231023

1024+
The inheritance can also be used as a quick way to override keys on the command line - by using stdin as the config file. For example:
1025+
1026+
```bash
1027+
echo '{INHERIT: mkdocs.yml, site_name: "Renamed site"}' | mkdocs build -f -
1028+
```
1029+
10241030
[Theme Developer Guide]: ../dev-guide/themes.md
10251031
[pymdk-extensions]: https://python-markdown.github.io/extensions/
10261032
[pymkd]: https://python-markdown.github.io/

mkdocs/__main__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ def __del__(self):
106106
pass_state = click.make_pass_decorator(State, ensure=True)
107107

108108
clean_help = "Remove old files from the site_dir before building (the default)."
109-
config_help = "Provide a specific MkDocs config"
109+
config_help = (
110+
"Provide a specific MkDocs config. This can be a file name, or '-' to read from stdin."
111+
)
110112
dev_addr_help = "IP address and port to serve documentation locally (default: localhost:8000)"
111113
strict_help = "Enable strict mode. This will cause MkDocs to abort the build on any warnings."
112114
theme_help = "The theme to use when building your documentation."

mkdocs/commands/gh_deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def gh_deploy(
116116

117117
if message is None:
118118
message = default_message
119-
sha = _get_current_sha(os.path.dirname(config.config_file_path))
119+
sha = _get_current_sha(os.path.dirname(config.config_file_path or ''))
120120
message = message.format(version=mkdocs.__version__, sha=sha)
121121

122122
log.info(

mkdocs/config/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,10 @@ def _open_config_file(config_file: str | IO | None) -> Iterator[IO]:
331331
else:
332332
log.debug(f"Loading configuration file: {result_config_file}")
333333
# Ensure file descriptor is at beginning
334-
result_config_file.seek(0)
334+
try:
335+
result_config_file.seek(0)
336+
except OSError:
337+
pass
335338

336339
try:
337340
yield result_config_file

mkdocs/config/config_options.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,8 +814,7 @@ def run_validation(self, value: object) -> theme.Theme:
814814

815815
# Ensure custom_dir is an absolute path
816816
if 'custom_dir' in theme_config and not os.path.isabs(theme_config['custom_dir']):
817-
assert self.config_file_path is not None
818-
config_dir = os.path.dirname(self.config_file_path)
817+
config_dir = os.path.dirname(self.config_file_path or '')
819818
theme_config['custom_dir'] = os.path.join(config_dir, theme_config['custom_dir'])
820819

821820
if 'custom_dir' in theme_config and not os.path.isdir(theme_config['custom_dir']):

mkdocs/config/defaults.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def get_schema() -> base.PlainConfigSchema:
1616
class MkDocsConfig(base.Config):
1717
"""The configuration of MkDocs itself (the root object of mkdocs.yml)."""
1818

19-
config_file_path: str = c.Optional(c.Type(str)) # type: ignore[assignment]
20-
"""Reserved for internal use, stores the mkdocs.yml config file."""
19+
config_file_path: str | None = c.Optional(c.Type(str)) # type: ignore[assignment]
20+
"""The path to the mkdocs.yml config file. Can't be populated from the config."""
2121

2222
site_name = c.Type(str)
2323
"""The title to use for the documentation."""
@@ -136,3 +136,8 @@ class MkDocsConfig(base.Config):
136136

137137
watch = c.ListOfPaths(default=[])
138138
"""A list of extra paths to watch while running `mkdocs serve`."""
139+
140+
def load_dict(self, patch: dict) -> None:
141+
super().load_dict(patch)
142+
if 'config_file_path' in patch:
143+
raise base.ValidationError("Can't set config_file_path in config")

mkdocs/tests/base.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,17 @@ def get_markdown_toc(markdown_source):
2323
return md.toc_tokens
2424

2525

26-
def load_config(**cfg) -> MkDocsConfig:
26+
def load_config(config_file_path: str | None = None, **cfg) -> MkDocsConfig:
2727
"""Helper to build a simple config for testing."""
2828
path_base = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'integration', 'minimal')
29-
cfg = cfg or {}
3029
if 'site_name' not in cfg:
3130
cfg['site_name'] = 'Example'
32-
if 'config_file_path' not in cfg:
33-
cfg['config_file_path'] = os.path.join(path_base, 'mkdocs.yml')
3431
if 'docs_dir' not in cfg:
3532
# Point to an actual dir to avoid a 'does not exist' error on validation.
3633
cfg['docs_dir'] = os.path.join(path_base, 'docs')
3734
if 'plugins' not in cfg:
3835
cfg['plugins'] = []
39-
conf = MkDocsConfig(config_file_path=cfg['config_file_path'])
36+
conf = MkDocsConfig(config_file_path=config_file_path or os.path.join(path_base, 'mkdocs.yml'))
4037
conf.load_dict(cfg)
4138

4239
errors_warnings = conf.validate()

mkdocs/tests/config/config_tests.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,10 @@ def test_theme(self, mytheme, custom):
218218
self.assertEqual({k: conf['theme'][k] for k in iter(conf['theme'])}, result['vars'])
219219

220220
def test_empty_nav(self):
221-
conf = defaults.MkDocsConfig()
222-
conf.load_dict(
223-
{
224-
'site_name': 'Example',
225-
'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
226-
}
221+
conf = defaults.MkDocsConfig(
222+
config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml')
227223
)
224+
conf.load_dict({'site_name': 'Example'})
228225
conf.validate()
229226
self.assertEqual(conf['nav'], None)
230227

@@ -242,34 +239,26 @@ def test_error_on_pages(self):
242239
self.assertEqual(warnings, [])
243240

244241
def test_doc_dir_in_site_dir(self):
245-
j = os.path.join
246-
247242
test_configs = (
248-
{'docs_dir': j('site', 'docs'), 'site_dir': 'site'},
243+
{'docs_dir': os.path.join('site', 'docs'), 'site_dir': 'site'},
249244
{'docs_dir': 'docs', 'site_dir': '.'},
250245
{'docs_dir': '.', 'site_dir': '.'},
251246
{'docs_dir': 'docs', 'site_dir': ''},
252247
{'docs_dir': '', 'site_dir': ''},
253248
{'docs_dir': 'docs', 'site_dir': 'docs'},
254249
)
255250

256-
cfg = {
257-
'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml'),
258-
}
259-
260251
for test_config in test_configs:
261252
with self.subTest(test_config):
262-
patch = {**cfg, **test_config}
263-
264253
# Same as the default schema, but don't verify the docs_dir exists.
265254
conf = config.Config(
266255
schema=(
267256
('docs_dir', c.Dir(default='docs')),
268257
('site_dir', c.SiteDir(default='site')),
269-
('config_file_path', c.Type(str)),
270-
)
258+
),
259+
config_file_path=os.path.join(os.path.abspath('..'), 'mkdocs.yml'),
271260
)
272-
conf.load_dict(patch)
261+
conf.load_dict(test_config)
273262

274263
errors, warnings = conf.validate()
275264

0 commit comments

Comments
 (0)