10
10
import operator
11
11
import functools
12
12
import itertools
13
+ import posixpath
13
14
import collections
14
15
15
16
from configparser import ConfigParser
@@ -371,10 +372,6 @@ def path(self):
371
372
"""
372
373
return vars (self ).get ('path' , sys .path )
373
374
374
- @property
375
- def pattern (self ):
376
- return '.*' if self .name is None else re .escape (self .name )
377
-
378
375
@abc .abstractmethod
379
376
def find_distributions (self , context = Context ()):
380
377
"""
@@ -386,6 +383,73 @@ def find_distributions(self, context=Context()):
386
383
"""
387
384
388
385
386
+ class FastPath :
387
+ """
388
+ Micro-optimized class for searching a path for
389
+ children.
390
+ """
391
+
392
+ def __init__ (self , root ):
393
+ self .root = root
394
+
395
+ def joinpath (self , child ):
396
+ return pathlib .Path (self .root , child )
397
+
398
+ def children (self ):
399
+ with suppress (Exception ):
400
+ return os .listdir (self .root or '' )
401
+ with suppress (Exception ):
402
+ return self .zip_children ()
403
+ return []
404
+
405
+ def zip_children (self ):
406
+ zip_path = zipfile .Path (self .root )
407
+ names = zip_path .root .namelist ()
408
+ self .joinpath = zip_path .joinpath
409
+
410
+ return (
411
+ posixpath .split (child )[0 ]
412
+ for child in names
413
+ )
414
+
415
+ def is_egg (self , search ):
416
+ root_n_low = os .path .split (self .root )[1 ].lower ()
417
+
418
+ return (
419
+ root_n_low == search .normalized + '.egg'
420
+ or root_n_low .startswith (search .prefix )
421
+ and root_n_low .endswith ('.egg' ))
422
+
423
+ def search (self , name ):
424
+ for child in self .children ():
425
+ n_low = child .lower ()
426
+ if (n_low in name .exact_matches
427
+ or n_low .startswith (name .prefix )
428
+ and n_low .endswith (name .suffixes )
429
+ # legacy case:
430
+ or self .is_egg (name ) and n_low == 'egg-info' ):
431
+ yield self .joinpath (child )
432
+
433
+
434
+ class Prepared :
435
+ """
436
+ A prepared search for metadata on a possibly-named package.
437
+ """
438
+ normalized = ''
439
+ prefix = ''
440
+ suffixes = '.dist-info' , '.egg-info'
441
+ exact_matches = ['' ][:0 ]
442
+
443
+ def __init__ (self , name ):
444
+ self .name = name
445
+ if name is None :
446
+ return
447
+ self .normalized = name .lower ().replace ('-' , '_' )
448
+ self .prefix = self .normalized + '-'
449
+ self .exact_matches = [
450
+ self .normalized + suffix for suffix in self .suffixes ]
451
+
452
+
389
453
class MetadataPathFinder (DistributionFinder ):
390
454
@classmethod
391
455
def find_distributions (cls , context = DistributionFinder .Context ()):
@@ -397,45 +461,17 @@ def find_distributions(cls, context=DistributionFinder.Context()):
397
461
(or all names if ``None`` indicated) along the paths in the list
398
462
of directories ``context.path``.
399
463
"""
400
- found = cls ._search_paths (context .pattern , context .path )
464
+ found = cls ._search_paths (context .name , context .path )
401
465
return map (PathDistribution , found )
402
466
403
467
@classmethod
404
- def _search_paths (cls , pattern , paths ):
468
+ def _search_paths (cls , name , paths ):
405
469
"""Find metadata directories in paths heuristically."""
406
470
return itertools .chain .from_iterable (
407
- cls . _search_path ( path , pattern )
408
- for path in map (cls . _switch_path , paths )
471
+ path . search ( Prepared ( name ) )
472
+ for path in map (FastPath , paths )
409
473
)
410
474
411
- @staticmethod
412
- def _switch_path (path ):
413
- PYPY_OPEN_BUG = False
414
- if not PYPY_OPEN_BUG or os .path .isfile (path ): # pragma: no branch
415
- with suppress (Exception ):
416
- return zipfile .Path (path )
417
- return pathlib .Path (path )
418
-
419
- @classmethod
420
- def _matches_info (cls , normalized , item ):
421
- template = r'{pattern}(-.*)?\.(dist|egg)-info'
422
- manifest = template .format (pattern = normalized )
423
- return re .match (manifest , item .name , flags = re .IGNORECASE )
424
-
425
- @classmethod
426
- def _matches_legacy (cls , normalized , item ):
427
- template = r'{pattern}-.*\.egg[\\/]EGG-INFO'
428
- manifest = template .format (pattern = normalized )
429
- return re .search (manifest , str (item ), flags = re .IGNORECASE )
430
-
431
- @classmethod
432
- def _search_path (cls , root , pattern ):
433
- if not root .is_dir ():
434
- return ()
435
- normalized = pattern .replace ('-' , '_' )
436
- return (item for item in root .iterdir ()
437
- if cls ._matches_info (normalized , item )
438
- or cls ._matches_legacy (normalized , item ))
439
475
440
476
441
477
class PathDistribution (Distribution ):
0 commit comments