Skip to content

Commit 79129d5

Browse files
kamilkrzyskowsquidfunk
authored andcommitted
Added exclusion logic for info plugin
1 parent 64a8b6a commit 79129d5

File tree

4 files changed

+224
-0
lines changed

4 files changed

+224
-0
lines changed

material/plugins/info/info.gitignore

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# 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+
# ---
16+
17+
# Byte-compiled / optimized / DLL files
18+
# Python cache directory
19+
.*__pycache__/
20+
21+
# macOS
22+
23+
.*\.DS_Store
24+
25+
# .dotfiles in the root directory
26+
27+
^/\.[^/]+$
28+
29+
# Generated files and folders
30+
31+
^/.*\.zip
32+
33+
# Allow .github or .devcontainer directories
34+
# Exclude .cache files and folders
35+
# Exclude known IDE directories
36+
37+
.*\.cache/?
38+
^/\.vscode/
39+
^/\.vs/
40+
^/\.idea/

material/plugins/info/plugin.py

+72
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import os
2424
import platform
2525
import requests
26+
import site
2627
import sys
2728

2829
from colorama import Fore, Style
@@ -31,6 +32,7 @@
3132
from markdown.extensions.toc import slugify
3233
from mkdocs.plugins import BasePlugin, event_priority
3334
from mkdocs.utils import get_theme_dir
35+
import regex
3436
from zipfile import ZipFile, ZIP_DEFLATED
3537

3638
from .config import InfoConfig
@@ -49,6 +51,9 @@ def __init__(self, *args, **kwargs):
4951
# Initialize incremental builds
5052
self.is_serve = False
5153

54+
# Initialize empty members
55+
self.exclusion_patterns = []
56+
5257
# Determine whether we're serving the site
5358
def on_startup(self, *, command, dirty):
5459
self.is_serve = command == "serve"
@@ -111,12 +116,42 @@ def on_config(self, config):
111116
example, _ = os.path.splitext(example)
112117
example = "-".join([present, slugify(example, "-")])
113118

119+
# Load exclusion patterns
120+
self.exclusion_patterns = _load_exclusion_patterns()
121+
122+
# Exclude the site_dir at project root
123+
if config.site_dir.startswith(os.getcwd()):
124+
self.exclusion_patterns.append(_resolve_pattern(config.site_dir))
125+
126+
# Exclude the site-packages directory
127+
for path in site.getsitepackages():
128+
if path.startswith(os.getcwd()):
129+
self.exclusion_patterns.append(_resolve_pattern(path))
130+
114131
# Create self-contained example from project
115132
files: list[str] = []
116133
with ZipFile(archive, "a", ZIP_DEFLATED, False) as f:
117134
for abs_root, dirnames, filenames in os.walk(os.getcwd()):
135+
# Prune the folders in-place to prevent
136+
# scanning excluded folders
137+
for name in list(dirnames):
138+
path = os.path.join(abs_root, name)
139+
if self._is_excluded(_resolve_pattern(path)):
140+
dirnames.remove(name)
141+
continue
142+
# Multi-language setup from #2346 separates the
143+
# language config, so each mkdocs.yml file is
144+
# unaware of other site_dir directories. Therefore,
145+
# we add this with the assumption a site_dir contains
146+
# the sitemap file.
147+
sitemap_gz = os.path.join(path, "sitemap.xml.gz")
148+
if os.path.exists(sitemap_gz):
149+
log.debug(f"Excluded site_dir: {path}")
150+
dirnames.remove(name)
118151
for name in filenames:
119152
path = os.path.join(abs_root, name)
153+
if self._is_excluded(_resolve_pattern(path)):
154+
continue
120155
path = os.path.relpath(path, os.path.curdir)
121156
f.write(path, os.path.join(example, path))
122157

@@ -225,6 +260,15 @@ def _help_on_customizations_and_exit(self):
225260
if self.config.archive_stop_on_violation:
226261
sys.exit(1)
227262

263+
# Exclude files, which we don't want in our zip file
264+
def _is_excluded(self, posix_path: str) -> bool:
265+
for pattern in self.exclusion_patterns:
266+
if regex.match(pattern, posix_path):
267+
log.debug(f"Excluded pattern '{pattern}': {posix_path}")
268+
return True
269+
270+
return False
271+
228272
# -----------------------------------------------------------------------------
229273
# Helper functions
230274
# -----------------------------------------------------------------------------
@@ -239,6 +283,34 @@ def _size(value, factor = 1):
239283
return f"{color}{value:3.1f} {unit}"
240284
value /= 1000.0
241285

286+
# Load info.gitignore, ignore any empty lines or # comments
287+
def _load_exclusion_patterns(path: str = None):
288+
if path is None:
289+
path = os.path.dirname(os.path.abspath(__file__))
290+
path = os.path.join(path, "info.gitignore")
291+
292+
with open(path, encoding = "utf-8") as file:
293+
lines = map(str.strip, file.readlines())
294+
295+
return [line for line in lines if line and not line.startswith("#")]
296+
297+
# For the pattern matching it is best to remove the CWD
298+
# prefix and keep only the relative root of the reproduction.
299+
# Additionally, as the patterns are in POSIX format,
300+
# assure that the path is also in POSIX format.
301+
# Side-effect: It appends "/" for directory patterns.
302+
def _resolve_pattern(abspath: str):
303+
path = abspath.replace(os.getcwd(), "", 1).replace(os.sep, "/")
304+
305+
if not path:
306+
return "/"
307+
308+
# Check abspath, as the file needs to exist
309+
if not os.path.isfile(abspath):
310+
return path.rstrip("/") + "/"
311+
312+
return path
313+
242314
# -----------------------------------------------------------------------------
243315
# Data
244316
# -----------------------------------------------------------------------------

src/plugins/info/info.gitignore

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# 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+
# ---
16+
17+
# Byte-compiled / optimized / DLL files
18+
# Python cache directory
19+
.*__pycache__/
20+
21+
# macOS
22+
23+
.*\.DS_Store
24+
25+
# .dotfiles in the root directory
26+
27+
^/\.[^/]+$
28+
29+
# Generated files and folders
30+
31+
^/.*\.zip
32+
33+
# Allow .github or .devcontainer directories
34+
# Exclude .cache files and folders
35+
# Exclude known IDE directories
36+
37+
.*\.cache/?
38+
^/\.vscode/
39+
^/\.vs/
40+
^/\.idea/

src/plugins/info/plugin.py

+72
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import os
2424
import platform
2525
import requests
26+
import site
2627
import sys
2728

2829
from colorama import Fore, Style
@@ -31,6 +32,7 @@
3132
from markdown.extensions.toc import slugify
3233
from mkdocs.plugins import BasePlugin, event_priority
3334
from mkdocs.utils import get_theme_dir
35+
import regex
3436
from zipfile import ZipFile, ZIP_DEFLATED
3537

3638
from .config import InfoConfig
@@ -49,6 +51,9 @@ def __init__(self, *args, **kwargs):
4951
# Initialize incremental builds
5052
self.is_serve = False
5153

54+
# Initialize empty members
55+
self.exclusion_patterns = []
56+
5257
# Determine whether we're serving the site
5358
def on_startup(self, *, command, dirty):
5459
self.is_serve = command == "serve"
@@ -111,12 +116,42 @@ def on_config(self, config):
111116
example, _ = os.path.splitext(example)
112117
example = "-".join([present, slugify(example, "-")])
113118

119+
# Load exclusion patterns
120+
self.exclusion_patterns = _load_exclusion_patterns()
121+
122+
# Exclude the site_dir at project root
123+
if config.site_dir.startswith(os.getcwd()):
124+
self.exclusion_patterns.append(_resolve_pattern(config.site_dir))
125+
126+
# Exclude the site-packages directory
127+
for path in site.getsitepackages():
128+
if path.startswith(os.getcwd()):
129+
self.exclusion_patterns.append(_resolve_pattern(path))
130+
114131
# Create self-contained example from project
115132
files: list[str] = []
116133
with ZipFile(archive, "a", ZIP_DEFLATED, False) as f:
117134
for abs_root, dirnames, filenames in os.walk(os.getcwd()):
135+
# Prune the folders in-place to prevent
136+
# scanning excluded folders
137+
for name in list(dirnames):
138+
path = os.path.join(abs_root, name)
139+
if self._is_excluded(_resolve_pattern(path)):
140+
dirnames.remove(name)
141+
continue
142+
# Multi-language setup from #2346 separates the
143+
# language config, so each mkdocs.yml file is
144+
# unaware of other site_dir directories. Therefore,
145+
# we add this with the assumption a site_dir contains
146+
# the sitemap file.
147+
sitemap_gz = os.path.join(path, "sitemap.xml.gz")
148+
if os.path.exists(sitemap_gz):
149+
log.debug(f"Excluded site_dir: {path}")
150+
dirnames.remove(name)
118151
for name in filenames:
119152
path = os.path.join(abs_root, name)
153+
if self._is_excluded(_resolve_pattern(path)):
154+
continue
120155
path = os.path.relpath(path, os.path.curdir)
121156
f.write(path, os.path.join(example, path))
122157

@@ -225,6 +260,15 @@ def _help_on_customizations_and_exit(self):
225260
if self.config.archive_stop_on_violation:
226261
sys.exit(1)
227262

263+
# Exclude files, which we don't want in our zip file
264+
def _is_excluded(self, posix_path: str) -> bool:
265+
for pattern in self.exclusion_patterns:
266+
if regex.match(pattern, posix_path):
267+
log.debug(f"Excluded pattern '{pattern}': {posix_path}")
268+
return True
269+
270+
return False
271+
228272
# -----------------------------------------------------------------------------
229273
# Helper functions
230274
# -----------------------------------------------------------------------------
@@ -239,6 +283,34 @@ def _size(value, factor = 1):
239283
return f"{color}{value:3.1f} {unit}"
240284
value /= 1000.0
241285

286+
# Load info.gitignore, ignore any empty lines or # comments
287+
def _load_exclusion_patterns(path: str = None):
288+
if path is None:
289+
path = os.path.dirname(os.path.abspath(__file__))
290+
path = os.path.join(path, "info.gitignore")
291+
292+
with open(path, encoding = "utf-8") as file:
293+
lines = map(str.strip, file.readlines())
294+
295+
return [line for line in lines if line and not line.startswith("#")]
296+
297+
# For the pattern matching it is best to remove the CWD
298+
# prefix and keep only the relative root of the reproduction.
299+
# Additionally, as the patterns are in POSIX format,
300+
# assure that the path is also in POSIX format.
301+
# Side-effect: It appends "/" for directory patterns.
302+
def _resolve_pattern(abspath: str):
303+
path = abspath.replace(os.getcwd(), "", 1).replace(os.sep, "/")
304+
305+
if not path:
306+
return "/"
307+
308+
# Check abspath, as the file needs to exist
309+
if not os.path.isfile(abspath):
310+
return path.rstrip("/") + "/"
311+
312+
return path
313+
242314
# -----------------------------------------------------------------------------
243315
# Data
244316
# -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)