Skip to content

Welcome to Check MkDocs

This is a simple .pre-commit-config.yaml hook to check if MkDocs is configured correctly.

Features

  • Validates the MkDocs configuration file (default is mkdocs.yml).
  • Builds the MkDocs project documentation via mkdocs build.
  • Checks the built documentation via mkdocs serve.
  • Can be used as a pre-commit hook.
  • Optionally, it can generate a build of the MkDocs documentation.

Installation via Pip

You can install this tool via pip:

pip install check-mkdocs

Local Installation

This tool is packaged with Poetry. To install it, you can clone the repository and use Poetry to install the dependencies:

git clone https://github.com/RodrigoGonzalez/check-mkdocs.git
cd check-mkdocs
poetry install

Pre-Commit Usage

Add this to your .pre-commit-config.yaml, where mkdocs.yml is the name of your MkDocs YAML configuration file:

repos:
    - repo: https://github.com/RodrigoGonzalez/check-mkdocs
      rev: v1.2.0
      hooks:
        - id: check-mkdocs
          name: check-mkdocs
          args: ["--config", "mkdocs.yml"]  # Optional, mkdocs.yml is the default
          # If you have additional plugins or libraries that are not included in
          # check-mkdocs, add them here
          additional_dependencies: ['mkdocs-material']

Command-Line Usage

To run this hook, you can use the following command:

check-mkdocs --config=mkdocs.yml

This works as well:

check-mkdocs mkdocs.yml

And if mkdocs.yml is the name of your MkDocs YAML configuration file, this works too:

check-mkdocs

These commands will validate the MkDocs configuration file, build the project documentation, and start the server. If there's an error in any of these steps, the tool will print an error message and return an error code.

The --generate-build Argument

The --generate-build argument is a command-line flag that you can use to instruct the check_mkdocs tool to generate a build of the MkDocs documentation. This argument is optional, and its default value is False, which means that by default, the tool will not generate a build.

When you provide this argument, the tool will call the build function from mkdocs.commands.build with the loaded configuration. This function will build the project documentation and place the built documentation in the site_dir directory specified in the configuration. If the site_dir directory is not specified in the configuration, the tool will use the MkDocs default location as the site_dir (i.e., site/).

Here's how you can use this argument:

check-mkdocs --config=mkdocs.yml --generate-build

This command will validate the MkDocs configuration file, build the project documentation, and start the server. If there's an error in any of these steps, the tool will print an error message and return an error code.

Known Issues

Missing MkDocs Plugins

Given the enormous number of plugins available for MkDocs, it's possible that some plugins are not included in this tool. In this case you will see an error message similar to this:

check-mkdocs.............................................................Failed
- hook id: check-mkdocs
- exit code: 1

Config value 'theme': Unrecognised theme name: 'material'. The available installed themes are: mkdocs, readthedocs
Config value 'markdown_extensions': Failed to load extension 'pymdownx.snippets'.
ModuleNotFoundError: No module named 'pymdownx'
Config value 'plugins': The "mkdocstrings" plugin is not installed

make: *** [pre-commit] Error 1

If you have additional plugins or libraries that are not included in check-mkdocs, add them here:

repos:
    - repo: https://github.com/RodrigoGonzalez/check-mkdocs
      hooks:
        - id: check-mkdocs
          # If you have additional plugins or libraries that are not included in check-mkdocs, add them here
          additional_dependencies: ['mkdocs-material', 'mkdocstrings']

main(argv: None = None) -> int

The main function is the entry point of the program.

Parameters

None, optional

An optional argument representing the command-line arguments. Defaults to None if not provided.

Returns

int The return value of the function, which is an integer.

Raises

FileNotFoundError If the specified config file does not exist.

Description

The main function creates an argument parser object using the argparse.ArgumentParser() class. It adds a positional argument config to the parser, which represents the path to the configuration file. The nargs='?' option specifies that the argument is optional and can be omitted.

The default='mkdocs.yml' option specifies the default value of the argument if it is not provided.

Additionally, it adds a keyword argument --config to the parser. This argument also represents the path to the configuration file. If both the positional and keyword arguments are provided, the keyword argument will take precedence.

Another argument --generate-build is added to the parser. This argument is a flag to generate a build of the documentation. The default value is False.

The function parses the command-line arguments using the parser.parse_args(argv) method, which returns an object containing the parsed arguments.

The function retrieves the value of the config argument from the parsed arguments.

If the specified config file does not exist, the function raises a FileNotFoundError with a descriptive error message.

The function then attempts to open the config file in read mode using the open(config_file, 'r') function. If successful, it reads the contents of the file and loads them as a YAML object using the yaml.safe_load(f) function.

The function checks if the 'site_name' key is present in the loaded YAML. If not, it prints an error message.

The function then loads the configuration using the load_config(config_file=config_file) function. If there is an error in the configuration file, it prints an error message.

The function then attempts to build the documentation using the build(config) function. If there is an error during the build process, it returns a user-friendly error message.

The function then attempts to start the server using the serve(config_file=config_file) function. If there is an error during the server start process, it returns a user-friendly error message.

Finally, the function returns 0 if all the above processes are successful.

Source code in src/check_mkdocs/check_mkdocs.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def main(argv: None = None) -> int:
    """
    The main function is the entry point of the program.

    Parameters
    ----------
    argv : None, optional
        An optional argument representing the command-line arguments. Defaults
        to None if not provided.

    Returns
    -------
    int
        The return value of the function, which is an integer.

    Raises
    ------
    FileNotFoundError
        If the specified config file does not exist.

    Description
    -----------
    The main function creates an argument parser object using the
    `argparse.ArgumentParser()` class. It adds a positional argument
    `config` to the parser, which represents the path to the
    configuration file. The `nargs='?'` option specifies that the argument is
    optional and can be omitted.

    The `default='mkdocs.yml'` option specifies the default value of the
    argument if it is not provided.

    Additionally, it adds a keyword argument `--config` to the parser. This
    argument also represents the path to the configuration file. If both the
    positional and keyword arguments are provided, the keyword argument will
    take precedence.

    Another argument `--generate-build` is added to the parser. This argument
    is a flag to generate a build of the documentation. The default value is False.

    The function parses the command-line arguments using the
    `parser.parse_args(argv)` method, which returns an object containing the
    parsed arguments.

    The function retrieves the value of the `config` argument from the
    parsed arguments.

    If the specified config file does not exist, the function raises a
    `FileNotFoundError` with a descriptive error message.

    The function then attempts to open the config file in read mode using the
    `open(config_file, 'r')` function. If successful, it reads the contents of
    the file and loads them as a YAML object using the `yaml.safe_load(f)`
    function.

    The function checks if the 'site_name' key is present in the loaded YAML.
    If not, it prints an error message.

    The function then loads the configuration using the
    `load_config(config_file=config_file)` function. If there is an error in
    the configuration file, it prints an error message.

    The function then attempts to build the documentation using the
    `build(config)` function. If there is an error during the build process,
    it returns a user-friendly error message.

    The function then attempts to start the server using the
    `serve(config_file=config_file)` function. If there is an error during the
    server start process, it returns a user-friendly error message.

    Finally, the function returns 0 if all the above processes are successful.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "config",
        nargs="?",
        default="mkdocs.yml",
        help="Path to the configuration file. Default is 'mkdocs.yml'",
    )
    parser.add_argument(
        "--config",
        dest="config_opt",
        help=(
            "Path to the configuration file. Overrides the positional 'config' "
            "argument if provided."
        ),
    )
    parser.add_argument(
        "--generate-build",
        dest="generate_build",
        action="store_true",
        default=False,
        help="Flag to generate a build of the documentation in your project. Default is False.",
    )
    args = parser.parse_args(argv)

    config_file = args.config_opt or args.config

    if not os.path.exists(config_file):
        raise FileNotFoundError(
            f"Config file '{config_file}' not found. Please specify a " f"config file."
        )

    # Load the YAML to check initial errors
    try:
        with open(config_file, "r") as f:
            config = yaml.safe_load(f)
        # Check if 'site_name' key is present in the loaded YAML
        if "site_name" not in config:
            print(
                f"{config_file}: Missing site_name field. This is a required "
                f"field. See: https://www.mkdocs.org/user-guide/configuration/"
            )

    except Exception as e:
        return _generate_user_friendly_error_message(config_file, "Error loading YAML", e)

    # Load the configuration
    try:
        config = load_config(config_file=config_file)
    except ConfigurationError as e:
        print(f"Error in configuration file: {e}")
        return _generate_user_friendly_error_message(config_file, "Error in configuration file", e)

    # Build the documentation
    try:
        print("Building the documentation...")
        if args.generate_build:
            subprocess.run(["mkdocs", "build", "--config-file", config_file], check=True)
        else:
            with tempfile.TemporaryDirectory() as temp_dir:
                subprocess.run(
                    ["mkdocs", "build", "--config-file", config_file, "--site-dir", temp_dir],
                    check=True,
                )
    except Exception as e:
        return _generate_user_friendly_error_message(
            config_file, "Error building the documentation", e
        )

    print("Trying to start the server...")
    # Start the server
    try:
        server_process = subprocess.Popen(
            ["mkdocs", "serve", "--config-file", config_file, "--no-livereload", "--dirty"]
        )
        time.sleep(5)  # wait for 5 seconds to let the server start
        print("Shutting down...")
        server_process.terminate()
        server_process.wait()
    except Exception as e:
        return _generate_user_friendly_error_message(config_file, "Error starting the server", e)

    return 0