Skip to content

Commit 7e27f4d

Browse files
committed
Merge branch 'master' of github.com:squidfunk/mkdocs-material
2 parents dc97a56 + 2d39824 commit 7e27f4d

File tree

7 files changed

+169
-157
lines changed

7 files changed

+169
-157
lines changed

Diff for: material/plugins/info/info.gitignore

-37
This file was deleted.

Diff for: material/plugins/info/patterns.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
def get_exclusion_patterns():
2+
"""
3+
Regex patterns, which will be compared against directory and file names
4+
case-sensitively. https://docs.python.org/3/library/re.html#re.search is the
5+
matching function and scans the whole string to find any pattern match. Used
6+
with the https://pypi.org/project/regex/ module.
7+
8+
Additional remarks for pattern creation:
9+
- The compared paths will be always in POSIX format.
10+
- Each directory path will have a / at the end to allow to distinguish them
11+
from files.
12+
- Patterns for dynamic or custom paths like Virtual Environments (venv) or
13+
build site directories are created during plugin runtime.
14+
"""
15+
return [
16+
r"/__pycache__/", # Python cache directory
17+
18+
r"/\.DS_Store$", # macOS
19+
20+
r"/[^/]+\.zip$", # Generated files and folders
21+
22+
r"/[^/]*\.cache($|/)", # .cache files and folders
23+
24+
r"/\.vscode/", # Common autogenerated IDE directories
25+
r"/\.vs/",
26+
r"/\.idea/",
27+
]

Diff for: material/plugins/info/plugin.py

+57-41
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from zipfile import ZipFile, ZIP_DEFLATED
4040

4141
from .config import InfoConfig
42+
from .patterns import get_exclusion_patterns
4243

4344
# -----------------------------------------------------------------------------
4445
# Classes
@@ -56,6 +57,7 @@ def __init__(self, *args, **kwargs):
5657

5758
# Initialize empty members
5859
self.exclusion_patterns = []
60+
self.excluded_entries = []
5961

6062
# Determine whether we're serving the site
6163
def on_startup(self, *, command, dirty):
@@ -183,15 +185,18 @@ def on_config(self, config):
183185
example, _ = os.path.splitext(example)
184186
example = "-".join([present, slugify(example, "-")])
185187

186-
# Load exclusion patterns
187-
self.exclusion_patterns = _load_exclusion_patterns()
188+
# Get local copy of the exclusion patterns
189+
self.exclusion_patterns = get_exclusion_patterns()
190+
self.excluded_entries = []
188191

189192
# Exclude the site_dir at project root
190193
if config.site_dir.startswith(os.getcwd()):
191194
self.exclusion_patterns.append(_resolve_pattern(config.site_dir))
192195

193-
# Exclude the site-packages directory
194-
for path in site.getsitepackages():
196+
# Exclude the Virtual Environment directory. site.getsitepackages() has
197+
# inconsistent results across operating systems, and relies on the
198+
# PREFIXES that will contain the absolute path to the activated venv.
199+
for path in site.PREFIXES:
195200
if path.startswith(os.getcwd()):
196201
self.exclusion_patterns.append(_resolve_pattern(path))
197202

@@ -211,24 +216,17 @@ def on_config(self, config):
211216
files: list[str] = []
212217
with ZipFile(archive, "a", ZIP_DEFLATED, False) as f:
213218
for abs_root, dirnames, filenames in os.walk(os.getcwd()):
219+
# Set and print progress indicator
220+
indicator = f"Processing: {abs_root}"
221+
print(indicator, end="\r", flush=True)
222+
214223
# Prune the folders in-place to prevent their processing
215224
for name in list(dirnames):
216225
# Resolve the absolute directory path
217226
path = os.path.join(abs_root, name)
218227

219228
# Exclude the directory and all subdirectories
220-
if self._is_excluded(_resolve_pattern(path)):
221-
dirnames.remove(name)
222-
continue
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
229-
sitemap_gz = os.path.join(path, "sitemap.xml.gz")
230-
if os.path.exists(sitemap_gz):
231-
log.debug(f"Excluded site_dir: {path}")
229+
if self._is_excluded(path):
232230
dirnames.remove(name)
233231

234232
# Write files to the in-memory archive
@@ -237,13 +235,16 @@ def on_config(self, config):
237235
path = os.path.join(abs_root, name)
238236

239237
# Exclude the file
240-
if self._is_excluded(_resolve_pattern(path)):
238+
if self._is_excluded(path):
241239
continue
242240

243241
# Resolve the relative path to create a matching structure
244242
path = os.path.relpath(path, os.path.curdir)
245243
f.write(path, os.path.join(example, path))
246244

245+
# Clear the line for the next indicator
246+
print(" " * len(indicator), end="\r", flush=True)
247+
247248
# Add information on installed packages
248249
f.writestr(
249250
os.path.join(example, "requirements.lock.txt"),
@@ -261,11 +262,14 @@ def on_config(self, config):
261262
"system": platform.platform(),
262263
"architecture": platform.architecture(),
263264
"python": platform.python_version(),
265+
"cwd": os.getcwd(),
264266
"command": " ".join([
265267
sys.argv[0].rsplit(os.sep, 1)[-1],
266268
*sys.argv[1:]
267269
]),
268-
"sys.path": sys.path
270+
"env:$PYTHONPATH": os.getenv("PYTHONPATH", ""),
271+
"sys.path": sys.path,
272+
"excluded_entries": self.excluded_entries
269273
},
270274
default = str,
271275
indent = 2
@@ -363,24 +367,45 @@ def _help_on_not_in_cwd(self, outside_root):
363367
print(Style.NORMAL)
364368
for path in outside_root:
365369
print(f" {path}")
366-
print(" \nTo assure that all project files are found please adjust")
370+
print("\n To assure that all project files are found please adjust")
367371
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")
372+
print(" root directory of the project.")
373+
print("\n Please also make sure `mkdocs build` is run in the actual")
370374
print(" root directory of the project.")
371375
print(Style.RESET_ALL)
372376

373377
# Exit, unless explicitly told not to
374378
if self.config.archive_stop_on_violation:
375379
sys.exit(1)
376380

377-
# Exclude files which we don't want in our zip file
378-
def _is_excluded(self, posix_path: str) -> bool:
381+
# Check if path is excluded and should be omitted from the zip. Use pattern
382+
# matching for files and folders, and lookahead specific files in folders to
383+
# skip them. Side effect: Save excluded paths to save them in the zip file.
384+
def _is_excluded(self, abspath: str) -> bool:
385+
386+
# Resolve the path into POSIX format to match the patterns
387+
pattern_path = _resolve_pattern(abspath, return_path = True)
388+
379389
for pattern in self.exclusion_patterns:
380-
if regex.match(pattern, posix_path):
381-
log.debug(f"Excluded pattern '{pattern}': {posix_path}")
390+
if regex.search(pattern, pattern_path):
391+
log.debug(f"Excluded pattern '{pattern}': {abspath}")
392+
self.excluded_entries.append(f"{pattern} - {pattern_path}")
382393
return True
383394

395+
# File exclusion should be limited to pattern matching
396+
if os.path.isfile(abspath):
397+
return False
398+
399+
# Projects, which don't use the projects plugin for multi-language
400+
# support could have separate build folders for each config file or
401+
# language. Therefore, we exclude them with the assumption a site_dir
402+
# contains the sitemap file. Example of such a setup: https://t.ly/DLQcy
403+
sitemap_gz = os.path.join(abspath, "sitemap.xml.gz")
404+
if os.path.exists(sitemap_gz):
405+
log.debug(f"Excluded site_dir: {abspath}")
406+
self.excluded_entries.append(f"sitemap.xml.gz - {pattern_path}")
407+
return True
408+
384409
return False
385410

386411
# -----------------------------------------------------------------------------
@@ -435,31 +460,22 @@ def _load_yaml(abs_src_path: str):
435460

436461
return result
437462

438-
# Load info.gitignore, ignore any empty lines or # comments
439-
def _load_exclusion_patterns(path: str = None):
440-
if path is None:
441-
path = os.path.dirname(os.path.abspath(__file__))
442-
path = os.path.join(path, "info.gitignore")
443-
444-
with open(path, encoding = "utf-8") as file:
445-
lines = map(str.strip, file.readlines())
446-
447-
return [line for line in lines if line and not line.startswith("#")]
448-
449463
# Get a normalized POSIX path for the pattern matching with removed current
450464
# working directory prefix. Directory paths end with a '/' to allow more control
451-
# in the pattern creation for files and directories.
452-
def _resolve_pattern(abspath: str):
453-
path = abspath.replace(os.getcwd(), "", 1).replace(os.sep, "/")
465+
# in the pattern creation for files and directories. The patterns are matched
466+
# using the search function, so they are prefixed with ^ for specificity.
467+
def _resolve_pattern(abspath: str, return_path: bool = False):
468+
path = abspath.replace(os.getcwd(), "", 1)
469+
path = path.replace(os.sep, "/").rstrip("/")
454470

455471
if not path:
456472
return "/"
457473

458474
# Check abspath, as the file needs to exist
459475
if not os.path.isfile(abspath):
460-
return path.rstrip("/") + "/"
476+
path = path + "/"
461477

462-
return path
478+
return path if return_path else f"^{path}"
463479

464480
# Get project configuration with resolved absolute paths for validation
465481
def _get_project_config(project_config_file: str):

Diff for: src/plugins/info/info.gitignore

-37
This file was deleted.

Diff for: src/plugins/info/patterns.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
def get_exclusion_patterns():
2+
"""
3+
Regex patterns, which will be compared against directory and file names
4+
case-sensitively. https://docs.python.org/3/library/re.html#re.search is the
5+
matching function and scans the whole string to find any pattern match. Used
6+
with the https://pypi.org/project/regex/ module.
7+
8+
Additional remarks for pattern creation:
9+
- The compared paths will be always in POSIX format.
10+
- Each directory path will have a / at the end to allow to distinguish them
11+
from files.
12+
- Patterns for dynamic or custom paths like Virtual Environments (venv) or
13+
build site directories are created during plugin runtime.
14+
"""
15+
return [
16+
r"/__pycache__/", # Python cache directory
17+
18+
r"/\.DS_Store$", # macOS
19+
20+
r"/[^/]+\.zip$", # Generated files and folders
21+
22+
r"/[^/]*\.cache($|/)", # .cache files and folders
23+
24+
r"/\.vscode/", # Common autogenerated IDE directories
25+
r"/\.vs/",
26+
r"/\.idea/",
27+
]

0 commit comments

Comments
 (0)