summaryrefslogtreecommitdiff
path: root/src/flake8/formatting/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/flake8/formatting/base.py')
-rw-r--r--src/flake8/formatting/base.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py
new file mode 100644
index 0000000..4fda6f4
--- /dev/null
+++ b/src/flake8/formatting/base.py
@@ -0,0 +1,161 @@
+"""The base class and interface for all formatting plugins."""
+from __future__ import print_function
+
+
+class BaseFormatter(object):
+ """Class defining the formatter interface.
+
+ .. attribute:: options
+
+ The options parsed from both configuration files and the command-line.
+
+ .. attribute:: filename
+
+ If specified by the user, the path to store the results of the run.
+
+ .. attribute:: output_fd
+
+ Initialized when the :meth:`start` is called. This will be a file
+ object opened for writing.
+
+ .. attribute:: newline
+
+ The string to add to the end of a line. This is only used when the
+ output filename has been specified.
+ """
+
+ def __init__(self, options):
+ """Initialize with the options parsed from config and cli.
+
+ This also calls a hook, :meth:`after_init`, so subclasses do not need
+ to call super to call this method.
+
+ :param optparse.Values options:
+ User specified configuration parsed from both configuration files
+ and the command-line interface.
+ """
+ self.options = options
+ self.filename = options.output_file
+ self.output_fd = None
+ self.newline = '\n'
+ self.after_init()
+
+ def after_init(self):
+ """Initialize the formatter further."""
+ pass
+
+ def start(self):
+ """Prepare the formatter to receive input.
+
+ This defaults to initializing :attr:`output_fd` if :attr:`filename`
+ """
+ if self.filename:
+ self.output_fd = open(self.filename, 'w')
+
+ def handle(self, error):
+ """Handle an error reported by Flake8.
+
+ This defaults to calling :meth:`format`, :meth:`show_source`, and
+ then :meth:`write`. To extend how errors are handled, override this
+ method.
+
+ :param error:
+ This will be an instance of :class:`~flake8.style_guide.Error`.
+ :type error:
+ flake8.style_guide.Error
+ """
+ line = self.format(error)
+ source = self.show_source(error)
+ self.write(line, source)
+
+ def format(self, error):
+ """Format an error reported by Flake8.
+
+ This method **must** be implemented by subclasses.
+
+ :param error:
+ This will be an instance of :class:`~flake8.style_guide.Error`.
+ :type error:
+ flake8.style_guide.Error
+ :returns:
+ The formatted error string.
+ :rtype:
+ str
+ """
+ raise NotImplementedError('Subclass of BaseFormatter did not implement'
+ ' format.')
+
+ def show_benchmarks(self, benchmarks):
+ """Format and print the benchmarks."""
+ # NOTE(sigmavirus24): The format strings are a little confusing, even
+ # to me, so here's a quick explanation:
+ # We specify the named value first followed by a ':' to indicate we're
+ # formatting the value.
+ # Next we use '<' to indicate we want the value left aligned.
+ # Then '10' is the width of the area.
+ # For floats, finally, we only want only want at most 3 digits after
+ # the decimal point to be displayed. This is the precision and it
+ # can not be specified for integers which is why we need two separate
+ # format strings.
+ float_format = '{value:<10.3} {statistic}'.format
+ int_format = '{value:<10} {statistic}'.format
+ for statistic, value in benchmarks:
+ if isinstance(value, int):
+ benchmark = int_format(statistic=statistic, value=value)
+ else:
+ benchmark = float_format(statistic=statistic, value=value)
+ self._write(benchmark)
+
+ def show_source(self, error):
+ """Show the physical line generating the error.
+
+ This also adds an indicator for the particular part of the line that
+ is reported as generating the problem.
+
+ :param error:
+ This will be an instance of :class:`~flake8.style_guide.Error`.
+ :type error:
+ flake8.style_guide.Error
+ :returns:
+ The formatted error string if the user wants to show the source.
+ If the user does not want to show the source, this will return
+ ``None``.
+ :rtype:
+ str
+ """
+ if not self.options.show_source:
+ return None
+ pointer = (' ' * error.column_number) + '^'
+ # Physical lines have a newline at the end, no need to add an extra
+ # one
+ return error.physical_line + pointer
+
+ def _write(self, output):
+ """Handle logic of whether to use an output file or print()."""
+ if self.output_fd is not None:
+ self.output_fd.write(output + self.newline)
+ else:
+ print(output)
+
+ def write(self, line, source):
+ """Write the line either to the output file or stdout.
+
+ This handles deciding whether to write to a file or print to standard
+ out for subclasses. Override this if you want behaviour that differs
+ from the default.
+
+ :param str line:
+ The formatted string to print or write.
+ :param str source:
+ The source code that has been formatted and associated with the
+ line of output.
+ """
+ self._write(line)
+ if source:
+ self._write(source)
+
+ def stop(self):
+ """Clean up after reporting is finished."""
+ if self.output_fd is not None:
+ self.output_fd.close()
+ self.output_fd = None