Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support docs from the root directory. #3450

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions mkdocs/commands/serve.py
Expand Up @@ -3,10 +3,13 @@
import logging
import shutil
import tempfile
from os.path import isdir, isfile, join
from os.path import isdir, isfile, join, relpath
from typing import TYPE_CHECKING
from urllib.parse import urlsplit

import pathspec
import pathspec.gitignore

from mkdocs.commands.build import build
from mkdocs.config import load_config
from mkdocs.livereload import LiveReloadServer, _serve_url
Expand Down Expand Up @@ -42,9 +45,17 @@ def serve(
def get_config():
config = load_config(
config_file=config_file,
site_dir=site_dir,
**kwargs,
)
# Exclude the original site directory in case it is within the docs directory
exclude_site_dir = pathspec.gitignore.GitIgnoreSpec.from_lines(
[relpath(config.site_dir, config.docs_dir)]
)
config.exclude_docs = (
config.exclude_docs + exclude_site_dir if config.exclude_docs else exclude_site_dir
)
# Set the site directory to the temporary build directory
config.site_dir = site_dir
config.watch.extend(watch)
return config

Expand Down
15 changes: 0 additions & 15 deletions mkdocs/config/config_options.py
Expand Up @@ -737,14 +737,6 @@ def post_validation(self, config: Config, key_name: str):
if not config.config_file_path:
return

# Validate that the dir is not the parent dir of the config file.
if os.path.dirname(config.config_file_path) == config[key_name]:
raise ValidationError(
f"The '{key_name}' should not be the parent directory of the"
f" config file. Use a child directory instead so that the"
f" '{key_name}' is a sibling of the config file."
)


class File(FilesystemObject):
"""
Expand Down Expand Up @@ -803,13 +795,6 @@ def post_validation(self, config: Config, key_name: str):
f"it will be deleted if --clean is passed to mkdocs build. "
f"(site_dir: '{site_dir}', docs_dir: '{docs_dir}')"
)
elif (site_dir + os.sep).startswith(docs_dir.rstrip(os.sep) + os.sep):
raise ValidationError(
f"The 'site_dir' should not be within the 'docs_dir' as this "
f"leads to the build directory being copied into itself and "
f"duplicate nested files in the 'site_dir'. "
f"(site_dir: '{site_dir}', docs_dir: '{docs_dir}')"
)


class Theme(BaseConfigOption[theme.Theme]):
Expand Down
10 changes: 9 additions & 1 deletion mkdocs/structure/files.py
Expand Up @@ -7,6 +7,7 @@
import posixpath
import shutil
import warnings
from os.path import relpath
from pathlib import PurePath
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Sequence
from urllib.parse import quote as urlquote
Expand Down Expand Up @@ -321,8 +322,15 @@ def is_css(self) -> bool:

def set_exclusions(files: Iterable[File], config: MkDocsConfig) -> None:
"""Re-calculate which files are excluded, based on the patterns in the config."""
# exclude the site directory
try:
site_rel_path = relpath(config.site_dir, config.docs_dir)
site_exclude = pathspec.gitignore.GitIgnoreSpec.from_lines([site_rel_path])
except ValueError:
site_exclude = None
default_exclude = _default_exclude + site_exclude if site_exclude else _default_exclude
exclude: pathspec.gitignore.GitIgnoreSpec | None = config.get('exclude_docs')
exclude = _default_exclude + exclude if exclude else _default_exclude
exclude = default_exclude + exclude if exclude else default_exclude
drafts: pathspec.gitignore.GitIgnoreSpec | None = config.get('draft_docs')
nav_exclude: pathspec.gitignore.GitIgnoreSpec | None = config.get('not_in_nav')

Expand Down
31 changes: 0 additions & 31 deletions mkdocs/tests/config/config_options_legacy_tests.py
Expand Up @@ -768,20 +768,6 @@ class Schema:
)
self.assertEqual(conf['dir'], os.path.join(base_path, 'foo'))

def test_site_dir_is_config_dir_fails(self):
class Schema:
dir = c.DocsDir()

with self.expect_error(
dir="The 'dir' should not be the parent directory of the config file. "
"Use a child directory instead so that the 'dir' is a sibling of the config file."
):
self.get_config(
Schema,
{'dir': '.'},
config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
)


class ListOfPathsTest(TestCase):
def test_valid_path(self):
Expand Down Expand Up @@ -882,23 +868,6 @@ def test_doc_dir_in_site_dir(self):
):
self.get_config(self.Schema, test_config)

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

test_configs = (
{'docs_dir': 'docs', 'site_dir': j('docs', 'site')},
{'docs_dir': '.', 'site_dir': 'site'},
{'docs_dir': '', 'site_dir': 'site'},
{'docs_dir': '/', 'site_dir': 'site'},
)

for test_config in test_configs:
with self.subTest(test_config):
with self.expect_error(
site_dir=re.compile(r"The 'site_dir' should not be within the 'docs_dir'.*")
):
self.get_config(self.Schema, test_config)

def test_common_prefix(self):
"""Legitimate settings with common prefixes should not fail validation."""
test_configs = (
Expand Down
31 changes: 0 additions & 31 deletions mkdocs/tests/config/config_options_tests.py
Expand Up @@ -972,20 +972,6 @@ class Schema(Config):
)
self.assertEqual(conf.dir, os.path.join(base_path, 'foo'))

def test_site_dir_is_config_dir_fails(self) -> None:
class Schema(Config):
dir = c.DocsDir()

with self.expect_error(
dir="The 'dir' should not be the parent directory of the config file. "
"Use a child directory instead so that the 'dir' is a sibling of the config file."
):
self.get_config(
Schema,
{'dir': '.'},
config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
)


class ListOfPathsTest(TestCase):
def test_valid_path(self) -> None:
Expand Down Expand Up @@ -1095,23 +1081,6 @@ def test_doc_dir_in_site_dir(self) -> None:
):
self.get_config(self.Schema, test_config)

def test_site_dir_in_docs_dir(self) -> None:
j = os.path.join

test_configs = (
{'docs_dir': 'docs', 'site_dir': j('docs', 'site')},
{'docs_dir': '.', 'site_dir': 'site'},
{'docs_dir': '', 'site_dir': 'site'},
{'docs_dir': '/', 'site_dir': 'site'},
)

for test_config in test_configs:
with self.subTest(test_config):
with self.expect_error(
site_dir=re.compile(r"The 'site_dir' should not be within the 'docs_dir'.*")
):
self.get_config(self.Schema, test_config)

def test_common_prefix(self) -> None:
"""Legitimate settings with common prefixes should not fail validation."""
test_configs = (
Expand Down
19 changes: 18 additions & 1 deletion mkdocs/tests/structure/file_tests.py
Expand Up @@ -3,7 +3,7 @@
import unittest
from unittest import mock

from mkdocs.structure.files import File, Files, _sort_files, get_files
from mkdocs.structure.files import File, Files, InclusionLevel, _sort_files, get_files
from mkdocs.tests.base import PathAssertionMixin, load_config, tempdir


Expand Down Expand Up @@ -562,6 +562,23 @@ def test_get_files_exclude_readme_with_index(self, tdir):
self.assertIsInstance(files, Files)
self.assertEqual([f.src_uri for f in files], ['index.md', 'foo.md'])

@tempdir(
files=[
'index.md',
'site/foo.md',
]
)
def test_get_files_exclude_site_dir(self, tdir):
config = load_config(docs_dir=tdir, site_dir=os.path.join(tdir, "site"))
files = get_files(config)
self.assertIsInstance(files, Files)
self.assertEqual([f.src_uri for f in files], ['index.md', 'site/foo.md'])
for f in files:
if f.src_uri == 'site/foo.md':
self.assertEqual(f.inclusion, InclusionLevel.EXCLUDED)
else:
self.assertEqual(f.inclusion, InclusionLevel.INCLUDED)

@tempdir()
@tempdir(files={'test.txt': 'source content'})
def test_copy_file(self, src_dir, dest_dir):
Expand Down