Skip to content

Commit 6e8c8d8

Browse files
committed
Add stdin (- as filename) support
1 parent 7293e4d commit 6e8c8d8

File tree

2 files changed

+92
-7
lines changed

2 files changed

+92
-7
lines changed

pyformat.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import io
3232
import signal
3333
import sys
34+
from typing import Tuple
3435

3536
import autoflake
3637
import autopep8
@@ -76,16 +77,43 @@ def format_code(source, aggressive=False, apply_config=False, filename='',
7677
return formatted_source
7778

7879

80+
def detect_io_encoding(input_file: io.BytesIO, limit_byte_check=-1):
81+
"""Return file encoding."""
82+
try:
83+
from lib2to3.pgen2 import tokenize as lib2to3_tokenize
84+
encoding: str = lib2to3_tokenize.detect_encoding(input_file.readline)[
85+
0]
86+
87+
input_file.read(limit_byte_check).decode(encoding)
88+
89+
return encoding
90+
except (LookupError, SyntaxError, UnicodeDecodeError):
91+
return 'latin-1'
92+
93+
94+
def read_file(filename: str) -> Tuple[str, str]:
95+
"""Read file from filesystem or from stdin when `-` is given."""
96+
97+
if is_stdin(filename):
98+
data = sys.stdin.buffer.read()
99+
else:
100+
with open(filename, 'rb') as fp:
101+
data = fp.read()
102+
input_file = io.BytesIO(data)
103+
encoding = detect_io_encoding(input_file)
104+
return data.decode(encoding), encoding
105+
106+
107+
def is_stdin(filename: str):
108+
return filename == '-'
109+
110+
79111
def format_file(filename, args, standard_out):
80112
"""Run format_code() on a file.
81113
82114
Return True if the new formatting differs from the original.
83-
84115
"""
85-
encoding = autopep8.detect_encoding(filename)
86-
with autopep8.open_with_encoding(filename,
87-
encoding=encoding) as input_file:
88-
source = input_file.read()
116+
source, encoding = read_file(filename)
89117

90118
if not source:
91119
return False
@@ -98,6 +126,12 @@ def format_file(filename, args, standard_out):
98126
remove_all_unused_imports=args.remove_all_unused_imports,
99127
remove_unused_variables=args.remove_unused_variables)
100128

129+
# Always write to stdout (even when no changes were made) when working with
130+
# in-place stdin. This is what most tools (editors) expect.
131+
if args.in_place and is_stdin(filename):
132+
standard_out.write(formatted_source)
133+
return True
134+
101135
if source != formatted_source:
102136
if args.in_place:
103137
with autopep8.open_with_encoding(filename, mode='w',
@@ -142,7 +176,6 @@ def format_multiple_files(filenames, args, standard_out, standard_error):
142176
"""Format files and return booleans (any_changes, any_errors).
143177
144178
Optionally format files recursively.
145-
146179
"""
147180
filenames = autopep8.find_files(list(filenames),
148181
args.recursive,
@@ -211,7 +244,6 @@ def _main(argv, standard_out, standard_error):
211244
"""Internal main entry point.
212245
213246
Return exit status. 0 means no error.
214-
215247
"""
216248
args = parse_args(argv)
217249

test_pyformat.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
import tempfile
1515
import unittest
16+
from unittest import mock
1617

1718
import pyformat
1819

@@ -144,6 +145,58 @@ def test_format_multiple_files_with_nonexistent_file_and_verbose(self):
144145

145146
class TestSystem(unittest.TestCase):
146147

148+
def test_diff_stdin(self):
149+
input_b = b'''\
150+
import os
151+
x = "abc"
152+
'''
153+
stdin = io.TextIOWrapper(io.BytesIO(input_b), 'UTF-8')
154+
with mock.patch.object(sys, 'stdin', stdin):
155+
output_file = io.StringIO()
156+
pyformat._main(argv=['my_fake_program', '-'],
157+
standard_out=output_file,
158+
standard_error=None)
159+
self.assertEqual('''\
160+
@@ -1,2 +1,2 @@
161+
import os
162+
-x = "abc"
163+
+x = 'abc'
164+
''', '\n'.join(output_file.getvalue().split('\n')[2:]))
165+
166+
def test_in_place_stdin(self):
167+
input_b = b'''\
168+
import os
169+
x = "abc"
170+
'''
171+
stdin = io.TextIOWrapper(io.BytesIO(input_b), 'UTF-8')
172+
with mock.patch.object(sys, 'stdin', stdin):
173+
output_file = io.StringIO()
174+
pyformat._main(argv=['my_fake_program', '--in-place', '-'],
175+
standard_out=output_file,
176+
standard_error=None)
177+
self.assertEqual('''\
178+
import os
179+
x = 'abc'
180+
''', '\n'.join(output_file.getvalue().split('\n')))
181+
182+
183+
def test_in_place_stdin_no_change(self):
184+
input_b = b'''\
185+
import os
186+
x = 'abc'
187+
'''
188+
stdin = io.TextIOWrapper(io.BytesIO(input_b), 'UTF-8')
189+
with mock.patch.object(sys, 'stdin', stdin):
190+
output_file = io.StringIO()
191+
pyformat._main(argv=['my_fake_program', '--in-place', '-'],
192+
standard_out=output_file,
193+
standard_error=None)
194+
self.assertEqual('''\
195+
import os
196+
x = 'abc'
197+
''', '\n'.join(output_file.getvalue().split('\n')))
198+
199+
147200
def test_diff(self):
148201
with temporary_file('''\
149202
import os

0 commit comments

Comments
 (0)