Skip to content

Commit

Permalink
Adding typer
Browse files Browse the repository at this point in the history
  • Loading branch information
royreznik committed Feb 3, 2024
1 parent b8cc3d1 commit 52f3fd6
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 43 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Test
on:
push:
branches:
- master
pull_request:
paths:
- rexi/**
- tests/**

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: snok/install-poetry@v1
- uses: actions/cache@v3
id: cached-poetry-dependencies
with:
path: .venv
key: venv-Linux-3.11-${{ hashFiles('**/pyproject.toml') }}
- name: install deps
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: make install
- name: lint
run: make lint

test:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- uses: snok/install-poetry@v1
- uses: actions/cache@v3
id: cached-poetry-dependencies
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}
- name: install deps
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: rm -rf poetry.lock && make install
- name: test
run: make test
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ publish: build
poetry publish

format:
poetry run black rexi/ tests/
poetry run ruff --fix rexi/ tests/
poetry run ruff format rexi/ tests/

lint:
poetry run ruff rexi/ tests/
Expand Down
23 changes: 22 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ classifiers = [
]

[tool.poetry.scripts]
rexi = "rexi.rexi:main"
rexi = "rexi.cli:app"

[tool.poetry.dependencies]
python = "^3.10"
textual = "^0.48.1"
colorama = "^0.4.6"
typer = "^0.9.0"


[tool.poetry.group.dev.dependencies]
Expand Down
21 changes: 21 additions & 0 deletions rexi/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os
import sys

import typer

from .rexi import RexiApp

app = typer.Typer()


# noinspection SpellCheckingInspection
@app.command("rexi")
def rexi_cli() -> None:
stdin = sys.stdin.read()
try:
os.close(sys.stdin.fileno())
except OSError:
pass
sys.stdin = open("/dev/tty", "rb") # type: ignore[assignment]
app: RexiApp[int] = RexiApp(stdin)
app.run()
29 changes: 6 additions & 23 deletions rexi/rexi.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import dataclasses
import os
import re
import sys
from typing import cast, Match, Iterable, Optional

from colorama import Fore, Style
from colorama import Fore
from textual import on
from textual.app import App, ComposeResult, ReturnType
from textual.containers import ScrollableContainer, Horizontal
Expand Down Expand Up @@ -45,7 +43,10 @@ def compose(self) -> ComposeResult:
with Horizontal(id="inputs"):
yield Input(placeholder="Enter regex pattern")
yield Select(
zip(self.regex_modes, self.regex_modes), id="select", allow_blank=False, value=self.regex_current_mode
zip(self.regex_modes, self.regex_modes),
id="select",
allow_blank=False,
value=self.regex_current_mode,
)

with ScrollableContainer(id="result"):
Expand Down Expand Up @@ -132,12 +133,7 @@ def combine_matches_groups(

@staticmethod
def _combine_groups(match: Match[str]) -> list["GroupMatch"]:
groups = [
GroupMatch([index], group, start, end, is_first=True)
for index, (group, (start, end)) in enumerate(
[[match.group(0), match.regs[0]]]
)
]
groups = [GroupMatch([0], match.group(0), *match.regs[0], is_first=True)]
groups += [
GroupMatch([index], group, start, end)
for index, (group, (start, end)) in enumerate(
Expand All @@ -151,16 +147,3 @@ def _combine_groups(match: Match[str]) -> list["GroupMatch"]:
if group_match in groups:
groups[groups.index(group_match)].keys.append(group_name)
return groups


def main() -> None:
stdin = sys.stdin.read()
os.close(sys.stdin.fileno())
sys.stdin = open("/dev/tty", "rb") # type: ignore[assignment]

app: RexiApp[int] = RexiApp(stdin)
app.run()


if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from io import BytesIO
from unittest.mock import Mock

from _pytest.monkeypatch import MonkeyPatch
from typer.testing import CliRunner
from rexi.cli import app


def test_app(monkeypatch: MonkeyPatch) -> None:
"""
Couldn't find a better way to test the CLI without patching everything :(
"""
runner = CliRunner()
text = b"This iS! aTe xt2 F0r T3sT!ng"
a = BytesIO(text)
class_mock = Mock()
instance_mock = Mock()
open_mock = Mock()
with monkeypatch.context():
class_mock.return_value = instance_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")
class_mock.assert_called_once_with(text.decode())
instance_mock.run.assert_called_once()
5 changes: 3 additions & 2 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
def test_group_match_equals(
group_one: GroupMatch, group_two: GroupMatch, expected_equals: bool
):
) -> None:
assert (group_one == group_two) == expected_equals


Expand All @@ -42,7 +42,8 @@ def test_group_match_equals(
],
],
)
def test_combine_groups(pattern, content, expected_groups):
def test_combine_groups(pattern: str, content: str, expected_groups: list[GroupMatch]) -> None:
matches = re.match(pattern, content)
assert matches # sanity
result = RexiApp._combine_groups(matches)
assert result == expected_groups, result[0].end
37 changes: 22 additions & 15 deletions tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,43 @@

import pytest
from colorama import Fore
from textual.pilot import Pilot
from textual.widgets import Static

from rexi.rexi import RexiApp, UNDERLINE, RESET_UNDERLINE


@pytest.mark.parametrize(
("start_mode", "pattern", "expected_output"),
[
["match", ".*(aTe).*", f"{UNDERLINE}This iS! {Fore.RED}aTe{Fore.RESET} xt2 F0r T3sT!ng{RESET_UNDERLINE}"],
["match", ".*(aTe.*)", f"{UNDERLINE}This iS! {Fore.RED}aTe xt2 F0r T3sT!ng{Fore.RESET}{RESET_UNDERLINE}"],
["finditer", "(aTe)", f"This iS! {UNDERLINE}{Fore.RED}aTe{Fore.RESET}{RESET_UNDERLINE} xt2 F0r T3sT!ng"]
]
[
"match",
".*(aTe).*",
f"{UNDERLINE}This iS! {Fore.RED}aTe{Fore.RESET} xt2 F0r T3sT!ng{RESET_UNDERLINE}",
],
[
"match",
".*(aTe.*)",
f"{UNDERLINE}This iS! {Fore.RED}aTe xt2 F0r T3sT!ng{Fore.RESET}{RESET_UNDERLINE}",
],
[
"finditer",
"(aTe)",
f"This iS! {UNDERLINE}{Fore.RED}aTe{Fore.RESET}{RESET_UNDERLINE} xt2 F0r T3sT!ng",
],
],
)
async def test_input_box(start_mode, pattern, expected_output):
app = RexiApp("This iS! aTe xt2 F0r T3sT!ng", start_mode=start_mode)
async def test_input_box(start_mode: str, pattern: str, expected_output: str) -> None:
app: RexiApp[int] = RexiApp("This iS! aTe xt2 F0r T3sT!ng", start_mode=start_mode)
async with app.run_test() as pilot:
pilot: Pilot
await pilot.click("Input")
await pilot.press(*list(pattern))
result = str(cast(Static, app.query_one("#output")).renderable)
assert (
result
== expected_output
)
assert result == expected_output


async def test_switch_modes():
app = RexiApp("This iS! aTe xt2 F0r T3sT!ng")
async def test_switch_modes() -> None:
app: RexiApp[int] = RexiApp("This iS! aTe xt2 F0r T3sT!ng")
async with app.run_test() as pilot:
pilot: Pilot
assert app.regex_current_mode == "finditer"
await pilot.click("SelectCurrent")
await pilot.click("SelectOverlay", offset=(2, 2))
Expand Down

0 comments on commit 52f3fd6

Please sign in to comment.