Add script detection, env shebang parser, and opt-in content guessing
This commit is contained in:
@@ -74,6 +74,7 @@ class TestFileInput:
|
||||
path=str(file),
|
||||
content="x = 1\n",
|
||||
explicit_type=None,
|
||||
guess_content=False,
|
||||
)
|
||||
|
||||
def test_render_called_with_correct_options(self, tmp_file) -> None:
|
||||
@@ -180,6 +181,7 @@ class TestStdinInput:
|
||||
path=None,
|
||||
content="hello\n",
|
||||
explicit_type=None,
|
||||
guess_content=False,
|
||||
)
|
||||
|
||||
def test_stdin_shows_tty_prompt(self) -> None:
|
||||
@@ -216,6 +218,7 @@ class TestTypeOverride:
|
||||
|
||||
_, kwargs = mock_detect_type.call_args
|
||||
assert kwargs["explicit_type"] == "json"
|
||||
assert kwargs["guess_content"] is False
|
||||
|
||||
def test_explicit_type_short_flag(self, tmp_file) -> None:
|
||||
file = tmp_file("def foo(): pass\n", suffix=".txt")
|
||||
@@ -236,6 +239,28 @@ class TestThemeOption:
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
class TestGuessContentOption:
|
||||
"""Tests for the guess-content flag."""
|
||||
|
||||
def test_guess_content_is_forwarded(self) -> None:
|
||||
with patch("rp.cli.detect_type") as mock_detect_type:
|
||||
mock_detect_type.return_value = "diff"
|
||||
with patch("rp.cli.render"):
|
||||
runner.invoke(app, ["--guess-content", "--no-pager"], input="hello\n")
|
||||
|
||||
_, kwargs = mock_detect_type.call_args
|
||||
assert kwargs["guess_content"] is True
|
||||
|
||||
def test_guess_content_disabled_by_default(self) -> None:
|
||||
with patch("rp.cli.detect_type") as mock_detect_type:
|
||||
mock_detect_type.return_value = "text"
|
||||
with patch("rp.cli.render"):
|
||||
runner.invoke(app, ["--no-pager"], input="hello\n")
|
||||
|
||||
_, kwargs = mock_detect_type.call_args
|
||||
assert kwargs["guess_content"] is False
|
||||
|
||||
|
||||
class TestLineNumbersOption:
|
||||
"""Tests for the line number flags."""
|
||||
|
||||
|
||||
@@ -70,7 +70,13 @@ class TestExtensionMap:
|
||||
assert detect_type(path="archive.tar.gz") == "text"
|
||||
|
||||
def test_hidden_file(self) -> None:
|
||||
assert detect_type(path=".bashrc") == "text"
|
||||
assert detect_type(path=".bashrc") == "bash"
|
||||
|
||||
def test_zshrc(self) -> None:
|
||||
assert detect_type(path=".zshrc") == "bash"
|
||||
|
||||
def test_profile(self) -> None:
|
||||
assert detect_type(path=".profile") == "bash"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("suffix", "expected"),
|
||||
@@ -80,8 +86,73 @@ class TestExtensionMap:
|
||||
assert detect_type(path=f"example{suffix}") == expected
|
||||
|
||||
|
||||
class TestShebangDetection:
|
||||
"""Priority 4: detect common scripts from shebangs."""
|
||||
|
||||
def test_env_python3_shebang(self) -> None:
|
||||
assert detect_type(content="#!/usr/bin/env python3\nprint('hi')\n") == "python"
|
||||
|
||||
def test_env_python3_with_assignment(self) -> None:
|
||||
assert (
|
||||
detect_type(content="#!/usr/bin/env FOO=1 python3\nprint('hi')\n")
|
||||
== "python"
|
||||
)
|
||||
|
||||
def test_env_ignore_environment_before_program(self) -> None:
|
||||
assert detect_type(content="#!/usr/bin/env -i bash\necho hi\n") == "bash"
|
||||
|
||||
def test_env_split_string_shebang(self) -> None:
|
||||
assert (
|
||||
detect_type(content="#!/usr/bin/env -S python3 -u\nprint('hi')\n")
|
||||
== "python"
|
||||
)
|
||||
|
||||
def test_env_compact_split_string_shebang(self) -> None:
|
||||
assert (
|
||||
detect_type(content="#!/usr/bin/env -Spython3 -u\nprint('hi')\n")
|
||||
== "python"
|
||||
)
|
||||
|
||||
def test_env_quoted_split_string_shebang(self) -> None:
|
||||
assert (
|
||||
detect_type(
|
||||
content='#!/usr/bin/env --split-string="python3 -u"\nprint("hi")\n'
|
||||
)
|
||||
== "python"
|
||||
)
|
||||
|
||||
def test_env_long_option_with_equals(self) -> None:
|
||||
assert (
|
||||
detect_type(content="#!/usr/bin/env --ignore-signal=TERM bash\necho hi\n")
|
||||
== "bash"
|
||||
)
|
||||
|
||||
def test_direct_python_shebang(self) -> None:
|
||||
assert detect_type(content="#!/usr/bin/python3\nprint('hi')\n") == "python"
|
||||
|
||||
def test_bash_shebang(self) -> None:
|
||||
assert detect_type(content="#!/bin/bash\necho hi\n") == "bash"
|
||||
|
||||
def test_sh_shebang(self) -> None:
|
||||
assert detect_type(content="#!/bin/sh\necho hi\n") == "bash"
|
||||
|
||||
def test_zsh_shebang(self) -> None:
|
||||
assert detect_type(content="#!/bin/zsh\necho hi\n") == "bash"
|
||||
|
||||
def test_extension_wins_over_shebang(self) -> None:
|
||||
assert (
|
||||
detect_type(path="script.py", content="#!/bin/bash\necho hi\n") == "python"
|
||||
)
|
||||
|
||||
def test_filename_wins_over_shebang(self) -> None:
|
||||
assert detect_type(path=".bashrc", content="#!/usr/bin/env python3\n") == "bash"
|
||||
|
||||
def test_unrecognized_shebang_falls_through(self) -> None:
|
||||
assert detect_type(content="#!/usr/bin/env ruby\nputs 'hi'\n") == "text"
|
||||
|
||||
|
||||
class TestContentJsonDetection:
|
||||
"""Priority 4: detect JSON from content when path gives no clue."""
|
||||
"""Priority 5: detect JSON from content when path gives no clue."""
|
||||
|
||||
def test_json_object(self, sample_json: str) -> None:
|
||||
assert detect_type(content=sample_json) == "json"
|
||||
@@ -104,9 +175,31 @@ class TestContentJsonDetection:
|
||||
def test_content_with_valid_path_takes_priority(self) -> None:
|
||||
assert detect_type(path="script.py", content='{"a": 1}') == "python"
|
||||
|
||||
def test_shebang_takes_priority_over_json(self) -> None:
|
||||
assert detect_type(content='#!/usr/bin/env python3\n{"a": 1}\n') == "python"
|
||||
|
||||
|
||||
class TestGuessContent:
|
||||
"""Priority 6: optional heuristic content guessing."""
|
||||
|
||||
def test_guess_content_disabled_by_default(self) -> None:
|
||||
diff = "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new\n"
|
||||
assert detect_type(content=diff) == "text"
|
||||
|
||||
def test_guess_content_detects_diff(self) -> None:
|
||||
diff = "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new\n"
|
||||
assert detect_type(content=diff, guess_content=True) == "diff"
|
||||
|
||||
def test_guess_content_rejects_low_confidence_false_positive(self) -> None:
|
||||
python_snippet = "def f():\n return 1\n"
|
||||
assert detect_type(content=python_snippet, guess_content=True) == "text"
|
||||
|
||||
def test_json_still_wins_over_guess_content(self, sample_json: str) -> None:
|
||||
assert detect_type(content=sample_json, guess_content=True) == "json"
|
||||
|
||||
|
||||
class TestFallback:
|
||||
"""Priority 5: fallback to 'text'."""
|
||||
"""Priority 7: fallback to 'text'."""
|
||||
|
||||
def test_no_args(self) -> None:
|
||||
assert detect_type() == "text"
|
||||
|
||||
Reference in New Issue
Block a user