Skip to content

Commit

Permalink
Initial rough version, incomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjc committed Jan 10, 2019
0 parents commit c825789
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#Ignore the build directory (and its sub-directories):
build

#Ignore the distribution directory
dist

#Ignore another Python specific build folder:
flake8_black.egg-info/

#Ignore backup files from some Unix editors,
*~
*.swp
*.bak

#Ignore patches and any original files created by patch command
*.diff
*.orig
*.rej

#Ignore these hidden files from Mac OS X
.DS_Store

#Ignore hidden files from Dolphin window manager
.directory
19 changes: 19 additions & 0 deletions LICENSE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright 2019, Peter Cock, The James Hutton Institute, UK.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include README.rst
include LICENSE.rst
include requirements.txt
116 changes: 116 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
flake-black
===========

.. image:: https://img.shields.io/pypi/v/flake8-black.svg
:alt: Released on the Python Package Index (PyPI)
:target: https://pypi.python.org/pypi/flake8-black
.. image:: https://img.shields.io/travis/peterjc/flake8-black/master.svg
:alt: Testing with TravisCI
:target: https://travis-ci.org/peterjc/flake8-black/branches
.. image:: https://landscape.io/github/peterjc/flake8-black/master/landscape.svg?style=flat
:alt: Landscape Code Metrics
:target: https://landscape.io/github/peterjc/flake8-black/

Introduction
------------

This is an MIT licensed `flake8 <https://gitlab.com/pycqa/flake8>`_ plugin
for validating Python code style with the command line code formatting tool
`black <https://github.com/ambv/black>`_. It is available to install from
the Python Package Index (PyPI):

- https://pypi.python.org/pypi/flake8-black

Black, "The Uncompromising Code Formatter", is normally run to edit your
Python code in place to match their coding style, a strict subset of the
`PEP 8 style guide <https://www.python.org/dev/peps/pep-0008/>`_.

The point of this plugin is to be able to run ``black --check ...`` from
within the ``flake8`` plugin ecosystem. You might use this via a ``git``
pre-commit hook, or as part of your continuous integration testing.

Flake8 Validation codes
-----------------------

Early versions of flake8 assumed a single character prefix for the validation
codes, which became problematic with collisions in the plugin ecosystem. Since
v3.0, flake8 has supported longer prefixes therefore this plugin uses ``BLK``
as its prefix.

====== =======================================================================
Code Description
------ -----------------------------------------------------------------------
BLK100 Black would edit your Python file(s).
BLK900 Error invoking Black.
====== =======================================================================


Installation and usage
----------------------

Python 3.6 or later is required to run ``black``, so that is recommanded but
``black`` can be used on Python code written for older versions of Python.

Install ``flake8-black`` using ``pip``, which should install ``flake8`` and
``black`` as well if not already present::

$ pip install flake8-black

The new validator should be automatically included when using ``flake8`` which
may now report additional validation codes starting with ``BLK`` (as defined
above). For example::

$ flake8 example.py

You can request only the ``BLK`` codes be shown using::

$ flake8 --select BLK example.py

We recommend using the following settings in your ``flake8`` configuration,
for example in your ``.flake8`` configuration::

[flake8]
# Recommend matching the black default line length of 88,
# rather than the flake8 default of 79:
max-line-length = 88
extend-ignore =
# See https://github.com/PyCQA/pycodestyle/issues/373
E203,

In order not to trigger flake8's ``E501 line too long`` errors, the plugin
passes the ``flake8`` maximum line length when it calls ``black``,
equivalent to doing ``black -l 88 --check *.py`` at the command line.

Note currently ``pycodestyle`` gives false positives on the spaces ``black``
uses for slices, which ``flake8`` reports as ``E203: whitespace before ':'``.
Until this is fixed, and ``flake8`` is updated, we suggest disabling this
style check.


Version History
---------------

======= ========== ===========================================================
Version Released Changes
------- ---------- -----------------------------------------------------------
v0.0.1 *pending* - Initial public release.
======= ========== ===========================================================


Developers
----------

This plugin is on GitHub at https://github.com/peterjc/flake8-black

To make a new release once tested locally and on TravisCI::

$ git tag vX.Y.Z
$ python setup.py sdist --formats=gztar
$ twine upload dist/flake8-black-X.Y.Z.tar.gz
$ git push origin master --tags

TODO
----

- Define different error codes based on what changes black would make?
- Create a full test suite and use this for continuous integration.
108 changes: 108 additions & 0 deletions flake8_black.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Check Python code passes black style validation via flake8.
This is a plugin for the tool flake8 tool for checking Python
soucre code using the tool black.
"""

import io
import logging
import sys
import tokenize

import black


__version__ = "0.0.1"

log = logging.getLogger(__name__)

black_prefix = "BLK"


def find_diff_start(old_src, new_src):
old_lines = old_src.split("\n")
new_lines = new_src.split("\n")

for line in range(min(len(old_lines), len(new_lines))):
old = old_lines[line]
new = new_lines[line]
if old == new:
continue
for col in range(min(len(old), len(new))):
if old[col] != new[col]:
return line, col
# Difference at the end of the line...
return line, min(len(old), len(new)) + 1
# Difference at the of the file...
return min(len(old_lines), min(new_lines)) + 1, 0


class BlackStyleChecker(object):
"""Checker of Python code using black."""

name = "black"
version = __version__

STDIN_NAMES = set(['stdin', '-', '(none)', None])

def __init__(self, tree, filename='(none)', builtins=None):
"""Initialise."""
self.tree = tree
self.filename = filename
try:
self.load_source()
self.err = None
except Exception as err:
self.source = None
self.err = err
# TODO, read flake8 config
self.line_length = 79

def run(self):
"""Use black to check code style."""
# Is there any reason not to call load_source here?
msg = None
line = 0
col = 0
if self.err is not None:
assert self.source is None
msg = black_prefix + "900 Failed to load file: %s" % self.err
yield 0, 0, msg, type(self)
elif not self.source:
# Empty file, nothing to change
pass
else:
# Call black...
try:
# Set mode?
new_code = black.format_file_contents(
self.source,
line_length=self.line_length,
fast=False)
except black.NothingChanged:
pass
except black.InvalidInput:
msg = "901 Invalid input"
except Exception as e:
msg = "999 Unexpected exception: %s" % e
else:
assert new_code != self.source, \
"Black made changes without raising NothingChanged"
line, col = find_diff_start(self.source, new_code)
msg = "100 Black would make changes"
if msg:
# If we don't know the line or column numbers, leaving as zero.
yield line, col, black_prefix + msg, type(self)

def load_source(self):
"""Load the source for the specified file."""
if self.filename in self.STDIN_NAMES:
self.filename = 'stdin'
if sys.version_info[0] < 3:
self.source = sys.stdin.read()
else:
self.source = io.TextIOWrapper(sys.stdin.buffer,
errors='ignore').read()
else:
with tokenize.open(self.filename) as handle:
self.source = handle.read()
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This is a plugin for flake8, so we require that.
# Our prefix is not single letter, so we need v3:
flake8 >= 3.0.0

# We need black, which in turn needs Python 3.6+
black
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[wheel]
universal = 1
36 changes: 36 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import with_statement

from setuptools import setup


def get_version(fname="flake8_black.py"):
with open(fname) as f:
for line in f:
if line.startswith("__version__"):
return eval(line.split("=")[-1])


setup(
name="flake8-black",
version=get_version(),
description="flake8 plugin to call black as a code style validator",
long_description=open("README.rst").read(),
license="MIT",
author="Peter J. A. Cock",
author_email="p.j.a.cock@googlemail.com",
url="https://github.com/peterjc/flake8-black",
classifiers=[
"Intended Audience :: Developers",
"Framework :: Flake8",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"License :: OSI Approved :: MIT License",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
],
keywords="PEP8",
py_modules=["flake8_black"],
install_requires=["flake8 >= 3.0.0", "black"],
entry_points={"flake8.extension": ["BLK = flake8_black:BlackStyleChecker"]},
)

0 comments on commit c825789

Please sign in to comment.