Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / lint.py @ 1026

History | View | Annotate | Download (56.6 KB)

1
# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
2
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
3
#
4
# This program is free software; you can redistribute it and/or modify it under
5
# the terms of the GNU General Public License as published by the Free Software
6
# Foundation; either version 2 of the License, or (at your option) any later
7
# version.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
12
#
13
# You should have received a copy of the GNU General Public License along with
14
# this program; if not, write to the Free Software Foundation, Inc.,
15
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
""" %prog [options] module_or_package
17

18
  Check that a module satisfies a coding standard (and more !).
19

20
    %prog --help
21

22
  Display this help message and exit.
23

24
    %prog --help-msg <msg-id>[,<msg-id>]
25

26
  Display help messages about given message identifiers and exit.
27
"""
28
from __future__ import print_function
29

    
30
import collections
31
import contextlib
32
import operator
33
import os
34
try:
35
    import multiprocessing
36
except ImportError:
37
    multiprocessing = None
38
import sys
39
import tokenize
40
import warnings
41

    
42
import six
43

    
44
import astroid
45
from astroid.__pkginfo__ import version as astroid_version
46
from astroid import modutils
47
from pylint import checkers
48
from pylint import interfaces
49
from pylint import reporters
50
from pylint import utils
51
from pylint import config
52
from pylint.__pkginfo__ import version
53
from pylint.reporters.ureports import nodes as report_nodes
54

    
55

    
56
MANAGER = astroid.MANAGER
57
INCLUDE_IDS_HELP = ("Deprecated. It was used to include message\'s "
58
                    "id in output. Use --msg-template instead.")
59
SYMBOLS_HELP = ("Deprecated. It was used to include symbolic ids of "
60
                "messages in output. Use --msg-template instead.")
61

    
62
def _get_new_args(message):
63
    location = (
64
        message.abspath,
65
        message.path,
66
        message.module,
67
        message.obj,
68
        message.line,
69
        message.column,
70
    )
71
    return (
72
        message.msg_id,
73
        message.symbol,
74
        location,
75
        message.msg,
76
        message.confidence,
77
    )
78

    
79
def _get_python_path(filepath):
80
    dirname = os.path.realpath(os.path.expanduser(filepath))
81
    if not os.path.isdir(dirname):
82
        dirname = os.path.dirname(dirname)
83
    while True:
84
        if not os.path.exists(os.path.join(dirname, "__init__.py")):
85
            return dirname
86
        old_dirname = dirname
87
        dirname = os.path.dirname(dirname)
88
        if old_dirname == dirname:
89
            return os.getcwd()
90

    
91

    
92
def _merge_stats(stats):
93
    merged = {}
94
    by_msg = collections.Counter()
95
    for stat in stats:
96
        message_stats = stat.pop('by_msg', {})
97
        by_msg.update(message_stats)
98

    
99
        for key, item in six.iteritems(stat):
100
            if key not in merged:
101
                merged[key] = item
102
            else:
103
                if isinstance(item, dict):
104
                    merged[key].update(item)
105
                else:
106
                    merged[key] = merged[key] + item
107

    
108
    merged['by_msg'] = by_msg
109
    return merged
110

    
111

    
112
@contextlib.contextmanager
113
def _patch_sysmodules():
114
    # Context manager that permits running pylint, on Windows, with -m switch
115
    # and with --jobs, as in 'python -2 -m pylint .. --jobs'.
116
    # For more details why this is needed,
117
    # see Python issue http://bugs.python.org/issue10845.
118

    
119
    mock_main = __name__ != '__main__' # -m switch
120
    if mock_main:
121
        sys.modules['__main__'] = sys.modules[__name__]
122

    
123
    try:
124
        yield
125
    finally:
126
        if mock_main:
127
            sys.modules.pop('__main__')
128

    
129

    
130
# Python Linter class #########################################################
131

    
132
MSGS = {
133
    'F0001': ('%s',
134
              'fatal',
135
              'Used when an error occurred preventing the analysis of a \
136
              module (unable to find it for instance).'),
137
    'F0002': ('%s: %s',
138
              'astroid-error',
139
              'Used when an unexpected error occurred while building the '
140
              'Astroid  representation. This is usually accompanied by a '
141
              'traceback. Please report such errors !'),
142
    'F0010': ('error while code parsing: %s',
143
              'parse-error',
144
              'Used when an exception occured while building the Astroid '
145
              'representation which could be handled by astroid.'),
146

    
147
    'I0001': ('Unable to run raw checkers on built-in module %s',
148
              'raw-checker-failed',
149
              'Used to inform that a built-in module has not been checked '
150
              'using the raw checkers.'),
151

    
152
    'I0010': ('Unable to consider inline option %r',
153
              'bad-inline-option',
154
              'Used when an inline option is either badly formatted or can\'t '
155
              'be used inside modules.'),
156

    
157
    'I0011': ('Locally disabling %s (%s)',
158
              'locally-disabled',
159
              'Used when an inline option disables a message or a messages '
160
              'category.'),
161
    'I0012': ('Locally enabling %s (%s)',
162
              'locally-enabled',
163
              'Used when an inline option enables a message or a messages '
164
              'category.'),
165
    'I0013': ('Ignoring entire file',
166
              'file-ignored',
167
              'Used to inform that the file will not be checked'),
168
    'I0020': ('Suppressed %s (from line %d)',
169
              'suppressed-message',
170
              'A message was triggered on a line, but suppressed explicitly '
171
              'by a disable= comment in the file. This message is not '
172
              'generated for messages that are ignored due to configuration '
173
              'settings.'),
174
    'I0021': ('Useless suppression of %s',
175
              'useless-suppression',
176
              'Reported when a message is explicitly disabled for a line or '
177
              'a block of code, but never triggered.'),
178
    'I0022': ('Pragma "%s" is deprecated, use "%s" instead',
179
              'deprecated-pragma',
180
              'Some inline pylint options have been renamed or reworked, '
181
              'only the most recent form should be used. '
182
              'NOTE:skip-all is only available with pylint >= 0.26',
183
              {'old_names': [('I0014', 'deprecated-disable-all')]}),
184

    
185
    'E0001': ('%s',
186
              'syntax-error',
187
              'Used when a syntax error is raised for a module.'),
188

    
189
    'E0011': ('Unrecognized file option %r',
190
              'unrecognized-inline-option',
191
              'Used when an unknown inline option is encountered.'),
192
    'E0012': ('Bad option value %r',
193
              'bad-option-value',
194
              'Used when a bad value for an inline option is encountered.'),
195
    }
196

    
197

    
198
if multiprocessing is not None:
199
    class ChildLinter(multiprocessing.Process):
200
        def run(self):
201
            # pylint: disable=no-member, unbalanced-tuple-unpacking
202
            tasks_queue, results_queue, self._config = self._args
203

    
204
            self._config["jobs"] = 1  # Child does not parallelize any further.
205
            self._python3_porting_mode = self._config.pop(
206
                'python3_porting_mode', None)
207
            self._plugins = self._config.pop('plugins', None)
208

    
209
            # Run linter for received files/modules.
210
            for file_or_module in iter(tasks_queue.get, 'STOP'):
211
                result = self._run_linter(file_or_module[0])
212
                try:
213
                    results_queue.put(result)
214
                except Exception as ex:
215
                    print("internal error with sending report for module %s" %
216
                          file_or_module, file=sys.stderr)
217
                    print(ex, file=sys.stderr)
218
                    results_queue.put({})
219

    
220
        def _run_linter(self, file_or_module):
221
            linter = PyLinter()
222

    
223
            # Register standard checkers.
224
            linter.load_default_plugins()
225
            # Load command line plugins.
226
            if self._plugins:
227
                linter.load_plugin_modules(self._plugins)
228

    
229
            linter.load_configuration(**self._config)
230
            linter.set_reporter(reporters.CollectingReporter())
231

    
232
            # Enable the Python 3 checker mode. This option is
233
            # passed down from the parent linter up to here, since
234
            # the Python 3 porting flag belongs to the Run class,
235
            # instead of the Linter class.
236
            if self._python3_porting_mode:
237
                linter.python3_porting_mode()
238

    
239
            # Run the checks.
240
            linter.check(file_or_module)
241

    
242
            msgs = [_get_new_args(m) for m in linter.reporter.messages]
243
            return (file_or_module, linter.file_state.base_name, linter.current_name,
244
                    msgs, linter.stats, linter.msg_status)
245

    
246

    
247
class PyLinter(config.OptionsManagerMixIn,
248
               utils.MessagesHandlerMixIn,
249
               utils.ReportsHandlerMixIn,
250
               checkers.BaseTokenChecker):
251
    """lint Python modules using external checkers.
252

253
    This is the main checker controlling the other ones and the reports
254
    generation. It is itself both a raw checker and an astroid checker in order
255
    to:
256
    * handle message activation / deactivation at the module level
257
    * handle some basic but necessary stats'data (number of classes, methods...)
258

259
    IDE plugins developpers: you may have to call
260
    `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want
261
    to ensure the latest code version is actually checked.
262
    """
263

    
264
    __implements__ = (interfaces.ITokenChecker, )
265

    
266
    name = 'master'
267
    priority = 0
268
    level = 0
269
    msgs = MSGS
270

    
271
    @staticmethod
272
    def make_options():
273
        return (('ignore',
274
                 {'type' : 'csv', 'metavar' : '<file>[,<file>...]',
275
                  'dest' : 'black_list', 'default' : ('CVS',),
276
                  'help' : 'Add files or directories to the blacklist. '
277
                           'They should be base names, not paths.'}),
278
                ('persistent',
279
                 {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>',
280
                  'level': 1,
281
                  'help' : 'Pickle collected data for later comparisons.'}),
282

    
283
                ('load-plugins',
284
                 {'type' : 'csv', 'metavar' : '<modules>', 'default' : (),
285
                  'level': 1,
286
                  'help' : 'List of plugins (as comma separated values of '
287
                           'python modules names) to load, usually to register '
288
                           'additional checkers.'}),
289

    
290
                ('output-format',
291
                 {'default': 'text', 'type': 'string', 'metavar' : '<format>',
292
                  'short': 'f',
293
                  'group': 'Reports',
294
                  'help' : 'Set the output format. Available formats are text,'
295
                           ' parseable, colorized, msvs (visual studio) and html. You '
296
                           'can also give a reporter class, eg mypackage.mymodule.'
297
                           'MyReporterClass.'}),
298

    
299
                ('files-output',
300
                 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
301
                  'group': 'Reports', 'level': 1,
302
                  'help' : 'Put messages in a separate file for each module / '
303
                           'package specified on the command line instead of printing '
304
                           'them on stdout. Reports (if any) will be written in a file '
305
                           'name "pylint_global.[txt|html]".'}),
306

    
307
                ('reports',
308
                 {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
309
                  'short': 'r',
310
                  'group': 'Reports',
311
                  'help' : 'Tells whether to display a full report or only the '
312
                           'messages'}),
313

    
314
                ('evaluation',
315
                 {'type' : 'string', 'metavar' : '<python_expression>',
316
                  'group': 'Reports', 'level': 1,
317
                  'default': '10.0 - ((float(5 * error + warning + refactor + '
318
                             'convention) / statement) * 10)',
319
                  'help' : 'Python expression which should return a note less '
320
                           'than 10 (10 is the highest note). You have access '
321
                           'to the variables errors warning, statement which '
322
                           'respectively contain the number of errors / '
323
                           'warnings messages and the total number of '
324
                           'statements analyzed. This is used by the global '
325
                           'evaluation report (RP0004).'}),
326

    
327
                ('comment', utils.deprecated_option(opt_type='yn')),
328

    
329
                ('confidence',
330
                 {'type' : 'multiple_choice', 'metavar': '<levels>',
331
                  'default': '',
332
                  'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS],
333
                  'group': 'Messages control',
334
                  'help' : 'Only show warnings with the listed confidence levels.'
335
                           ' Leave empty to show all. Valid levels: %s' % (
336
                               ', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS),)}),
337

    
338
                ('enable',
339
                 {'type' : 'csv', 'metavar': '<msg ids>',
340
                  'short': 'e',
341
                  'group': 'Messages control',
342
                  'help' : 'Enable the message, report, category or checker with the '
343
                           'given id(s). You can either give multiple identifier '
344
                           'separated by comma (,) or put this option multiple time. '
345
                           'See also the "--disable" option for examples. '}),
346

    
347
                ('disable',
348
                 {'type' : 'csv', 'metavar': '<msg ids>',
349
                  'short': 'd',
350
                  'group': 'Messages control',
351
                  'help' : 'Disable the message, report, category or checker '
352
                           'with the given id(s). You can either give multiple identifiers'
353
                           ' separated by comma (,) or put this option multiple times '
354
                           '(only on the command line, not in the configuration file '
355
                           'where it should appear only once).'
356
                           'You can also use "--disable=all" to disable everything first '
357
                           'and then reenable specific checks. For example, if you want '
358
                           'to run only the similarities checker, you can use '
359
                           '"--disable=all --enable=similarities". '
360
                           'If you want to run only the classes checker, but have no '
361
                           'Warning level messages displayed, use'
362
                           '"--disable=all --enable=classes --disable=W"'}),
363

    
364
                ('msg-template',
365
                 {'type' : 'string', 'metavar': '<template>',
366
                  'group': 'Reports',
367
                  'help' : ('Template used to display messages. '
368
                            'This is a python new-style format string '
369
                            'used to format the message information. '
370
                            'See doc for all details')
371
                 }),
372

    
373
                ('include-ids', utils.deprecated_option('i', 'yn', INCLUDE_IDS_HELP)),
374
                ('symbols', utils.deprecated_option('s', 'yn', SYMBOLS_HELP)),
375

    
376
                ('jobs',
377
                 {'type' : 'int', 'metavar': '<n-processes>',
378
                  'short': 'j',
379
                  'default': 1,
380
                  'help' : '''Use multiple processes to speed up Pylint.''',
381
                 }),
382

    
383
                ('unsafe-load-any-extension',
384
                 {'type': 'yn', 'metavar': '<yn>', 'default': False, 'hide': True,
385
                  'help': ('Allow loading of arbitrary C extensions. Extensions'
386
                           ' are imported into the active Python interpreter and'
387
                           ' may run arbitrary code.')}),
388

    
389
                ('extension-pkg-whitelist',
390
                 {'type': 'csv', 'metavar': '<pkg[,pkg]>', 'default': [],
391
                  'help': ('A comma-separated list of package or module names'
392
                           ' from where C extensions may be loaded. Extensions are'
393
                           ' loading into the active Python interpreter and may run'
394
                           ' arbitrary code')}
395
                ),
396

    
397
                ('optimize-ast',
398
                 {'type': 'yn', 'metavar': '<yn>', 'default': False,
399
                  'help': ('Allow optimization of some AST trees. This will '
400
                           'activate a peephole AST optimizer, which will '
401
                           'apply various small optimizations. For instance, '
402
                           'it can be used to obtain the result of joining '
403
                           'multiple strings with the addition operator. '
404
                           'Joining a lot of strings can lead to a maximum '
405
                           'recursion error in Pylint and this flag can prevent '
406
                           'that. It has one side effect, the resulting AST '
407
                           'will be different than the one from reality.')}
408
                ),
409
               )
410

    
411
    option_groups = (
412
        ('Messages control', 'Options controling analysis messages'),
413
        ('Reports', 'Options related to output formating and reporting'),
414
        )
415

    
416
    def __init__(self, options=(), reporter=None, option_groups=(),
417
                 pylintrc=None):
418
        # some stuff has to be done before ancestors initialization...
419
        #
420
        # messages store / checkers / reporter / astroid manager
421
        self.msgs_store = utils.MessagesStore()
422
        self.reporter = None
423
        self._reporter_name = None
424
        self._reporters = {}
425
        self._checkers = collections.defaultdict(list)
426
        self._pragma_lineno = {}
427
        self._ignore_file = False
428
        # visit variables
429
        self.file_state = utils.FileState()
430
        self.current_name = None
431
        self.current_file = None
432
        self.stats = None
433
        # init options
434
        self._external_opts = options
435
        self.options = options + PyLinter.make_options()
436
        self.option_groups = option_groups + PyLinter.option_groups
437
        self._options_methods = {
438
            'enable': self.enable,
439
            'disable': self.disable}
440
        self._bw_options_methods = {'disable-msg': self.disable,
441
                                    'enable-msg': self.enable}
442
        full_version = '%%prog %s, \nastroid %s\nPython %s' % (
443
            version, astroid_version, sys.version)
444
        utils.MessagesHandlerMixIn.__init__(self)
445
        utils.ReportsHandlerMixIn.__init__(self)
446
        super(PyLinter, self).__init__(
447
            usage=__doc__,
448
            version=full_version,
449
            config_file=pylintrc or config.PYLINTRC)
450
        checkers.BaseTokenChecker.__init__(self)
451
        # provided reports
452
        self.reports = (('RP0001', 'Messages by category',
453
                         report_total_messages_stats),
454
                        ('RP0002', '% errors / warnings by module',
455
                         report_messages_by_module_stats),
456
                        ('RP0003', 'Messages',
457
                         report_messages_stats),
458
                        ('RP0004', 'Global evaluation',
459
                         self.report_evaluation),
460
                       )
461
        self.register_checker(self)
462
        self._dynamic_plugins = set()
463
        self._python3_porting_mode = False
464
        self._error_mode = False
465
        self.load_provider_defaults()
466
        if reporter:
467
            self.set_reporter(reporter)
468

    
469
    def load_default_plugins(self):
470
        checkers.initialize(self)
471
        reporters.initialize(self)
472
        # Make sure to load the default reporter, because
473
        # the option has been set before the plugins had been loaded.
474
        if not self.reporter:
475
            self._load_reporter()
476

    
477
    def load_plugin_modules(self, modnames):
478
        """take a list of module names which are pylint plugins and load
479
        and register them
480
        """
481
        for modname in modnames:
482
            if modname in self._dynamic_plugins:
483
                continue
484
            self._dynamic_plugins.add(modname)
485
            module = modutils.load_module_from_name(modname)
486
            module.register(self)
487

    
488
    def _load_reporter(self):
489
        name = self._reporter_name.lower()
490
        if name in self._reporters:
491
            self.set_reporter(self._reporters[name]())
492
        else:
493
            qname = self._reporter_name
494
            module = modutils.load_module_from_name(
495
                modutils.get_module_part(qname))
496
            class_name = qname.split('.')[-1]
497
            reporter_class = getattr(module, class_name)
498
            self.set_reporter(reporter_class())
499

    
500
    def set_reporter(self, reporter):
501
        """set the reporter used to display messages and reports"""
502
        self.reporter = reporter
503
        reporter.linter = self
504

    
505
    def set_option(self, optname, value, action=None, optdict=None):
506
        """overridden from config.OptionsProviderMixin to handle some
507
        special options
508
        """
509
        if optname in self._options_methods or \
510
                optname in self._bw_options_methods:
511
            if value:
512
                try:
513
                    meth = self._options_methods[optname]
514
                except KeyError:
515
                    meth = self._bw_options_methods[optname]
516
                    warnings.warn('%s is deprecated, replace it by %s' % (optname,
517
                                                                          optname.split('-')[0]),
518
                                  DeprecationWarning)
519
                value = utils._check_csv(value)
520
                if isinstance(value, (list, tuple)):
521
                    for _id in value:
522
                        meth(_id, ignore_unknown=True)
523
                else:
524
                    meth(value)
525
                return # no need to call set_option, disable/enable methods do it
526
        elif optname == 'output-format':
527
            self._reporter_name = value
528
            # If the reporters are already available, load
529
            # the reporter class.
530
            if self._reporters:
531
                self._load_reporter()
532

    
533
        try:
534
            checkers.BaseTokenChecker.set_option(self, optname,
535
                                                 value, action, optdict)
536
        except config.UnsupportedAction:
537
            print('option %s can\'t be read from config file' % \
538
                  optname, file=sys.stderr)
539

    
540
    def register_reporter(self, reporter_class):
541
        self._reporters[reporter_class.name] = reporter_class
542

    
543
    def report_order(self):
544
        reports = sorted(self._reports, key=lambda x: getattr(x, 'name', ''))
545
        try:
546
            # Remove the current reporter and add it
547
            # at the end of the list.
548
            reports.pop(reports.index(self))
549
        except ValueError:
550
            pass
551
        else:
552
            reports.append(self)
553
        return reports
554

    
555
    # checkers manipulation methods ############################################
556

    
557
    def register_checker(self, checker):
558
        """register a new checker
559

560
        checker is an object implementing IRawChecker or / and IAstroidChecker
561
        """
562
        assert checker.priority <= 0, 'checker priority can\'t be >= 0'
563
        self._checkers[checker.name].append(checker)
564
        for r_id, r_title, r_cb in checker.reports:
565
            self.register_report(r_id, r_title, r_cb, checker)
566
        self.register_options_provider(checker)
567
        if hasattr(checker, 'msgs'):
568
            self.msgs_store.register_messages(checker)
569
        checker.load_defaults()
570

    
571
        # Register the checker, but disable all of its messages.
572
        # TODO(cpopa): we should have a better API for this.
573
        if not getattr(checker, 'enabled', True):
574
            self.disable(checker.name)
575

    
576
    def disable_noerror_messages(self):
577
        for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category):
578
            if msgcat == 'E':
579
                for msgid in msgids:
580
                    self.enable(msgid)
581
            else:
582
                for msgid in msgids:
583
                    self.disable(msgid)
584

    
585
    def disable_reporters(self):
586
        """disable all reporters"""
587
        for _reporters in six.itervalues(self._reports):
588
            for report_id, _, _ in _reporters:
589
                self.disable_report(report_id)
590

    
591
    def error_mode(self):
592
        """error mode: enable only errors; no reports, no persistent"""
593
        self._error_mode = True
594
        self.disable_noerror_messages()
595
        self.disable('miscellaneous')
596
        if self._python3_porting_mode:
597
            self.disable('all')
598
            for msg_id in self._checker_messages('python3'):
599
                if msg_id.startswith('E'):
600
                    self.enable(msg_id)
601
        else:
602
            self.disable('python3')
603
        self.set_option('reports', False)
604
        self.set_option('persistent', False)
605

    
606
    def python3_porting_mode(self):
607
        """Disable all other checkers and enable Python 3 warnings."""
608
        self.disable('all')
609
        self.enable('python3')
610
        if self._error_mode:
611
            # The error mode was activated, using the -E flag.
612
            # So we'll need to enable only the errors from the
613
            # Python 3 porting checker.
614
            for msg_id in self._checker_messages('python3'):
615
                if msg_id.startswith('E'):
616
                    self.enable(msg_id)
617
                else:
618
                    self.disable(msg_id)
619
        self._python3_porting_mode = True
620

    
621
    # block level option handling #############################################
622
    #
623
    # see func_block_disable_msg.py test case for expected behaviour
624

    
625
    def process_tokens(self, tokens):
626
        """process tokens from the current module to search for module/block
627
        level options
628
        """
629
        control_pragmas = {'disable', 'enable'}
630
        for (tok_type, content, start, _, _) in tokens:
631
            if tok_type != tokenize.COMMENT:
632
                continue
633
            match = utils.OPTION_RGX.search(content)
634
            if match is None:
635
                continue
636
            if match.group(1).strip() == "disable-all" or \
637
                    match.group(1).strip() == 'skip-file':
638
                if match.group(1).strip() == "disable-all":
639
                    self.add_message('deprecated-pragma', line=start[0],
640
                                     args=('disable-all', 'skip-file'))
641
                self.add_message('file-ignored', line=start[0])
642
                self._ignore_file = True
643
                return
644
            try:
645
                opt, value = match.group(1).split('=', 1)
646
            except ValueError:
647
                self.add_message('bad-inline-option', args=match.group(1).strip(),
648
                                 line=start[0])
649
                continue
650
            opt = opt.strip()
651
            if opt in self._options_methods or opt in self._bw_options_methods:
652
                try:
653
                    meth = self._options_methods[opt]
654
                except KeyError:
655
                    meth = self._bw_options_methods[opt]
656
                    # found a "(dis|en)able-msg" pragma deprecated suppresssion
657
                    self.add_message('deprecated-pragma', line=start[0],
658
                                     args=(opt, opt.replace('-msg', '')))
659
                for msgid in utils._splitstrip(value):
660
                    # Add the line where a control pragma was encountered.
661
                    if opt in control_pragmas:
662
                        self._pragma_lineno[msgid] = start[0]
663

    
664
                    try:
665
                        if (opt, msgid) == ('disable', 'all'):
666
                            self.add_message('deprecated-pragma', line=start[0],
667
                                             args=('disable=all', 'skip-file'))
668
                            self.add_message('file-ignored', line=start[0])
669
                            self._ignore_file = True
670
                            return
671
                        meth(msgid, 'module', start[0])
672
                    except utils.UnknownMessage:
673
                        self.add_message('bad-option-value', args=msgid, line=start[0])
674
            else:
675
                self.add_message('unrecognized-inline-option', args=opt, line=start[0])
676

    
677

    
678
    # code checking methods ###################################################
679

    
680
    def get_checkers(self):
681
        """return all available checkers as a list"""
682
        return [self] + [c for _checkers in six.itervalues(self._checkers)
683
                         for c in _checkers if c is not self]
684

    
685
    def prepare_checkers(self):
686
        """return checkers needed for activated messages and reports"""
687
        if not self.config.reports:
688
            self.disable_reporters()
689
        # get needed checkers
690
        neededcheckers = [self]
691
        for checker in self.get_checkers()[1:]:
692
            # fatal errors should not trigger enable / disabling a checker
693
            messages = set(msg for msg in checker.msgs
694
                           if msg[0] != 'F' and self.is_message_enabled(msg))
695
            if (messages or
696
                    any(self.report_is_enabled(r[0]) for r in checker.reports)):
697
                neededcheckers.append(checker)
698
        # Sort checkers by priority
699
        neededcheckers = sorted(neededcheckers,
700
                                key=operator.attrgetter('priority'),
701
                                reverse=True)
702
        return neededcheckers
703

    
704
    def should_analyze_file(self, modname, path): # pylint: disable=unused-argument, no-self-use
705
        """Returns whether or not a module should be checked.
706

707
        This implementation returns True for all python source file, indicating
708
        that all files should be linted.
709

710
        Subclasses may override this method to indicate that modules satisfying
711
        certain conditions should not be linted.
712

713
        :param str modname: The name of the module to be checked.
714
        :param str path: The full path to the source code of the module.
715
        :returns: True if the module should be checked.
716
        :rtype: bool
717
        """
718
        return path.endswith('.py')
719

    
720
    def check(self, files_or_modules):
721
        """main checking entry: check a list of files or modules from their
722
        name.
723
        """
724
        # initialize msgs_state now that all messages have been registered into
725
        # the store
726
        for msg in self.msgs_store.messages:
727
            if not msg.may_be_emitted():
728
                self._msgs_state[msg.msgid] = False
729

    
730
        if not isinstance(files_or_modules, (list, tuple)):
731
            files_or_modules = (files_or_modules,)
732

    
733
        if self.config.jobs == 1:
734
            self._do_check(files_or_modules)
735
        else:
736
            with _patch_sysmodules():
737
                self._parallel_check(files_or_modules)
738

    
739
    def _get_jobs_config(self):
740
        child_config = {}
741
        filter_options = {'symbols', 'include-ids', 'long-help'}
742
        filter_options.update((opt_name for opt_name, _ in self._external_opts))
743
        for opt_providers in six.itervalues(self._all_options):
744
            for optname, optdict, val in opt_providers.options_and_values():
745
                if optdict.get('deprecated'):
746
                    continue
747

    
748
                if optname not in filter_options:
749
                    child_config[optname] = utils._format_option_value(
750
                        optdict, val)
751
        child_config['python3_porting_mode'] = self._python3_porting_mode
752
        child_config['plugins'] = self._dynamic_plugins
753
        return child_config
754

    
755
    def _parallel_task(self, files_or_modules):
756
        # Prepare configuration for child linters.
757
        child_config = self._get_jobs_config()
758

    
759
        children = []
760
        manager = multiprocessing.Manager()
761
        tasks_queue = manager.Queue()
762
        results_queue = manager.Queue()
763

    
764
        for _ in range(self.config.jobs):
765
            child_linter = ChildLinter(args=(tasks_queue, results_queue,
766
                                             child_config))
767
            child_linter.start()
768
            children.append(child_linter)
769

    
770
        # Send files to child linters.
771
        expanded_files = self.expand_files(files_or_modules)
772
        for files_or_module in expanded_files:
773
            path = files_or_module['path']
774
            tasks_queue.put([path])
775

    
776
        # collect results from child linters
777
        failed = False
778
        for _ in expanded_files:
779
            try:
780
                result = results_queue.get()
781
            except Exception as ex:
782
                print("internal error while receiving results from child linter",
783
                      file=sys.stderr)
784
                print(ex, file=sys.stderr)
785
                failed = True
786
                break
787
            yield result
788

    
789
        # Stop child linters and wait for their completion.
790
        for _ in range(self.config.jobs):
791
            tasks_queue.put('STOP')
792
        for child in children:
793
            child.join()
794

    
795
        if failed:
796
            print("Error occured, stopping the linter.", file=sys.stderr)
797
            sys.exit(32)
798

    
799
    def _parallel_check(self, files_or_modules):
800
        # Reset stats.
801
        self.open()
802

    
803
        all_stats = []
804
        module = None
805
        for result in self._parallel_task(files_or_modules):
806
            (
807
                _,
808
                self.file_state.base_name,
809
                module,
810
                messages,
811
                stats,
812
                msg_status
813
            ) = result
814

    
815
            for msg in messages:
816
                msg = utils.Message(*msg)
817
                self.set_current_module(module)
818
                self.reporter.handle_message(msg)
819

    
820
            all_stats.append(stats)
821
            self.msg_status |= msg_status
822

    
823
        self.stats = _merge_stats(all_stats)
824
        self.current_name = module
825

    
826
        # Insert stats data to local checkers.
827
        for checker in self.get_checkers():
828
            if checker is not self:
829
                checker.stats = self.stats
830

    
831
    def _do_check(self, files_or_modules):
832
        walker = utils.PyLintASTWalker(self)
833
        _checkers = self.prepare_checkers()
834
        tokencheckers = [c for c in _checkers
835
                         if interfaces.implements(c, interfaces.ITokenChecker)
836
                         and c is not self]
837
        rawcheckers = [c for c in _checkers
838
                       if interfaces.implements(c, interfaces.IRawChecker)]
839
        # notify global begin
840
        for checker in _checkers:
841
            checker.open()
842
            if interfaces.implements(checker, interfaces.IAstroidChecker):
843
                walker.add_checker(checker)
844
        # build ast and check modules or packages
845
        for descr in self.expand_files(files_or_modules):
846
            modname, filepath = descr['name'], descr['path']
847
            if not descr['isarg'] and not self.should_analyze_file(modname, filepath):
848
                continue
849
            if self.config.files_output:
850
                reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension)
851
                self.reporter.set_output(open(reportfile, 'w'))
852
            self.set_current_module(modname, filepath)
853
            # get the module representation
854
            ast_node = self.get_ast(filepath, modname)
855
            if ast_node is None:
856
                continue
857
            # XXX to be correct we need to keep module_msgs_state for every
858
            # analyzed module (the problem stands with localized messages which
859
            # are only detected in the .close step)
860
            self.file_state = utils.FileState(descr['basename'])
861
            self._ignore_file = False
862
            # fix the current file (if the source file was not available or
863
            # if it's actually a c extension)
864
            self.current_file = ast_node.file # pylint: disable=maybe-no-member
865
            self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)
866
            # warn about spurious inline messages handling
867
            spurious_messages = self.file_state.iter_spurious_suppression_messages(self.msgs_store)
868
            for msgid, line, args in spurious_messages:
869
                self.add_message(msgid, line, None, args)
870
        # notify global end
871
        self.stats['statement'] = walker.nbstatements
872
        for checker in reversed(_checkers):
873
            checker.close()
874

    
875
    def expand_files(self, modules):
876
        """get modules and errors from a list of modules and handle errors
877
        """
878
        result, errors = utils.expand_modules(modules, self.config.black_list)
879
        for error in errors:
880
            message = modname = error["mod"]
881
            key = error["key"]
882
            self.set_current_module(modname)
883
            if key == "fatal":
884
                message = str(error["ex"]).replace(os.getcwd() + os.sep, '')
885
            self.add_message(key, args=message)
886
        return result
887

    
888
    def set_current_module(self, modname, filepath=None):
889
        """set the name of the currently analyzed module and
890
        init statistics for it
891
        """
892
        if not modname and filepath is None:
893
            return
894
        self.reporter.on_set_current_module(modname, filepath)
895
        self.current_name = modname
896
        self.current_file = filepath or modname
897
        self.stats['by_module'][modname] = {}
898
        self.stats['by_module'][modname]['statement'] = 0
899
        for msg_cat in six.itervalues(utils.MSG_TYPES):
900
            self.stats['by_module'][modname][msg_cat] = 0
901

    
902
    def get_ast(self, filepath, modname):
903
        """return a ast(roid) representation for a module"""
904
        try:
905
            return MANAGER.ast_from_file(filepath, modname, source=True)
906
        except astroid.AstroidBuildingException as ex:
907
            if isinstance(ex.args[0], SyntaxError):
908
                ex = ex.args[0]
909
                self.add_message('syntax-error',
910
                                 line=ex.lineno or 0,
911
                                 args=ex.msg)
912
            else:
913
                self.add_message('parse-error', args=ex)
914
        except Exception as ex: # pylint: disable=broad-except
915
            import traceback
916
            traceback.print_exc()
917
            self.add_message('astroid-error', args=(ex.__class__, ex))
918

    
919
    def check_astroid_module(self, ast_node, walker,
920
                             rawcheckers, tokencheckers):
921
        """Check a module from its astroid representation."""
922
        try:
923
            tokens = utils.tokenize_module(ast_node)
924
        except tokenize.TokenError as ex:
925
            self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0])
926
            return
927

    
928
        if not ast_node.pure_python:
929
            self.add_message('raw-checker-failed', args=ast_node.name)
930
        else:
931
            #assert astroid.file.endswith('.py')
932
            # invoke ITokenChecker interface on self to fetch module/block
933
            # level options
934
            self.process_tokens(tokens)
935
            if self._ignore_file:
936
                return False
937
            # walk ast to collect line numbers
938
            self.file_state.collect_block_lines(self.msgs_store, ast_node)
939
            # run raw and tokens checkers
940
            for checker in rawcheckers:
941
                checker.process_module(ast_node)
942
            for checker in tokencheckers:
943
                checker.process_tokens(tokens)
944
        # generate events to astroid checkers
945
        walker.walk(ast_node)
946
        return True
947

    
948
    # IAstroidChecker interface #################################################
949

    
950
    def open(self):
951
        """initialize counters"""
952
        self.stats = {'by_module' : {},
953
                      'by_msg' : {},
954
                     }
955
        MANAGER.optimize_ast = self.config.optimize_ast
956
        MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
957
        MANAGER.extension_package_whitelist.update(
958
            self.config.extension_pkg_whitelist)
959
        for msg_cat in six.itervalues(utils.MSG_TYPES):
960
            self.stats[msg_cat] = 0
961

    
962
    def generate_reports(self):
963
        """close the whole package /module, it's time to make reports !
964

965
        if persistent run, pickle results for later comparison
966
        """
967
        # Display whatever messages are left on the reporter.
968
        self.reporter.display_messages(report_nodes.Section())
969

    
970
        if self.file_state.base_name is not None:
971
            # load previous results if any
972
            previous_stats = config.load_results(self.file_state.base_name)
973
            # XXX code below needs refactoring to be more reporter agnostic
974
            self.reporter.on_close(self.stats, previous_stats)
975
            if self.config.reports:
976
                sect = self.make_reports(self.stats, previous_stats)
977
                if self.config.files_output:
978
                    filename = 'pylint_global.' + self.reporter.extension
979
                    self.reporter.set_output(open(filename, 'w'))
980
            else:
981
                sect = report_nodes.Section()
982
            if self.config.reports:
983
                self.reporter.display_reports(sect)
984
            # save results if persistent run
985
            if self.config.persistent:
986
                config.save_results(self.stats, self.file_state.base_name)
987
        else:
988
            self.reporter.on_close(self.stats, {})
989

    
990
    # specific reports ########################################################
991

    
992
    def report_evaluation(self, sect, stats, previous_stats):
993
        """make the global evaluation report"""
994
        # check with at least check 1 statements (usually 0 when there is a
995
        # syntax error preventing pylint from further processing)
996
        if stats['statement'] == 0:
997
            raise utils.EmptyReport()
998
        # get a global note for the code
999
        evaluation = self.config.evaluation
1000
        try:
1001
            note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used
1002
        except Exception as ex: # pylint: disable=broad-except
1003
            msg = 'An exception occurred while rating: %s' % ex
1004
        else:
1005
            stats['global_note'] = note
1006
            msg = 'Your code has been rated at %.2f/10' % note
1007
            pnote = previous_stats.get('global_note')
1008
            if pnote is not None:
1009
                msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote)
1010
        sect.append(report_nodes.Text(msg))
1011

    
1012
# some reporting functions ####################################################
1013

    
1014
def report_total_messages_stats(sect, stats, previous_stats):
1015
    """make total errors / warnings report"""
1016
    lines = ['type', 'number', 'previous', 'difference']
1017
    lines += checkers.table_lines_from_stats(stats, previous_stats,
1018
                                             ('convention', 'refactor',
1019
                                              'warning', 'error'))
1020
    sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1))
1021

    
1022
def report_messages_stats(sect, stats, _):
1023
    """make messages type report"""
1024
    if not stats['by_msg']:
1025
        # don't print this report when we didn't detected any errors
1026
        raise utils.EmptyReport()
1027
    in_order = sorted([(value, msg_id)
1028
                       for msg_id, value in six.iteritems(stats['by_msg'])
1029
                       if not msg_id.startswith('I')])
1030
    in_order.reverse()
1031
    lines = ('message id', 'occurrences')
1032
    for value, msg_id in in_order:
1033
        lines += (msg_id, str(value))
1034
    sect.append(report_nodes.Table(children=lines, cols=2, rheaders=1))
1035

    
1036
def report_messages_by_module_stats(sect, stats, _):
1037
    """make errors / warnings by modules report"""
1038
    if len(stats['by_module']) == 1:
1039
        # don't print this report when we are analysing a single module
1040
        raise utils.EmptyReport()
1041
    by_mod = collections.defaultdict(dict)
1042
    for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'):
1043
        total = stats[m_type]
1044
        for module in six.iterkeys(stats['by_module']):
1045
            mod_total = stats['by_module'][module][m_type]
1046
            if total == 0:
1047
                percent = 0
1048
            else:
1049
                percent = float((mod_total)*100) / total
1050
            by_mod[module][m_type] = percent
1051
    sorted_result = []
1052
    for module, mod_info in six.iteritems(by_mod):
1053
        sorted_result.append((mod_info['error'],
1054
                              mod_info['warning'],
1055
                              mod_info['refactor'],
1056
                              mod_info['convention'],
1057
                              module))
1058
    sorted_result.sort()
1059
    sorted_result.reverse()
1060
    lines = ['module', 'error', 'warning', 'refactor', 'convention']
1061
    for line in sorted_result:
1062
        # Don't report clean modules.
1063
        if all(entry == 0 for entry in line[:-1]):
1064
            continue
1065
        lines.append(line[-1])
1066
        for val in line[:-1]:
1067
            lines.append('%.2f' % val)
1068
    if len(lines) == 5:
1069
        raise utils.EmptyReport()
1070
    sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1))
1071

    
1072

    
1073
# utilities ###################################################################
1074

    
1075

    
1076
class ArgumentPreprocessingError(Exception):
1077
    """Raised if an error occurs during argument preprocessing."""
1078

    
1079

    
1080
def preprocess_options(args, search_for):
1081
    """look for some options (keys of <search_for>) which have to be processed
1082
    before others
1083

1084
    values of <search_for> are callback functions to call when the option is
1085
    found
1086
    """
1087
    i = 0
1088
    while i < len(args):
1089
        arg = args[i]
1090
        if arg.startswith('--'):
1091
            try:
1092
                option, val = arg[2:].split('=', 1)
1093
            except ValueError:
1094
                option, val = arg[2:], None
1095
            try:
1096
                cb, takearg = search_for[option]
1097
            except KeyError:
1098
                i += 1
1099
            else:
1100
                del args[i]
1101
                if takearg and val is None:
1102
                    if i >= len(args) or args[i].startswith('-'):
1103
                        msg = 'Option %s expects a value' % option
1104
                        raise ArgumentPreprocessingError(msg)
1105
                    val = args[i]
1106
                    del args[i]
1107
                elif not takearg and val is not None:
1108
                    msg = "Option %s doesn't expects a value" % option
1109
                    raise ArgumentPreprocessingError(msg)
1110
                cb(option, val)
1111
        else:
1112
            i += 1
1113

    
1114

    
1115
@contextlib.contextmanager
1116
def fix_import_path(args):
1117
    """Prepare sys.path for running the linter checks.
1118

1119
    Within this context, each of the given arguments is importable.
1120
    Paths are added to sys.path in corresponding order to the arguments.
1121
    We avoid adding duplicate directories to sys.path.
1122
    `sys.path` is reset to its original value upon exitign this context.
1123
    """
1124
    orig = list(sys.path)
1125
    changes = []
1126
    for arg in args:
1127
        path = _get_python_path(arg)
1128
        if path in changes:
1129
            continue
1130
        else:
1131
            changes.append(path)
1132
    sys.path[:] = changes + sys.path
1133
    try:
1134
        yield
1135
    finally:
1136
        sys.path[:] = orig
1137

    
1138

    
1139
class Run(object):
1140
    """helper class to use as main for pylint :
1141

1142
    run(*sys.argv[1:])
1143
    """
1144
    LinterClass = PyLinter
1145
    option_groups = (
1146
        ('Commands', 'Options which are actually commands. Options in this \
1147
group are mutually exclusive.'),
1148
        )
1149

    
1150
    def __init__(self, args, reporter=None, exit=True):
1151
        self._rcfile = None
1152
        self._plugins = []
1153
        try:
1154
            preprocess_options(args, {
1155
                # option: (callback, takearg)
1156
                'init-hook':   (cb_init_hook, True),
1157
                'rcfile':       (self.cb_set_rcfile, True),
1158
                'load-plugins': (self.cb_add_plugins, True),
1159
                })
1160
        except ArgumentPreprocessingError as ex:
1161
            print(ex, file=sys.stderr)
1162
            sys.exit(32)
1163

    
1164
        self.linter = linter = self.LinterClass((
1165
            ('rcfile',
1166
             {'action' : 'callback', 'callback' : lambda *args: 1,
1167
              'type': 'string', 'metavar': '<file>',
1168
              'help' : 'Specify a configuration file.'}),
1169

    
1170
            ('init-hook',
1171
             {'action' : 'callback', 'callback' : lambda *args: 1,
1172
              'type' : 'string', 'metavar': '<code>',
1173
              'level': 1,
1174
              'help' : 'Python code to execute, usually for sys.path '
1175
                       'manipulation such as pygtk.require().'}),
1176

    
1177
            ('help-msg',
1178
             {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
1179
              'callback' : self.cb_help_message,
1180
              'group': 'Commands',
1181
              'help' : 'Display a help message for the given message id and '
1182
                       'exit. The value may be a comma separated list of message ids.'}),
1183

    
1184
            ('list-msgs',
1185
             {'action' : 'callback', 'metavar': '<msg-id>',
1186
              'callback' : self.cb_list_messages,
1187
              'group': 'Commands', 'level': 1,
1188
              'help' : "Generate pylint's messages."}),
1189

    
1190
            ('list-conf-levels',
1191
             {'action' : 'callback',
1192
              'callback' : cb_list_confidence_levels,
1193
              'group': 'Commands', 'level': 1,
1194
              'help' : "Generate pylint's messages."}),
1195

    
1196
            ('full-documentation',
1197
             {'action' : 'callback', 'metavar': '<msg-id>',
1198
              'callback' : self.cb_full_documentation,
1199
              'group': 'Commands', 'level': 1,
1200
              'help' : "Generate pylint's full documentation."}),
1201

    
1202
            ('generate-rcfile',
1203
             {'action' : 'callback', 'callback' : self.cb_generate_config,
1204
              'group': 'Commands',
1205
              'help' : 'Generate a sample configuration file according to '
1206
                       'the current configuration. You can put other options '
1207
                       'before this one to get them in the generated '
1208
                       'configuration.'}),
1209

    
1210
            ('generate-man',
1211
             {'action' : 'callback', 'callback' : self.cb_generate_manpage,
1212
              'group': 'Commands',
1213
              'help' : "Generate pylint's man page.", 'hide': True}),
1214

    
1215
            ('errors-only',
1216
             {'action' : 'callback', 'callback' : self.cb_error_mode,
1217
              'short': 'E',
1218
              'help' : 'In error mode, checkers without error messages are '
1219
                       'disabled and for others, only the ERROR messages are '
1220
                       'displayed, and no reports are done by default'''}),
1221

    
1222
            ('py3k',
1223
             {'action' : 'callback', 'callback' : self.cb_python3_porting_mode,
1224
              'help' : 'In Python 3 porting mode, all checkers will be '
1225
                       'disabled and only messages emitted by the porting '
1226
                       'checker will be displayed'}),
1227

    
1228
            ('profile', utils.deprecated_option(opt_type='yn')),
1229

    
1230
            ), option_groups=self.option_groups, pylintrc=self._rcfile)
1231
        # register standard checkers
1232
        linter.load_default_plugins()
1233
        # load command line plugins
1234
        linter.load_plugin_modules(self._plugins)
1235
        # add some help section
1236
        linter.add_help_section('Environment variables', config.ENV_HELP, level=1)
1237
        # pylint: disable=bad-continuation
1238
        linter.add_help_section('Output',
1239
'Using the default text output, the message format is :                          \n'
1240
'                                                                                \n'
1241
'        MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE                                \n'
1242
'                                                                                \n'
1243
'There are 5 kind of message types :                                             \n'
1244
'    * (C) convention, for programming standard violation                        \n'
1245
'    * (R) refactor, for bad code smell                                          \n'
1246
'    * (W) warning, for python specific problems                                 \n'
1247
'    * (E) error, for probable bugs in the code                                  \n'
1248
'    * (F) fatal, if an error occurred which prevented pylint from doing further\n'
1249
'processing.\n'
1250
                                , level=1)
1251
        linter.add_help_section('Output status code',
1252
'Pylint should leave with following status code:                                 \n'
1253
'    * 0 if everything went fine                                                 \n'
1254
'    * 1 if a fatal message was issued                                           \n'
1255
'    * 2 if an error message was issued                                          \n'
1256
'    * 4 if a warning message was issued                                         \n'
1257
'    * 8 if a refactor message was issued                                        \n'
1258
'    * 16 if a convention message was issued                                     \n'
1259
'    * 32 on usage error                                                         \n'
1260
'                                                                                \n'
1261
'status 1 to 16 will be bit-ORed so you can know which different categories has\n'
1262
'been issued by analysing pylint output status code\n',
1263
                                level=1)
1264
        # read configuration
1265
        linter.disable('suppressed-message')
1266
        linter.disable('useless-suppression')
1267
        linter.read_config_file()
1268
        config_parser = linter.cfgfile_parser
1269
        # run init hook, if present, before loading plugins
1270
        if config_parser.has_option('MASTER', 'init-hook'):
1271
            cb_init_hook('init-hook',
1272
                         utils._unquote(config_parser.get('MASTER',
1273
                                                          'init-hook')))
1274
        # is there some additional plugins in the file configuration, in
1275
        if config_parser.has_option('MASTER', 'load-plugins'):
1276
            plugins = utils._splitstrip(
1277
                config_parser.get('MASTER', 'load-plugins'))
1278
            linter.load_plugin_modules(plugins)
1279
        # now we can load file config and command line, plugins (which can
1280
        # provide options) have been registered
1281
        linter.load_config_file()
1282
        if reporter:
1283
            # if a custom reporter is provided as argument, it may be overridden
1284
            # by file parameters, so re-set it here, but before command line
1285
            # parsing so it's still overrideable by command line option
1286
            linter.set_reporter(reporter)
1287
        try:
1288
            args = linter.load_command_line_configuration(args)
1289
        except SystemExit as exc:
1290
            if exc.code == 2: # bad options
1291
                exc.code = 32
1292
            raise
1293
        if not args:
1294
            print(linter.help())
1295
            sys.exit(32)
1296

    
1297
        if linter.config.jobs < 0:
1298
            print("Jobs number (%d) should be greater than 0"
1299
                  % linter.config.jobs, file=sys.stderr)
1300
            sys.exit(32)
1301
        if linter.config.jobs > 1 or linter.config.jobs == 0:
1302
            if multiprocessing is None:
1303
                print("Multiprocessing library is missing, "
1304
                      "fallback to single process", file=sys.stderr)
1305
                linter.set_option("jobs", 1)
1306
            else:
1307
                if linter.config.jobs == 0:
1308
                    linter.config.jobs = multiprocessing.cpu_count()
1309

    
1310
        # insert current working directory to the python path to have a correct
1311
        # behaviour
1312
        with fix_import_path(args):
1313
            linter.check(args)
1314
            linter.generate_reports()
1315
        if exit:
1316
            sys.exit(self.linter.msg_status)
1317

    
1318
    def cb_set_rcfile(self, name, value):
1319
        """callback for option preprocessing (i.e. before option parsing)"""
1320
        self._rcfile = value
1321

    
1322
    def cb_add_plugins(self, name, value):
1323
        """callback for option preprocessing (i.e. before option parsing)"""
1324
        self._plugins.extend(utils._splitstrip(value))
1325

    
1326
    def cb_error_mode(self, *args, **kwargs):
1327
        """error mode:
1328
        * disable all but error messages
1329
        * disable the 'miscellaneous' checker which can be safely deactivated in
1330
          debug
1331
        * disable reports
1332
        * do not save execution information
1333
        """
1334
        self.linter.error_mode()
1335

    
1336
    def cb_generate_config(self, *args, **kwargs):
1337
        """optik callback for sample config file generation"""
1338
        self.linter.generate_config(skipsections=('COMMANDS',))
1339
        sys.exit(0)
1340

    
1341
    def cb_generate_manpage(self, *args, **kwargs):
1342
        """optik callback for sample config file generation"""
1343
        from pylint import __pkginfo__
1344
        self.linter.generate_manpage(__pkginfo__)
1345
        sys.exit(0)
1346

    
1347
    def cb_help_message(self, option, optname, value, parser):
1348
        """optik callback for printing some help about a particular message"""
1349
        self.linter.msgs_store.help_message(utils._splitstrip(value))
1350
        sys.exit(0)
1351

    
1352
    def cb_full_documentation(self, option, optname, value, parser):
1353
        """optik callback for printing full documentation"""
1354
        self.linter.print_full_documentation()
1355
        sys.exit(0)
1356

    
1357
    def cb_list_messages(self, option, optname, value, parser): # FIXME
1358
        """optik callback for printing available messages"""
1359
        self.linter.msgs_store.list_messages()
1360
        sys.exit(0)
1361

    
1362
    def cb_python3_porting_mode(self, *args, **kwargs):
1363
        """Activate only the python3 porting checker."""
1364
        self.linter.python3_porting_mode()
1365

    
1366

    
1367
def cb_list_confidence_levels(option, optname, value, parser):
1368
    for level in interfaces.CONFIDENCE_LEVELS:
1369
        print('%-18s: %s' % level)
1370
    sys.exit(0)
1371

    
1372
def cb_init_hook(optname, value):
1373
    """exec arbitrary code to set sys.path for instance"""
1374
    exec(value) # pylint: disable=exec-used
1375

    
1376

    
1377
if __name__ == '__main__':
1378
    Run(sys.argv[1:])