Skip to content

Commit 63de275

Browse files
kamilkrzyskowsquidfunk
authored andcommitted
Extended info plugin's validation message
Add more elaborate comments Fix lines to span to 80 characters Fix import order Fix variable naming
1 parent 4478522 commit 63de275

File tree

4 files changed

+158
-110
lines changed

4 files changed

+158
-110
lines changed

material/plugins/info/info.gitignore

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
# Custom .gitignore-like file
2-
# The difference is that those are https://docs.python.org/3/library/re.html
3-
# regex patterns, that will be compared against directory and file names
4-
# case-sensitively.
5-
6-
# Additional info:
7-
# The paths will be always in POSIX format.
8-
# Each directory path will have a / at the end to make it easier to
9-
# distinguish them from files.
10-
11-
12-
# Patterns for dynamic or custom paths like Virtual Environments (venv)
13-
# or build site directories are created during plugin runtime.
14-
15-
# ---
2+
#
3+
# The difference is that those are regex patterns, which will be compared
4+
# against directory and file names case-sensitively. The plugin uses the
5+
# external https://pypi.org/project/regex/ module.
6+
#
7+
# Additional remarks for pattern creation:
8+
# - The compared paths will be always in POSIX format.
9+
# - Each directory path will have a / at the end to allow to distinguish them
10+
# from files.
11+
# - Patterns for dynamic or custom paths like Virtual Environments (venv) or
12+
# build site directories are created during plugin runtime.
1613

1714
# Byte-compiled / optimized / DLL files
1815
# Python cache directory

material/plugins/info/plugin.py

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@
2323
import logging
2424
import os
2525
import platform
26+
import regex
2627
import requests
2728
import site
2829
import sys
29-
3030
import yaml
31+
3132
from colorama import Fore, Style
3233
from importlib.metadata import distributions, version
3334
from io import BytesIO
3435
from markdown.extensions.toc import slugify
3536
from mkdocs.config.defaults import MkDocsConfig
3637
from mkdocs.plugins import BasePlugin, event_priority
3738
from mkdocs.utils import get_yaml_loader
38-
import regex
3939
from zipfile import ZipFile, ZIP_DEFLATED
4040

4141
from .config import InfoConfig
@@ -110,21 +110,24 @@ def on_config(self, config):
110110
log.error("Please remove 'hooks' setting.")
111111
self._help_on_customizations_and_exit()
112112

113-
# Assure that possible relative paths, which will be validated
114-
# or used to generate other paths are absolute.
113+
# Assure all paths that will be validated are absolute. Convert possible
114+
# relative config_file_path to absolute. Its absolute directory path is
115+
# being later used to resolve other paths.
115116
config.config_file_path = _convert_to_abs(config.config_file_path)
116117
config_file_parent = os.path.dirname(config.config_file_path)
117118

118-
# The theme.custom_dir property cannot be set, therefore a helper
119-
# variable is used.
120-
custom_dir = config.theme.custom_dir
121-
if custom_dir:
122-
custom_dir = _convert_to_abs(
123-
custom_dir,
119+
# Convert relative custom_dir path to absolute. The Theme.custom_dir
120+
# property cannot be set, therefore a helper variable is used.
121+
if config.theme.custom_dir:
122+
abs_custom_dir = _convert_to_abs(
123+
config.theme.custom_dir,
124124
abs_prefix = config_file_parent
125125
)
126+
else:
127+
abs_custom_dir = ""
126128

127-
# Support projects plugin
129+
# Extract the absolute path to projects plugin's directory to explicitly
130+
# support path validation and dynamic exclusion for the plugin
128131
projects_plugin = config.plugins.get("material/projects")
129132
if projects_plugin:
130133
abs_projects_dir = _convert_to_abs(
@@ -134,29 +137,39 @@ def on_config(self, config):
134137
else:
135138
abs_projects_dir = ""
136139

137-
# Load the current MkDocs config(s) to get access to INHERIT
140+
# MkDocs removes the INHERIT configuration key during load, and doesn't
141+
# expose the information in any way, as the parent configuration is
142+
# merged into one. To validate that the INHERIT config file will be
143+
# included in the ZIP file the current config file must be loaded again
144+
# without parsing. Each file can have their own INHERIT key, so a list
145+
# of configurations is supported. The INHERIT path is converted during
146+
# load to absolute.
138147
loaded_configs = _load_yaml(config.config_file_path)
139148
if not isinstance(loaded_configs, list):
140149
loaded_configs = [loaded_configs]
141150

142-
# Validate different MkDocs paths to assure that
143-
# they're children of the current working directory.
151+
# We need to make sure the user put every file in the current working
152+
# directory. To assure the reproduction inside the ZIP file can be run,
153+
# validate that the MkDocs paths are children of the current root.
144154
paths_to_validate = [
145155
config.config_file_path,
146156
config.docs_dir,
147-
custom_dir or "",
157+
abs_custom_dir,
148158
abs_projects_dir,
149159
*[cfg.get("INHERIT", "") for cfg in loaded_configs]
150160
]
151161

162+
# Convert relative hook paths to absolute path
152163
for hook in config.hooks:
153164
path = _convert_to_abs(hook, abs_prefix = config_file_parent)
154165
paths_to_validate.append(path)
155166

167+
# Remove valid paths from the list
156168
for path in list(paths_to_validate):
157169
if not path or path.startswith(os.getcwd()):
158170
paths_to_validate.remove(path)
159171

172+
# Report the invalid paths to the user
160173
if paths_to_validate:
161174
log.error(f"One or more paths aren't children of root")
162175
self._help_on_not_in_cwd(paths_to_validate)
@@ -198,26 +211,36 @@ def on_config(self, config):
198211
files: list[str] = []
199212
with ZipFile(archive, "a", ZIP_DEFLATED, False) as f:
200213
for abs_root, dirnames, filenames in os.walk(os.getcwd()):
201-
# Prune the folders in-place to prevent
202-
# scanning excluded folders
214+
# Prune the folders in-place to prevent their processing
203215
for name in list(dirnames):
216+
# Resolve the absolute directory path
204217
path = os.path.join(abs_root, name)
218+
219+
# Exclude the directory and all subdirectories
205220
if self._is_excluded(_resolve_pattern(path)):
206221
dirnames.remove(name)
207222
continue
208-
# Multi-language setup from #2346 separates the
209-
# language config, so each mkdocs.yml file is
210-
# unaware of other site_dir directories. Therefore,
211-
# we add this with the assumption a site_dir contains
212-
# the sitemap file.
223+
224+
# Projects, which don't use the projects plugin for
225+
# multi-language support could have separate build folders
226+
# for each config file or language. Therefore, we exclude
227+
# them with the assumption a site_dir contains the sitemap
228+
# file. Example of such a setup: https://t.ly/DLQcy
213229
sitemap_gz = os.path.join(path, "sitemap.xml.gz")
214230
if os.path.exists(sitemap_gz):
215231
log.debug(f"Excluded site_dir: {path}")
216232
dirnames.remove(name)
233+
234+
# Write files to the in-memory archive
217235
for name in filenames:
236+
# Resolve the absolute file path
218237
path = os.path.join(abs_root, name)
238+
239+
# Exclude the file
219240
if self._is_excluded(_resolve_pattern(path)):
220241
continue
242+
243+
# Resolve the relative path to create a matching structure
221244
path = os.path.relpath(path, os.path.curdir)
222245
f.write(path, os.path.join(example, path))
223246

@@ -320,27 +343,31 @@ def _help_on_customizations_and_exit(self):
320343
print(Style.NORMAL)
321344
print(" - extra_css")
322345
print(" - extra_javascript")
346+
print(Fore.YELLOW)
347+
print(" If you're using customizations from the theme's documentation")
348+
print(" and you want to report a bug specific to those customizations")
349+
print(" then set the 'archive_stop_on_violation: false' option in the")
350+
print(" info plugin config.")
323351
print(Style.RESET_ALL)
324352

325353
# Exit, unless explicitly told not to
326354
if self.config.archive_stop_on_violation:
327355
sys.exit(1)
328356

329357
# Print help on not in current working directory and exit
330-
def _help_on_not_in_cwd(self, bad_paths):
358+
def _help_on_not_in_cwd(self, outside_root):
331359
print(Fore.RED)
332360
print(" The current working (root) directory:\n")
333361
print(f" {os.getcwd()}\n")
334362
print(" is not a parent of the following paths:")
335363
print(Style.NORMAL)
336-
for path in bad_paths:
364+
for path in outside_root:
337365
print(f" {path}")
338-
print()
339-
print(" To assure that all project files are found")
340-
print(" please adjust your config or file structure and")
341-
print(" put everything within the root directory of the project.\n")
342-
print(" Please also make sure `mkdocs build` is run in")
343-
print(" the actual root directory of the project.")
366+
print(" \nTo assure that all project files are found please adjust")
367+
print(" your config or file structure and put everything within the")
368+
print(" root directory of the project.\n")
369+
print(" Please also make sure `mkdocs build` is run in the actual")
370+
print(" root directory of the project.")
344371
print(Style.RESET_ALL)
345372

346373
# Exit, unless explicitly told not to
@@ -370,17 +397,19 @@ def _size(value, factor = 1):
370397
return f"{color}{value:3.1f} {unit}"
371398
value /= 1000.0
372399

373-
# To validate if a file is within the file tree,
374-
# it needs to be absolute, so that it is possible to
400+
# Get the absolute path with set prefix. To validate if a file is inside the
401+
# current working directory it needs to be absolute, so that it is possible to
375402
# check the prefix.
376403
def _convert_to_abs(path: str, abs_prefix: str = None) -> str:
377404
if os.path.isabs(path): return path
378405
if abs_prefix is None: abs_prefix = os.getcwd()
379406
return os.path.normpath(os.path.join(abs_prefix, path))
380407

381-
# Custom YAML loader - required to handle the parent INHERIT config.
382-
# It converts the INHERIT path to absolute as a side effect.
383-
# Returns the loaded config, or a list of all loaded configs.
408+
# Get the loaded config, or a list with all loaded configs. MkDocs removes the
409+
# INHERIT configuration key during load, and doesn't expose the information in
410+
# any way, as the parent configuration is merged into one. The INHERIT path is
411+
# needed for validation. This custom YAML loader replicates MkDocs' loading
412+
# logic. Side effect: It converts the INHERIT path to absolute.
384413
def _load_yaml(abs_src_path: str):
385414

386415
with open(abs_src_path, "r", encoding ="utf-8-sig") as file:
@@ -417,11 +446,9 @@ def _load_exclusion_patterns(path: str = None):
417446

418447
return [line for line in lines if line and not line.startswith("#")]
419448

420-
# For the pattern matching it is best to remove the CWD
421-
# prefix and keep only the relative root of the reproduction.
422-
# Additionally, as the patterns are in POSIX format,
423-
# assure that the path is also in POSIX format.
424-
# Side-effect: It appends "/" for directory patterns.
449+
# Get a normalized POSIX path for the pattern matching with removed current
450+
# working directory prefix. Directory paths end with a '/' to allow more control
451+
# in the pattern creation for files and directories.
425452
def _resolve_pattern(abspath: str):
426453
path = abspath.replace(os.getcwd(), "", 1).replace(os.sep, "/")
427454

@@ -434,7 +461,7 @@ def _resolve_pattern(abspath: str):
434461

435462
return path
436463

437-
# Get project configuration
464+
# Get project configuration with resolved absolute paths for validation
438465
def _get_project_config(project_config_file: str):
439466
with open(project_config_file, encoding="utf-8") as file:
440467
config = MkDocsConfig(config_file_path = project_config_file)

src/plugins/info/info.gitignore

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
# Custom .gitignore-like file
2-
# The difference is that those are https://docs.python.org/3/library/re.html
3-
# regex patterns, that will be compared against directory and file names
4-
# case-sensitively.
5-
6-
# Additional info:
7-
# The paths will be always in POSIX format.
8-
# Each directory path will have a / at the end to make it easier to
9-
# distinguish them from files.
10-
11-
12-
# Patterns for dynamic or custom paths like Virtual Environments (venv)
13-
# or build site directories are created during plugin runtime.
14-
15-
# ---
2+
#
3+
# The difference is that those are regex patterns, which will be compared
4+
# against directory and file names case-sensitively. The plugin uses the
5+
# external https://pypi.org/project/regex/ module.
6+
#
7+
# Additional remarks for pattern creation:
8+
# - The compared paths will be always in POSIX format.
9+
# - Each directory path will have a / at the end to allow to distinguish them
10+
# from files.
11+
# - Patterns for dynamic or custom paths like Virtual Environments (venv) or
12+
# build site directories are created during plugin runtime.
1613

1714
# Byte-compiled / optimized / DLL files
1815
# Python cache directory

0 commit comments

Comments
 (0)