Skip to content

Commit df6e1a4

Browse files
Jma353ambv
authored andcommitted
Add diff support to blackd (#969)
1 parent b65af23 commit df6e1a4

File tree

5 files changed

+70
-2
lines changed

5 files changed

+70
-2
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,9 @@ which if present, should have the value `1`, otherwise the request is rejected w
838838

839839
The headers controlling how code is formatted are:
840840

841+
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
842+
response, mentioning the name of the problematic header in the message body.
843+
841844
- `X-Line-Length`: corresponds to the `--line-length` command line flag.
842845
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
843846
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:
849852
a set of comma-separated Python versions, optionally prefixed with `py`. For example,
850853
to request code that is compatible with Python 3.5 and 3.6, set the header to
851854
`py3.5,py3.6`.
855+
- `X-Diff`: corresponds to the `--diff` command line flag. If present, a diff of the
856+
formats will be output.
852857

853858
If any of these headers are set to invalid values, `blackd` returns a `HTTP 400` error
854859
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).
10341039

10351040
- `blackd` now returns the version of _Black_ in the response headers (#1013)
10361041

1042+
- `blackd` can now output the diff of formats on source code when the `X-Diff` header is
1043+
provided (#969)
1044+
10371045
### 19.3b0
10381046

10391047
- new option `--target-version` to control which Python versions _Black_-formatted code

blackd.py

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from concurrent.futures import Executor, ProcessPoolExecutor
3+
from datetime import datetime
34
from functools import partial
45
import logging
56
from multiprocessing import freeze_support
@@ -21,13 +22,15 @@
2122
PYTHON_VARIANT_HEADER = "X-Python-Variant"
2223
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
2324
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
25+
DIFF_HEADER = "X-Diff"
2426

2527
BLACK_HEADERS = [
2628
PROTOCOL_VERSION_HEADER,
2729
LINE_LENGTH_HEADER,
2830
PYTHON_VARIANT_HEADER,
2931
SKIP_STRING_NORMALIZATION_HEADER,
3032
FAST_OR_SAFE_HEADER,
33+
DIFF_HEADER,
3134
]
3235

3336
# Response headers
@@ -112,10 +115,25 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
112115
req_bytes = await request.content.read()
113116
charset = request.charset if request.charset is not None else "utf8"
114117
req_str = req_bytes.decode(charset)
118+
then = datetime.utcnow()
119+
115120
loop = asyncio.get_event_loop()
116121
formatted_str = await loop.run_in_executor(
117122
executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode)
118123
)
124+
125+
# Only output the diff in the HTTP response
126+
only_diff = bool(request.headers.get(DIFF_HEADER, False))
127+
if only_diff:
128+
now = datetime.utcnow()
129+
src_name = f"In\t{then} +0000"
130+
dst_name = f"Out\t{now} +0000"
131+
loop = asyncio.get_event_loop()
132+
formatted_str = await loop.run_in_executor(
133+
executor,
134+
partial(black.diff, req_str, formatted_str, src_name, dst_name),
135+
)
136+
119137
return web.Response(
120138
content_type=request.content_type,
121139
charset=charset,

tests/data/blackd_diff.diff

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--- [Deterministic header]
2+
+++ [Deterministic header]
3+
@@ -1,7 +1,6 @@
4+
-def abc ():
5+
- return ["hello", "world",
6+
- "!"]
7+
+def abc():
8+
+ return ["hello", "world", "!"]
9+
10+
-print( "Incorrect formatting"
11+
-)
12+
13+
+print("Incorrect formatting")
14+
+

tests/data/blackd_diff.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def abc ():
2+
return ["hello", "world",
3+
"!"]
4+
5+
print( "Incorrect formatting"
6+
)

tests/test_black.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
fs = partial(black.format_str, mode=black.FileMode())
3636
THIS_FILE = Path(__file__)
3737
THIS_DIR = THIS_FILE.parent
38+
DETERMINISTIC_HEADER = "[Deterministic header]"
3839
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
3940
PY36_ARGS = [
4041
f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
@@ -259,7 +260,7 @@ def test_piping_diff(self) -> None:
259260
black.main, args, input=BytesIO(source.encode("utf8"))
260261
)
261262
self.assertEqual(result.exit_code, 0)
262-
actual = diff_header.sub("[Deterministic header]", result.output)
263+
actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
263264
actual = actual.rstrip() + "\n" # the diff output has a trailing space
264265
self.assertEqual(expected, actual)
265266

@@ -340,7 +341,7 @@ def test_expression_diff(self) -> None:
340341
finally:
341342
os.unlink(tmp_file)
342343
actual = result.output
343-
actual = diff_header.sub("[Deterministic header]", actual)
344+
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
344345
actual = actual.rstrip() + "\n" # the diff output has a trailing space
345346
if expected != actual:
346347
dump = black.dump_to_file(actual)
@@ -1689,6 +1690,27 @@ async def test_blackd_pyi(self) -> None:
16891690
self.assertEqual(response.status, 200)
16901691
self.assertEqual(await response.text(), expected)
16911692

1693+
@skip_if_exception("ClientOSError")
1694+
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1695+
@unittest_run_loop
1696+
async def test_blackd_diff(self) -> None:
1697+
diff_header = re.compile(
1698+
rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1699+
rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1700+
)
1701+
1702+
source, _ = read_data("blackd_diff.py")
1703+
expected, _ = read_data("blackd_diff.diff")
1704+
1705+
response = await self.client.post(
1706+
"/", data=source, headers={blackd.DIFF_HEADER: "true"}
1707+
)
1708+
self.assertEqual(response.status, 200)
1709+
1710+
actual = await response.text()
1711+
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1712+
self.assertEqual(actual, expected)
1713+
16921714
@skip_if_exception("ClientOSError")
16931715
@unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
16941716
@unittest_run_loop

0 commit comments

Comments
 (0)