1
1
from collections import OrderedDict
2
+ import re
2
3
import pprint
3
4
import sys
4
5
5
- from typing import Dict , List , Mapping , MutableMapping , Optional , Set , Tuple
6
+ from typing import Dict , List , Mapping , MutableMapping , Optional , Pattern , Set , Tuple
6
7
7
8
from mypy import defaults
8
9
@@ -167,8 +168,8 @@ def __init__(self) -> None:
167
168
self .plugins = [] # type: List[str]
168
169
169
170
# Per-module options (raw)
170
- pm_opts = OrderedDict () # type: OrderedDict[str, Dict[str, object]]
171
- self .per_module_options = pm_opts
171
+ self . per_module_options = OrderedDict () # type: OrderedDict[str, Dict[str, object]]
172
+ self .glob_options = [] # type: List[Tuple[str, Pattern[str]]]
172
173
self .unused_configs = set () # type: Set[str]
173
174
174
175
# -- development options --
@@ -208,27 +209,56 @@ def __ne__(self, other: object) -> bool:
208
209
def __repr__ (self ) -> str :
209
210
return 'Options({})' .format (pprint .pformat (self .snapshot ()))
210
211
212
+ def apply_changes (self , changes : Dict [str , object ]) -> 'Options' :
213
+ new_options = Options ()
214
+ new_options .__dict__ .update (self .__dict__ )
215
+ new_options .__dict__ .update (changes )
216
+ return new_options
217
+
211
218
def build_per_module_cache (self ) -> None :
212
219
self .per_module_cache = {}
213
- # Since configs inherit from glob configs above them in the hierarchy,
220
+
221
+ # Config precedence is as follows:
222
+ # 1. Concrete section names: foo.bar.baz
223
+ # 2. "Unstructured" glob patterns: foo.*.baz, in the order
224
+ # they appear in the file (last wins)
225
+ # 3. "Well-structured" wildcard patterns: foo.bar.*, in specificity order.
226
+
227
+ # Since structured configs inherit from structured configs above them in the hierarchy,
214
228
# we need to process per-module configs in a careful order.
215
- # We have to process foo.* before foo.bar.* before foo.bar.
216
- # To do this, process all glob configs before non-glob configs and
229
+ # We have to process foo.* before foo.bar.* before foo.bar,
230
+ # and we need to apply *.bar to foo.bar but not to foo.bar.*.
231
+ # To do this, process all well-structured glob configs before non-glob configs and
217
232
# exploit the fact that foo.* sorts earlier ASCIIbetically (unicodebetically?)
218
233
# than foo.bar.*.
219
- keys = (sorted (k for k in self .per_module_options .keys () if k .endswith ('.*' )) +
220
- [k for k in self .per_module_options .keys () if not k .endswith ('.*' )])
221
- for key in keys :
234
+ # (A section being "processed last" results in its config "winning".)
235
+ # Unstructured glob configs are stored and are all checked for each module.
236
+ unstructured_glob_keys = [k for k in self .per_module_options .keys ()
237
+ if '*' in k [:- 1 ]]
238
+ structured_keys = [k for k in self .per_module_options .keys ()
239
+ if '*' not in k [:- 1 ]]
240
+ wildcards = sorted (k for k in structured_keys if k .endswith ('.*' ))
241
+ concrete = [k for k in structured_keys if not k .endswith ('.*' )]
242
+
243
+ for glob in unstructured_glob_keys :
244
+ self .glob_options .append ((glob , self .compile_glob (glob )))
245
+
246
+ # We (for ease of implementation) treat unstructured glob
247
+ # sections as used if any real modules use them or if any
248
+ # concrete config sections use them. This means we need to
249
+ # track which get used while constructing.
250
+ self .unused_configs = set (unstructured_glob_keys )
251
+
252
+ for key in wildcards + concrete :
222
253
# Find what the options for this key would be, just based
223
254
# on inheriting from parent configs.
224
255
options = self .clone_for_module (key )
225
256
# And then update it with its per-module options.
226
- new_options = Options ()
227
- new_options .__dict__ .update (options .__dict__ )
228
- new_options .__dict__ .update (self .per_module_options [key ])
229
- self .per_module_cache [key ] = new_options
257
+ self .per_module_cache [key ] = options .apply_changes (self .per_module_options [key ])
230
258
231
- self .unused_configs = set (keys )
259
+ # Add the more structured sections into unused configs, since
260
+ # they only count as used if actually used by a real module.
261
+ self .unused_configs .update (structured_keys )
232
262
233
263
def clone_for_module (self , module : str ) -> 'Options' :
234
264
"""Create an Options object that incorporates per-module options.
@@ -250,18 +280,38 @@ def clone_for_module(self, module: str) -> 'Options':
250
280
# in that order, looking for an entry.
251
281
# This is technically quadratic in the length of the path, but module paths
252
282
# don't actually get all that long.
283
+ options = self
253
284
path = module .split ('.' )
254
285
for i in range (len (path ), 0 , - 1 ):
255
286
key = '.' .join (path [:i ] + ['*' ])
256
287
if key in self .per_module_cache :
257
288
self .unused_configs .discard (key )
258
- return self .per_module_cache [key ]
289
+ options = self .per_module_cache [key ]
290
+ break
291
+
292
+ # OK and *now* we need to look for unstructured glob matches.
293
+ # We only do this for concrete modules, not structured wildcards.
294
+ if not module .endswith ('.*' ):
295
+ for key , pattern in self .glob_options :
296
+ if pattern .match (module ):
297
+ self .unused_configs .discard (key )
298
+ options = options .apply_changes (self .per_module_options [key ])
259
299
260
300
# We could update the cache to directly point to modules once
261
301
# they have been looked up, but in testing this made things
262
302
# slower and not faster, so we don't bother.
263
303
264
- return self
304
+ return options
305
+
306
+ def compile_glob (self , s : str ) -> Pattern [str ]:
307
+ # Compile one of the glob patterns to a regex so that '.*' can
308
+ # match *zero or more* module sections. This means we compile
309
+ # '.*' into '(\..*)?'.
310
+ parts = s .split ('.' )
311
+ expr = re .escape (parts [0 ]) if parts [0 ] != '*' else '.*'
312
+ for part in parts [1 :]:
313
+ expr += re .escape ('.' + part ) if part != '*' else '(\..*)?'
314
+ return re .compile (expr + '\\ Z' )
265
315
266
316
def select_options_affecting_cache (self ) -> Mapping [str , object ]:
267
317
return {opt : getattr (self , opt ) for opt in self .OPTIONS_AFFECTING_CACHE }
0 commit comments