39
39
from zipfile import ZipFile , ZIP_DEFLATED
40
40
41
41
from .config import InfoConfig
42
+ from .patterns import get_exclusion_patterns
42
43
43
44
# -----------------------------------------------------------------------------
44
45
# Classes
@@ -56,6 +57,7 @@ def __init__(self, *args, **kwargs):
56
57
57
58
# Initialize empty members
58
59
self .exclusion_patterns = []
60
+ self .excluded_entries = []
59
61
60
62
# Determine whether we're serving the site
61
63
def on_startup (self , * , command , dirty ):
@@ -183,15 +185,18 @@ def on_config(self, config):
183
185
example , _ = os .path .splitext (example )
184
186
example = "-" .join ([present , slugify (example , "-" )])
185
187
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 = []
188
191
189
192
# Exclude the site_dir at project root
190
193
if config .site_dir .startswith (os .getcwd ()):
191
194
self .exclusion_patterns .append (_resolve_pattern (config .site_dir ))
192
195
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 :
195
200
if path .startswith (os .getcwd ()):
196
201
self .exclusion_patterns .append (_resolve_pattern (path ))
197
202
@@ -211,24 +216,17 @@ def on_config(self, config):
211
216
files : list [str ] = []
212
217
with ZipFile (archive , "a" , ZIP_DEFLATED , False ) as f :
213
218
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
+
214
223
# Prune the folders in-place to prevent their processing
215
224
for name in list (dirnames ):
216
225
# Resolve the absolute directory path
217
226
path = os .path .join (abs_root , name )
218
227
219
228
# 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 ):
232
230
dirnames .remove (name )
233
231
234
232
# Write files to the in-memory archive
@@ -237,13 +235,16 @@ def on_config(self, config):
237
235
path = os .path .join (abs_root , name )
238
236
239
237
# Exclude the file
240
- if self ._is_excluded (_resolve_pattern ( path ) ):
238
+ if self ._is_excluded (path ):
241
239
continue
242
240
243
241
# Resolve the relative path to create a matching structure
244
242
path = os .path .relpath (path , os .path .curdir )
245
243
f .write (path , os .path .join (example , path ))
246
244
245
+ # Clear the line for the next indicator
246
+ print (" " * len (indicator ), end = "\r " , flush = True )
247
+
247
248
# Add information on installed packages
248
249
f .writestr (
249
250
os .path .join (example , "requirements.lock.txt" ),
@@ -261,11 +262,14 @@ def on_config(self, config):
261
262
"system" : platform .platform (),
262
263
"architecture" : platform .architecture (),
263
264
"python" : platform .python_version (),
265
+ "cwd" : os .getcwd (),
264
266
"command" : " " .join ([
265
267
sys .argv [0 ].rsplit (os .sep , 1 )[- 1 ],
266
268
* sys .argv [1 :]
267
269
]),
268
- "sys.path" : sys .path
270
+ "env:$PYTHONPATH" : os .getenv ("PYTHONPATH" , "" ),
271
+ "sys.path" : sys .path ,
272
+ "excluded_entries" : self .excluded_entries
269
273
},
270
274
default = str ,
271
275
indent = 2
@@ -363,24 +367,45 @@ def _help_on_not_in_cwd(self, outside_root):
363
367
print (Style .NORMAL )
364
368
for path in outside_root :
365
369
print (f" { path } " )
366
- print (" \n To assure that all project files are found please adjust" )
370
+ print ("\n To assure that all project files are found please adjust" )
367
371
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" )
370
374
print (" root directory of the project." )
371
375
print (Style .RESET_ALL )
372
376
373
377
# Exit, unless explicitly told not to
374
378
if self .config .archive_stop_on_violation :
375
379
sys .exit (1 )
376
380
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
+
379
389
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 } " )
382
393
return True
383
394
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
+
384
409
return False
385
410
386
411
# -----------------------------------------------------------------------------
@@ -435,31 +460,22 @@ def _load_yaml(abs_src_path: str):
435
460
436
461
return result
437
462
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
-
449
463
# Get a normalized POSIX path for the pattern matching with removed current
450
464
# 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 ("/" )
454
470
455
471
if not path :
456
472
return "/"
457
473
458
474
# Check abspath, as the file needs to exist
459
475
if not os .path .isfile (abspath ):
460
- return path . rstrip ( "/" ) + "/"
476
+ path = path + "/"
461
477
462
- return path
478
+ return path if return_path else f"^ { path } "
463
479
464
480
# Get project configuration with resolved absolute paths for validation
465
481
def _get_project_config (project_config_file : str ):
0 commit comments