Skip to content

Commit

Permalink
Properly support config_file_path being None
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
oprypin committed Jun 18, 2023
1 parent 285461a commit d5bb15f
Show file tree
Hide file tree
Showing 8 changed files with 31 additions and 30 deletions.
6 changes: 6 additions & 0 deletions docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,12 @@ Therefore, defining paths in a parent file which is inherited by multiple
different sites may not work as expected. It is generally best to define
path based options in the primary configuration file only.

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:

```bash
echo '{INHERIT: mkdocs.yml, site_name: "Renamed site"}' | mkdocs build -f -
```

[Theme Developer Guide]: ../dev-guide/themes.md
[pymdk-extensions]: https://python-markdown.github.io/extensions/
[pymkd]: https://python-markdown.github.io/
Expand Down
4 changes: 3 additions & 1 deletion mkdocs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def __del__(self):
pass_state = click.make_pass_decorator(State, ensure=True)

clean_help = "Remove old files from the site_dir before building (the default)."
config_help = "Provide a specific MkDocs config"
config_help = (
"Provide a specific MkDocs config. This can be a file name, or '-' to read from stdin."
)
dev_addr_help = "IP address and port to serve documentation locally (default: localhost:8000)"
strict_help = "Enable strict mode. This will cause MkDocs to abort the build on any warnings."
theme_help = "The theme to use when building your documentation."
Expand Down
2 changes: 1 addition & 1 deletion mkdocs/commands/gh_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def gh_deploy(

if message is None:
message = default_message
sha = _get_current_sha(os.path.dirname(config.config_file_path))
sha = _get_current_sha(os.path.dirname(config.config_file_path or ''))
message = message.format(version=mkdocs.__version__, sha=sha)

log.info(
Expand Down
5 changes: 4 additions & 1 deletion mkdocs/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,10 @@ def _open_config_file(config_file: str | IO | None) -> Iterator[IO]:
else:
log.debug(f"Loading configuration file: {result_config_file}")
# Ensure file descriptor is at beginning
result_config_file.seek(0)
try:
result_config_file.seek(0)
except OSError:
pass

try:
yield result_config_file
Expand Down
3 changes: 1 addition & 2 deletions mkdocs/config/config_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,8 +814,7 @@ def run_validation(self, value: object) -> theme.Theme:

# Ensure custom_dir is an absolute path
if 'custom_dir' in theme_config and not os.path.isabs(theme_config['custom_dir']):
assert self.config_file_path is not None
config_dir = os.path.dirname(self.config_file_path)
config_dir = os.path.dirname(self.config_file_path or '')
theme_config['custom_dir'] = os.path.join(config_dir, theme_config['custom_dir'])

if 'custom_dir' in theme_config and not os.path.isdir(theme_config['custom_dir']):
Expand Down
9 changes: 7 additions & 2 deletions mkdocs/config/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def get_schema() -> base.PlainConfigSchema:
class MkDocsConfig(base.Config):
"""The configuration of MkDocs itself (the root object of mkdocs.yml)."""

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

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

watch = c.ListOfPaths(default=[])
"""A list of extra paths to watch while running `mkdocs serve`."""

def load_dict(self, patch: dict) -> None:
super().load_dict(patch)
if 'config_file_path' in patch:
raise base.ValidationError("Can't set config_file_path in config")
7 changes: 2 additions & 5 deletions mkdocs/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,17 @@ def get_markdown_toc(markdown_source):
return md.toc_tokens


def load_config(**cfg) -> MkDocsConfig:
def load_config(config_file_path: str | None = None, **cfg) -> MkDocsConfig:
"""Helper to build a simple config for testing."""
path_base = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'integration', 'minimal')
cfg = cfg or {}
if 'site_name' not in cfg:
cfg['site_name'] = 'Example'
if 'config_file_path' not in cfg:
cfg['config_file_path'] = os.path.join(path_base, 'mkdocs.yml')
if 'docs_dir' not in cfg:
# Point to an actual dir to avoid a 'does not exist' error on validation.
cfg['docs_dir'] = os.path.join(path_base, 'docs')
if 'plugins' not in cfg:
cfg['plugins'] = []
conf = MkDocsConfig(config_file_path=cfg['config_file_path'])
conf = MkDocsConfig(config_file_path=config_file_path or os.path.join(path_base, 'mkdocs.yml'))
conf.load_dict(cfg)

errors_warnings = conf.validate()
Expand Down
25 changes: 7 additions & 18 deletions mkdocs/tests/config/config_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,10 @@ def test_theme(self, mytheme, custom):
self.assertEqual({k: conf['theme'][k] for k in iter(conf['theme'])}, result['vars'])

def test_empty_nav(self):
conf = defaults.MkDocsConfig()
conf.load_dict(
{
'site_name': 'Example',
'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
}
conf = defaults.MkDocsConfig(
config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml')
)
conf.load_dict({'site_name': 'Example'})
conf.validate()
self.assertEqual(conf['nav'], None)

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

def test_doc_dir_in_site_dir(self):
j = os.path.join

test_configs = (
{'docs_dir': j('site', 'docs'), 'site_dir': 'site'},
{'docs_dir': os.path.join('site', 'docs'), 'site_dir': 'site'},
{'docs_dir': 'docs', 'site_dir': '.'},
{'docs_dir': '.', 'site_dir': '.'},
{'docs_dir': 'docs', 'site_dir': ''},
{'docs_dir': '', 'site_dir': ''},
{'docs_dir': 'docs', 'site_dir': 'docs'},
)

cfg = {
'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml'),
}

for test_config in test_configs:
with self.subTest(test_config):
patch = {**cfg, **test_config}

# Same as the default schema, but don't verify the docs_dir exists.
conf = config.Config(
schema=(
('docs_dir', c.Dir(default='docs')),
('site_dir', c.SiteDir(default='site')),
('config_file_path', c.Type(str)),
)
),
config_file_path=os.path.join(os.path.abspath('..'), 'mkdocs.yml'),
)
conf.load_dict(patch)
conf.load_dict(test_config)

errors, warnings = conf.validate()

Expand Down

0 comments on commit d5bb15f

Please sign in to comment.