Add diff support to blackd (#969)

This commit is contained in:
Joe Antonakakis 2019-10-28 06:25:26 -07:00 committed by Łukasz Langa
parent b65af236cf
commit df6e1a41f7
5 changed files with 70 additions and 2 deletions

View File

@ -838,6 +838,9 @@ which if present, should have the value `1`, otherwise the request is rejected w
The headers controlling how code is formatted are:
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
response, mentioning the name of the problematic header in the message body.
- `X-Line-Length`: corresponds to the `--line-length` command line flag.
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
command line flag. If present and its value is not the empty string, no string
@ -849,6 +852,8 @@ The headers controlling how code is formatted are:
a set of comma-separated Python versions, optionally prefixed with `py`. For example,
to request code that is compatible with Python 3.5 and 3.6, set the header to
`py3.5,py3.6`.
- `X-Diff`: corresponds to the `--diff` command line flag. If present, a diff of the
formats will be output.
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
response, mentioning the name of the problematic header in the message body.
@ -1034,6 +1039,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
- `blackd` now returns the version of _Black_ in the response headers (#1013)
- `blackd` can now output the diff of formats on source code when the `X-Diff` header is
provided (#969)
### 19.3b0
- new option `--target-version` to control which Python versions _Black_-formatted code

View File

@ -1,5 +1,6 @@
import asyncio
from concurrent.futures import Executor, ProcessPoolExecutor
from datetime import datetime
from functools import partial
import logging
from multiprocessing import freeze_support
@ -21,6 +22,7 @@
PYTHON_VARIANT_HEADER = "X-Python-Variant"
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
DIFF_HEADER = "X-Diff"
BLACK_HEADERS = [
PROTOCOL_VERSION_HEADER,
@ -28,6 +30,7 @@
PYTHON_VARIANT_HEADER,
SKIP_STRING_NORMALIZATION_HEADER,
FAST_OR_SAFE_HEADER,
DIFF_HEADER,
]
# Response headers
@ -112,10 +115,25 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
req_bytes = await request.content.read()
charset = request.charset if request.charset is not None else "utf8"
req_str = req_bytes.decode(charset)
then = datetime.utcnow()
loop = asyncio.get_event_loop()
formatted_str = await loop.run_in_executor(
executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
)
# Only output the diff in the HTTP response
only_diff = bool(request.headers.get(DIFF_HEADER, False))
if only_diff:
now = datetime.utcnow()
src_name = f"In\t{then} +0000"
dst_name = f"Out\t{now} +0000"
loop = asyncio.get_event_loop()
formatted_str = await loop.run_in_executor(
executor,
partial(black.diff, req_str, formatted_str, src_name, dst_name),
)
return web.Response(
content_type=request.content_type,
charset=charset,

View File

@ -0,0 +1,14 @@
--- [Deterministic header]
+++ [Deterministic header]
@@ -1,7 +1,6 @@
-def abc ():
- return ["hello", "world",
- "!"]
+def abc():
+ return ["hello", "world", "!"]
-print( "Incorrect formatting"
-)
+print("Incorrect formatting")
+

View File

@ -0,0 +1,6 @@
def abc ():
return ["hello", "world",
"!"]
print( "Incorrect formatting"
)

View File

@ -35,6 +35,7 @@
fs = partial(black.format_str, mode=black.FileMode())
THIS_FILE = Path(__file__)
THIS_DIR = THIS_FILE.parent
DETERMINISTIC_HEADER = "[Deterministic header]"
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
PY36_ARGS = [
f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
@ -259,7 +260,7 @@ def test_piping_diff(self) -> None:
black.main, args, input=BytesIO(source.encode("utf8"))
)
self.assertEqual(result.exit_code, 0)
actual = diff_header.sub("[Deterministic header]", result.output)
actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
actual = actual.rstrip() + "\n" # the diff output has a trailing space
self.assertEqual(expected, actual)
@ -340,7 +341,7 @@ def test_expression_diff(self) -> None:
finally:
os.unlink(tmp_file)
actual = result.output
actual = diff_header.sub("[Deterministic header]", actual)
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
actual = actual.rstrip() + "\n" # the diff output has a trailing space
if expected != actual:
dump = black.dump_to_file(actual)
@ -1689,6 +1690,27 @@ async def test_blackd_pyi(self) -> None:
self.assertEqual(response.status, 200)
self.assertEqual(await response.text(), expected)
@skip_if_exception("ClientOSError")
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
@unittest_run_loop
async def test_blackd_diff(self) -> None:
diff_header = re.compile(
rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
)
source, _ = read_data("blackd_diff.py")
expected, _ = read_data("blackd_diff.diff")
response = await self.client.post(
"/", data=source, headers={blackd.DIFF_HEADER: "true"}
)
self.assertEqual(response.status, 200)
actual = await response.text()
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
self.assertEqual(actual, expected)
@skip_if_exception("ClientOSError")
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
@unittest_run_loop