4
4
# LICENSE file in the root directory of this source tree.
5
5
6
6
import logging
7
+ import sys
7
8
import traceback
8
9
from functools import partial
9
10
from pathlib import Path
16
17
from .config import collect_rules , generate_config
17
18
from .engine import LintRunner
18
19
from .format import format_module
19
- from .ftypes import Config , FileContent , LintViolation , Options , Result
20
+ from .ftypes import Config , FileContent , LintViolation , Options , Result , STDIN
20
21
21
22
LOG = logging .getLogger (__name__ )
22
23
23
24
24
- def print_result (result : Result , show_diff : bool = False ) -> int :
25
+ def print_result (
26
+ result : Result , * , show_diff : bool = False , stderr : bool = False
27
+ ) -> int :
25
28
"""
26
29
Print linting results in a simple format designed for human eyes.
27
30
@@ -44,7 +47,9 @@ def print_result(result: Result, show_diff: bool = False) -> int:
44
47
if result .violation .autofixable :
45
48
message += " (has autofix)"
46
49
click .secho (
47
- f"{ path } @{ start_line } :{ start_col } { rule_name } : { message } " , fg = "yellow"
50
+ f"{ path } @{ start_line } :{ start_col } { rule_name } : { message } " ,
51
+ fg = "yellow" ,
52
+ err = stderr ,
48
53
)
49
54
if show_diff and result .violation .diff :
50
55
echo_color_precomputed_diff (result .violation .diff )
@@ -53,8 +58,8 @@ def print_result(result: Result, show_diff: bool = False) -> int:
53
58
elif result .error :
54
59
# An exception occurred while processing a file
55
60
error , tb = result .error
56
- click .secho (f"{ path } : EXCEPTION: { error } " , fg = "red" )
57
- click .echo (tb .strip ())
61
+ click .secho (f"{ path } : EXCEPTION: { error } " , fg = "red" , err = stderr )
62
+ click .echo (tb .strip (), err = stderr )
58
63
return True
59
64
60
65
else :
@@ -117,6 +122,36 @@ def fixit_bytes(
117
122
return None
118
123
119
124
125
+ def fixit_stdin (
126
+ path : Path ,
127
+ * ,
128
+ autofix : bool = False ,
129
+ options : Optional [Options ] = None ,
130
+ ) -> Generator [Result , bool , None ]:
131
+ """
132
+ Wrapper around :func:`fixit_bytes` for formatting content from STDIN.
133
+
134
+ The resulting fixed content will be printed to STDOUT.
135
+
136
+ Requires passing a path that represents the filesystem location matching the
137
+ contents to be linted. This will be used to resolve the ``fixit.toml`` config
138
+ file(s).
139
+ """
140
+ path = path .resolve ()
141
+
142
+ try :
143
+ content : FileContent = sys .stdin .buffer .read ()
144
+ config = generate_config (path , options = options )
145
+
146
+ updated = yield from fixit_bytes (path , content , config = config , autofix = autofix )
147
+ if autofix :
148
+ sys .stdout .buffer .write (updated or content )
149
+
150
+ except Exception as error :
151
+ LOG .debug ("Exception while fixit_stdin" , exc_info = error )
152
+ yield Result (path , violation = None , error = (error , traceback .format_exc ()))
153
+
154
+
120
155
def fixit_file (
121
156
path : Path ,
122
157
* ,
@@ -177,6 +212,16 @@ def fixit_paths(
177
212
Yields :class:`Result` objects for each path, lint error, or exception found.
178
213
See :func:`fixit_bytes` for semantics.
179
214
215
+ If the first given path is STDIN (``Path("-")``), then content will be linted
216
+ from STDIN using :func:`fixit_stdin`. The fixed content will be written to STDOUT.
217
+ A second path argument may be given, which represents the original content's true
218
+ path name, and will be used:
219
+ - to resolve the ``fixit.toml`` configuration file(s)
220
+ - when printing status messages, diffs, or errors.
221
+ If no second path argument is given, it will default to "stdin" in the current
222
+ working directory.
223
+ Any further path names will result in a runtime error.
224
+
180
225
.. note::
181
226
182
227
Currently does not support applying individual fixes when ``parallel=True``,
@@ -188,10 +233,25 @@ def fixit_paths(
188
233
return
189
234
190
235
expanded_paths : List [Path ] = []
191
- for path in paths :
192
- expanded_paths .extend (trailrunner .walk (path ))
193
-
194
- if len (expanded_paths ) == 1 or not parallel :
236
+ is_stdin = False
237
+ stdin_path = Path ("stdin" )
238
+ for i , path in enumerate (paths ):
239
+ if path == STDIN :
240
+ if i == 0 :
241
+ is_stdin = True
242
+ else :
243
+ LOG .warning ("Cannot mix stdin ('-') with normal paths, ignoring" )
244
+ elif is_stdin :
245
+ if i == 1 :
246
+ stdin_path = path
247
+ else :
248
+ raise ValueError ("too many stdin paths" )
249
+ else :
250
+ expanded_paths .extend (trailrunner .walk (path ))
251
+
252
+ if is_stdin :
253
+ yield from fixit_stdin (stdin_path , autofix = autofix , options = options )
254
+ elif len (expanded_paths ) == 1 or not parallel :
195
255
for path in expanded_paths :
196
256
yield from fixit_file (path , autofix = autofix , options = options )
197
257
else :
0 commit comments