Source code for runly.exceptions

"""
Custom exceptions for RunLy.
"""

from typing import List, Optional


[docs] class RunLyError(Exception): """Base exception for all RunLy errors."""
[docs] def __init__(self, message: str, details: Optional[str] = None) -> None: self.message = message self.details = details super().__init__(message)
def __str__(self) -> str: if self.details: return f"{self.message}\nDetails: {self.details}" return self.message
[docs] class JustfileError(RunLyError): """Base exception for justfile-related errors."""
[docs] def __init__( self, message: str, filepath: Optional[str] = None, line_number: Optional[int] = None, details: Optional[str] = None, ) -> None: self.filepath = filepath self.line_number = line_number location = "" if filepath: location = f" in {filepath}" if line_number: location += f" at line {line_number}" super().__init__(f"{message}{location}", details)
class JustfileParseError(JustfileError): """Exception raised when parsing a justfile fails.""" pass class JustfileSyntaxError(JustfileError): """Exception raised when justfile has syntax errors.""" pass
[docs] class JustfileNotFoundError(JustfileError): """Exception raised when justfile cannot be found.""" pass
[docs] class CommandError(RunLyError): """Base exception for command-related errors."""
[docs] def __init__( self, message: str, command_name: Optional[str] = None, details: Optional[str] = None, ) -> None: self.command_name = command_name if command_name: message = f"Command '{command_name}': {message}" super().__init__(message, details)
[docs] class CommandNotFoundError(CommandError): """Exception raised when a command is not found.""" pass
[docs] class CommandExecutionError(CommandError): """Exception raised when command execution fails."""
[docs] def __init__( self, message: str, command_name: Optional[str] = None, exit_code: Optional[int] = None, stdout: Optional[str] = None, stderr: Optional[str] = None, ) -> None: self.exit_code = exit_code self.stdout = stdout self.stderr = stderr details = [] if exit_code is not None: details.append(f"Exit code: {exit_code}") if stderr: details.append(f"Error output: {stderr}") if stdout and not stderr: # Only show stdout if no stderr details.append(f"Output: {stdout}") super().__init__(message, command_name, "\n".join(details) if details else None)
[docs] class DependencyError(CommandError): """Base exception for dependency-related errors.""" pass
class DependencyCycleError(DependencyError): """Exception raised when a dependency cycle is detected.""" def __init__(self, cycle_path: List[str]) -> None: self.cycle_path = cycle_path cycle_str = " -> ".join(cycle_path) super().__init__( f"Dependency cycle detected: {cycle_str}", details=f"Commands involved: {', '.join(set(cycle_path))}" ) class DependencyNotFoundError(DependencyError): """Exception raised when a command dependency is not found.""" pass class ArgumentError(CommandError): """Exception raised when command arguments are invalid.""" def __init__( self, message: str, command_name: Optional[str] = None, expected_args: Optional[int] = None, provided_args: Optional[int] = None, ) -> None: self.expected_args = expected_args self.provided_args = provided_args details = None if expected_args is not None and provided_args is not None: details = f"Expected {expected_args} arguments, got {provided_args}" super().__init__(message, command_name, details) class VariableError(RunLyError): """Exception raised when variable expansion fails.""" def __init__(self, message: str, variable_name: Optional[str] = None) -> None: self.variable_name = variable_name if variable_name: message = f"Variable '{variable_name}': {message}" super().__init__(message) class ConfigurationError(RunLyError): """Exception raised when configuration is invalid.""" pass