Source code for pylint_report.pylint_report

#!/usr/bin/env python3
"""Custom JSON reporter for pylint, and JSON to HTML export utility."""
import argparse
import html
import json
import sys
from datetime import datetime
from pathlib import Path

import jinja2
import pandas as pd
from pylint.reporters.base_reporter import BaseReporter

CURRENT_DIR = Path(__file__).resolve().parent

TEMPLATE_FILE = "style/template.html.j2"
DEFAULT_CSS_FILE = "style/pylint-report.css"
COLS2KEEP = ["line", "column", "symbol", "type", "obj", "message"]


[docs] def get_score(stats): """Compute score. Note ----- https://pylint.pycqa.org/en/latest/user_guide/configuration/all-options.html#evaluation """ f = stats.get("fatal", False) e = stats.get("error", 0) w = stats.get("warning", 0) r = stats.get("refactor", 0) c = stats.get("convention", 0) s = stats.get("statement", 0) if s == 0: return None return max(0, 0 if f else 10 * (1 - ((5 * e + w + r + c) / s)))
[docs] def get_template(): """Return jinja2 template.""" return jinja2.Environment( loader=jinja2.FileSystemLoader(CURRENT_DIR), keep_trailing_newline=True, undefined=jinja2.StrictUndefined, ).get_template(TEMPLATE_FILE)
[docs] def json2html(data, external_css): """Generate an html file based on JSON data.""" if not external_css: with open(CURRENT_DIR / DEFAULT_CSS_FILE, "r", encoding="utf-8") as h: css = h.read() else: css = None if data["messages"]: msg = { name: df.sort_values(["line", "column"]).reset_index(drop=True) for name, df in pd.DataFrame(data["messages"]).groupby("module") } else: msg = {} score = get_score(data["stats"]) score = None if score is None else f"{score:0.2f}" now = datetime.now() context = dict( cols2keep=COLS2KEEP, date=now.strftime("%Y-%d-%m"), time=now.strftime("%H:%M:%S"), score=score, external_css=external_css, css=css, modules=data["stats"]["by_module"], msg=msg, ) return get_template().render(context)
class _SetEncoder(json.JSONEncoder): """Handle sets when dumping to json. Note ----- See https://stackoverflow.com/a/8230505 """ def default(self, o): if isinstance(o, set): return list(o) return json.JSONEncoder.default(self, o)
[docs] class CustomJsonReporter(BaseReporter): """Customize the default json reporter. Note ----- See ``pylint/reporters/json_reporter.py`` """ name = "custom json" def __init__(self, output=None): """Construct object.""" super().__init__(sys.stdout if output is None else output) self.messages = []
[docs] def handle_message(self, msg): """Manage message of different type and in the context of path.""" self.messages.append( { "type": msg.category, "module": msg.module, "obj": msg.obj, "line": msg.line, "column": msg.column, "path": msg.path, "symbol": msg.symbol, "message": html.escape(msg.msg or "", quote=False), "message-id": msg.msg_id, } )
[docs] def display_messages(self, layout): """See ``pylint/reporters/base_reporter.py``."""
[docs] def display_reports(self, layout): """See ``pylint/reporters/base_reporter.py``."""
def _display(self, layout): """See ``pylint/reporters/base_reporter.py``."""
[docs] def on_close(self, stats, previous_stats): """See ``pylint/reporters/base_reporter.py``.""" if not isinstance(stats, dict): # behavior from version 2.12.0 stats = { key: getattr(stats, key) for key in [ "by_module", "statement", "error", "warning", "refactor", "convention", ] } print( json.dumps( {"messages": self.messages, "stats": stats}, cls=_SetEncoder, indent=2 ), file=self.out, )
[docs] def register(linter): """Register a reporter (required by :mod:`pylint`).""" linter.register_reporter(CustomJsonReporter)
[docs] def get_parser(): """Define cli parser.""" parser = argparse.ArgumentParser() # see https://stackoverflow.com/a/11038508 parser.add_argument( "json_file", nargs="?", type=argparse.FileType("r"), default=sys.stdin, help="JSON file/stdin generated by ``pylint``", ) parser.add_argument( "-o", dest="html_file", type=argparse.FileType("w"), default=sys.stdout, help="ame of html file to generate (send to stdout by default)", ) parser.add_argument( "-e", "--external-css", action="store_true", help=( "use external ``pylint-report.css`` file " "(by default CSS styles are stored in the HTML)" ), ) return parser
[docs] def main(argv=None): """Main.""" args = get_parser().parse_args(argv) with args.json_file as h: json_data = json.load(h) print(json2html(json_data, args.external_css), file=args.html_file)
[docs] def sphinx_argparse_func(): # pragma: no cover """Return a parser to use with sphinx-argparse.""" return get_parser()
if __name__ == "__main__": # pragma: no cover main()