summaryrefslogtreecommitdiff
path: root/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'utils.py')
-rw-r--r--utils.py353
1 files changed, 353 insertions, 0 deletions
diff --git a/utils.py b/utils.py
new file mode 100644
index 000000000..5b3921268
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,353 @@
+# Copyright (c) 2003-2005 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""some various utilities and helper classes, most of them used in the
+main pylint class
+"""
+
+__revision__ = "$Id: utils.py,v 1.13 2006-04-19 09:17:40 syt Exp $"
+
+from os import linesep
+
+from logilab.astng import Module
+from logilab.common.textutils import normalize_text
+from logilab.common.ureports import Section
+
+from pylint.checkers import EmptyReport
+
+class UnknownMessage(Exception):
+ """raised when a unregistered message id is encountered"""
+
+
+MSG_TYPES = {
+ 'I' : 'info',
+ 'C' : 'convention',
+ 'R' : 'refactor',
+ 'W' : 'warning',
+ 'E' : 'error',
+ 'F' : 'fatal'
+ }
+MSG_CATEGORIES = MSG_TYPES.keys()
+
+
+def sort_checkers(checkers, enabled_only=True):
+ """return a list of enabled checker sorted by priority"""
+ if enabled_only:
+ checkers = [(-checker.priority, checker) for checker in checkers
+ if checker.is_enabled()]
+ else:
+ checkers = [(-checker.priority, checker) for checker in checkers]
+ checkers.sort()
+ return [item[1] for item in checkers]
+
+def sort_msgs(msg_ids):
+ """sort message identifiers according to their category first"""
+ msg_order = ['I', 'C', 'R', 'W', 'E', 'F']
+ def cmp_func(msgid1, msgid2):
+ """comparison function for two message identifiers"""
+ if msgid1[0] != msgid2[0]:
+ return cmp(msg_order.index(msgid1[0]), msg_order.index(msgid2[0]))
+ else:
+ return cmp(msgid1, msgid2)
+ msg_ids.sort(cmp_func)
+ return msg_ids
+
+def get_module_and_frameid(node):
+ """return the module name and the frame id in the module"""
+ frame = node.frame()
+ module, obj = '', []
+ while frame:
+ if isinstance(frame, Module):
+ module = frame.name
+ else:
+ obj.append(getattr(frame, 'name', '<lambda>'))
+ try:
+ frame = frame.parent.frame()
+ except AttributeError:
+ frame = None
+ obj.reverse()
+ return module, '.'.join(obj)
+
+
+class MessagesHandlerMixIn:
+ """a mix-in class containing all the messages related methods for the main
+ lint class
+ """
+
+ def __init__(self):
+ # dictionary of registered messages
+ self._messages = {}
+ self._messages_help = {}
+ self._msgs_state = {}
+ self._module_msgs_state = None
+ self._msg_cats_state = {}
+ self._module_msg_cats_state = None
+
+ def register_messages(self, checker):
+ """register a dictionary of messages
+
+ Keys are message ids, values are a 2-uple with the message type and the
+ message itself
+
+ message ids should be a string of len 4, where the to first characters
+ are the checker id and the two last the message id in this checker
+ """
+ msgs_dict = checker.msgs
+ chk_id = None
+ for msg_id, (msg, msg_help) in msgs_dict.items():
+ # avoid duplicate / malformed ids
+ assert not self._messages.has_key(msg_id), \
+ 'Message id %r is already defined' % msg_id
+ assert len(msg_id) == 5, 'Invalid message id %s' % msg_id
+ assert chk_id is None or chk_id == msg_id[1:3], \
+ 'Inconsistent checker part in message id %r' %msg_id
+ assert msg_id[0] in MSG_CATEGORIES, \
+ 'Bad message type %s in %r' % (msg_id[0], msg_id)
+ chk_id = msg_id[1:3]
+ if checker is not None:
+ add = ' This message belongs to the %s checker.' % checker.name
+ msg_help += add
+ self._messages_help[msg_id] = msg_help
+ self._messages[msg_id] = msg
+
+ def get_message_help(self, msg_id):
+ """return the help string for the given message id"""
+ msg_id = self.check_message_id(msg_id)
+ msg = self._messages_help[msg_id]
+ msg = normalize_text(' '.join(msg.split()), indent=' ')
+ return '%s:\n%s' % (msg_id, msg)
+
+ def disable_message(self, msg_id, scope='package', line=None):
+ """don't output message of the given id"""
+ assert scope in ('package', 'module')
+ msg_id = self.check_message_id(msg_id)
+ if scope == 'module':
+ assert line > 0
+ if msg_id != 'I0011':
+ self.add_message('I0011', line=line, args=msg_id)
+ #self._module_msgs_state[msg_id] = False
+ try:
+ self._module_msgs_state[msg_id][line] = False
+ except KeyError:
+ self._module_msgs_state[msg_id] = {line: False}
+
+ else:
+ msgs = self._msgs_state
+ msgs[msg_id] = False
+ # sync configuration object
+ self.config.disable_msg = [mid for mid, val in msgs.items()
+ if not val]
+
+ def enable_message(self, msg_id, scope='package', line=None):
+ """reenable message of the given id"""
+ assert scope in ('package', 'module')
+ msg_id = self.check_message_id(msg_id)
+ if scope == 'module':
+ assert line > 0
+ self.add_message('I0012', line=line, args=msg_id)
+ try:
+ self._module_msgs_state[msg_id][line] = True
+ except KeyError:
+ self._module_msgs_state[msg_id] = {line: True}
+ else:
+ msgs = self._msgs_state
+ msgs[msg_id] = True
+ # sync configuration object
+ self.config.enable_msg = [mid for mid, val in msgs.items() if val]
+
+ def disable_message_category(self, msg_cat_id, scope='package', line=None):
+ """don't output message in the given category"""
+ assert scope in ('package', 'module')
+ msg_cat_id = msg_cat_id[0].upper()
+ if scope == 'module':
+ self.add_message('I0011', line=line, args=msg_cat_id)
+ self._module_msg_cats_state[msg_cat_id] = False
+ else:
+ self._msg_cats_state[msg_cat_id] = False
+
+ def enable_message_category(self, msg_cat_id, scope='package', line=None):
+ """reenable message of the given category"""
+ assert scope in ('package', 'module')
+ msg_cat_id = msg_cat_id[0].upper()
+ if scope == 'module':
+ self.add_message('I0012', line=line, args=msg_cat_id)
+ self._module_msg_cats_state[msg_cat_id] = True
+ else:
+ self._msg_cats_state[msg_cat_id] = True
+
+ def check_message_id(self, msg_id):
+ """raise UnknownMessage if the message id is not defined"""
+ msg_id = msg_id.upper()
+ if not self._messages.has_key(msg_id):
+ raise UnknownMessage('No such message id %s' % msg_id)
+ return msg_id
+
+ def is_message_enabled(self, msg_id, line=None):
+ """return true if the message associated to the given message id is
+ enabled
+ """
+ try:
+ if not self._module_msg_cats_state[msg_id[0]]:
+ return False
+ except (KeyError, TypeError):
+ if not self._msg_cats_state.get(msg_id[0], True):
+ return False
+ if line is None:
+ return self._msgs_state.get(msg_id, True)
+ try:
+ return self._module_msgs_state[msg_id][line]
+ except (KeyError, TypeError):
+ return self._msgs_state.get(msg_id, True)
+
+ def add_message(self, msg_id, line=None, node=None, args=None):
+ """add the message corresponding to the given id.
+
+ If provided, msg is expanded using args
+
+ astng checkers should provide the node argument, raw checkers should
+ provide the line argument.
+ """
+ if line is None and node is not None:
+ line = node.lineno or node.statement().lineno
+ #if not isinstance(node, Module):
+ # assert line > 0, node.__class__
+ # should this message be displayed
+ if not self.is_message_enabled(msg_id, line):
+ return
+ # update stats
+ msg_cat = MSG_TYPES[msg_id[0]]
+ self.stats[msg_cat] += 1
+ self.stats['by_module'][self.current_name][msg_cat] += 1
+ try:
+ self.stats['by_msg'][msg_id] += 1
+ except KeyError:
+ self.stats['by_msg'][msg_id] = 1
+ msg = self._messages[msg_id]
+ # expand message ?
+ if args:
+ msg %= args
+ # get module and object
+ if node is None:
+ module, obj = self.current_name, ''
+ path = self.current_file
+ else:
+ module, obj = get_module_and_frameid(node)
+ path = node.root().file
+ # add the message
+ self.reporter.add_message(msg_id, (path, module, obj, line or 0), msg)
+
+ def help_message(self, msgids):
+ """display help messages for the given message identifiers"""
+ for msg_id in msgids:
+ try:
+ print self.get_message_help(msg_id)
+ print
+ except UnknownMessage, ex:
+ print ex
+ print
+ continue
+
+ def list_messages(self):
+ """list available messages"""
+ for checker in sort_checkers(self._checkers.keys()):
+ print checker.name.capitalize()
+ print '-' * len(checker.name)
+ print
+ if checker.__doc__: # __doc__ is None with -OO
+ print 'Description'
+ print '~~~~~~~~~~~'
+ print linesep.join([line.strip()
+ for line in checker.__doc__.splitlines()])
+ print
+ if not checker.msgs:
+ continue
+ print 'Messages'
+ print '~~~~~~~~'
+ for msg_id in sort_msgs(checker.msgs.keys()):
+ print self.get_message_help(msg_id)
+ print
+ print
+ print
+
+
+class ReportsHandlerMixIn:
+ """a mix-in class containing all the reports and stats manipulation
+ related methods for the main lint class
+ """
+ def __init__(self):
+ self._reports = {}
+ self._reports_state = {}
+
+ def register_report(self, r_id, r_title, r_cb, checker):
+ """register a report
+
+ r_id is the unique identifier for the report
+ r_title the report's title
+ r_cb the method to call to make the report
+ checker is the checker defining the report
+ """
+ r_id = r_id.upper()
+ self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) )
+
+ def enable_report(self, r_id):
+ """disable the report of the given id"""
+ r_id = r_id.upper()
+ self._reports_state[r_id] = True
+
+ def disable_report(self, r_id):
+ """disable the report of the given id"""
+ r_id = r_id.upper()
+ self._reports_state[r_id] = False
+
+ def is_report_enabled(self, r_id):
+ """return true if the report associated to the given identifier is
+ enabled
+ """
+ return self._reports_state.get(r_id, True)
+
+ def make_reports(self, stats, old_stats):
+ """render registered reports"""
+ if self.config.files_output:
+ filename = 'pylint_global.' + self.reporter.extension
+ self.reporter.set_output(open(filename, 'w'))
+ sect = Section('Report',
+ '%s statements analysed.'% (self.stats['statement']))
+ checkers = sort_checkers(self._reports.keys())
+ checkers.reverse()
+ for checker in checkers:
+ for r_id, r_title, r_cb in self._reports[checker]:
+ if not self.is_report_enabled(r_id):
+ continue
+ report_sect = Section(r_title)
+ try:
+ r_cb(report_sect, stats, old_stats)
+ except EmptyReport:
+ continue
+ report_sect.report_id = r_id
+ sect.append(report_sect)
+ self.reporter.display_results(sect)
+
+ def add_stats(self, **kwargs):
+ """add some stats entries to the statistic dictionary
+ raise an AssertionError if there is a key conflict
+ """
+ for key, value in kwargs.items():
+ if key[-1] == '_':
+ key = key[:-1]
+ assert not self.stats.has_key(key)
+ self.stats[key] = value
+ return self.stats
+