Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stdin works on Windows, too, without regressing issue #5 #13

Merged
merged 6 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ jobs:
run: make test
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}

36 changes: 16 additions & 20 deletions rexi/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import select
import sys
from typing import Annotated, Optional

Expand All @@ -9,6 +8,13 @@

app = typer.Typer()

def is_stdin_a_tty() -> bool:
"""Wrapper for sys.stdin.isatty.

Trying to directly mock/patch sys.stdin.isatty wasn't working,
but it's easy to patch a function that calls sys.stdin.isatty()
"""
return sys.stdin.isatty()

# noinspection SpellCheckingInspection
@app.command("rexi")
Expand Down Expand Up @@ -40,30 +46,20 @@ def rexi_cli(
) -> None:
if input_file:
input_text = input_file.read()
else:
"""
Yep this part is abit ugly.
couldn't find a better way to implement it so it will work with `typer`, `pytest` and `textual`
""" # noqa: E501
if not select.select(
[
sys.stdin,
],
[],
[],
0.0,
)[0]:
raise typer.BadParameter(
"stdin is empty, "
"please provide text thru the stdin "
"or use the `-i` flag"
)
elif not is_stdin_a_tty():
input_text = sys.stdin.read()
try:
os.close(sys.stdin.fileno())
except OSError:
pass
sys.stdin = open("/dev/tty", "rb") # type: ignore[assignment]
# Windows uses "con:" for stdin device name
sys.stdin = open("con:" if os.name == "nt" else "/dev/tty", "rb") # type: ignore[assignment]
else:
raise typer.BadParameter(
"stdin is empty, "
"please provide text thru the stdin "
"or use the `-i` flag"
)
app: RexiApp[int] = RexiApp(
input_text, initial_mode=initial_mode, initial_pattern=initial_pattern
)
Expand Down
34 changes: 24 additions & 10 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import os
from io import BytesIO
from pathlib import Path
from unittest.mock import Mock

import pytest
from _pytest.monkeypatch import MonkeyPatch
from typer.testing import CliRunner

from rexi.cli import app
from rexi.cli import app, is_stdin_a_tty


def test_on_args(monkeypatch: MonkeyPatch) -> None:
def test_no_args(monkeypatch: MonkeyPatch) -> None:
"""
Couldn't find a better way to test the CLI without patching everything :(
"""
Expand All @@ -18,17 +20,20 @@ def test_on_args(monkeypatch: MonkeyPatch) -> None:
class_mock = Mock()
instance_mock = Mock()
open_mock = Mock()
select = Mock()
read_mock = Mock()
isatty_mock = Mock()

with monkeypatch.context():
select.return_value = [[True]]
read_mock.return_value = ""
class_mock.return_value = instance_mock
monkeypatch.setattr("select.select", select)
isatty_mock.return_value = False
monkeypatch.setattr("rexi.cli.is_stdin_a_tty", isatty_mock)
monkeypatch.setattr("rexi.cli.RexiApp", class_mock)
monkeypatch.setattr("builtins.open", open_mock)
runner.invoke(app, input=a)

open_mock.assert_called_once_with("/dev/tty", "rb")
open_mock.assert_called_once_with(
"con:" if os.name == "nt" else "/dev/tty", "rb"
)
class_mock.assert_called_once_with(
text.decode(), initial_mode=None, initial_pattern=None
)
Expand Down Expand Up @@ -79,9 +84,18 @@ def test_no_stdin_error(monkeypatch: MonkeyPatch) -> None:
Couldn't find a better way to test the CLI without patching everything :(
"""
runner = CliRunner()
select = Mock()
isatty_mock = Mock()
with monkeypatch.context():
select.return_value = [[]]
monkeypatch.setattr("select.select", select)
isatty_mock.return_value = True
monkeypatch.setattr("rexi.cli.is_stdin_a_tty", isatty_mock)
result = runner.invoke(app)
assert "Invalid value" in result.output


@pytest.mark.parametrize('input_value', [True, False])
def test_is_stdin_a_tty(monkeypatch: MonkeyPatch, input_value: bool) -> None:
isatty_mock = Mock()
with monkeypatch.context():
isatty_mock.return_value = input_value
monkeypatch.setattr("rexi.cli.sys.stdin.isatty", isatty_mock)
assert is_stdin_a_tty() == input_value
Loading