Add piping from stdin to stdout with a - (#25)

Being able to format code by piping it through the formatter makes it much easier to integrate with tools like google/vim-codefmt or Chiel92/vim-autoformat.
This commit is contained in:
treuherz 2018-03-19 18:07:10 +00:00 committed by Łukasz Langa
parent 2fa31ff314
commit 10d8976a79
2 changed files with 58 additions and 21 deletions

View File

@ -74,7 +74,9 @@ class CannotSplit(Exception):
@click.argument( @click.argument(
'src', 'src',
nargs=-1, nargs=-1,
type=click.Path(exists=True, file_okay=True, dir_okay=True, readable=True), type=click.Path(
exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
),
) )
@click.pass_context @click.pass_context
def main( def main(
@ -89,6 +91,8 @@ def main(
elif p.is_file(): elif p.is_file():
# if a file was explicitly given, we don't care about its extension # if a file was explicitly given, we don't care about its extension
sources.append(p) sources.append(p)
elif s == '-':
sources.append(Path('-'))
else: else:
err(f'invalid path: {s}') err(f'invalid path: {s}')
if len(sources) == 0: if len(sources) == 0:
@ -97,9 +101,12 @@ def main(
p = sources[0] p = sources[0]
report = Report() report = Report()
try: try:
changed = format_file_in_place( if not p.is_file() and str(p) == '-':
p, line_length=line_length, fast=fast, write_back=not check changed = format_stdin_to_stdout(line_length=line_length, fast=fast)
) else:
changed = format_file_in_place(
p, line_length=line_length, fast=fast, write_back=not check
)
report.done(p, changed) report.done(p, changed)
except Exception as exc: except Exception as exc:
report.failed(p, str(exc)) report.failed(p, str(exc))
@ -156,34 +163,50 @@ def format_file_in_place(
src: Path, line_length: int, fast: bool, write_back: bool = False src: Path, line_length: int, fast: bool, write_back: bool = False
) -> bool: ) -> bool:
"""Format the file and rewrite if changed. Return True if changed.""" """Format the file and rewrite if changed. Return True if changed."""
with tokenize.open(src) as src_buffer:
src_contents = src_buffer.read()
try: try:
contents, encoding = format_file(src, line_length=line_length, fast=fast) contents = format_file_contents(
src_contents, line_length=line_length, fast=fast
)
except NothingChanged: except NothingChanged:
return False return False
if write_back: if write_back:
with open(src, "w", encoding=encoding) as f: with open(src, "w", encoding=src_buffer.encoding) as f:
f.write(contents) f.write(contents)
return True return True
def format_file( def format_stdin_to_stdout(line_length: int, fast: bool) -> bool:
src: Path, line_length: int, fast: bool """Format file on stdin and pipe output to stdout. Return True if changed."""
) -> Tuple[FileContent, Encoding]: contents = sys.stdin.read()
try:
contents = format_file_contents(contents, line_length=line_length, fast=fast)
return True
except NothingChanged:
return False
finally:
sys.stdout.write(contents)
def format_file_contents(
src_contents: str, line_length: int, fast: bool
) -> FileContent:
"""Reformats a file and returns its contents and encoding.""" """Reformats a file and returns its contents and encoding."""
with tokenize.open(src) as src_buffer:
src_contents = src_buffer.read()
if src_contents.strip() == '': if src_contents.strip() == '':
raise NothingChanged(src) raise NothingChanged
dst_contents = format_str(src_contents, line_length=line_length) dst_contents = format_str(src_contents, line_length=line_length)
if src_contents == dst_contents: if src_contents == dst_contents:
raise NothingChanged(src) raise NothingChanged
if not fast: if not fast:
assert_equivalent(src_contents, dst_contents) assert_equivalent(src_contents, dst_contents)
assert_stable(src_contents, dst_contents, line_length=line_length) assert_stable(src_contents, dst_contents, line_length=line_length)
return dst_contents, src_buffer.encoding return dst_contents
def format_str(src_contents: str, line_length: int) -> FileContent: def format_str(src_contents: str, line_length: int) -> FileContent:

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from functools import partial from functools import partial
from io import StringIO
from pathlib import Path from pathlib import Path
import sys
from typing import Any, List, Tuple from typing import Any, List, Tuple
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@ -10,7 +12,7 @@
import black import black
ll = 88 ll = 88
ff = partial(black.format_file, line_length=ll, fast=True) ff = partial(black.format_file_in_place, line_length=ll, fast=True)
fs = partial(black.format_str, line_length=ll) fs = partial(black.format_str, line_length=ll)
THIS_FILE = Path(__file__) THIS_FILE = Path(__file__)
THIS_DIR = THIS_FILE.parent THIS_DIR = THIS_FILE.parent
@ -70,8 +72,7 @@ def test_self(self) -> None:
self.assertFormatEqual(expected, actual) self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll) black.assert_stable(source, actual, line_length=ll)
with self.assertRaises(black.NothingChanged): self.assertFalse(ff(THIS_FILE))
ff(THIS_FILE)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_black(self) -> None: def test_black(self) -> None:
@ -80,8 +81,22 @@ def test_black(self) -> None:
self.assertFormatEqual(expected, actual) self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll) black.assert_stable(source, actual, line_length=ll)
with self.assertRaises(black.NothingChanged): self.assertFalse(ff(THIS_DIR / '..' / 'black.py'))
ff(THIS_FILE)
def test_piping(self) -> None:
source, expected = read_data('../black')
hold_stdin, hold_stdout = sys.stdin, sys.stdout
try:
sys.stdin, sys.stdout = StringIO(source), StringIO()
sys.stdin.name = '<stdin>'
black.format_stdin_to_stdout(line_length=ll, fast=True)
sys.stdout.seek(0)
actual = sys.stdout.read()
finally:
sys.stdin, sys.stdout = hold_stdin, hold_stdout
self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_setup(self) -> None: def test_setup(self) -> None:
@ -90,8 +105,7 @@ def test_setup(self) -> None:
self.assertFormatEqual(expected, actual) self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll) black.assert_stable(source, actual, line_length=ll)
with self.assertRaises(black.NothingChanged): self.assertFalse(ff(THIS_DIR / '..' / 'setup.py'))
ff(THIS_FILE)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_function(self) -> None: def test_function(self) -> None: