1#!/usr/bin/env python 2 3import os 4import tempfile 5import unittest 6from tempfile import TemporaryDirectory 7 8import mkdocs 9from mkdocs import config 10from mkdocs.config import config_options 11from mkdocs.config import defaults 12from mkdocs.exceptions import ConfigurationError 13from mkdocs.localization import parse_locale 14from mkdocs.tests.base import dedent 15 16 17class ConfigTests(unittest.TestCase): 18 def test_missing_config_file(self): 19 20 def load_missing_config(): 21 config.load_config(config_file='bad_filename.yaml') 22 self.assertRaises(ConfigurationError, load_missing_config) 23 24 def test_missing_site_name(self): 25 c = config.Config(schema=defaults.get_schema()) 26 c.load_dict({}) 27 errors, warnings = c.validate() 28 self.assertEqual(len(errors), 1) 29 self.assertEqual(errors[0][0], 'site_name') 30 self.assertEqual(str(errors[0][1]), 'Required configuration not provided.') 31 32 self.assertEqual(len(warnings), 0) 33 34 def test_empty_config(self): 35 def load_empty_config(): 36 config.load_config(config_file='/dev/null') 37 self.assertRaises(ConfigurationError, load_empty_config) 38 39 def test_nonexistant_config(self): 40 def load_empty_config(): 41 config.load_config(config_file='/path/that/is/not/real') 42 self.assertRaises(ConfigurationError, load_empty_config) 43 44 def test_invalid_config(self): 45 file_contents = dedent(""" 46 - ['index.md', 'Introduction'] 47 - ['index.md', 'Introduction'] 48 - ['index.md', 'Introduction'] 49 """) 50 config_file = tempfile.NamedTemporaryFile('w', delete=False) 51 try: 52 config_file.write(file_contents) 53 config_file.flush() 54 config_file.close() 55 56 self.assertRaises( 57 ConfigurationError, 58 config.load_config, config_file=open(config_file.name, 'rb') 59 ) 60 finally: 61 os.remove(config_file.name) 62 63 def test_config_option(self): 64 """ 65 Users can explicitly set the config file using the '--config' option. 66 Allows users to specify a config other than the default `mkdocs.yml`. 67 """ 68 expected_result = { 69 'site_name': 'Example', 70 'pages': [ 71 {'Introduction': 'index.md'} 72 ], 73 } 74 file_contents = dedent(""" 75 site_name: Example 76 pages: 77 - 'Introduction': 'index.md' 78 """) 79 with TemporaryDirectory() as temp_path: 80 os.mkdir(os.path.join(temp_path, 'docs')) 81 config_path = os.path.join(temp_path, 'mkdocs.yml') 82 config_file = open(config_path, 'w') 83 84 config_file.write(file_contents) 85 config_file.flush() 86 config_file.close() 87 88 result = config.load_config(config_file=config_file.name) 89 self.assertEqual(result['site_name'], expected_result['site_name']) 90 self.assertEqual(result['pages'], expected_result['pages']) 91 92 def test_theme(self): 93 with TemporaryDirectory() as mytheme, TemporaryDirectory() as custom: 94 configs = [ 95 dict(), # default theme 96 {"theme": "readthedocs"}, # builtin theme 97 {"theme": {'name': 'readthedocs'}}, # builtin as complex 98 {"theme": {'name': None, 'custom_dir': mytheme}}, # custom only as complex 99 {"theme": {'name': 'readthedocs', 'custom_dir': custom}}, # builtin and custom as complex 100 { # user defined variables 101 'theme': { 102 'name': 'mkdocs', 103 'locale': 'fr', 104 'static_templates': ['foo.html'], 105 'show_sidebar': False, 106 'some_var': 'bar' 107 } 108 } 109 ] 110 111 mkdocs_dir = os.path.abspath(os.path.dirname(mkdocs.__file__)) 112 mkdocs_templates_dir = os.path.join(mkdocs_dir, 'templates') 113 theme_dir = os.path.abspath(os.path.join(mkdocs_dir, 'themes')) 114 115 results = ( 116 { 117 'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir], 118 'static_templates': ['404.html', 'sitemap.xml'], 119 'vars': { 120 'locale': parse_locale('en'), 121 'include_search_page': False, 122 'search_index_only': False, 123 'analytics': {'gtag': None}, 124 'highlightjs': True, 125 'hljs_style': 'github', 126 'hljs_languages': [], 127 'navigation_depth': 2, 128 'nav_style': 'primary', 129 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} 130 } 131 }, { 132 'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], 133 'static_templates': ['404.html', 'sitemap.xml'], 134 'vars': { 135 'locale': parse_locale('en'), 136 'include_search_page': True, 137 'search_index_only': False, 138 'analytics': {'gtag': None}, 139 'highlightjs': True, 140 'hljs_languages': [], 141 'include_homepage_in_sidebar': True, 142 'prev_next_buttons_location': 'bottom', 143 'navigation_depth': 4, 144 'sticky_navigation': True, 145 'titles_only': False, 146 'collapse_navigation': True 147 } 148 }, { 149 'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], 150 'static_templates': ['404.html', 'sitemap.xml'], 151 'vars': { 152 'locale': parse_locale('en'), 153 'include_search_page': True, 154 'search_index_only': False, 155 'analytics': {'gtag': None}, 156 'highlightjs': True, 157 'hljs_languages': [], 158 'include_homepage_in_sidebar': True, 159 'prev_next_buttons_location': 'bottom', 160 'navigation_depth': 4, 161 'sticky_navigation': True, 162 'titles_only': False, 163 'collapse_navigation': True 164 } 165 }, { 166 'dirs': [mytheme, mkdocs_templates_dir], 167 'static_templates': ['sitemap.xml'], 168 'vars': {'locale': parse_locale('en')} 169 }, { 170 'dirs': [custom, os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], 171 'static_templates': ['404.html', 'sitemap.xml'], 172 'vars': { 173 'locale': parse_locale('en'), 174 'include_search_page': True, 175 'search_index_only': False, 176 'analytics': {'gtag': None}, 177 'highlightjs': True, 178 'hljs_languages': [], 179 'include_homepage_in_sidebar': True, 180 'prev_next_buttons_location': 'bottom', 181 'navigation_depth': 4, 182 'sticky_navigation': True, 183 'titles_only': False, 184 'collapse_navigation': True 185 } 186 }, { 187 'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir], 188 'static_templates': ['404.html', 'sitemap.xml', 'foo.html'], 189 'vars': { 190 'locale': parse_locale('fr'), 191 'show_sidebar': False, 192 'some_var': 'bar', 193 'include_search_page': False, 194 'search_index_only': False, 195 'analytics': {'gtag': None}, 196 'highlightjs': True, 197 'hljs_style': 'github', 198 'hljs_languages': [], 199 'navigation_depth': 2, 200 'nav_style': 'primary', 201 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} 202 } 203 } 204 ) 205 206 for config_contents, result in zip(configs, results): 207 208 c = config.Config(schema=(('theme', config_options.Theme(default='mkdocs')),)) 209 c.load_dict(config_contents) 210 errors, warnings = c.validate() 211 self.assertEqual(len(errors), 0) 212 self.assertEqual(c['theme'].dirs, result['dirs']) 213 self.assertEqual(c['theme'].static_templates, set(result['static_templates'])) 214 self.assertEqual({k: c['theme'][k] for k in iter(c['theme'])}, result['vars']) 215 216 def test_empty_nav(self): 217 conf = config.Config(schema=defaults.get_schema()) 218 conf.load_dict({ 219 'site_name': 'Example', 220 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') 221 }) 222 conf.validate() 223 self.assertEqual(conf['nav'], None) 224 225 def test_copy_pages_to_nav(self): 226 # TODO: remove this when pages config setting is fully deprecated. 227 conf = config.Config(schema=defaults.get_schema()) 228 conf.load_dict({ 229 'site_name': 'Example', 230 'pages': ['index.md', 'about.md'], 231 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') 232 }) 233 conf.validate() 234 self.assertEqual(conf['nav'], ['index.md', 'about.md']) 235 236 def test_dont_overwrite_nav_with_pages(self): 237 # TODO: remove this when pages config setting is fully deprecated. 238 conf = config.Config(schema=defaults.get_schema()) 239 conf.load_dict({ 240 'site_name': 'Example', 241 'pages': ['index.md', 'about.md'], 242 'nav': ['foo.md', 'bar.md'], 243 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') 244 }) 245 conf.validate() 246 self.assertEqual(conf['nav'], ['foo.md', 'bar.md']) 247 248 def test_doc_dir_in_site_dir(self): 249 250 j = os.path.join 251 252 test_configs = ( 253 {'docs_dir': j('site', 'docs'), 'site_dir': 'site'}, 254 {'docs_dir': 'docs', 'site_dir': '.'}, 255 {'docs_dir': '.', 'site_dir': '.'}, 256 {'docs_dir': 'docs', 'site_dir': ''}, 257 {'docs_dir': '', 'site_dir': ''}, 258 {'docs_dir': 'docs', 'site_dir': 'docs'}, 259 ) 260 261 conf = { 262 'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml') 263 } 264 265 for test_config in test_configs: 266 267 patch = conf.copy() 268 patch.update(test_config) 269 270 # Same as the default schema, but don't verify the docs_dir exists. 271 c = config.Config(schema=( 272 ('docs_dir', config_options.Dir(default='docs')), 273 ('site_dir', config_options.SiteDir(default='site')), 274 ('config_file_path', config_options.Type(str)) 275 )) 276 c.load_dict(patch) 277 278 errors, warnings = c.validate() 279 280 self.assertEqual(len(errors), 1) 281 self.assertEqual(warnings, []) 282 283 def testConfigInstancesUnique(self): 284 conf = mkdocs.config.Config(mkdocs.config.defaults.get_schema()) 285 conf.load_dict({'site_name': 'foo'}) 286 conf.validate() 287 self.assertIsNone(conf['mdx_configs'].get('toc')) 288 289 conf = mkdocs.config.Config(mkdocs.config.defaults.get_schema()) 290 conf.load_dict({'site_name': 'foo', 'markdown_extensions': [{"toc": {"permalink": "aaa"}}]}) 291 conf.validate() 292 self.assertEqual(conf['mdx_configs'].get('toc'), {'permalink': 'aaa'}) 293 294 conf = mkdocs.config.Config(mkdocs.config.defaults.get_schema()) 295 conf.load_dict({'site_name': 'foo'}) 296 conf.validate() 297 self.assertIsNone(conf['mdx_configs'].get('toc')) 298