Skip to content

Commit

Permalink
Support docs from the root directory.
Browse files Browse the repository at this point in the history
  • Loading branch information
athackst committed Dec 11, 2023
1 parent 1cbf327 commit 0389e27
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 81 deletions.
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
11 changes: 10 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 @@ -329,8 +330,16 @@ 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)
except ValueError:
site_rel_path = None
finally:
site_exclude = pathspec.gitignore.GitIgnoreSpec.from_lines([site_rel_path])
default_exclude = _default_exclude + site_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
17 changes: 17 additions & 0 deletions mkdocs/tests/integration/root_docs/README.md
@@ -0,0 +1,17 @@
# Welcome to MkDocs

For full documentation visit [mkdocs.org](https://www.mkdocs.org).

## Commands

* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs help` - Print this help message.

## Project layout

mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.
8 changes: 8 additions & 0 deletions mkdocs/tests/integration/root_docs/mkdocs.yml
@@ -0,0 +1,8 @@
site_name: MyTest
docs_dir: .

nav:
- 'README.md'
- 'testing.md'

site_author: "Allison Thackston"
1 change: 1 addition & 0 deletions mkdocs/tests/integration/root_docs/testing.md
@@ -0,0 +1 @@
Page content.
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 @@ -583,6 +583,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

0 comments on commit 0389e27

Please sign in to comment.