3939from zipfile import ZipFile , ZIP_DEFLATED
4040
4141from .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 (" \n To 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
465481def _get_project_config (project_config_file : str ):
0 commit comments