11import argparse
22import configparser
33import glob as fileglob
4+ from io import StringIO
45import os
56import re
67import sys
@@ -119,24 +120,22 @@ def parse_config_file(options: Options, filename: Optional[str],
119120 print ("%s: No [mypy] section in config file" % file_read , file = stderr )
120121 else :
121122 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 )
125125 for k , v in updates .items ():
126126 setattr (options , k , v )
127127 options .report_dirs .update (report_dirs )
128128
129129 for name , section in parser .items ():
130130 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 )
134133 if report_dirs :
135- print ("%s: Per -module sections should not specify reports (%s)" %
134+ print ("%sPer -module sections should not specify reports (%s)" %
136135 (prefix , ', ' .join (s + '_report' for s in sorted (report_dirs ))),
137136 file = stderr )
138137 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)" %
140139 (prefix , ', ' .join (sorted (set (updates ) - PER_MODULE_OPTIONS ))),
141140 file = stderr )
142141 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],
149148
150149 if (any (c in glob for c in '?[]!' ) or
151150 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 "
153152 "with '*' in some components (e.g spam.*.eggs.*)"
154153 % prefix ,
155154 file = stderr )
@@ -159,7 +158,6 @@ def parse_config_file(options: Options, filename: Optional[str],
159158
160159def parse_section (prefix : str , template : Options ,
161160 section : Mapping [str , str ],
162- stdout : TextIO = sys .stdout ,
163161 stderr : TextIO = sys .stderr
164162 ) -> Tuple [Dict [str , object ], Dict [str , str ]]:
165163 """Parse one section of a config file.
@@ -179,17 +177,17 @@ def parse_section(prefix: str, template: Options,
179177 if report_type in defaults .REPORTER_NAMES :
180178 report_dirs [report_type ] = section [key ]
181179 else :
182- print ("%s: Unrecognized report type: %s" % (prefix , key ),
180+ print ("%sUnrecognized report type: %s" % (prefix , key ),
183181 file = stderr )
184182 continue
185183 if key .startswith ('x_' ):
186184 continue # Don't complain about `x_blah` flags
187185 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 "
189187 "individual flags instead (see 'mypy -h' for the list of flags enabled "
190188 "in strict mode)" % prefix , file = stderr )
191189 else :
192- print ("%s: Unrecognized option: %s = %s" % (prefix , key , section [key ]),
190+ print ("%sUnrecognized option: %s = %s" % (prefix , key , section [key ]),
193191 file = stderr )
194192 continue
195193 ct = type (dv )
@@ -201,29 +199,116 @@ def parse_section(prefix: str, template: Options,
201199 try :
202200 v = ct (section .get (key ))
203201 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 )
205203 continue
206204 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 )
208206 continue
209207 except ValueError as err :
210- print ("%s: %s: %s" % (prefix , key , err ), file = stderr )
208+ print ("%s%s: %s" % (prefix , key , err ), file = stderr )
211209 continue
212210 if key == 'cache_dir' :
213211 v = os .path .expanduser (v )
214212 if key == 'silent_imports' :
215- print ("%s: silent_imports has been replaced by "
213+ print ("%ssilent_imports has been replaced by "
216214 "ignore_missing_imports=True; follow_imports=skip" % prefix , file = stderr )
217215 if v :
218216 if 'ignore_missing_imports' not in results :
219217 results ['ignore_missing_imports' ] = True
220218 if 'follow_imports' not in results :
221219 results ['follow_imports' ] = 'skip'
222220 if key == 'almost_silent' :
223- print ("%s: almost_silent has been replaced by "
221+ print ("%salmost_silent has been replaced by "
224222 "follow_imports=error" % prefix , file = stderr )
225223 if v :
226224 if 'follow_imports' not in results :
227225 results ['follow_imports' ] = 'error'
228226 results [key ] = v
229227 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