Repository: mkdocs/mkdocs

Issue:
Ignore the site_dir contents if it is in the docs_dir
We have been telling people to not do this, but it comes up again and again. So I wonder if it is a usecase we should support? I don't really like it. Perhaps we just need to error more strongly and tell users rather than causing obscure problems.

Discussion:

Patch:
diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md
index 426ef3c8..b8d9926b 100644
--- a/docs/about/release-notes.md
+++ b/docs/about/release-notes.md
@@ -64,9 +64,9 @@ created and third-party templates:
 #### Increased Template Customization. (#607)
 
 The built-in themes have been updated by having each of their many parts wrapped
-in template blocks which allow each individual block to be easily overriden
+in template blocks which allow each individual block to be easily overridden
 using the `theme_dir` config setting. Without any new settings, you can use a
-differant analytics service, replace the default search function, or alter the
+different analytics service, replace the default search function, or alter the
 behavior of the navigation, among other things. See the relevant
 [documentation][blocks] for more details.
 
@@ -112,16 +112,26 @@ It is important to note that this method for building the pages is for developme
 of content only, since the navigation and other links do not get updated on other
 pages.
 
+#### Stricter Directory Validation
+
+Previously, a warning was issued if the `site_dir` was a child directory of the
+`docs_dir`. This now raises an error. Additionally, an error is now raised if
+the `docs_dir` is set to the directory which contains your config file rather
+than a child directory. You will need to rearrange you directory structure to
+better conform with the documented [layout].
+
+[layout]: ../user-guide/writing-your-docs/#file-layout
+
 ### Other Changes and Additions to Version 0.16.0
 
 * Bugfix: Support `gh-deploy` command on Windows with Python 3 (#722)
-* Bugfix: Include .woff2 font files in Pyhton package build (#894)
+* Bugfix: Include .woff2 font files in Python package build (#894)
 * Various updates and improvements to Documentation Home Page/Tutorial (#870)
 * Bugfix: Support livereload for config file changes (#735)
 * Bugfix: Non-media template files are no longer copied with media files (#807)
-* Add a flag (-e/--theme-dir) to specifiy theme directory with the commands
+* Add a flag (-e/--theme-dir) to specify theme directory with the commands
   `mkdocs build` and `mkdocs serve` (#832)
-* Fixed issues with Unicode filenames under Windows and Python 2. (#833)
+* Fixed issues with Unicode file names under Windows and Python 2. (#833)
 * Improved the styling of in-line code in the MkDocs theme. (#718)
 * Bugfix: convert variables to JSON when being passed to JavaScript (#850)
 * Updated the ReadTheDocs theme to match the upstream font sizes and colours
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
index b3cb9389..70f610c4 100644
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -229,6 +229,15 @@ class Dir(Type):
 
         return os.path.abspath(value)
 
+    def post_validation(self, config, key_name):
+
+        # 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(
+                ("The '{0}' should not be the parent directory of the config "
+                 "file. Use a child directory instead so that the config file "
+                 "is a sibling of the config file.").format(key_name))
+
 
 class SiteDir(Dir):
     """
@@ -239,6 +248,8 @@ class SiteDir(Dir):
 
     def post_validation(self, config, key_name):
 
+        super(SiteDir, self).post_validation(config, key_name)
+
         # Validate that the docs_dir and site_dir don't contain the
         # other as this will lead to copying back and forth on each
         # and eventually make a deep nested mess.
@@ -250,7 +261,7 @@ class SiteDir(Dir):
                  "(site_dir: '{0}', docs_dir: '{1}')"
                  ).format(config['site_dir'], config['docs_dir']))
         elif (config['site_dir'] + os.sep).startswith(config['docs_dir'] + os.sep):
-            self.warnings.append(
+            raise ValidationError(
                 ("The 'site_dir' should not be within the 'docs_dir' as this "
                  "leads to the build directory being copied into itself and "
                  "duplicate nested files in the 'site_dir'."


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
index f6215df2..aead1c85 100644
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -26,6 +26,8 @@ def load_config(cfg=None):
     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(os.path.abspath('.'), 'mkdocs.yml')
     conf = config.Config(schema=config.DEFAULT_SCHEMA)
     conf.load_dict(cfg)
 
diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py
index c492d797..de9396bd 100644
--- a/mkdocs/tests/config/config_options_tests.py
+++ b/mkdocs/tests/config/config_options_tests.py
@@ -186,6 +186,20 @@ class DirTest(unittest.TestCase):
         self.assertRaises(config_options.ValidationError,
                           option.validate, [])
 
+    def test_doc_dir_is_config_dir(self):
+
+        test_config = {
+            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
+            'docs_dir': '.'
+        }
+
+        docs_dir = config_options.Dir()
+
+        test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
+
+        self.assertRaises(config_options.ValidationError,
+                          docs_dir.post_validation, test_config, 'docs_dir')
+
 
 class SiteDirTest(unittest.TestCase):
 
@@ -207,12 +221,13 @@ class SiteDirTest(unittest.TestCase):
         )
 
         for test_config in test_configs:
+            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')
 
             test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
             test_config['site_dir'] = option.validate(test_config['site_dir'])
 
             self.assertRaises(config_options.ValidationError,
-                              option.post_validation, test_config, 'key')
+                              option.post_validation, test_config, 'site_dir')
 
     def test_site_dir_in_docs_dir(self):
 
@@ -225,6 +240,7 @@ class SiteDirTest(unittest.TestCase):
         )
 
         for test_config in test_configs:
+            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')
 
             docs_dir = config_options.Dir()
             option = config_options.SiteDir()
@@ -232,11 +248,8 @@ class SiteDirTest(unittest.TestCase):
             test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
             test_config['site_dir'] = option.validate(test_config['site_dir'])
 
-            option.post_validation(test_config, 'key')
-            self.assertEqual(len(option.warnings), 1)
-            self.assertEqual(
-                option.warnings[0][:50],
-                "The 'site_dir' should not be within the 'docs_dir'")
+            self.assertRaises(config_options.ValidationError,
+                              option.post_validation, test_config, 'site_dir')
 
 
 class ThemeTest(unittest.TestCase):
diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py
index 04e51de3..497cfa6f 100644
--- a/mkdocs/tests/config/config_tests.py
+++ b/mkdocs/tests/config/config_tests.py
@@ -135,7 +135,8 @@ class ConfigTests(unittest.TestCase):
             conf = config.Config(schema=config.DEFAULT_SCHEMA)
             conf.load_dict({
                 'site_name': 'Example',
-                'docs_dir': tmp_dir
+                'docs_dir': tmp_dir,
+                'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
             })
             conf.validate()
             self.assertEqual(['index.md', 'about.md'], conf['pages'])
@@ -159,7 +160,8 @@ class ConfigTests(unittest.TestCase):
             conf = config.Config(schema=config.DEFAULT_SCHEMA)
             conf.load_dict({
                 'site_name': 'Example',
-                'docs_dir': tmp_dir
+                'docs_dir': tmp_dir,
+                'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
             })
             conf.validate()
             self.assertEqual([
@@ -197,6 +199,7 @@ class ConfigTests(unittest.TestCase):
 
         conf = {
             'site_name': 'Example',
+            'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml')
         }
 
         for test_config in test_configs: