Skip to content

Commit

Permalink
feat: support cat foo.py | fixit lint --stdin --stdin-filepath foo.py
Browse files Browse the repository at this point in the history
Similarly for `fixit fix`
  • Loading branch information
llllvvuu committed Sep 7, 2023
1 parent 84378a8 commit 13d01d3
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 7 deletions.
22 changes: 20 additions & 2 deletions docs/guide/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,20 @@ Lint one or more paths, and print a list of lint errors.

.. code:: console
$ fixit lint [--diff] [PATH ...]
$ fixit lint [--diff] [--stdin --stdin-filepath] [PATH ...]
.. attribute:: --diff / -d

Show suggested fixes, in unified diff format, when available.

.. attribute:: --stdin

Lint code from stdin.

.. attribute:: --stdin-filepath

Required with --stdin; Analyze code from stdin as if it is from this filepath.


``fix``
^^^^^^^
Expand All @@ -64,7 +72,8 @@ Lint one or more paths, and apply suggested fixes.

.. code:: console
$ fixit fix [--interactive | --automatic [--diff]] [PATH ...]
$ fixit fix [--interactive | --automatic [--diff]] \
[--stdin --stdin-filepath] [PATH ...]
.. attribute:: --interactive / -i

Expand All @@ -79,6 +88,15 @@ Lint one or more paths, and apply suggested fixes.

Show applied fixes in unified diff format when applied automatically.

.. attribute:: --stdin

Fix code from stdin and output fixed code to stdout. Ignores --interactive/--automatic/--diff.

.. attribute:: --stdin-filepath

Required with --stdin; Analyze code from stdin as if it is from this filepath.



``test``
^^^^^^^^
Expand Down
55 changes: 50 additions & 5 deletions src/fixit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,35 @@

from fixit import __version__

from .api import fixit_paths, print_result
from .api import fixit_bytes, fixit_paths, print_result
from .config import collect_rules, generate_config, parse_rule
from .ftypes import Config, Options, QualifiedRule, Tags
from .rule import LintRule
from .testing import generate_lint_rule_test_cases
from .util import capture


stdin_option = click.option(
"--stdin", is_flag=True, default=False, help="Lint code from stdin"
)
stdin_filepath_option = click.option(
"--stdin-filepath",
type=click.Path(path_type=Path),
default=None,
help="Analyze code from stdin as if it is from this filepath",
)


def generator_from_stdin(
ctx: click.Context, stdin_filepath: Optional[Path], options: Options, fix=False
):
if not stdin_filepath:
ctx.fail("--stdin-filepath is required with --stdin")
content = sys.stdin.buffer.read()
config = generate_config(stdin_filepath, options=options)
return fixit_bytes(stdin_filepath, content, autofix=fix, config=config)


def splash(
visited: Set[Path], dirty: Set[Path], autofixes: int = 0, fixed: int = 0
) -> None:
Expand Down Expand Up @@ -101,25 +122,33 @@ def main(
@main.command()
@click.pass_context
@click.option("--diff", "-d", is_flag=True, help="Show diff of suggested changes")
@stdin_option
@stdin_filepath_option
@click.argument("paths", nargs=-1, type=click.Path(path_type=Path))
def lint(
ctx: click.Context,
diff: bool,
stdin: bool,
stdin_filepath: Optional[Path],
paths: Sequence[Path],
):
"""
lint one or more paths and return suggestions
lint stdin or one or more paths and return suggestions
"""
options: Options = ctx.obj

if not paths:
paths = [Path.cwd()]
if stdin:
results = generator_from_stdin(ctx, stdin_filepath, options=options)
else:
if not paths:
paths = [Path.cwd()]
results = fixit_paths(paths, options=options)

exit_code = 0
visited: Set[Path] = set()
dirty: Set[Path] = set()
autofixes = 0
for result in fixit_paths(paths, options=options):
for result in results:
visited.add(result.path)

if print_result(result, show_diff=diff):
Expand All @@ -145,11 +174,15 @@ def lint(
help="how to apply fixes; interactive by default",
)
@click.option("--diff", "-d", is_flag=True, help="show diff even with --automatic")
@stdin_option
@stdin_filepath_option
@click.argument("paths", nargs=-1, type=click.Path(path_type=Path))
def fix(
ctx: click.Context,
interactive: bool,
diff: bool,
stdin: bool,
stdin_filepath: Optional[Path],
paths: Sequence[Path],
):
"""
Expand All @@ -168,10 +201,22 @@ def fix(
autofixes = 0
fixed = 0

if stdin:
generator = capture(
generator_from_stdin(ctx, stdin_filepath, options=options, fix=True)
)
for _ in generator:
pass
if not generator.result:
raise Exception("Internal Error: fixit_bytes returned None")
print(generator.result.decode(), end="")
ctx.exit(exit_code)

# TODO: make this parallel
generator = capture(
fixit_paths(paths, autofix=autofix, options=options, parallel=False)
)

for result in generator:
visited.add(result.path)
if print_result(result, show_diff=interactive or diff):
Expand Down
32 changes: 32 additions & 0 deletions src/fixit/tests/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,38 @@ def func():
expected_format, path.read_text(), "unexpected file output"
)

with self.subTest("linting via stdin"):
result = self.runner.invoke(
main,
["lint", "--stdin", "--stdin-filepath", path.as_posix()],
input=content, # provide content via stdin
catch_exceptions=False,
)

self.assertNotEqual(result.output, "")
self.assertNotEqual(result.exit_code, 0)
self.assertRegex(
result.output,
r"file\.py@\d+:\d+ NoRedundantFString: .+ \(has autofix\)",
)

with self.subTest("fixing with formatting via stdin"):
result = self.runner.invoke(
main,
[
"fix",
"--automatic",
"--stdin",
"--stdin-filepath",
path.as_posix(),
],
input=content,
catch_exceptions=False,
)

self.assertEqual(result.exit_code, 0)
self.assertEqual(expected_format, result.output, "unexpected stdout")

def test_this_file_is_clean(self) -> None:
path = Path(__file__).resolve().as_posix()
result = self.runner.invoke(main, ["lint", path], catch_exceptions=False)
Expand Down

0 comments on commit 13d01d3

Please sign in to comment.