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 @ 1227

History | View | Annotate | Download (56.8 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 sys_path():
63
  if sys.getClassLoader()!=None:
64
    path = getattr(sys.getClassLoader(),"path",None)
65
    if path != None:
66
      return path(sys.path)
67
  path = [ folder for folder in sys.path if not folder.startswith("__") ]
68
  return path
69

    
70
def _get_new_args(message):
71
    location = (
72
        message.abspath,
73
        message.path,
74
        message.module,
75
        message.obj,
76
        message.line,
77
        message.column,
78
    )
79
    return (
80
        message.msg_id,
81
        message.symbol,
82
        location,
83
        message.msg,
84
        message.confidence,
85
    )
86

    
87
def _get_python_path(filepath):
88
    dirname = os.path.realpath(os.path.expanduser(filepath))
89
    if not os.path.isdir(dirname):
90
        dirname = os.path.dirname(dirname)
91
    while True:
92
        if not os.path.exists(os.path.join(dirname, "__init__.py")):
93
            return dirname
94
        old_dirname = dirname
95
        dirname = os.path.dirname(dirname)
96
        if old_dirname == dirname:
97
            return os.getcwd()
98

    
99

    
100
def _merge_stats(stats):
101
    merged = {}
102
    by_msg = collections.Counter()
103
    for stat in stats:
104
        message_stats = stat.pop('by_msg', {})
105
        by_msg.update(message_stats)
106

    
107
        for key, item in six.iteritems(stat):
108
            if key not in merged:
109
                merged[key] = item
110
            else:
111
                if isinstance(item, dict):
112
                    merged[key].update(item)
113
                else:
114
                    merged[key] = merged[key] + item
115

    
116
    merged['by_msg'] = by_msg
117
    return merged
118

    
119

    
120
@contextlib.contextmanager
121
def _patch_sysmodules():
122
    # Context manager that permits running pylint, on Windows, with -m switch
123
    # and with --jobs, as in 'python -2 -m pylint .. --jobs'.
124
    # For more details why this is needed,
125
    # see Python issue http://bugs.python.org/issue10845.
126

    
127
    mock_main = __name__ != '__main__' # -m switch
128
    if mock_main:
129
        sys.modules['__main__'] = sys.modules[__name__]
130

    
131
    try:
132
        yield
133
    finally:
134
        if mock_main:
135
            sys.modules.pop('__main__')
136

    
137

    
138
# Python Linter class #########################################################
139

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

    
155
    'I0001': ('Unable to run raw checkers on built-in module %s',
156
              'raw-checker-failed',
157
              'Used to inform that a built-in module has not been checked '
158
              'using the raw checkers.'),
159

    
160
    'I0010': ('Unable to consider inline option %r',
161
              'bad-inline-option',
162
              'Used when an inline option is either badly formatted or can\'t '
163
              'be used inside modules.'),
164

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

    
193
    'E0001': ('%s',
194
              'syntax-error',
195
              'Used when a syntax error is raised for a module.'),
196

    
197
    'E0011': ('Unrecognized file option %r',
198
              'unrecognized-inline-option',
199
              'Used when an unknown inline option is encountered.'),
200
    'E0012': ('Bad option value %r',
201
              'bad-option-value',
202
              'Used when a bad value for an inline option is encountered.'),
203
    }
204

    
205

    
206
if multiprocessing is not None:
207
    class ChildLinter(multiprocessing.Process):
208
        def run(self):
209
            # pylint: disable=no-member, unbalanced-tuple-unpacking
210
            tasks_queue, results_queue, self._config = self._args
211

    
212
            self._config["jobs"] = 1  # Child does not parallelize any further.
213
            self._python3_porting_mode = self._config.pop(
214
                'python3_porting_mode', None)
215
            self._plugins = self._config.pop('plugins', None)
216

    
217
            # Run linter for received files/modules.
218
            for file_or_module in iter(tasks_queue.get, 'STOP'):
219
                result = self._run_linter(file_or_module[0])
220
                try:
221
                    results_queue.put(result)
222
                except Exception as ex:
223
                    print("internal error with sending report for module %s" %
224
                          file_or_module, file=sys.stderr)
225
                    print(ex, file=sys.stderr)
226
                    results_queue.put({})
227

    
228
        def _run_linter(self, file_or_module):
229
            linter = PyLinter()
230

    
231
            # Register standard checkers.
232
            linter.load_default_plugins()
233
            # Load command line plugins.
234
            if self._plugins:
235
                linter.load_plugin_modules(self._plugins)
236

    
237
            linter.load_configuration(**self._config)
238
            linter.set_reporter(reporters.CollectingReporter())
239

    
240
            # Enable the Python 3 checker mode. This option is
241
            # passed down from the parent linter up to here, since
242
            # the Python 3 porting flag belongs to the Run class,
243
            # instead of the Linter class.
244
            if self._python3_porting_mode:
245
                linter.python3_porting_mode()
246

    
247
            # Run the checks.
248
            linter.check(file_or_module)
249

    
250
            msgs = [_get_new_args(m) for m in linter.reporter.messages]
251
            return (file_or_module, linter.file_state.base_name, linter.current_name,
252
                    msgs, linter.stats, linter.msg_status)
253

    
254

    
255
class PyLinter(config.OptionsManagerMixIn,
256
               utils.MessagesHandlerMixIn,
257
               utils.ReportsHandlerMixIn,
258
               checkers.BaseTokenChecker):
259
    """lint Python modules using external checkers.
260

261
    This is the main checker controlling the other ones and the reports
262
    generation. It is itself both a raw checker and an astroid checker in order
263
    to:
264
    * handle message activation / deactivation at the module level
265
    * handle some basic but necessary stats'data (number of classes, methods...)
266

267
    IDE plugins developpers: you may have to call
268
    `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want
269
    to ensure the latest code version is actually checked.
270
    """
271

    
272
    __implements__ = (interfaces.ITokenChecker, )
273

    
274
    name = 'master'
275
    priority = 0
276
    level = 0
277
    msgs = MSGS
278

    
279
    @staticmethod
280
    def make_options():
281
        return (('ignore',
282
                 {'type' : 'csv', 'metavar' : '<file>[,<file>...]',
283
                  'dest' : 'black_list', 'default' : ('CVS',),
284
                  'help' : 'Add files or directories to the blacklist. '
285
                           'They should be base names, not paths.'}),
286
                ('persistent',
287
                 {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>',
288
                  'level': 1,
289
                  'help' : 'Pickle collected data for later comparisons.'}),
290

    
291
                ('load-plugins',
292
                 {'type' : 'csv', 'metavar' : '<modules>', 'default' : (),
293
                  'level': 1,
294
                  'help' : 'List of plugins (as comma separated values of '
295
                           'python modules names) to load, usually to register '
296
                           'additional checkers.'}),
297

    
298
                ('output-format',
299
                 {'default': 'text', 'type': 'string', 'metavar' : '<format>',
300
                  'short': 'f',
301
                  'group': 'Reports',
302
                  'help' : 'Set the output format. Available formats are text,'
303
                           ' parseable, colorized, msvs (visual studio) and html. You '
304
                           'can also give a reporter class, eg mypackage.mymodule.'
305
                           'MyReporterClass.'}),
306

    
307
                ('files-output',
308
                 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
309
                  'group': 'Reports', 'level': 1,
310
                  'help' : 'Put messages in a separate file for each module / '
311
                           'package specified on the command line instead of printing '
312
                           'them on stdout. Reports (if any) will be written in a file '
313
                           'name "pylint_global.[txt|html]".'}),
314

    
315
                ('reports',
316
                 {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
317
                  'short': 'r',
318
                  'group': 'Reports',
319
                  'help' : 'Tells whether to display a full report or only the '
320
                           'messages'}),
321

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

    
335
                ('comment', utils.deprecated_option(opt_type='yn')),
336

    
337
                ('confidence',
338
                 {'type' : 'multiple_choice', 'metavar': '<levels>',
339
                  'default': '',
340
                  'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS],
341
                  'group': 'Messages control',
342
                  'help' : 'Only show warnings with the listed confidence levels.'
343
                           ' Leave empty to show all. Valid levels: %s' % (
344
                               ', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS),)}),
345

    
346
                ('enable',
347
                 {'type' : 'csv', 'metavar': '<msg ids>',
348
                  'short': 'e',
349
                  'group': 'Messages control',
350
                  'help' : 'Enable the message, report, category or checker with the '
351
                           'given id(s). You can either give multiple identifier '
352
                           'separated by comma (,) or put this option multiple time. '
353
                           'See also the "--disable" option for examples. '}),
354

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

    
372
                ('msg-template',
373
                 {'type' : 'string', 'metavar': '<template>',
374
                  'group': 'Reports',
375
                  'help' : ('Template used to display messages. '
376
                            'This is a python new-style format string '
377
                            'used to format the message information. '
378
                            'See doc for all details')
379
                 }),
380

    
381
                ('include-ids', utils.deprecated_option('i', 'yn', INCLUDE_IDS_HELP)),
382
                ('symbols', utils.deprecated_option('s', 'yn', SYMBOLS_HELP)),
383

    
384
                ('jobs',
385
                 {'type' : 'int', 'metavar': '<n-processes>',
386
                  'short': 'j',
387
                  'default': 1,
388
                  'help' : '''Use multiple processes to speed up Pylint.''',
389
                 }),
390

    
391
                ('unsafe-load-any-extension',
392
                 {'type': 'yn', 'metavar': '<yn>', 'default': False, 'hide': True,
393
                  'help': ('Allow loading of arbitrary C extensions. Extensions'
394
                           ' are imported into the active Python interpreter and'
395
                           ' may run arbitrary code.')}),
396

    
397
                ('extension-pkg-whitelist',
398
                 {'type': 'csv', 'metavar': '<pkg[,pkg]>', 'default': [],
399
                  'help': ('A comma-separated list of package or module names'
400
                           ' from where C extensions may be loaded. Extensions are'
401
                           ' loading into the active Python interpreter and may run'
402
                           ' arbitrary code')}
403
                ),
404

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

    
419
    option_groups = (
420
        ('Messages control', 'Options controling analysis messages'),
421
        ('Reports', 'Options related to output formating and reporting'),
422
        )
423

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

    
477
    def load_default_plugins(self):
478
        checkers.initialize(self)
479
        reporters.initialize(self)
480
        # Make sure to load the default reporter, because
481
        # the option has been set before the plugins had been loaded.
482
        if not self.reporter:
483
            self._load_reporter()
484

    
485
    def load_plugin_modules(self, modnames):
486
        """take a list of module names which are pylint plugins and load
487
        and register them
488
        """
489
        for modname in modnames:
490
            if modname in self._dynamic_plugins:
491
                continue
492
            self._dynamic_plugins.add(modname)
493
            module = modutils.load_module_from_name(modname)
494
            module.register(self)
495

    
496
    def _load_reporter(self):
497
        name = self._reporter_name.lower()
498
        if name in self._reporters:
499
            self.set_reporter(self._reporters[name]())
500
        else:
501
            qname = self._reporter_name
502
            module = modutils.load_module_from_name(
503
                modutils.get_module_part(qname))
504
            class_name = qname.split('.')[-1]
505
            reporter_class = getattr(module, class_name)
506
            self.set_reporter(reporter_class())
507

    
508
    def set_reporter(self, reporter):
509
        """set the reporter used to display messages and reports"""
510
        self.reporter = reporter
511
        reporter.linter = self
512

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

    
541
        try:
542
            checkers.BaseTokenChecker.set_option(self, optname,
543
                                                 value, action, optdict)
544
        except config.UnsupportedAction:
545
            print('option %s can\'t be read from config file' % \
546
                  optname, file=sys.stderr)
547

    
548
    def register_reporter(self, reporter_class):
549
        self._reporters[reporter_class.name] = reporter_class
550

    
551
    def report_order(self):
552
        reports = sorted(self._reports, key=lambda x: getattr(x, 'name', ''))
553
        try:
554
            # Remove the current reporter and add it
555
            # at the end of the list.
556
            reports.pop(reports.index(self))
557
        except ValueError:
558
            pass
559
        else:
560
            reports.append(self)
561
        return reports
562

    
563
    # checkers manipulation methods ############################################
564

    
565
    def register_checker(self, checker):
566
        """register a new checker
567

568
        checker is an object implementing IRawChecker or / and IAstroidChecker
569
        """
570
        assert checker.priority <= 0, 'checker priority can\'t be >= 0'
571
        self._checkers[checker.name].append(checker)
572
        for r_id, r_title, r_cb in checker.reports:
573
            self.register_report(r_id, r_title, r_cb, checker)
574
        self.register_options_provider(checker)
575
        if hasattr(checker, 'msgs'):
576
            self.msgs_store.register_messages(checker)
577
        checker.load_defaults()
578

    
579
        # Register the checker, but disable all of its messages.
580
        # TODO(cpopa): we should have a better API for this.
581
        if not getattr(checker, 'enabled', True):
582
            self.disable(checker.name)
583

    
584
    def disable_noerror_messages(self):
585
        for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category):
586
            if msgcat == 'E':
587
                for msgid in msgids:
588
                    self.enable(msgid)
589
            else:
590
                for msgid in msgids:
591
                    self.disable(msgid)
592

    
593
    def disable_reporters(self):
594
        """disable all reporters"""
595
        for _reporters in six.itervalues(self._reports):
596
            for report_id, _, _ in _reporters:
597
                self.disable_report(report_id)
598

    
599
    def error_mode(self):
600
        """error mode: enable only errors; no reports, no persistent"""
601
        self._error_mode = True
602
        self.disable_noerror_messages()
603
        self.disable('miscellaneous')
604
        if self._python3_porting_mode:
605
            self.disable('all')
606
            for msg_id in self._checker_messages('python3'):
607
                if msg_id.startswith('E'):
608
                    self.enable(msg_id)
609
        else:
610
            self.disable('python3')
611
        self.set_option('reports', False)
612
        self.set_option('persistent', False)
613

    
614
    def python3_porting_mode(self):
615
        """Disable all other checkers and enable Python 3 warnings."""
616
        self.disable('all')
617
        self.enable('python3')
618
        if self._error_mode:
619
            # The error mode was activated, using the -E flag.
620
            # So we'll need to enable only the errors from the
621
            # Python 3 porting checker.
622
            for msg_id in self._checker_messages('python3'):
623
                if msg_id.startswith('E'):
624
                    self.enable(msg_id)
625
                else:
626
                    self.disable(msg_id)
627
        self._python3_porting_mode = True
628

    
629
    # block level option handling #############################################
630
    #
631
    # see func_block_disable_msg.py test case for expected behaviour
632

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

    
672
                    try:
673
                        if (opt, msgid) == ('disable', 'all'):
674
                            self.add_message('deprecated-pragma', line=start[0],
675
                                             args=('disable=all', 'skip-file'))
676
                            self.add_message('file-ignored', line=start[0])
677
                            self._ignore_file = True
678
                            return
679
                        meth(msgid, 'module', start[0])
680
                    except utils.UnknownMessage:
681
                        self.add_message('bad-option-value', args=msgid, line=start[0])
682
            else:
683
                self.add_message('unrecognized-inline-option', args=opt, line=start[0])
684

    
685

    
686
    # code checking methods ###################################################
687

    
688
    def get_checkers(self):
689
        """return all available checkers as a list"""
690
        return [self] + [c for _checkers in six.itervalues(self._checkers)
691
                         for c in _checkers if c is not self]
692

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

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

715
        This implementation returns True for all python source file, indicating
716
        that all files should be linted.
717

718
        Subclasses may override this method to indicate that modules satisfying
719
        certain conditions should not be linted.
720

721
        :param str modname: The name of the module to be checked.
722
        :param str path: The full path to the source code of the module.
723
        :returns: True if the module should be checked.
724
        :rtype: bool
725
        """
726
        return path.endswith('.py')
727

    
728
    def check(self, files_or_modules):
729
        """main checking entry: check a list of files or modules from their
730
        name.
731
        """
732
        # initialize msgs_state now that all messages have been registered into
733
        # the store
734
        for msg in self.msgs_store.messages:
735
            if not msg.may_be_emitted():
736
                self._msgs_state[msg.msgid] = False
737

    
738
        if not isinstance(files_or_modules, (list, tuple)):
739
            files_or_modules = (files_or_modules,)
740

    
741
        if self.config.jobs == 1:
742
            self._do_check(files_or_modules)
743
        else:
744
            with _patch_sysmodules():
745
                self._parallel_check(files_or_modules)
746

    
747
    def _get_jobs_config(self):
748
        child_config = {}
749
        filter_options = {'symbols', 'include-ids', 'long-help'}
750
        filter_options.update((opt_name for opt_name, _ in self._external_opts))
751
        for opt_providers in six.itervalues(self._all_options):
752
            for optname, optdict, val in opt_providers.options_and_values():
753
                if optdict.get('deprecated'):
754
                    continue
755

    
756
                if optname not in filter_options:
757
                    child_config[optname] = utils._format_option_value(
758
                        optdict, val)
759
        child_config['python3_porting_mode'] = self._python3_porting_mode
760
        child_config['plugins'] = self._dynamic_plugins
761
        return child_config
762

    
763
    def _parallel_task(self, files_or_modules):
764
        # Prepare configuration for child linters.
765
        child_config = self._get_jobs_config()
766

    
767
        children = []
768
        manager = multiprocessing.Manager()
769
        tasks_queue = manager.Queue()
770
        results_queue = manager.Queue()
771

    
772
        for _ in range(self.config.jobs):
773
            child_linter = ChildLinter(args=(tasks_queue, results_queue,
774
                                             child_config))
775
            child_linter.start()
776
            children.append(child_linter)
777

    
778
        # Send files to child linters.
779
        expanded_files = self.expand_files(files_or_modules)
780
        for files_or_module in expanded_files:
781
            path = files_or_module['path']
782
            tasks_queue.put([path])
783

    
784
        # collect results from child linters
785
        failed = False
786
        for _ in expanded_files:
787
            try:
788
                result = results_queue.get()
789
            except Exception as ex:
790
                print("internal error while receiving results from child linter",
791
                      file=sys.stderr)
792
                print(ex, file=sys.stderr)
793
                failed = True
794
                break
795
            yield result
796

    
797
        # Stop child linters and wait for their completion.
798
        for _ in range(self.config.jobs):
799
            tasks_queue.put('STOP')
800
        for child in children:
801
            child.join()
802

    
803
        if failed:
804
            print("Error occured, stopping the linter.", file=sys.stderr)
805
            sys.exit(32)
806

    
807
    def _parallel_check(self, files_or_modules):
808
        # Reset stats.
809
        self.open()
810

    
811
        all_stats = []
812
        module = None
813
        for result in self._parallel_task(files_or_modules):
814
            (
815
                _,
816
                self.file_state.base_name,
817
                module,
818
                messages,
819
                stats,
820
                msg_status
821
            ) = result
822

    
823
            for msg in messages:
824
                msg = utils.Message(*msg)
825
                self.set_current_module(module)
826
                self.reporter.handle_message(msg)
827

    
828
            all_stats.append(stats)
829
            self.msg_status |= msg_status
830

    
831
        self.stats = _merge_stats(all_stats)
832
        self.current_name = module
833

    
834
        # Insert stats data to local checkers.
835
        for checker in self.get_checkers():
836
            if checker is not self:
837
                checker.stats = self.stats
838

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

    
883
    def expand_files(self, modules):
884
        """get modules and errors from a list of modules and handle errors
885
        """
886
        result, errors = utils.expand_modules(modules, self.config.black_list)
887
        for error in errors:
888
            message = modname = error["mod"]
889
            key = error["key"]
890
            self.set_current_module(modname)
891
            if key == "fatal":
892
                message = str(error["ex"]).replace(os.getcwd() + os.sep, '')
893
            self.add_message(key, args=message)
894
        return result
895

    
896
    def set_current_module(self, modname, filepath=None):
897
        """set the name of the currently analyzed module and
898
        init statistics for it
899
        """
900
        if not modname and filepath is None:
901
            return
902
        self.reporter.on_set_current_module(modname, filepath)
903
        self.current_name = modname
904
        self.current_file = filepath or modname
905
        self.stats['by_module'][modname] = {}
906
        self.stats['by_module'][modname]['statement'] = 0
907
        for msg_cat in six.itervalues(utils.MSG_TYPES):
908
            self.stats['by_module'][modname][msg_cat] = 0
909

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

    
927
    def check_astroid_module(self, ast_node, walker,
928
                             rawcheckers, tokencheckers):
929
        """Check a module from its astroid representation."""
930
        try:
931
            tokens = utils.tokenize_module(ast_node)
932
        except tokenize.TokenError as ex:
933
            self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0])
934
            return
935

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

    
956
    # IAstroidChecker interface #################################################
957

    
958
    def open(self):
959
        """initialize counters"""
960
        self.stats = {'by_module' : {},
961
                      'by_msg' : {},
962
                     }
963
        MANAGER.optimize_ast = self.config.optimize_ast
964
        MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
965
        MANAGER.extension_package_whitelist.update(
966
            self.config.extension_pkg_whitelist)
967
        for msg_cat in six.itervalues(utils.MSG_TYPES):
968
            self.stats[msg_cat] = 0
969

    
970
    def generate_reports(self):
971
        """close the whole package /module, it's time to make reports !
972

973
        if persistent run, pickle results for later comparison
974
        """
975
        # Display whatever messages are left on the reporter.
976
        self.reporter.display_messages(report_nodes.Section())
977

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

    
998
    # specific reports ########################################################
999

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

    
1020
# some reporting functions ####################################################
1021

    
1022
def report_total_messages_stats(sect, stats, previous_stats):
1023
    """make total errors / warnings report"""
1024
    lines = ['type', 'number', 'previous', 'difference']
1025
    lines += checkers.table_lines_from_stats(stats, previous_stats,
1026
                                             ('convention', 'refactor',
1027
                                              'warning', 'error'))
1028
    sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1))
1029

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

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

    
1080

    
1081
# utilities ###################################################################
1082

    
1083

    
1084
class ArgumentPreprocessingError(Exception):
1085
    """Raised if an error occurs during argument preprocessing."""
1086

    
1087

    
1088
def preprocess_options(args, search_for):
1089
    """look for some options (keys of <search_for>) which have to be processed
1090
    before others
1091

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

    
1122

    
1123
@contextlib.contextmanager
1124
def fix_import_path(args):
1125
    """Prepare sys.path for running the linter checks.
1126

1127
    Within this context, each of the given arguments is importable.
1128
    Paths are added to sys.path in corresponding order to the arguments.
1129
    We avoid adding duplicate directories to sys.path.
1130
    `sys.path` is reset to its original value upon exitign this context.
1131
    """
1132
    orig = list(sys_path())
1133
    changes = []
1134
    for arg in args:
1135
        path = _get_python_path(arg)
1136
        if path in changes:
1137
            continue
1138
        else:
1139
            changes.append(path)
1140
    sys.path[:] = changes + sys_path()
1141
    try:
1142
        yield
1143
    finally:
1144
        sys.path[:] = orig
1145

    
1146

    
1147
class Run(object):
1148
    """helper class to use as main for pylint :
1149

1150
    run(*sys.argv[1:])
1151
    """
1152
    LinterClass = PyLinter
1153
    option_groups = (
1154
        ('Commands', 'Options which are actually commands. Options in this \
1155
group are mutually exclusive.'),
1156
        )
1157

    
1158
    def __init__(self, args, reporter=None, exit=True):
1159
        self._rcfile = None
1160
        self._plugins = []
1161
        try:
1162
            preprocess_options(args, {
1163
                # option: (callback, takearg)
1164
                'init-hook':   (cb_init_hook, True),
1165
                'rcfile':       (self.cb_set_rcfile, True),
1166
                'load-plugins': (self.cb_add_plugins, True),
1167
                })
1168
        except ArgumentPreprocessingError as ex:
1169
            print(ex, file=sys.stderr)
1170
            sys.exit(32)
1171

    
1172
        self.linter = linter = self.LinterClass((
1173
            ('rcfile',
1174
             {'action' : 'callback', 'callback' : lambda *args: 1,
1175
              'type': 'string', 'metavar': '<file>',
1176
              'help' : 'Specify a configuration file.'}),
1177

    
1178
            ('init-hook',
1179
             {'action' : 'callback', 'callback' : lambda *args: 1,
1180
              'type' : 'string', 'metavar': '<code>',
1181
              'level': 1,
1182
              'help' : 'Python code to execute, usually for sys.path '
1183
                       'manipulation such as pygtk.require().'}),
1184

    
1185
            ('help-msg',
1186
             {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
1187
              'callback' : self.cb_help_message,
1188
              'group': 'Commands',
1189
              'help' : 'Display a help message for the given message id and '
1190
                       'exit. The value may be a comma separated list of message ids.'}),
1191

    
1192
            ('list-msgs',
1193
             {'action' : 'callback', 'metavar': '<msg-id>',
1194
              'callback' : self.cb_list_messages,
1195
              'group': 'Commands', 'level': 1,
1196
              'help' : "Generate pylint's messages."}),
1197

    
1198
            ('list-conf-levels',
1199
             {'action' : 'callback',
1200
              'callback' : cb_list_confidence_levels,
1201
              'group': 'Commands', 'level': 1,
1202
              'help' : "Generate pylint's messages."}),
1203

    
1204
            ('full-documentation',
1205
             {'action' : 'callback', 'metavar': '<msg-id>',
1206
              'callback' : self.cb_full_documentation,
1207
              'group': 'Commands', 'level': 1,
1208
              'help' : "Generate pylint's full documentation."}),
1209

    
1210
            ('generate-rcfile',
1211
             {'action' : 'callback', 'callback' : self.cb_generate_config,
1212
              'group': 'Commands',
1213
              'help' : 'Generate a sample configuration file according to '
1214
                       'the current configuration. You can put other options '
1215
                       'before this one to get them in the generated '
1216
                       'configuration.'}),
1217

    
1218
            ('generate-man',
1219
             {'action' : 'callback', 'callback' : self.cb_generate_manpage,
1220
              'group': 'Commands',
1221
              'help' : "Generate pylint's man page.", 'hide': True}),
1222

    
1223
            ('errors-only',
1224
             {'action' : 'callback', 'callback' : self.cb_error_mode,
1225
              'short': 'E',
1226
              'help' : 'In error mode, checkers without error messages are '
1227
                       'disabled and for others, only the ERROR messages are '
1228
                       'displayed, and no reports are done by default'''}),
1229

    
1230
            ('py3k',
1231
             {'action' : 'callback', 'callback' : self.cb_python3_porting_mode,
1232
              'help' : 'In Python 3 porting mode, all checkers will be '
1233
                       'disabled and only messages emitted by the porting '
1234
                       'checker will be displayed'}),
1235

    
1236
            ('profile', utils.deprecated_option(opt_type='yn')),
1237

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

    
1305
        if linter.config.jobs < 0:
1306
            print("Jobs number (%d) should be greater than 0"
1307
                  % linter.config.jobs, file=sys.stderr)
1308
            sys.exit(32)
1309
        if linter.config.jobs > 1 or linter.config.jobs == 0:
1310
            if multiprocessing is None:
1311
                print("Multiprocessing library is missing, "
1312
                      "fallback to single process", file=sys.stderr)
1313
                linter.set_option("jobs", 1)
1314
            else:
1315
                if linter.config.jobs == 0:
1316
                    linter.config.jobs = multiprocessing.cpu_count()
1317

    
1318
        # insert current working directory to the python path to have a correct
1319
        # behaviour
1320
        with fix_import_path(args):
1321
            linter.check(args)
1322
            linter.generate_reports()
1323
        if exit:
1324
            sys.exit(self.linter.msg_status)
1325

    
1326
    def cb_set_rcfile(self, name, value):
1327
        """callback for option preprocessing (i.e. before option parsing)"""
1328
        self._rcfile = value
1329

    
1330
    def cb_add_plugins(self, name, value):
1331
        """callback for option preprocessing (i.e. before option parsing)"""
1332
        self._plugins.extend(utils._splitstrip(value))
1333

    
1334
    def cb_error_mode(self, *args, **kwargs):
1335
        """error mode:
1336
        * disable all but error messages
1337
        * disable the 'miscellaneous' checker which can be safely deactivated in
1338
          debug
1339
        * disable reports
1340
        * do not save execution information
1341
        """
1342
        self.linter.error_mode()
1343

    
1344
    def cb_generate_config(self, *args, **kwargs):
1345
        """optik callback for sample config file generation"""
1346
        self.linter.generate_config(skipsections=('COMMANDS',))
1347
        sys.exit(0)
1348

    
1349
    def cb_generate_manpage(self, *args, **kwargs):
1350
        """optik callback for sample config file generation"""
1351
        from pylint import __pkginfo__
1352
        self.linter.generate_manpage(__pkginfo__)
1353
        sys.exit(0)
1354

    
1355
    def cb_help_message(self, option, optname, value, parser):
1356
        """optik callback for printing some help about a particular message"""
1357
        self.linter.msgs_store.help_message(utils._splitstrip(value))
1358
        sys.exit(0)
1359

    
1360
    def cb_full_documentation(self, option, optname, value, parser):
1361
        """optik callback for printing full documentation"""
1362
        self.linter.print_full_documentation()
1363
        sys.exit(0)
1364

    
1365
    def cb_list_messages(self, option, optname, value, parser): # FIXME
1366
        """optik callback for printing available messages"""
1367
        self.linter.msgs_store.list_messages()
1368
        sys.exit(0)
1369

    
1370
    def cb_python3_porting_mode(self, *args, **kwargs):
1371
        """Activate only the python3 porting checker."""
1372
        self.linter.python3_porting_mode()
1373

    
1374

    
1375
def cb_list_confidence_levels(option, optname, value, parser):
1376
    for level in interfaces.CONFIDENCE_LEVELS:
1377
        print('%-18s: %s' % level)
1378
    sys.exit(0)
1379

    
1380
def cb_init_hook(optname, value):
1381
    """exec arbitrary code to set sys.path for instance"""
1382
    exec(value) # pylint: disable=exec-used
1383

    
1384

    
1385
if __name__ == '__main__':
1386
    Run(sys.argv[1:])