Skip to content

Commit

Permalink
Release version 0.0.0.dev3
Browse files Browse the repository at this point in the history
  • Loading branch information
AAriam committed Sep 26, 2024
1 parent 54a2c85 commit 43524a2
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 109 deletions.
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ namespaces = true

# ----------------------------------------- Project Metadata -------------------------------------
[project]
version = "0.0.0.dev2"
version = "0.0.0.dev3"
name = "PyShellMan"
requires-python = ">=3.10"
dependencies = [
"rich",
"rich >= 13.5",
"MDit == 0.0.0.dev14",
"ExceptionMan == 0.0.0.dev14",
]
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
rich >= 13.5
MDit == 0.0.0.dev14
ExceptionMan == 0.0.0.dev14
2 changes: 1 addition & 1 deletion src/pyshellman/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

from pyshellman import exception
from pyshellman.output import ShellOutput
from pyshellman.shell import run
from pyshellman.shell import run, Runner
from pyshellman import pip, python
100 changes: 50 additions & 50 deletions src/pyshellman/exception.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,69 @@
class PyShellManError(Exception):
from __future__ import annotations as _annotations
from typing import TYPE_CHECKING as _TYPE_CHECKING
from functools import partial as _partial

from exceptionman import ReporterException as _ReporterException
import mdit as _mdit

if _TYPE_CHECKING:
from pyshellman.output import ShellOutput


class PyShellManError(_ReporterException):
"""Base class for exceptions in this module."""
def __init__(self, message: str):
self.message = message
super().__init__(message)
def __init__(
self,
title: str,
intro: str,
output: ShellOutput
):
sphinx_config = {"html_title": "PyShellMan Error Report"}
sphinx_target_config = _mdit.target.sphinx(
renderer=_partial(
_mdit.render.sphinx,
config=_mdit.render.get_sphinx_config(sphinx_config)
)
)
report = _mdit.document(
heading=title,
body={"intro": intro},
section={"details": _mdit.document(heading="Execution Details", body=output.report())},
target_configs_md={"sphinx": sphinx_target_config},
)
super().__init__(report=report)
self.output = output
return


class PyShellManExecutionError(PyShellManError):
"""Exception raised for errors in the execution of a command."""
def __init__(self, command: str):
self.command = command
message = f"Shell command '{command}' could not be executed."
super().__init__(message)
def __init__(self, output: ShellOutput):
super().__init__(
title="Execution Error",
intro=f"Shell command was invalid and could not be executed.",
output=output
)
return


class PyShellManNonZeroExitCodeError(PyShellManError):
"""Exception raised for non-zero exit code in the execution of a command."""
def __init__(
self,
command: str,
code: int,
output: str | bytes | None = None,
error: str | bytes | None = None
):
self.command = command
self.code = code
self.output = output
self.error = error

error_details = ""
if error:
error_details = f"Error:\n{error}\n{'='*50}\n"
if output:
error_details += f"Output:\n{output}"
message = f"Shell command '{command}' failed with exit code {code} "
if error_details:
message += f"and the following output:\n{error_details}"
else:
message += "and no output."
super().__init__(message)
def __init__(self, output: ShellOutput):
super().__init__(
title="Non-Zero Exit Code Error",
intro=f"Shell command exited with a non-zero code.",
output=output
)
return


class PyShellManStderrError(PyShellManError):
"""Exception raised for non-empty stderr in the execution of a command."""

def __init__(
self,
command: str,
code: int,
output: str | bytes | None = None,
error: str | bytes | None = None
):
self.command = command
self.code = code
self.output = output
self.error = error

error_details = f"Error:\n{error}\n{'=' * 50}\n"
if output:
error_details += f"Output:\n{output}"
message = (
f"Shell command '{command}' failed with exit code {code} "
f"and the following output:\n{error_details}"
def __init__(self, output: ShellOutput):
super().__init__(
title="Non-Empty Stderr Error",
intro=f"Shell command produced output on stderr.",
output=output
)
super().__init__(message)
return
114 changes: 73 additions & 41 deletions src/pyshellman/output.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
from typing import NamedTuple as _NamedTuple
from __future__ import annotations as _annotations

from rich import text as _text, console as _console, panel as _panel, markdown as _markdown
from typing import TYPE_CHECKING as _TYPE_CHECKING

from rich import text as _text
import mdit as _mdit
import ansi_sgr as _sgr

class ShellOutput(_NamedTuple):
command: str
code: int | None = None
output: str | bytes | None = None
error: str | bytes | None = None
if _TYPE_CHECKING:
from pathlib import Path


class ShellOutput:

def __init__(
self,
title: str,
command: list[str],
cwd: Path,
code: int | None = None,
out: str | bytes | None = None,
err: str | bytes | None = None,
):
self.title = title
self.cwd = cwd
self.command = command
self.code = code
self.out = out
self.err = err
return

@property
def executed(self) -> bool:
Expand All @@ -19,41 +39,53 @@ def succeeded(self) -> bool:

@property
def details(self) -> dict[str, str | bytes | int]:
details = {"Command": self.command, "Executed": self.executed}
if self.executed:
details["Exit Code"] = self.code
if self.output:
details["Output"] = self.output
if self.error:
details["Error"] = self.error
return details
return {
"title": self.title,
"command": self.command,
"executed": self.executed,
"succeeded": self.succeeded,
"directory": self.cwd,
"code": self.code,
"out": self.out,
"err": self.err,
}

@property
def summary(self) -> str:
if not self.executed:
return f"Command could not be executed."
if not self.succeeded:
return f"Command failed with exit code {self.code}."
return f"Command executed successfully."
def report(self, dropdown: bool = True):

def __rich__(self):
group = [
_markdown.Markdown(f"- **Command**: `{self.command}`"),
_markdown.Markdown(f"- **Executed**: {self.executed}"),
]
if self.executed:
group.append(_markdown.Markdown(f"- **Exit Code**: {self.code}"))
if self.output:
output = self.output if not isinstance(self.output, str) else _text.Text.from_ansi(self.output)
group.append(_panel.Panel(output, title="Output"))
if self.error:
error = self.error if not isinstance(self.error, str) else _text.Text.from_ansi(self.error)
group.append(_panel.Panel(error, title="Error"))
out = _panel.Panel(
_console.Group(*group),
title="Shell Command Output",
def process_output(output: str | bytes, title: str, icon: str):
output = _mdit.element.rich(_text.Text.from_ansi(self.out)) if (
isinstance(output, str) and _sgr.has_sequence(output)
) else _mdit.element.code_block(str(output))
return _mdit.element.dropdown(
title=title,
body=output,
opened=True,
icon=icon,
)

emoji_fail = "πŸ”΄"
emoji_success = "🟒"
content = _mdit.block_container()
info_bar = _mdit.inline_container(
f"Execution: {emoji_success if self.executed else emoji_fail}",
separator="   ",
)
return out
if self.executed:
exit_info = emoji_success if self.succeeded else f"{emoji_fail} (Code: {self.code})"
info_bar.append(f"Exit: {exit_info}")
content.append(info_bar)
content.append(_mdit.element.code_block(" ".join(self.command), caption="Command"))
content.append(_mdit.element.code_block(str(self.cwd), caption="Directory"))
if self.out:
content.append(process_output(self.out, "Output", "πŸ“€"))
if self.err:
content.append(process_output(self.err, "Logs", "πŸ“"))
return _mdit.element.dropdown(
title=self.title,
body=content,
opened=True,
icon="🐚",
) if dropdown else content

def __str__(self):
return "\n".join([f"- {key}: {value}" for key, value in self.details.items()])
def __rich__(self):
return self.report().source("console")
3 changes: 3 additions & 0 deletions src/pyshellman/protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from typing import Literal

LogLevel = Literal["debug", "success", "info", "notice", "warning", "error", "critical"]
Loading

0 comments on commit 43524a2

Please sign in to comment.