diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-02-17 01:09:04 +0200 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-02-17 01:09:04 +0200 |
commit | d00adfbdc7b77280f6a7c97e39cb882b853da1d8 (patch) | |
tree | 26722500b372c31cda2f8e49701fe5735171684b /pylint/reporters | |
parent | fe36ec0486cfc9aba10d8d87b0f42077ad1e78db (diff) | |
parent | 1dc09ef472a1bc0ca312c7cd8a1a3c700b510b47 (diff) | |
download | pylint-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__.py | 133 | ||||
-rw-r--r-- | pylint/reporters/guireporter.py | 27 | ||||
-rw-r--r-- | pylint/reporters/html.py | 101 | ||||
-rw-r--r-- | pylint/reporters/json.py | 58 | ||||
-rw-r--r-- | pylint/reporters/text.py | 146 |
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) |