diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index 1fb55c94..e50a76b0 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -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/ diff --git a/mkdocs/__main__.py b/mkdocs/__main__.py index be33aa71..47884615 100644 --- a/mkdocs/__main__.py +++ b/mkdocs/__main__.py @@ -106,7 +106,9 @@ class State: 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." diff --git a/mkdocs/commands/gh_deploy.py b/mkdocs/commands/gh_deploy.py index f6e5fa05..de5fb99b 100644 --- a/mkdocs/commands/gh_deploy.py +++ b/mkdocs/commands/gh_deploy.py @@ -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( diff --git a/mkdocs/config/base.py b/mkdocs/config/base.py index f4e8279e..3d14d621 100644 --- a/mkdocs/config/base.py +++ b/mkdocs/config/base.py @@ -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 diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py index fb8a912f..9a5ed70f 100644 --- a/mkdocs/config/config_options.py +++ b/mkdocs/config/config_options.py @@ -814,8 +814,7 @@ class Theme(BaseConfigOption[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']): diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py index ade08ff8..3a2d69d0 100644 --- a/mkdocs/config/defaults.py +++ b/mkdocs/config/defaults.py @@ -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.""" @@ -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") diff --git a/mkdocs/tests/base.py b/mkdocs/tests/base.py index 28e3fd3c..c480eaa2 100644 --- a/mkdocs/tests/base.py +++ b/mkdocs/tests/base.py @@ -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() diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py index 10d092e9..c43690c4 100644 --- a/mkdocs/tests/config/config_tests.py +++ b/mkdocs/tests/config/config_tests.py @@ -218,13 +218,10 @@ class ConfigTests(unittest.TestCase): 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) @@ -242,10 +239,8 @@ class ConfigTests(unittest.TestCase): 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': ''}, @@ -253,23 +248,17 @@ class ConfigTests(unittest.TestCase): {'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()