Repository: mkdocs/mkdocs
Pull request #2894 (2022-07-18 20:26:43)

When a Markdown extension fails to load, print its name and traceback


Patch:
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
index 0a4843ac..cc4d5d98 100644
--- a/docs/user-guide/configuration.md
+++ b/docs/user-guide/configuration.md
@@ -10,8 +10,7 @@ Project settings are configured by default using a YAML configuration file in
 the project directory named `mkdocs.yml`. You can specify another path for it
 by using the `-f`/`--config-file` option (see `mkdocs build --help`).
 
-As a minimum, this configuration file must contain the `site_name` and
-`site_url` settings. All other settings are optional.
+As a minimum, this configuration file must contain the `site_name`. All other settings are optional.
 
 ## Project information
 
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
index 7c0db7c5..2074c7c0 100644
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -1,4 +1,6 @@
 import os
+import sys
+import traceback
 from collections import namedtuple
 from collections.abc import Sequence
 from urllib.parse import urlsplit, urlunsplit
@@ -633,10 +635,19 @@ class MarkdownExtensions(OptionallyRequired):
         extensions = utils.reduce_list(self.builtins + extensions)
 
         # Confirm that Markdown considers extensions to be valid
-        try:
-            markdown.Markdown(extensions=extensions, extension_configs=self.configdata)
-        except Exception as e:
-            raise ValidationError(e.args[0])
+        md = markdown.Markdown()
+        for ext in extensions:
+            try:
+                md.registerExtensions((ext,), self.configdata)
+            except Exception as e:
+                stack = []
+                for frame in reversed(traceback.extract_tb(sys.exc_info()[2])):
+                    if not frame.line:  # Ignore frames before <frozen importlib._bootstrap>
+                        break
+                    stack.insert(0, frame)
+                tb = ''.join(traceback.format_list(stack))
+
+                raise ValidationError(f"Failed to load extension '{ext}'.\n{tb}{type(e).__name__}: {e}")
 
         return extensions
 
diff --git a/mkdocs/structure/files.py b/mkdocs/structure/files.py
index 80aa8c3d..ada0c95a 100644
--- a/mkdocs/structure/files.py
+++ b/mkdocs/structure/files.py
@@ -243,8 +243,8 @@ def get_files(config):
             if _filter_paths(basename=filename, path=path, is_dir=False, exclude=exclude):
                 continue
             # Skip README.md if an index file also exists in dir
-            if filename.lower() == 'readme.md' and 'index.md' in filenames:
-                log.warning(f"Both index.md and readme.md found. Skipping readme.md from {source_dir}")
+            if filename == 'README.md' and 'index.md' in filenames:
+                log.warning(f"Both index.md and README.md found. Skipping README.md from {source_dir}")
                 continue
             files.append(File(path, config['docs_dir'], config['site_dir'], config['use_directory_urls']))
 
diff --git a/mkdocs/themes/mkdocs/locales/it/LC_MESSAGES/messages.po b/mkdocs/themes/mkdocs/locales/it/LC_MESSAGES/messages.po
new file mode 100644
index 00000000..454b2582
--- /dev/null
+++ b/mkdocs/themes/mkdocs/locales/it/LC_MESSAGES/messages.po
@@ -0,0 +1,97 @@
+# Italian translations for MkDocs.
+# Copyright (C) 2022 MkDocs
+# This file is distributed under the same license as the MkDocs project.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: MkDocs 1.2\n"
+"Report-Msgid-Bugs-To: https://github.com/mkdocs/mkdocs/issues\n"
+"POT-Creation-Date: 2021-04-08 22:44+0200\n"
+"PO-Revision-Date: 2022-06-05 12:23+0200\n"
+"Last-Translator: Francesco Maida <francesco.maida@gmail.com>\n"
+"Language-Team: fr <LL@li.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Generated-By: Babel 2.9.1\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: mkdocs/themes/mkdocs/404.html:8
+msgid "Page not found"
+msgstr "Pagina non trovata"
+
+#: mkdocs/themes/mkdocs/base.html:107
+#: mkdocs/themes/mkdocs/keyboard-modal.html:31
+#: mkdocs/themes/mkdocs/search-modal.html:5
+msgid "Search"
+msgstr "Cerca"
+
+#: mkdocs/themes/mkdocs/base.html:117
+msgid "Previous"
+msgstr "Precedente"
+
+#: mkdocs/themes/mkdocs/base.html:122
+msgid "Next"
+msgstr "Successivo"
+
+#: mkdocs/themes/mkdocs/base.html:133 mkdocs/themes/mkdocs/base.html:135
+#: mkdocs/themes/mkdocs/base.html:137 mkdocs/themes/mkdocs/base.html:139
+#, python-format
+msgid "Edit on %(repo_name)s"
+msgstr "Modifica su %(repo_name)s"
+
+#: mkdocs/themes/mkdocs/base.html:179
+#, python-format
+msgid "Documentation built with %(mkdocs_link)s."
+msgstr "Documentazione creata con %(mkdocs_link)s."
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:5
+msgid "Keyboard Shortcuts"
+msgstr "Tastiera Rapida"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:6
+#: mkdocs/themes/mkdocs/search-modal.html:6
+msgid "Close"
+msgstr "Chiudi"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:12
+msgid "Keys"
+msgstr "Chiavi"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:13
+msgid "Action"
+msgstr "Azione"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:19
+msgid "Open this help"
+msgstr "Apri questa guida"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:23
+msgid "Next page"
+msgstr "Pagina successiva"
+
+#: mkdocs/themes/mkdocs/keyboard-modal.html:27
+msgid "Previous page"
+msgstr "Pagina precedente"
+
+#: mkdocs/themes/mkdocs/search-modal.html:9
+msgid "From here you can search these documents. Enter your search terms below."
+msgstr "Da qui puoi cercare questi documenti. Inserisci i termini di ricerca qui sotto."
+
+#: mkdocs/themes/mkdocs/search-modal.html:12
+msgid "Search..."
+msgstr "Cerca..."
+
+#: mkdocs/themes/mkdocs/search-modal.html:12
+msgid "Type search term here"
+msgstr "Digita il termine di ricerca qui"
+
+#: mkdocs/themes/mkdocs/search-modal.html:15
+msgid "No results found"
+msgstr "Nessun risultato trovato"
+
+#: mkdocs/themes/mkdocs/toc.html:3
+msgid "Table of Contents"
+msgstr "Tabella dei Contenuti"
diff --git a/mkdocs/themes/readthedocs/locales/it/LC_MESSAGES/messages.po b/mkdocs/themes/readthedocs/locales/it/LC_MESSAGES/messages.po
new file mode 100644
index 00000000..25c3ed0f
--- /dev/null
+++ b/mkdocs/themes/readthedocs/locales/it/LC_MESSAGES/messages.po
@@ -0,0 +1,98 @@
+# Italian translations for MkDocs.
+# Copyright (C) 2022 MkDocs
+# This file is distributed under the same license as the MkDocs project.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: MkDocs 1.2\n"
+"Report-Msgid-Bugs-To: https://github.com/mkdocs/mkdocs/issues\n"
+"POT-Creation-Date: 2021-09-27 10:43-0400\n"
+"PO-Revision-Date: 2022-06-05 13:13+0200\n"
+"Last-Translator: Francesco Maida <francesco.maida@gmail.com>\n"
+"Language-Team: fr <LL@li.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Generated-By: Babel 2.9.1\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: mkdocs/themes/readthedocs/404.html:7
+msgid "Page not found"
+msgstr "Pagina non trovata"
+
+#: mkdocs/themes/readthedocs/base.html:97
+msgid "Logo"
+msgstr "Logo"
+
+#: mkdocs/themes/readthedocs/base.html:111
+msgid "Navigation menu"
+msgstr "Menu"
+
+#: mkdocs/themes/readthedocs/base.html:140
+msgid "Mobile navigation menu"
+msgstr "Menu di navigazione mobile"
+
+#: mkdocs/themes/readthedocs/breadcrumbs.html:3
+msgid "Docs"
+msgstr "Documenti"
+
+#: mkdocs/themes/readthedocs/breadcrumbs.html:24
+#, python-format
+msgid "Edit on %(repo_name)s"
+msgstr "Modifica su %(repo_name)s"
+
+#: mkdocs/themes/readthedocs/breadcrumbs.html:31
+msgid "Breadcrumb Navigation"
+msgstr "Navigazione breadcrumb"
+
+#: mkdocs/themes/readthedocs/breadcrumbs.html:33
+#: mkdocs/themes/readthedocs/footer.html:7
+#: mkdocs/themes/readthedocs/versions.html:17
+msgid "Previous"
+msgstr "Precedente"
+
+#: mkdocs/themes/readthedocs/breadcrumbs.html:36
+#: mkdocs/themes/readthedocs/footer.html:10
+#: mkdocs/themes/readthedocs/versions.html:20
+msgid "Next"
+msgstr "Successivo"
+
+#: mkdocs/themes/readthedocs/footer.html:5
+msgid "Footer Navigation"
+msgstr "Navigazione a pié di pagina"
+
+#: mkdocs/themes/readthedocs/footer.html:25
+#, python-format
+msgid "Built with %(mkdocs_link)s using a %(sphinx_link)s provided by %(rtd_link)s."
+msgstr "Creato con %(mkdocs_link)s utilizzando un %(sphinx_link)s fornito da %(rtd_link)s."
+
+#: mkdocs/themes/readthedocs/search.html:5
+msgid "Search Results"
+msgstr "Risultati della ricerca"
+
+#: mkdocs/themes/readthedocs/search.html:9
+msgid "Search the Docs"
+msgstr "Cerca nei documenti"
+
+#: mkdocs/themes/readthedocs/search.html:9
+#: mkdocs/themes/readthedocs/searchbox.html:3
+msgid "Type search term here"
+msgstr "Digita il termine di ricerca qui"
+
+#: mkdocs/themes/readthedocs/search.html:12
+msgid "No results found"
+msgstr "Nessun risultato trovato"
+
+#: mkdocs/themes/readthedocs/search.html:13
+msgid "Searching..."
+msgstr "Ricerca in corso…"
+
+#: mkdocs/themes/readthedocs/searchbox.html:3
+msgid "Search docs"
+msgstr "Cerca documenti"
+
+#: mkdocs/themes/readthedocs/versions.html:1
+msgid "Versions"
+msgstr "Versioni"


Test patch:
diff --git a/mkdocs/tests/structure/file_tests.py b/mkdocs/tests/structure/file_tests.py
index 6ecc2587..d3d3cfad 100644
--- a/mkdocs/tests/structure/file_tests.py
+++ b/mkdocs/tests/structure/file_tests.py
@@ -592,6 +592,7 @@ class TestFiles(PathAssertionMixin, unittest.TestCase):
 
     @tempdir(files=[
         'index.md',
+        'readme.md',
         'bar.css',
         'bar.html',
         'bar.jpg',
@@ -603,7 +604,7 @@ class TestFiles(PathAssertionMixin, unittest.TestCase):
     def test_get_files(self, tdir):
         config = load_config(docs_dir=tdir, extra_css=['bar.css'], extra_javascript=['bar.js'])
         files = get_files(config)
-        expected = ['index.md', 'bar.css', 'bar.html', 'bar.jpg', 'bar.js', 'bar.md']
+        expected = ['index.md', 'bar.css', 'bar.html', 'bar.jpg', 'bar.js', 'bar.md', 'readme.md']
         self.assertIsInstance(files, Files)
         self.assertEqual(len(files), len(expected))
         self.assertEqual([f.src_path for f in files], expected)