1
1
import argparse
2
2
import configparser
3
3
import glob as fileglob
4
+ from io import StringIO
4
5
import os
5
6
import re
6
7
import sys
@@ -119,24 +120,22 @@ def parse_config_file(options: Options, filename: Optional[str],
119
120
print ("%s: No [mypy] section in config file" % file_read , file = stderr )
120
121
else :
121
122
section = parser ['mypy' ]
122
- prefix = '%s: [%s]' % (file_read , 'mypy' )
123
- updates , report_dirs = parse_section (prefix , options , section ,
124
- stdout , stderr )
123
+ prefix = '%s: [%s]: ' % (file_read , 'mypy' )
124
+ updates , report_dirs = parse_section (prefix , options , section , stderr )
125
125
for k , v in updates .items ():
126
126
setattr (options , k , v )
127
127
options .report_dirs .update (report_dirs )
128
128
129
129
for name , section in parser .items ():
130
130
if name .startswith ('mypy-' ):
131
- prefix = '%s: [%s]' % (file_read , name )
132
- updates , report_dirs = parse_section (prefix , options , section ,
133
- stdout , stderr )
131
+ prefix = '%s: [%s]: ' % (file_read , name )
132
+ updates , report_dirs = parse_section (prefix , options , section , stderr )
134
133
if report_dirs :
135
- print ("%s: Per -module sections should not specify reports (%s)" %
134
+ print ("%sPer -module sections should not specify reports (%s)" %
136
135
(prefix , ', ' .join (s + '_report' for s in sorted (report_dirs ))),
137
136
file = stderr )
138
137
if set (updates ) - PER_MODULE_OPTIONS :
139
- print ("%s: Per -module sections should only specify per-module flags (%s)" %
138
+ print ("%sPer -module sections should only specify per-module flags (%s)" %
140
139
(prefix , ', ' .join (sorted (set (updates ) - PER_MODULE_OPTIONS ))),
141
140
file = stderr )
142
141
updates = {k : v for k , v in updates .items () if k in PER_MODULE_OPTIONS }
@@ -149,7 +148,7 @@ def parse_config_file(options: Options, filename: Optional[str],
149
148
150
149
if (any (c in glob for c in '?[]!' ) or
151
150
any ('*' in x and x != '*' for x in glob .split ('.' ))):
152
- print ("%s: Patterns must be fully-qualified module names, optionally "
151
+ print ("%sPatterns must be fully-qualified module names, optionally "
153
152
"with '*' in some components (e.g spam.*.eggs.*)"
154
153
% prefix ,
155
154
file = stderr )
@@ -159,7 +158,6 @@ def parse_config_file(options: Options, filename: Optional[str],
159
158
160
159
def parse_section (prefix : str , template : Options ,
161
160
section : Mapping [str , str ],
162
- stdout : TextIO = sys .stdout ,
163
161
stderr : TextIO = sys .stderr
164
162
) -> Tuple [Dict [str , object ], Dict [str , str ]]:
165
163
"""Parse one section of a config file.
@@ -179,17 +177,17 @@ def parse_section(prefix: str, template: Options,
179
177
if report_type in defaults .REPORTER_NAMES :
180
178
report_dirs [report_type ] = section [key ]
181
179
else :
182
- print ("%s: Unrecognized report type: %s" % (prefix , key ),
180
+ print ("%sUnrecognized report type: %s" % (prefix , key ),
183
181
file = stderr )
184
182
continue
185
183
if key .startswith ('x_' ):
186
184
continue # Don't complain about `x_blah` flags
187
185
elif key == 'strict' :
188
- print ("%s: Strict mode is not supported in configuration files: specify "
186
+ print ("%sStrict mode is not supported in configuration files: specify "
189
187
"individual flags instead (see 'mypy -h' for the list of flags enabled "
190
188
"in strict mode)" % prefix , file = stderr )
191
189
else :
192
- print ("%s: Unrecognized option: %s = %s" % (prefix , key , section [key ]),
190
+ print ("%sUnrecognized option: %s = %s" % (prefix , key , section [key ]),
193
191
file = stderr )
194
192
continue
195
193
ct = type (dv )
@@ -201,29 +199,116 @@ def parse_section(prefix: str, template: Options,
201
199
try :
202
200
v = ct (section .get (key ))
203
201
except argparse .ArgumentTypeError as err :
204
- print ("%s: %s: %s" % (prefix , key , err ), file = stderr )
202
+ print ("%s%s: %s" % (prefix , key , err ), file = stderr )
205
203
continue
206
204
else :
207
- print ("%s: Don 't know what type %s should have" % (prefix , key ), file = stderr )
205
+ print ("%sDon 't know what type %s should have" % (prefix , key ), file = stderr )
208
206
continue
209
207
except ValueError as err :
210
- print ("%s: %s: %s" % (prefix , key , err ), file = stderr )
208
+ print ("%s%s: %s" % (prefix , key , err ), file = stderr )
211
209
continue
212
210
if key == 'cache_dir' :
213
211
v = os .path .expanduser (v )
214
212
if key == 'silent_imports' :
215
- print ("%s: silent_imports has been replaced by "
213
+ print ("%ssilent_imports has been replaced by "
216
214
"ignore_missing_imports=True; follow_imports=skip" % prefix , file = stderr )
217
215
if v :
218
216
if 'ignore_missing_imports' not in results :
219
217
results ['ignore_missing_imports' ] = True
220
218
if 'follow_imports' not in results :
221
219
results ['follow_imports' ] = 'skip'
222
220
if key == 'almost_silent' :
223
- print ("%s: almost_silent has been replaced by "
221
+ print ("%salmost_silent has been replaced by "
224
222
"follow_imports=error" % prefix , file = stderr )
225
223
if v :
226
224
if 'follow_imports' not in results :
227
225
results ['follow_imports' ] = 'error'
228
226
results [key ] = v
229
227
return results , report_dirs
228
+
229
+
230
+ def split_directive (s : str ) -> Tuple [List [str ], List [str ]]:
231
+ """Split s on commas, except during quoted sections.
232
+
233
+ Returns the parts and a list of error messages."""
234
+ parts = []
235
+ cur = [] # type: List[str]
236
+ errors = []
237
+ i = 0
238
+ while i < len (s ):
239
+ if s [i ] == ',' :
240
+ parts .append ('' .join (cur ).strip ())
241
+ cur = []
242
+ elif s [i ] == '"' :
243
+ i += 1
244
+ while i < len (s ) and s [i ] != '"' :
245
+ cur .append (s [i ])
246
+ i += 1
247
+ if i == len (s ):
248
+ errors .append ("Unterminated quote in configuration comment" )
249
+ cur .clear ()
250
+ else :
251
+ cur .append (s [i ])
252
+ i += 1
253
+ if cur :
254
+ parts .append ('' .join (cur ).strip ())
255
+
256
+ return parts , errors
257
+
258
+
259
+ def mypy_comments_to_config_map (line : str ,
260
+ template : Options ) -> Tuple [Dict [str , str ], List [str ]]:
261
+ """Rewrite the mypy comment syntax into ini file syntax.
262
+
263
+ Returns
264
+ """
265
+ options = {}
266
+ entries , errors = split_directive (line )
267
+ for entry in entries :
268
+ if '=' not in entry :
269
+ name = entry
270
+ value = None
271
+ else :
272
+ name , value = [x .strip () for x in entry .split ('=' , 1 )]
273
+
274
+ name = name .replace ('-' , '_' )
275
+ if value is None :
276
+ if name .startswith ('no_' ) and not hasattr (template , name ):
277
+ name = name [3 :]
278
+ value = 'False'
279
+ else :
280
+ value = 'True'
281
+ options [name ] = value
282
+
283
+ return options , errors
284
+
285
+
286
+ def parse_mypy_comments (
287
+ args : List [Tuple [int , str ]],
288
+ template : Options ) -> Tuple [Dict [str , object ], List [Tuple [int , str ]]]:
289
+ """Parse a collection of inline mypy: configuration comments.
290
+
291
+ Returns a dictionary of options to be applied and a list of error messages
292
+ generated.
293
+ """
294
+
295
+ errors = [] # type: List[Tuple[int, str]]
296
+ sections = {}
297
+
298
+ for lineno , line in args :
299
+ # In order to easily match the behavior for bools, we abuse configparser.
300
+ # Oddly, the only way to get the SectionProxy object with the getboolean
301
+ # method is to create a config parser.
302
+ parser = configparser .RawConfigParser ()
303
+ options , parse_errors = mypy_comments_to_config_map (line , template )
304
+ parser ['dummy' ] = options
305
+ errors .extend ((lineno , x ) for x in parse_errors )
306
+
307
+ stderr = StringIO ()
308
+ new_sections , reports = parse_section ('' , template , parser ['dummy' ], stderr = stderr )
309
+ errors .extend ((lineno , x ) for x in stderr .getvalue ().strip ().split ('\n ' ) if x )
310
+ if reports :
311
+ errors .append ((lineno , "Reports not supported in inline configuration" ))
312
+ sections .update (new_sections )
313
+
314
+ return sections , errors
0 commit comments