summaryrefslogtreecommitdiff
path: root/pylint/reporters
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-02-17 01:09:04 +0200
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-02-17 01:09:04 +0200
commitd00adfbdc7b77280f6a7c97e39cb882b853da1d8 (patch)
tree26722500b372c31cda2f8e49701fe5735171684b /pylint/reporters
parentfe36ec0486cfc9aba10d8d87b0f42077ad1e78db (diff)
parent1dc09ef472a1bc0ca312c7cd8a1a3c700b510b47 (diff)
downloadpylint-d00adfbdc7b77280f6a7c97e39cb882b853da1d8.tar.gz
The HTML output accepts the `--msg-template` option.
Patch by Daniel Goldsmith. Closes issue #135.
Diffstat (limited to 'pylint/reporters')
-rw-r--r--pylint/reporters/__init__.py133
-rw-r--r--pylint/reporters/guireporter.py27
-rw-r--r--pylint/reporters/html.py101
-rw-r--r--pylint/reporters/json.py58
-rw-r--r--pylint/reporters/text.py146
5 files changed, 465 insertions, 0 deletions
diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py
new file mode 100644
index 0000000..ea3281f
--- /dev/null
+++ b/pylint/reporters/__init__.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""utilities methods and classes for reporters"""
+from __future__ import print_function
+
+import sys
+import locale
+import os
+
+
+from pylint import utils
+
+CMPS = ['=', '-', '+']
+
+# py3k has no more cmp builtin
+if sys.version_info >= (3, 0):
+ def cmp(a, b): # pylint: disable=redefined-builtin
+ return (a > b) - (a < b)
+
+def diff_string(old, new):
+ """given a old and new int value, return a string representing the
+ difference
+ """
+ diff = abs(old - new)
+ diff_str = "%s%s" % (CMPS[cmp(old, new)], diff and ('%.2f' % diff) or '')
+ return diff_str
+
+
+class BaseReporter(object):
+ """base class for reporters
+
+ symbols: show short symbolic names for messages.
+ """
+
+ extension = ''
+
+ def __init__(self, output=None):
+ self.linter = None
+ # self.include_ids = None # Deprecated
+ # self.symbols = None # Deprecated
+ self.section = 0
+ self.out = None
+ self.out_encoding = None
+ self.encode = None
+ self.set_output(output)
+ # Build the path prefix to strip to get relative paths
+ self.path_strip_prefix = os.getcwd() + os.sep
+
+ def handle_message(self, msg):
+ """Handle a new message triggered on the current file.
+
+ Invokes the legacy add_message API by default."""
+ self.add_message(
+ msg.msg_id, (msg.abspath, msg.module, msg.obj, msg.line, msg.column),
+ msg.msg)
+
+ def add_message(self, msg_id, location, msg):
+ """Deprecated, do not use."""
+ raise NotImplementedError
+
+ def set_output(self, output=None):
+ """set output stream"""
+ self.out = output or sys.stdout
+ # py3k streams handle their encoding :
+ if sys.version_info >= (3, 0):
+ self.encode = lambda x: x
+ return
+
+ def encode(string):
+ if not isinstance(string, unicode):
+ return string
+ encoding = (getattr(self.out, 'encoding', None) or
+ locale.getdefaultlocale()[1] or
+ sys.getdefaultencoding())
+ # errors=replace, we don't want to crash when attempting to show
+ # source code line that can't be encoded with the current locale
+ # settings
+ return string.encode(encoding, 'replace')
+ self.encode = encode
+
+ def writeln(self, string=''):
+ """write a line in the output buffer"""
+ print(self.encode(string), file=self.out)
+
+ def display_results(self, layout):
+ """display results encapsulated in the layout tree"""
+ self.section = 0
+ if hasattr(layout, 'report_id'):
+ layout.children[0].children[0].data += ' (%s)' % layout.report_id
+ self._display(layout)
+
+ def _display(self, layout):
+ """display the layout"""
+ raise NotImplementedError()
+
+ # Event callbacks
+
+ def on_set_current_module(self, module, filepath):
+ """starting analyzis of a module"""
+ pass
+
+ def on_close(self, stats, previous_stats):
+ """global end of analyzis"""
+ pass
+
+
+class CollectingReporter(BaseReporter):
+ """collects messages"""
+
+ name = 'collector'
+
+ def __init__(self):
+ BaseReporter.__init__(self)
+ self.messages = []
+
+ def handle_message(self, msg):
+ self.messages.append(msg)
+
+
+def initialize(linter):
+ """initialize linter with reporters in this package """
+ utils.register_plugins(linter, __path__[0])
diff --git a/pylint/reporters/guireporter.py b/pylint/reporters/guireporter.py
new file mode 100644
index 0000000..4ad4ebb
--- /dev/null
+++ b/pylint/reporters/guireporter.py
@@ -0,0 +1,27 @@
+""" reporter used by gui.py """
+
+import sys
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+from logilab.common.ureports import TextWriter
+
+
+class GUIReporter(BaseReporter):
+ """saves messages"""
+
+ __implements__ = IReporter
+ extension = ''
+
+ def __init__(self, gui, output=sys.stdout):
+ """init"""
+ BaseReporter.__init__(self, output)
+ self.gui = gui
+
+ def handle_message(self, msg):
+ """manage message of different type and in the context of path"""
+ self.gui.msg_queue.put(msg)
+
+ def _display(self, layout):
+ """launch layouts display"""
+ TextWriter().format(layout, self.out)
diff --git a/pylint/reporters/html.py b/pylint/reporters/html.py
new file mode 100644
index 0000000..1e050d3
--- /dev/null
+++ b/pylint/reporters/html.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""HTML reporter"""
+
+import itertools
+import string
+import sys
+
+from logilab.common.ureports import HTMLWriter, Section, Table
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+
+
+class HTMLReporter(BaseReporter):
+ """report messages and layouts in HTML"""
+
+ __implements__ = IReporter
+ name = 'html'
+ extension = 'html'
+
+ def __init__(self, output=sys.stdout):
+ BaseReporter.__init__(self, output)
+ self.msgs = []
+ # Add placeholders for title and parsed messages
+ self.header = None
+ self.msgargs = []
+
+ @staticmethod
+ def _parse_msg_template(msg_template):
+ formatter = string.Formatter()
+ parsed = formatter.parse(msg_template)
+ for item in parsed:
+ if item[1]:
+ yield item[1]
+
+ def _parse_template(self):
+ """Helper function to parse the message template"""
+ self.header = []
+ if self.linter.config.msg_template:
+ msg_template = self.linter.config.msg_template
+ else:
+ msg_template = '{category}{module}{obj}{line}{column}{msg}'
+
+ _header, _msgs = itertools.tee(self._parse_msg_template(msg_template))
+ self.header = list(_header)
+ self.msgargs = list(_msgs)
+
+ def handle_message(self, msg):
+ """manage message of different type and in the context of path"""
+
+ # It would be better to do this in init, but currently we do not
+ # have access to the linter (as it is setup in lint.set_reporter()
+ # Therefore we try to parse just the once.
+ if self.header is None:
+ self._parse_template()
+
+ # We want to add the lines given by the template
+ self.msgs += [str(getattr(msg, field)) for field in self.msgargs]
+
+ def set_output(self, output=None):
+ """set output stream
+
+ messages buffered for old output is processed first"""
+ if self.out and self.msgs:
+ self._display(Section())
+ BaseReporter.set_output(self, output)
+
+ def _display(self, layout):
+ """launch layouts display
+
+ overridden from BaseReporter to add insert the messages section
+ (in add_message, message is not displayed, just collected so it
+ can be displayed in an html table)
+ """
+ if self.msgs:
+ # add stored messages to the layout
+ msgs = self.header
+ cols = len(self.header)
+ msgs += self.msgs
+ sect = Section('Messages')
+ layout.append(sect)
+ sect.append(Table(cols=cols, children=msgs, rheaders=1))
+ self.msgs = []
+ HTMLWriter().format(layout, self.out)
+
+
+def register(linter):
+ """Register the reporter classes with the linter."""
+ linter.register_reporter(HTMLReporter)
diff --git a/pylint/reporters/json.py b/pylint/reporters/json.py
new file mode 100644
index 0000000..7dba52b
--- /dev/null
+++ b/pylint/reporters/json.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""JSON reporter"""
+from __future__ import absolute_import, print_function
+
+import json
+import sys
+from cgi import escape
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+
+
+class JSONReporter(BaseReporter):
+ """Report messages and layouts in JSON."""
+
+ __implements__ = IReporter
+ name = 'json'
+ extension = 'json'
+
+ def __init__(self, output=sys.stdout):
+ BaseReporter.__init__(self, output)
+ self.messages = []
+
+ def handle_message(self, message):
+ """Manage message of different type and in the context of path."""
+
+ self.messages.append({
+ 'type': message.category,
+ 'module': message.module,
+ 'obj': message.obj,
+ 'line': message.line,
+ 'column': message.column,
+ 'path': message.path,
+ 'symbol': message.symbol,
+ 'message': escape(message.msg or ''),
+ })
+
+ def _display(self, layout):
+ """Launch layouts display"""
+ if self.messages:
+ print(json.dumps(self.messages, indent=4), file=self.out)
+
+
+def register(linter):
+ """Register the reporter classes with the linter."""
+ linter.register_reporter(JSONReporter)
diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py
new file mode 100644
index 0000000..53c4a8d
--- /dev/null
+++ b/pylint/reporters/text.py
@@ -0,0 +1,146 @@
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""Plain text reporters:
+
+:text: the default one grouping messages by module
+:colorized: an ANSI colorized text reporter
+"""
+from __future__ import print_function
+
+import warnings
+
+from logilab.common.ureports import TextWriter
+from logilab.common.textutils import colorize_ansi
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+import six
+
+TITLE_UNDERLINES = ['', '=', '-', '.']
+
+
+class TextReporter(BaseReporter):
+ """reports messages and layouts in plain text"""
+
+ __implements__ = IReporter
+ name = 'text'
+ extension = 'txt'
+ line_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})'
+
+ def __init__(self, output=None):
+ BaseReporter.__init__(self, output)
+ self._modules = set()
+ self._template = None
+
+ def on_set_current_module(self, module, filepath):
+ self._template = six.text_type(self.linter.config.msg_template or self.line_format)
+
+ def write_message(self, msg):
+ """Convenience method to write a formated message with class default template"""
+ self.writeln(msg.format(self._template))
+
+ def handle_message(self, msg):
+ """manage message of different type and in the context of path"""
+ if msg.module not in self._modules:
+ if msg.module:
+ self.writeln('************* Module %s' % msg.module)
+ self._modules.add(msg.module)
+ else:
+ self.writeln('************* ')
+ self.write_message(msg)
+
+ def _display(self, layout):
+ """launch layouts display"""
+ print(file=self.out)
+ TextWriter().format(layout, self.out)
+
+
+class ParseableTextReporter(TextReporter):
+ """a reporter very similar to TextReporter, but display messages in a form
+ recognized by most text editors :
+
+ <filename>:<linenum>:<msg>
+ """
+ name = 'parseable'
+ line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
+
+ def __init__(self, output=None):
+ warnings.warn('%s output format is deprecated. This is equivalent '
+ 'to --msg-template=%s' % (self.name, self.line_format),
+ DeprecationWarning)
+ TextReporter.__init__(self, output)
+
+
+class VSTextReporter(ParseableTextReporter):
+ """Visual studio text reporter"""
+ name = 'msvs'
+ line_format = '{path}({line}): [{msg_id}({symbol}){obj}] {msg}'
+
+
+class ColorizedTextReporter(TextReporter):
+ """Simple TextReporter that colorizes text output"""
+
+ name = 'colorized'
+ COLOR_MAPPING = {
+ "I" : ("green", None),
+ 'C' : (None, "bold"),
+ 'R' : ("magenta", "bold, italic"),
+ 'W' : ("blue", None),
+ 'E' : ("red", "bold"),
+ 'F' : ("red", "bold, underline"),
+ 'S' : ("yellow", "inverse"), # S stands for module Separator
+ }
+
+ def __init__(self, output=None, color_mapping=None):
+ TextReporter.__init__(self, output)
+ self.color_mapping = color_mapping or \
+ dict(ColorizedTextReporter.COLOR_MAPPING)
+
+ def _get_decoration(self, msg_id):
+ """Returns the tuple color, style associated with msg_id as defined
+ in self.color_mapping
+ """
+ try:
+ return self.color_mapping[msg_id[0]]
+ except KeyError:
+ return None, None
+
+ def handle_message(self, msg):
+ """manage message of different types, and colorize output
+ using ansi escape codes
+ """
+ if msg.module not in self._modules:
+ color, style = self._get_decoration('S')
+ if msg.module:
+ modsep = colorize_ansi('************* Module %s' % msg.module,
+ color, style)
+ else:
+ modsep = colorize_ansi('************* %s' % msg.module,
+ color, style)
+ self.writeln(modsep)
+ self._modules.add(msg.module)
+ color, style = self._get_decoration(msg.C)
+
+ msg = msg._replace(
+ **{attr: colorize_ansi(getattr(msg, attr), color, style)
+ for attr in ('msg', 'symbol', 'category', 'C')})
+ self.write_message(msg)
+
+
+def register(linter):
+ """Register the reporter classes with the linter."""
+ linter.register_reporter(TextReporter)
+ linter.register_reporter(ParseableTextReporter)
+ linter.register_reporter(VSTextReporter)
+ linter.register_reporter(ColorizedTextReporter)