2
2
3
3
import os .path
4
4
5
- from typing import List , Sequence , Set , Tuple , Optional
5
+ from typing import List , Sequence , Set , Tuple , Optional , Dict
6
6
7
7
from mypy .build import BuildSource , PYTHON_EXTENSIONS
8
8
from mypy .fscache import FileSystemMetaCache
@@ -17,20 +17,23 @@ class InvalidSourceList(Exception):
17
17
18
18
19
19
def create_source_list (files : Sequence [str ], options : Options ,
20
- fscache : Optional [FileSystemMetaCache ] = None ) -> List [BuildSource ]:
20
+ fscache : Optional [FileSystemMetaCache ] = None ,
21
+ allow_empty_dir : bool = False ) -> List [BuildSource ]:
21
22
"""From a list of source files/directories, makes a list of BuildSources.
22
23
23
24
Raises InvalidSourceList on errors.
24
25
"""
25
26
fscache = fscache or FileSystemMetaCache ()
27
+ finder = SourceFinder (fscache )
28
+
26
29
targets = []
27
30
for f in files :
28
31
if f .endswith (PY_EXTENSIONS ):
29
32
# Can raise InvalidSourceList if a directory doesn't have a valid module name.
30
- targets .append (BuildSource (f , crawl_up (fscache , f )[ 1 ] , None ))
33
+ targets .append (BuildSource (f , finder . crawl_up (f ) , None ))
31
34
elif fscache .isdir (f ):
32
- sub_targets = expand_dir (fscache , f )
33
- if not sub_targets :
35
+ sub_targets = finder . expand_dir (f )
36
+ if not sub_targets and not allow_empty_dir :
34
37
raise InvalidSourceList ("There are no .py[i] files in directory '{}'"
35
38
.format (f ))
36
39
targets .extend (sub_targets )
@@ -52,60 +55,101 @@ def keyfunc(name: str) -> Tuple[int, str]:
52
55
return (- 1 , name )
53
56
54
57
55
- def expand_dir (fscache : FileSystemMetaCache ,
56
- arg : str , mod_prefix : str = '' ) -> List [BuildSource ]:
57
- """Convert a directory name to a list of sources to build."""
58
- f = get_init_file (fscache , arg )
59
- if mod_prefix and not f :
60
- return []
61
- seen = set () # type: Set[str]
62
- sources = []
63
- if f and not mod_prefix :
64
- top_dir , top_mod = crawl_up (fscache , f )
65
- mod_prefix = top_mod + '.'
66
- if mod_prefix :
67
- sources .append (BuildSource (f , mod_prefix .rstrip ('.' ), None ))
68
- names = fscache .listdir (arg )
69
- names .sort (key = keyfunc )
70
- for name in names :
71
- path = os .path .join (arg , name )
72
- if fscache .isdir (path ):
73
- sub_sources = expand_dir (fscache , path , mod_prefix + name + '.' )
74
- if sub_sources :
75
- seen .add (name )
76
- sources .extend (sub_sources )
58
+ class SourceFinder :
59
+ def __init__ (self , fscache : FileSystemMetaCache ) -> None :
60
+ self .fscache = fscache
61
+ # A cache for package names, mapping from module id to directory path
62
+ self .package_cache = {} # type: Dict[str, str]
63
+
64
+ def expand_dir (self , arg : str , mod_prefix : str = '' ) -> List [BuildSource ]:
65
+ """Convert a directory name to a list of sources to build."""
66
+ f = self .get_init_file (arg )
67
+ if mod_prefix and not f :
68
+ return []
69
+ seen = set () # type: Set[str]
70
+ sources = []
71
+ if f and not mod_prefix :
72
+ top_mod = self .crawl_up (f )
73
+ mod_prefix = top_mod + '.'
74
+ if mod_prefix :
75
+ sources .append (BuildSource (f , mod_prefix .rstrip ('.' ), None ))
76
+ names = self .fscache .listdir (arg )
77
+ names .sort (key = keyfunc )
78
+ for name in names :
79
+ path = os .path .join (arg , name )
80
+ if self .fscache .isdir (path ):
81
+ sub_sources = self .expand_dir (path , mod_prefix + name + '.' )
82
+ if sub_sources :
83
+ seen .add (name )
84
+ sources .extend (sub_sources )
85
+ else :
86
+ base , suffix = os .path .splitext (name )
87
+ if base == '__init__' :
88
+ continue
89
+ if base not in seen and '.' not in base and suffix in PY_EXTENSIONS :
90
+ seen .add (base )
91
+ src = BuildSource (path , mod_prefix + base , None )
92
+ sources .append (src )
93
+ return sources
94
+
95
+ def crawl_up (self , arg : str ) -> str :
96
+ """Given a .py[i] filename, return module.
97
+
98
+ We crawl up the path until we find a directory without
99
+ __init__.py[i], or until we run out of path components.
100
+ """
101
+ dir , mod = os .path .split (arg )
102
+ mod = strip_py (mod ) or mod
103
+ base = self .crawl_up_dir (dir )
104
+ if mod == '__init__' or not mod :
105
+ mod = base
77
106
else :
78
- base , suffix = os .path .splitext (name )
79
- if base == '__init__' :
80
- continue
81
- if base not in seen and '.' not in base and suffix in PY_EXTENSIONS :
82
- seen .add (base )
83
- src = BuildSource (path , mod_prefix + base , None )
84
- sources .append (src )
85
- return sources
107
+ mod = module_join (base , mod )
86
108
109
+ return mod
87
110
88
- def crawl_up ( fscache : FileSystemMetaCache , arg : str ) -> Tuple [ str , str ] :
89
- """Given a .py[i] filename , return (root directory, module) .
111
+ def crawl_up_dir ( self , dir : str ) -> str :
112
+ """Given a directory name , return the corresponding module name .
90
113
91
- We crawl up the path until we find a directory without
92
- __init__.py[i], or until we run out of path components.
93
- """
94
- dir , mod = os .path .split (arg )
95
- mod = strip_py (mod ) or mod
96
- while dir and get_init_file (fscache , dir ):
97
- dir , base = os .path .split (dir )
98
- if not base :
99
- break
100
- # Ensure that base is a valid python module name
101
- if not base .isidentifier ():
102
- raise InvalidSourceList ('{} is not a valid Python package name' .format (base ))
103
- if mod == '__init__' or not mod :
104
- mod = base
114
+ Use package_cache to cache results.
115
+ """
116
+ if dir in self .package_cache :
117
+ return self .package_cache [dir ]
118
+
119
+ parent_dir , base = os .path .split (dir )
120
+ if not dir or not self .get_init_file (dir ) or not base :
121
+ res = ''
105
122
else :
106
- mod = base + '.' + mod
123
+ # Ensure that base is a valid python module name
124
+ if not base .isidentifier ():
125
+ raise InvalidSourceList ('{} is not a valid Python package name' .format (base ))
126
+ parent = self .crawl_up_dir (parent_dir )
127
+ res = module_join (parent , base )
128
+
129
+ self .package_cache [dir ] = res
130
+ return res
131
+
132
+ def get_init_file (self , dir : str ) -> Optional [str ]:
133
+ """Check whether a directory contains a file named __init__.py[i].
134
+
135
+ If so, return the file's name (with dir prefixed). If not, return
136
+ None.
107
137
108
- return dir , mod
138
+ This prefers .pyi over .py (because of the ordering of PY_EXTENSIONS).
139
+ """
140
+ for ext in PY_EXTENSIONS :
141
+ f = os .path .join (dir , '__init__' + ext )
142
+ if self .fscache .isfile (f ):
143
+ return f
144
+ return None
145
+
146
+
147
+ def module_join (parent : str , child : str ) -> str :
148
+ """Join module ids, accounting for a possibly empty parent."""
149
+ if parent :
150
+ return parent + '.' + child
151
+ else :
152
+ return child
109
153
110
154
111
155
def strip_py (arg : str ) -> Optional [str ]:
@@ -117,18 +161,3 @@ def strip_py(arg: str) -> Optional[str]:
117
161
if arg .endswith (ext ):
118
162
return arg [:- len (ext )]
119
163
return None
120
-
121
-
122
- def get_init_file (fscache : FileSystemMetaCache , dir : str ) -> Optional [str ]:
123
- """Check whether a directory contains a file named __init__.py[i].
124
-
125
- If so, return the file's name (with dir prefixed). If not, return
126
- None.
127
-
128
- This prefers .pyi over .py (because of the ordering of PY_EXTENSIONS).
129
- """
130
- for ext in PY_EXTENSIONS :
131
- f = os .path .join (dir , '__init__' + ext )
132
- if fscache .isfile (f ):
133
- return f
134
- return None
0 commit comments