Repository: adamtheturtle/doccmd

Issue:
Handle subprocess errors gracefully
Handle `FileNotFound` for a command which does not exist.

Handle the command being a file which is not executable.

Discussion:

Patch:
diff --git a/src/doccmd/__init__.py b/src/doccmd/__init__.py
index 9571c0d..7d4de50 100644
--- a/src/doccmd/__init__.py
+++ b/src/doccmd/__init__.py
@@ -96,13 +96,13 @@ def _run_args_against_docs(
             example.evaluate()
         except subprocess.CalledProcessError as exc:
             sys.exit(exc.returncode)
-        except FileNotFoundError:
-            styled_not_found_message = click.style(
-                text=f"Command '{args[0]}' not found",
+        except OSError as exc:
+            styled_permission_message = click.style(
+                text=f"Error running command '{args[0]}': {exc}",
                 fg="red",
             )
-            click.echo(message=styled_not_found_message, err=True)
-            sys.exit(127)
+            click.echo(message=styled_permission_message, err=True)
+            sys.exit(exc.errno)
 
 
 @beartype


Test patch:
diff --git a/tests/test_doccmd.py b/tests/test_doccmd.py
index f1c9d0e..53b9b53 100644
--- a/tests/test_doccmd.py
+++ b/tests/test_doccmd.py
@@ -658,7 +658,42 @@ def test_command_not_found(tmp_path: Path) -> None:
         args=arguments,
         catch_exceptions=False,
     )
-    # 127 is commonly used to indicate that the command was not found.
-    expected_exit_code = 127
-    assert result.exit_code == expected_exit_code
-    assert f"Command '{non_existent_command}' not found" in result.stderr
+    assert result.exit_code != 0
+    expected_error = f"Error running command '{non_existent_command}':"
+    assert result.stderr.startswith(expected_error)
+
+
+def test_not_executable(tmp_path: Path) -> None:
+    """An error is shown when the command is a non-executable file."""
+    runner = CliRunner(mix_stderr=False)
+    rst_file = tmp_path / "example.rst"
+    not_executable_command = tmp_path / "non_executable"
+    not_executable_command.touch()
+    not_executable_command_with_args = (
+        f"{not_executable_command.as_posix()} --help"
+    )
+    content = """\
+    .. code-block:: python
+
+        x = 2 + 2
+        assert x == 4
+    """
+    rst_file.write_text(data=content, encoding="utf-8")
+    arguments = [
+        "--language",
+        "python",
+        "--command",
+        not_executable_command_with_args,
+        str(rst_file),
+    ]
+    result = runner.invoke(
+        cli=main,
+        args=arguments,
+        catch_exceptions=False,
+    )
+    assert result.exit_code != 0
+    expected_error = "Error running command:"
+    expected_error = (
+        f"Error running command '{not_executable_command.as_posix()}':"
+    )
+    assert result.stderr.startswith(expected_error)