Repository: mondeja/mkdocs-include-markdown-plugin (python)

Issue:
FR: strict mode
I just recently started using include-markdown more and found that it's great for composable snippets in documentation - thank you for the tool!

One thing I have encountered, though, is that mkdocs build goes through even though the referenced file path is wrong (e.g. the relative path doesn't lead anywhere). This can now cause silent breakage upon moving a part of the documentation elsewhere, instead of failing to build MkDocs

Would it be possible to add a configuration option to strictly fail the build if this happens?

Thank you!

Pull request:
Convert logger errors to build errors
Closes #162 

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 25a9968..11386cc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -70,7 +70,7 @@ jobs:
         run: hatch run +py=${{ matrix.py }} tests:all
       - name: Convert coverage to XML
         run: |
-          pip install coverage
+          pip install coverage covdefaults
           coverage xml
       - name: Upload coverage
         uses: codecov/codecov-action@v3
diff --git a/.github/workflows/update-gh-actions.yml b/.github/workflows/update-gh-actions.yml
new file mode 100644
index 0000000..1c782d8
--- /dev/null
+++ b/.github/workflows/update-gh-actions.yml
@@ -0,0 +1,19 @@
+name: Update GitHub Actions versions
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: 0 3 1 1/6 *
+
+jobs:
+  update:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3.3.0
+        with:
+          token: ${{ secrets.UPDATE_GH_ACTIONS_TOKEN }}
+
+      - name: Run GitHub Actions Version Updater
+        uses: saadmk11/github-actions-version-updater@v0.7.2
+        with:
+          token: ${{ secrets.UPDATE_GH_ACTIONS_TOKEN }}
diff --git a/.gitignore b/.gitignore
index c83b7d2..228c4b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,5 +15,6 @@ venv*/
 .vscode
 .mypy_cache/
 *.so
+.flakeheaven_cache/
 
 site/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b530b88..cc9eff5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,20 @@
 repos:
+  - repo: https://github.com/pre-commit/mirrors-prettier
+    rev: v3.0.0
+    hooks:
+      - id: prettier
+        types_or:
+          - toml
+          - css
+          - json
+          - json5
+          - yaml
+          - markdown
+        additional_dependencies:
+          - prettier
+          - prettier-plugin-toml
+        minimum_pre_commit_version: 2.9.0
+        exclude: ^locale
   - repo: https://github.com/mondeja/project-config
     rev: v0.8.0
     hooks:
@@ -10,7 +26,7 @@ repos:
       - id: check-useless-excludes
         name: check-useless-excludes
   - repo: https://github.com/asottile/pyupgrade
-    rev: v3.7.0
+    rev: v3.10.1
     hooks:
       - id: pyupgrade
         args:
@@ -26,24 +42,8 @@ repos:
         name: double-quote-string-fixer
       - id: debug-statements
         name: debug-statements
-  - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: v3.0.0-alpha.9-for-vscode
-    hooks:
-      - id: prettier
-        types_or:
-          - toml
-          - css
-          - json
-          - json5
-          - yaml
-          - markdown
-        additional_dependencies:
-          - prettier
-          - prettier-plugin-toml
-        minimum_pre_commit_version: 2.9.0
-        exclude: ^locale
   - repo: https://github.com/asottile/add-trailing-comma
-    rev: v2.5.1
+    rev: v3.0.1
     hooks:
       - id: add-trailing-comma
         name: add-trailing-comma
diff --git a/README.md b/README.md
index 5675e94..3ec959d 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,18 @@
 
 # mkdocs-include-markdown-plugin
 
-Mkdocs Markdown includer plugin.
-
 <!-- mdpo-disable -->
 
 [![PyPI][pypi-version-badge-link]][pypi-link]
+[![License][license-image]][license-link]
+
 [![Tests][tests-image]][tests-link]
 [![Coverage status][coverage-image]][coverage-link]
 
 <!-- mdpo-enable -->
 
+Mkdocs Markdown includer plugin.
+
 <!-- mdpo-disable -->
 <!-- mdpo-enable-next-line -->
 
@@ -221,10 +223,12 @@ Includes the content of a file or a group of files.
 
 [pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin
 [pypi-version-badge-link]: https://img.shields.io/pypi/v/mkdocs-include-markdown-plugin?logo=pypi&logoColor=white
-[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests
+[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master
 [tests-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/actions?query=workflow%3ACI
 [coverage-image]: https://img.shields.io/codecov/c/github/mondeja/mkdocs-include-markdown-plugin?logo=codecov&logoColor=white
 [coverage-link]: https://app.codecov.io/gh/mondeja/mkdocs-include-markdown-plugin
+[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-plugin?color=light-green&logo=apache&logoColor=white
+[license-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/blob/master/LICENSE
 [cibuildwheel-470]: https://github.com/joerick/cibuildwheel/issues/470
 [cibuildwheel-repo-link]: https://github.com/joerick/cibuildwheel
 [mkdocs-macros-plugin-link]: https://mkdocs-macros-plugin.readthedocs.io
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..91acdf0
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,8 @@
+comment: false
+coverage:
+  status:
+    project:
+      default:
+        informational: true
+        target: 100%
+        threshold: 2%
diff --git a/examples/exclude-self/docs/README.md b/examples/exclude-self/docs/README.md
new file mode 100644
index 0000000..f608232
--- /dev/null
+++ b/examples/exclude-self/docs/README.md
@@ -0,0 +1,5 @@
+# Frequently Asked Questions (FAQ)
+
+{%
+include-markdown "./[!README]**.md"
+%}
diff --git a/examples/exclude-self/docs/included.md b/examples/exclude-self/docs/included.md
new file mode 100644
index 0000000..333a243
--- /dev/null
+++ b/examples/exclude-self/docs/included.md
@@ -0,0 +1,1 @@
+Some included content.
diff --git a/examples/exclude-self/mkdocs.yml b/examples/exclude-self/mkdocs.yml
new file mode 100644
index 0000000..0f97266
--- /dev/null
+++ b/examples/exclude-self/mkdocs.yml
@@ -0,0 +1,3 @@
+site_name: Foo
+plugins:
+  - include-markdown
diff --git a/locale/es/README.md b/locale/es/README.md
index 5bf6b2f..eeee551 100644
--- a/locale/es/README.md
+++ b/locale/es/README.md
@@ -1,9 +1,12 @@
 # mkdocs-include-markdown-plugin
 
-Plugin de inclusiones Markdown para Mkdocs.
+[![PyPI][pypi-version-badge-link]][pypi-link]
+[![License][license-image]][license-link]
+
+[![Tests][tests-image]][tests-link] [![Coverage
+status][coverage-image]][coverage-link]
 
-[![PyPI][pypi-version-badge-link]][pypi-link] [![Tests][tests-image]][tests-link]
-[![Coverage status][coverage-image]][coverage-link]
+Plugin de inclusiones Markdown para Mkdocs.
 
 > Lee este documento en otros idiomas:
 >
@@ -213,10 +216,12 @@ separar este plugin de la documentación de
 
 [pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin
 [pypi-version-badge-link]: https://img.shields.io/pypi/v/mkdocs-include-markdown-plugin?logo=pypi&logoColor=white
-[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests
+[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master
 [tests-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/actions?query=workflow%3ACI
 [coverage-image]: https://img.shields.io/codecov/c/github/mondeja/mkdocs-include-markdown-plugin?logo=codecov&logoColor=white
 [coverage-link]: https://app.codecov.io/gh/mondeja/mkdocs-include-markdown-plugin
+[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-plugin?color=light-green&logo=apache&logoColor=white
+[license-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/blob/master/LICENSE
 [cibuildwheel-470]: https://github.com/joerick/cibuildwheel/issues/470
 [cibuildwheel-repo-link]: https://github.com/joerick/cibuildwheel
 [mkdocs-macros-plugin-link]: https://mkdocs-macros-plugin.readthedocs.io
diff --git a/locale/es/README.md.po b/locale/es/README.md.po
index 5b2d915..d72f010 100644
--- a/locale/es/README.md.po
+++ b/locale/es/README.md.po
@@ -328,15 +328,6 @@ msgstr ""
 msgid "[pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin"
 msgstr "[pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin"
 
-msgid ""
-"[tests-image]: "
-"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
-"include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests"
-msgstr ""
-"[tests-image]: "
-"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
-"include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests"
-
 msgid ""
 "[coverage-image]: https://img.shields.io/codecov/c/github/mondeja/mkdocs-"
 "include-markdown-plugin?logo=codecov&logoColor=white"
@@ -350,3 +341,26 @@ msgid ""
 msgstr ""
 "[coverage-link]: https://app.codecov.io/gh/mondeja/mkdocs-include-markdown-"
 "plugin"
+
+msgid ""
+"[tests-image]: "
+"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
+"include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master"
+msgstr ""
+"[tests-image]: "
+"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
+"include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master"
+
+msgid ""
+"[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-"
+"plugin?color=light-green&logo=apache&logoColor=white"
+msgstr ""
+"[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-"
+"plugin?color=light-green&logo=apache&logoColor=white"
+
+msgid ""
+"[license-link]: https://github.com/mondeja/mkdocs-include-markdown-"
+"plugin/blob/master/LICENSE"
+msgstr ""
+"[license-link]: https://github.com/mondeja/mkdocs-include-markdown-"
+"plugin/blob/master/LICENSE"
diff --git a/locale/fr/README.md b/locale/fr/README.md
index e58289e..b5a8ff6 100644
--- a/locale/fr/README.md
+++ b/locale/fr/README.md
@@ -1,9 +1,12 @@
 # mkdocs-include-markdown-plugin
 
-Plugin d'inclusion de Markdown pour Mkdocs.
+[![PyPI][pypi-version-badge-link]][pypi-link]
+[![License][license-image]][license-link]
+
+[![Tests][tests-image]][tests-link] [![Coverage
+status][coverage-image]][coverage-link]
 
-[![PyPI][pypi-version-badge-link]][pypi-link] [![Tests][tests-image]][tests-link]
-[![Coverage status][coverage-image]][coverage-link]
+Plugin d'inclusion de Markdown pour Mkdocs.
 
 > Lire ce document dans d'autres langues:
 >
@@ -213,10 +216,12 @@ autorisations][cibuildwheel-470] pour séparer ce plugin de la documentation de
 
 [pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin
 [pypi-version-badge-link]: https://img.shields.io/pypi/v/mkdocs-include-markdown-plugin?logo=pypi&logoColor=white
-[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests
+[tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master
 [tests-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/actions?query=workflow%3ACI
 [coverage-image]: https://img.shields.io/codecov/c/github/mondeja/mkdocs-include-markdown-plugin?logo=codecov&logoColor=white
 [coverage-link]: https://app.codecov.io/gh/mondeja/mkdocs-include-markdown-plugin
+[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-plugin?color=light-green&logo=apache&logoColor=white
+[license-link]: https://github.com/mondeja/mkdocs-include-markdown-plugin/blob/master/LICENSE
 [cibuildwheel-470]: https://github.com/joerick/cibuildwheel/issues/470
 [cibuildwheel-repo-link]: https://github.com/joerick/cibuildwheel
 [mkdocs-macros-plugin-link]: https://mkdocs-macros-plugin.readthedocs.io
diff --git a/locale/fr/README.md.po b/locale/fr/README.md.po
index 7b3fb6c..e7d3dc7 100644
--- a/locale/fr/README.md.po
+++ b/locale/fr/README.md.po
@@ -327,15 +327,6 @@ msgstr ""
 msgid "[pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin"
 msgstr "[pypi-link]: https://pypi.org/project/mkdocs-include-markdown-plugin"
 
-msgid ""
-"[tests-image]: "
-"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
-"include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests"
-msgstr ""
-"[tests-image]: "
-"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
-"include-markdown-plugin/ci.yml?branch=master&logo=github&label=tests"
-
 msgid ""
 "[coverage-image]: https://img.shields.io/codecov/c/github/mondeja/mkdocs-"
 "include-markdown-plugin?logo=codecov&logoColor=white"
@@ -349,3 +340,26 @@ msgid ""
 msgstr ""
 "[coverage-link]: https://app.codecov.io/gh/mondeja/mkdocs-include-markdown-"
 "plugin"
+
+msgid ""
+"[tests-image]: "
+"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
+"include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master"
+msgstr ""
+"[tests-image]: "
+"https://img.shields.io/github/actions/workflow/status/mondeja/mkdocs-"
+"include-markdown-plugin/ci.yml?logo=github&label=tests&branch=master"
+
+msgid ""
+"[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-"
+"plugin?color=light-green&logo=apache&logoColor=white"
+msgstr ""
+"[license-image]: https://img.shields.io/pypi/l/mkdocs-include-markdown-"
+"plugin?color=light-green&logo=apache&logoColor=white"
+
+msgid ""
+"[license-link]: https://github.com/mondeja/mkdocs-include-markdown-"
+"plugin/blob/master/LICENSE"
+msgstr ""
+"[license-link]: https://github.com/mondeja/mkdocs-include-markdown-"
+"plugin/blob/master/LICENSE"
diff --git a/pyproject.toml b/pyproject.toml
index 4b264b4..2db4310 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ lint = "pre-commit run -a"
 
 [tool.hatch.envs.tests]
 matrix-name-format = "{variable}-{value}"
-dependencies = ["pytest~=7.0", "coverage~=6.4"]
+dependencies = ["pytest~=7.0", "coverage~=6.4", "covdefaults"]
 
 [[tool.hatch.envs.tests.matrix]]
 python = ["py37", "py38", "py39", "py310", "py311"]
@@ -94,28 +94,25 @@ targets = [{ file = "pyproject.toml" }]
 [tool.project-config]
 cache = "2 days"
 style = [
-  "gh://mondeja/project-config-styles@v4.2/base/pre-commit/md2po2md.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/base.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/single-quotes.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/line-length-80.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/tests.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/google-docstrings.json5",
-  "gh://mondeja/project-config-styles@v4.2/python/mypy.json5",
+  "gh://mondeja/project-config-styles@v4.3/base/pre-commit/md2po2md.json5",
+  "gh://mondeja/project-config-styles@v4.3/python/base.json5",
+  "gh://mondeja/project-config-styles@v4.3/python/single-quotes.json5",
+  "gh://mondeja/project-config-styles@v4.3/python/tests.json5",
+  "gh://mondeja/project-config-styles@v4.3/python/google-docstrings.json5",
+  "gh://mondeja/project-config-styles@v4.3/python/mypy.json5",
 ]
 
 [tool.coverage.run]
 source = ["src"]
 omit = ["src/mkdocs_include_markdown_plugin/plugin.py"]
+plugins = ["covdefaults"]
 
 [tool.coverage.report]
 exclude_lines = [
-  "pragma: no cover",
   "def __repr__\\(",
-  "raise NotImplementedError",
   "@(abc\\.)?abstractmethod",
-  "class .*\\bProtocol\\):",
-  "if TYPE_CHECKING:",
 ]
+fail_under = 1
 
 [tool.flakeheaven]
 inline-quotes = "single"
diff --git a/src/mkdocs_include_markdown_plugin/event.py b/src/mkdocs_include_markdown_plugin/event.py
index 8692654..eb32de1 100644
--- a/src/mkdocs_include_markdown_plugin/event.py
+++ b/src/mkdocs_include_markdown_plugin/event.py
@@ -8,10 +8,12 @@ import logging
 import os
 import re
 import textwrap
+import urllib.request
 from collections.abc import MutableMapping
 from typing import TYPE_CHECKING, Any
 from urllib.parse import urlparse
-import urllib.request
+
+from mkdocs.exceptions import BuildError
 
 from mkdocs_include_markdown_plugin import process
 from mkdocs_include_markdown_plugin.config import (
@@ -55,13 +57,16 @@ TRUE_FALSE_BOOL_STR = {
     False: 'false',
 }
 
-def is_url(string):
+
+def is_url(string: str) -> bool:
+    """Determines if a string is a URL."""
     try:
         result = urlparse(string)
         return all([result.scheme, result.netloc])
     except ValueError:
         return False
 
+
 def bool_arg(arg: str) -> re.Pattern[str]:
     """Return a compiled regexp to match a boolean argument."""
     return re.compile(rf'{arg}=(\w+)')
@@ -131,12 +136,14 @@ def read_file(file_path: str, encoding: str) -> str:
     with open(file_path, encoding=encoding) as f:
         return f.read()
 
-def read_http(target_url: str, encoding: str) -> str:
+
+def read_http(target_url: str, encoding: str) -> Any:  # noqa: U100
     """Read an http location and return its content."""
     req = urllib.request.Request(target_url)
     with urllib.request.urlopen(req) as response:
         return response.read().decode('UTF-8')
-    
+
+
 def get_file_content(
     markdown: str,
     page_src_path: str,
@@ -163,12 +170,11 @@ def get_file_content(
                 markdown,
                 directive_match_start,
             )
-            logger.error(
+            raise BuildError(
                 "Found no path passed including with 'include'"
                 f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                 f':{lineno}',
             )
-            return ''
 
         arguments_string = match.group('arguments')
 
@@ -192,11 +198,10 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'exclude' argument in 'include' directive"
                     f' at {os.path.relpath(page_src_path, docs_dir)}:{lineno}',
                 )
-                ignore_paths = []
             else:
 
                 if os.path.isabs(exclude_string):
@@ -215,21 +220,18 @@ def get_file_content(
             ignore_paths=ignore_paths,
         )
         if is_url(filename):
-            file_paths_to_include=[file_path_glob]
-            logger.info('url found: ' + file_path_glob)
-
+            file_paths_to_include = [file_path_glob]
 
         if not file_paths_to_include:
             lineno = lineno_from_content_start(
                 markdown,
                 directive_match_start,
             )
-            logger.error(
+            raise BuildError(
                 f"No files found including '{raw_filename}'"
                 f' at {os.path.relpath(page_src_path, docs_dir)}'
                 f':{lineno}',
             )
-            return ''
         elif files_watcher is not None:
             if not is_url(file_path_glob):
                 files_watcher.included_files.extend(file_paths_to_include)
@@ -241,8 +243,9 @@ def get_file_content(
                     directive_match_start,
                 )
                 logger.warning(
-                    f"Not adding a watcher for {file_path_glob} of 'include-markdown'"
-                    f' directive at {os.path.relpath(page_src_path, docs_dir)}'
+                    f'Not adding a watcher for {file_path_glob} of'
+                    " 'include-markdown' directive at"
+                    f' {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno} not detected in the file{plural_suffix}'
                     f' {readable_files_to_include}',
                 )
@@ -277,12 +280,11 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     f"Invalid value for '{arg_name}' argument of 'include'"
                     f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno}. Possible values are true or false.',
                 )
-                return ''
 
         start_match = ARGUMENT_REGEXES['start'].search(arguments_string)
         if start_match:
@@ -292,7 +294,7 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'start' argument in 'include' directive at "
                     f'{os.path.relpath(page_src_path, docs_dir)}:{lineno}',
                 )
@@ -307,7 +309,7 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'end' argument in 'include' directive at "
                     f'{os.path.relpath(page_src_path, docs_dir)}:{lineno}',
                 )
@@ -322,12 +324,11 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'encoding' argument in 'include'"
                     ' directive at '
                     f'{os.path.relpath(page_src_path, docs_dir)}:{lineno}',
                 )
-                encoding = 'utf-8'
         else:
             encoding = default_encoding
 
@@ -388,7 +389,7 @@ def get_file_content(
 
             text_to_include += new_text_to_include
 
-        # warn if expected start or ends haven't been found in included content        
+        # warn if expected start or ends haven't been found in included content
         for i, argname in enumerate(['start', 'end']):
             if expected_but_any_found[i]:
                 value = locals()[argname]
@@ -422,13 +423,12 @@ def get_file_content(
                 markdown,
                 directive_match_start,
             )
-            logger.error(
+            raise BuildError(
                 "Found no path passed including with 'include-markdown'"
                 f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                 f':{lineno}',
             )
-            return ''
-        
+
         arguments_string = match.group('arguments')
 
         if os.path.isabs(filename):
@@ -451,12 +451,11 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'exclude' argument in 'include-markdown'"
                     f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno}',
                 )
-                ignore_paths = []
             else:
                 if os.path.isabs(exclude_string):
                     exclude_globstr = exclude_string
@@ -475,20 +474,18 @@ def get_file_content(
         )
 
         if is_url(filename):
-            file_paths_to_include=[file_path_glob]
-            logger.info('url found: ' + file_path_glob)
+            file_paths_to_include = [file_path_glob]
 
         if not file_paths_to_include:
             lineno = lineno_from_content_start(
                 markdown,
                 directive_match_start,
             )
-            logger.error(
+            raise BuildError(
                 f"No files found including '{raw_filename}' at"
                 f' {os.path.relpath(page_src_path, docs_dir)}'
                 f':{lineno}',
             )
-            return ''
         elif files_watcher is not None:
             if not is_url(file_path_glob):
                 files_watcher.included_files.extend(file_paths_to_include)
@@ -500,8 +497,9 @@ def get_file_content(
                     directive_match_start,
                 )
                 logger.warning(
-                    f"Not adding a watcher for {file_path_glob} of 'include-markdown'"
-                    f' directive at {os.path.relpath(page_src_path, docs_dir)}'
+                    f'Not adding a watcher for {file_path_glob}'
+                    " of 'include-markdown' directive at"
+                    f' {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno} not detected in the file{plural_suffix}'
                     f' {readable_files_to_include}',
                 )
@@ -544,13 +542,12 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     f"Invalid value for '{arg_name}' argument of"
                     " 'include-markdown' directive at"
                     f' {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno}. Possible values are true or false.',
                 )
-                return ''
 
         # start and end arguments
         start_match = ARGUMENT_REGEXES['start'].search(arguments_string)
@@ -561,7 +558,7 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'start' argument in 'include-markdown'"
                     f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno}',
@@ -577,7 +574,7 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'end' argument in 'include-markdown'"
                     f' directive at {os.path.relpath(page_src_path, docs_dir)}'
                     f':{lineno}',
@@ -593,7 +590,7 @@ def get_file_content(
                     markdown,
                     directive_match_start,
                 )
-                logger.error(
+                raise BuildError(
                     "Invalid empty 'encoding' argument in 'include-markdown'"
                     ' directive at '
                     f'{os.path.relpath(page_src_path, docs_dir)}:{lineno}',


Test patch:
diff --git a/tests/test_unit/test_arguments.py b/tests/test_unit/test_arguments.py
index efc2265..ab44e86 100644
--- a/tests/test_unit/test_arguments.py
+++ b/tests/test_unit/test_arguments.py
@@ -3,6 +3,7 @@ import os
 import re
 
 import pytest
+from mkdocs.exceptions import BuildError
 from testing_helpers import parametrize_directives, unix_only
 
 from mkdocs_include_markdown_plugin.event import on_page_markdown
@@ -40,23 +41,20 @@ def test_invalid_bool_arguments(directive, arguments, page, tmp_path, caplog):
 
         filename = 'includer.md'
 
-        result = on_page_markdown(
-            f'''{{%
+        with pytest.raises(BuildError) as exc:
+            on_page_markdown(
+                f'''{{%
     {directive} "{page_to_include_filepath}"
     {argument_name}=invalidoption
     %}}''',
-            page(tmp_path / filename),
-            tmp_path,
-        )
-        assert result == ''
-
-        assert len(caplog.records) == 1
-        assert caplog.records[0].msg == (
+                page(tmp_path / filename),
+                tmp_path,
+            )
+        assert str(exc.value) == (
             f"Invalid value for '{argument_name}' argument of '{directive}'"
             f' directive at {filename}:1. Possible values are true or false.'
         )
-
-        caplog.clear()
+        assert len(caplog.records) == 0
 
 
 @parametrize_directives
@@ -84,8 +82,15 @@ More content that should be ignored
     assert len(caplog.records) == 0
 
 
+@pytest.mark.parametrize('argument', ('start', 'end'))
 @parametrize_directives
-def test_invalid_start_end_arguments(directive, page, caplog, tmp_path):
+def test_invalid_start_end_arguments(
+    argument,
+    directive,
+    page,
+    caplog,
+    tmp_path,
+):
     page_to_include_filepath = tmp_path / 'included.md'
     included_content = '''Content that should be ignored
 <!-- start -->
@@ -95,27 +100,23 @@ More content that should be ignored
 '''
     page_to_include_filepath.write_text(included_content)
 
-    result = on_page_markdown(
-        f'''
+    with pytest.raises(BuildError) as exc:
+        on_page_markdown(
+            f'''
 {{%
   {directive} "{page_to_include_filepath}"
   comments=false
-  start=''
-  end=""
+  {argument}=''
 %}}''',
-        page(tmp_path / 'includer.md'),
-        tmp_path,
+            page(tmp_path / 'includer.md'),
+            tmp_path,
+        )
+    assert str(exc.value) == (
+        f"Invalid empty '{argument}' argument in '{directive}'"
+        ' directive at includer.md:2'
     )
-    assert result == f'\n{included_content}'
 
-    records_messages = [record.msg for record in caplog.records]
-    expected_args = ['start', 'end']
-    for arg_name in expected_args:
-        assert (
-            f"Invalid empty '{arg_name}' argument in '{directive}'"
-            ' directive at includer.md:2'
-        ) in records_messages
-    assert len(records_messages) == len(expected_args)
+    assert len([record.msg for record in caplog.records]) == 0
 
 
 @unix_only
@@ -159,22 +160,19 @@ def test_invalid_exclude_argument(directive, page, tmp_path, caplog):
     page_to_exclude_filepath.write_text('Content that should be excluded\n')
 
     includer_glob = os.path.join(str(drectory_to_include), '*.md')
-    result = on_page_markdown(
-        f'''{{%
+    with pytest.raises(BuildError) as exc:
+        on_page_markdown(
+            f'''{{%
   {directive} "{includer_glob}"
   comments=false
   exclude=
 %}}''',
-        page(tmp_path / 'includer.md'),
-        tmp_path,
-    )
-    assert result == (
-        'Content that should be excluded\n'
-        'Content that should be included\n'
-    )
+            page(tmp_path / 'includer.md'),
+            tmp_path,
+        )
 
-    assert len(caplog.records) == 1
-    assert caplog.records[0].msg == (
+    assert len(caplog.records) == 0
+    assert str(exc.value) == (
         f"Invalid empty 'exclude' argument in '{directive}' directive"
         ' at includer.md:1'
     )
@@ -185,19 +183,19 @@ def test_empty_encoding_argument(directive, page, tmp_path, caplog):
     page_to_include_filepath = tmp_path / 'included.md'
     page_to_include_filepath.write_text('Content to include')
 
-    result = on_page_markdown(
-        f'''{{%
+    with pytest.raises(BuildError) as exc:
+        on_page_markdown(
+            f'''{{%
   {directive} "{page_to_include_filepath}"
   comments=false
   encoding=
 %}}''',
-        page(tmp_path / 'includer.md'),
-        tmp_path,
-    )
-    assert result == 'Content to include'
+            page(tmp_path / 'includer.md'),
+            tmp_path,
+        )
 
-    assert len(caplog.records) == 1
-    assert caplog.records[0].msg == (
+    assert len(caplog.records) == 0
+    assert str(exc.value) == (
         f"Invalid empty 'encoding' argument in '{directive}'"
         ' directive at includer.md:1'
     )
@@ -220,17 +218,17 @@ class TestFilename:
         page_to_include_filepath = tmp_path / filename
         page_to_include_filepath.write_text('Foo\n')
 
-        result = on_page_markdown(
-            f'{{% {directive} "{page_to_include_filepath}" %}}',
-            page(tmp_path / 'includer.md'),
-            tmp_path,
-        )
-        assert not result
+        with pytest.raises(BuildError) as exc:
+            on_page_markdown(
+                f'{{% {directive} "{page_to_include_filepath}" %}}',
+                page(tmp_path / 'includer.md'),
+                tmp_path,
+            )
 
-        assert len(caplog.records) == 1
+        assert len(caplog.records) == 0
         assert re.match(
             r'^No files found including ',
-            caplog.records[0].msg,
+            str(exc.value),
         )
 
     @unix_only
@@ -358,31 +356,31 @@ class TestFilename:
 
         if escape:
             assert func() == included_content
-            assert len(caplog.records) == 0
         else:
-            assert func() == ''
-            assert len(caplog.records) == 1
+            with pytest.raises(BuildError) as exc:
+                func()
             assert re.match(
                 r'No files found including ',
-                caplog.records[0].msg,
+                str(exc.value),
             )
+        assert len(caplog.records) == 0
 
     @parametrize_directives
     def test_no_filename(self, directive, page, tmp_path, caplog):
         filename = 'includer.md'
 
-        # shouldn't raise errors
-        on_page_markdown(
-            f'\n\n{{% {directive} %}}',
-            page(tmp_path / filename),
-            tmp_path,
-        )
+        with pytest.raises(BuildError) as exc:
+            on_page_markdown(
+                f'\n\n{{% {directive} %}}',
+                page(tmp_path / filename),
+                tmp_path,
+            )
 
-        assert caplog.records[0].msg == (
+        assert str(exc.value) == (
             f"Found no path passed including with '{directive}' directive"
             f' at {filename}:3'
         )
-        assert len(caplog.records) == 1
+        assert len(caplog.records) == 0
 
     @parametrize_directives
     def test_non_existent_filename(self, directive, page, tmp_path, caplog):
@@ -395,11 +393,12 @@ class TestFilename:
         page_filepath = tmp_path / 'example.md'
         page_filepath.write_text(page_content)
 
-        assert on_page_markdown(
-            page_content,
-            page(page_filepath),
-            tmp_path,
-        ) == ''
+        with pytest.raises(BuildError) as exc:
+            on_page_markdown(
+                page_content,
+                page(page_filepath),
+                tmp_path,
+            )
 
-        assert len(caplog.records) == 1
-        assert re.match(r'No files found including ', caplog.records[0].msg)
+        assert len(caplog.records) == 0
+        assert re.match(r'No files found including ', str(exc.value))
diff --git a/tests/test_unit/test_exclude.py b/tests/test_unit/test_exclude.py
index 08d5d55..88047aa 100644
--- a/tests/test_unit/test_exclude.py
+++ b/tests/test_unit/test_exclude.py
@@ -5,6 +5,7 @@ import os
 import re
 
 import pytest
+from mkdocs.exceptions import BuildError
 from testing_helpers import parametrize_directives
 
 from mkdocs_include_markdown_plugin.event import on_page_markdown
@@ -97,10 +98,9 @@ def test_exclude(
     )
 
     if expected_result is None:
-        assert func() == ''
-        assert len(caplog.records) == 1
-        for record in caplog.records:
-            assert re.match(r'No files found including ', record.msg)
+        with pytest.raises(BuildError) as exc:
+            func()
+        assert re.match(r'No files found including ', str(exc.value))
     else:
         assert func() == expected_result
-        assert len(caplog.records) == 0
+    assert len(caplog.records) == 0
diff --git a/tests/test_unit/test_include.py b/tests/test_unit/test_include.py
index 9631855..0771fcb 100644
--- a/tests/test_unit/test_include.py
+++ b/tests/test_unit/test_include.py
@@ -300,6 +300,26 @@ Some text
             id='dedent=true',
         ),
 
+        # Include from URL
+        pytest.param(
+            '''# Header
+
+{% include "https://raw.githubusercontent.com/mondeja/mkdocs-include-markdown-plugin/master/examples/basic/docs/included.md" %}
+''',  # noqa: E501
+            '(not used)\n',
+            '''# Header
+
+Some ignored content.
+
+<--start-->
+
+Some included content.
+
+''',
+            [],
+            id='url',
+        ),
+
         # Content unindentation + preserve includer indent
         pytest.param(
             '''# Header
@@ -336,6 +356,7 @@ Some text
             [],
             id='rstrip-windows-trailing-newlines',
         ),
+
     ),
 )
 def test_include(
diff --git a/tests/test_unit/test_include_markdown.py b/tests/test_unit/test_include_markdown.py
index 1c671d1..2bacf6e 100644
--- a/tests/test_unit/test_include_markdown.py
+++ b/tests/test_unit/test_include_markdown.py
@@ -590,6 +590,34 @@ Example data
             id='heading-offset=-90',
         ),
 
+        # Include from URL
+        pytest.param(
+            '''# Header
+
+{%
+  include-markdown "https://raw.githubusercontent.com/mondeja/mkdocs-include-markdown-plugin/master/examples/basic/docs/included.md"
+%}
+
+After include.
+''',  # noqa: E501
+            'foo (not used)\n',
+            '''# Header
+
+<!-- BEGIN INCLUDE https://raw.githubusercontent.com/mondeja/mkdocs-include-markdown-plugin/master/examples/basic/docs/included.md -->
+Some ignored content.
+
+<--start-->
+
+Some included content.
+
+<!-- END INCLUDE -->
+
+After include.
+''',  # noqa: E501
+            [],
+            id='url',
+        ),
+
         # Custom encoding
         pytest.param(
             '''# Header