diff --git a/infer/lib/python/inferTraceBugs b/infer/lib/python/inferTraceBugs index 30e6ff674..6443c0b35 100755 --- a/infer/lib/python/inferTraceBugs +++ b/infer/lib/python/inferTraceBugs @@ -21,7 +21,7 @@ import shutil import subprocess import sys -from inferlib import analyze, utils +from inferlib import analyze, source, utils HTML_REPORT_DIR = 'report.html' TRACES_REPORT_DIR = 'traces' @@ -87,7 +87,7 @@ class Tracer(object): def __init__(self, args, level=sys.maxsize): self.args = args self.max_level = level - self.indenter = utils.Indenter() + self.indenter = source.Indenter() def build_node_tags(self, node): pass @@ -109,12 +109,12 @@ class Tracer(object): if not self.args.no_source: self.indenter.indent_push(node[utils.JSON_INDEX_TRACE_LEVEL]) - mode = utils.TERMINAL_FORMATTER + mode = source.TERMINAL_FORMATTER if self.args.html: - mode = utils.PLAIN_FORMATTER - self.indenter.add(utils.build_source_context(fname, - mode, - report_line)) + mode = source.PLAIN_FORMATTER + self.indenter.add(source.build_source_context(fname, + mode, + report_line)) self.indenter.indent_pop() self.indenter.newline() diff --git a/infer/lib/python/inferlib/issues.py b/infer/lib/python/inferlib/issues.py index 204ddac68..7a5101095 100644 --- a/infer/lib/python/inferlib/issues.py +++ b/infer/lib/python/inferlib/issues.py @@ -19,7 +19,7 @@ import sys import tempfile import xml.etree.ElementTree as ET -from . import config, utils +from . import config, source, utils # Increase the limit of the CSV parser to sys.maxlimit @@ -117,12 +117,12 @@ def print_errors(json_report, bugs_out): line = row[utils.JSON_INDEX_LINE] error_type = row[utils.JSON_INDEX_TYPE] msg = row[utils.JSON_INDEX_QUALIFIER] - indenter = utils.Indenter() + indenter = source.Indenter() indenter.indent_push() indenter.add( - utils.build_source_context(filename, - utils.TERMINAL_FORMATTER, - int(line))) + source.build_source_context(filename, + source.TERMINAL_FORMATTER, + int(line))) source_context = unicode(indenter) text_errors_list.append( u'{0}:{1}: {2}: {3}\n {4}\n{5}'.format( diff --git a/infer/lib/python/inferlib/source.py b/infer/lib/python/inferlib/source.py new file mode 100644 index 000000000..a033a7204 --- /dev/null +++ b/infer/lib/python/inferlib/source.py @@ -0,0 +1,99 @@ +# Copyright (c) 2013 - present Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the BSD style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import codecs +try: + import pygments + import pygments.formatters + import pygments.lexers +except ImportError: + pygments = None +import sys + +from . import utils + +BASE_INDENT = 2 +# how many lines of context around each report +SOURCE_CONTEXT = 2 + +# syntax highlighting modes +PLAIN_FORMATTER = 0 +TERMINAL_FORMATTER = 1 + + +class Indenter(str): + def __init__(self): + super(Indenter, self).__init__() + self.text = '' + self.indent = [] + + def indent_get(self): + indent = '' + for i in self.indent: + indent += i + return indent + + def indent_push(self, n=1): + self.indent.append(n * BASE_INDENT * ' ') + + def indent_pop(self): + return self.indent.pop() + + def newline(self): + self.text += '\n' + + def add(self, x): + if type(x) != unicode: + x = x.decode(utils.LOCALE) + lines = x.splitlines() + indent = self.indent_get() + lines = [indent + l for l in lines] + self.text += '\n'.join(lines) + + def __unicode__(self): + return self.text + + def __str__(self): + return unicode(self).encode(utils.LOCALE) + + +def build_source_context(source_name, mode, report_line): + start_line = max(1, report_line - SOURCE_CONTEXT) + # could go beyond last line, checked in the loop + end_line = report_line + SOURCE_CONTEXT + + n_length = len(str(end_line)) + line_number = 1 + s = '' + with codecs.open(source_name, 'r', encoding=utils.LOCALE) as source_file: + for line in source_file: + if start_line <= line_number <= end_line: + num = str(line_number).zfill(n_length) + caret = ' ' + if line_number == report_line: + caret = '> ' + s += u'%s. %s%s' % (num, caret, line) + line_number += 1 + return _syntax_highlighting(source_name, mode, s) + + +def _syntax_highlighting(source_name, mode, s): + if pygments is None or mode == PLAIN_FORMATTER: + return s + + lexer = pygments.lexers.get_lexer_for_filename(source_name) + formatter = None + if mode == TERMINAL_FORMATTER: + if not sys.stdout.isatty(): + return s + formatter = pygments.formatters.TerminalFormatter() + return pygments.highlight(s, lexer, formatter) diff --git a/infer/lib/python/inferlib/utils.py b/infer/lib/python/inferlib/utils.py index 06240649c..555a50466 100644 --- a/infer/lib/python/inferlib/utils.py +++ b/infer/lib/python/inferlib/utils.py @@ -20,12 +20,6 @@ import locale import logging import os import re -try: - import pygments - import pygments.formatters - import pygments.lexers -except ImportError: - pygments = None import subprocess import sys import tempfile @@ -108,14 +102,6 @@ BUCK_INFER_OUT = 'infer' FORMAT = '[%(levelname)s] %(message)s' DEBUG_FORMAT = '[%(levelname)s:%(filename)s:%(lineno)03d] %(message)s' -BASE_INDENT = 2 -# how many lines of context around each report -SOURCE_CONTEXT = 2 - -# syntax highlighting modes -PLAIN_FORMATTER = 0 -TERMINAL_FORMATTER = 1 - # Monkey patching subprocess (I'm so sorry!). if "check_output" not in dir(subprocess): @@ -413,72 +399,3 @@ class AbsolutePathAction(argparse.Action): """Convert a path from relative to absolute in the arg parser""" def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, os.path.abspath(values)) - - -class Indenter(str): - def __init__(self): - super(Indenter, self).__init__() - self.text = '' - self.indent = [] - - def indent_get(self): - indent = '' - for i in self.indent: - indent += i - return indent - - def indent_push(self, n=1): - self.indent.append(n * BASE_INDENT * ' ') - - def indent_pop(self): - return self.indent.pop() - - def newline(self): - self.text += '\n' - - def add(self, x): - if type(x) != unicode: - x = x.decode(LOCALE) - lines = x.splitlines() - indent = self.indent_get() - lines = [indent + l for l in lines] - self.text += '\n'.join(lines) - - def __unicode__(self): - return self.text - - def __str__(self): - return unicode(self).encode(LOCALE) - - -def syntax_highlighting(source_name, mode, s): - if pygments is None or mode == PLAIN_FORMATTER: - return s - - lexer = pygments.lexers.get_lexer_for_filename(source_name) - formatter = None - if mode == TERMINAL_FORMATTER: - if not sys.stdout.isatty(): - return s - formatter = pygments.formatters.TerminalFormatter() - return pygments.highlight(s, lexer, formatter) - - -def build_source_context(source_name, mode, report_line): - start_line = max(1, report_line - SOURCE_CONTEXT) - # could go beyond last line, checked in the loop - end_line = report_line + SOURCE_CONTEXT - - n_length = len(str(end_line)) - line_number = 1 - s = '' - with codecs.open(source_name, 'r', encoding=LOCALE) as source_file: - for line in source_file: - if start_line <= line_number <= end_line: - num = str(line_number).zfill(n_length) - caret = ' ' - if line_number == report_line: - caret = '> ' - s += u'%s. %s%s' % (num, caret, line) - line_number += 1 - return syntax_highlighting(source_name, mode, s)