# messenger

Module used to deal with error messages.

{{< katex />}}

## TermColor

ANSI color codes used for terminal output formatting.

{{% details title="Code block details" open=false %}}
```python
class TermColor(enum.Enum):
    """ANSI color codes used for terminal output formatting."""

    BLUE = "\033[34m"
    YELLOW = "\033[33m"
    RED = "\033[31m"
    CLEAN = "\033[0m"

    def __str__(self) -> str:
        """Return the value of the enumeration member.

        Returns
        -------
        `str`
            The value of a called enumeration.
        """
        return self.value
```
{{% /details %}}
### \_\_str\_\_

Return the value of the enumeration member.

#### Returns

**`str`**

The value of a called enumeration.

{{% details title="Code block details" open=false %}}
```python
def __str__(self) -> str:
        """Return the value of the enumeration member.

        Returns
        -------
        `str`
            The value of a called enumeration.
        """
        return self.value
```
{{% /details %}}
## Formatter

Utility class to generate formatted enum values for logging.

{{% details title="Code block details" open=false %}}
```python
class Formatter:
    """Utility class to generate formatted enum values for logging."""

    __NB_DIGIT: int = 2

    def __init__(
        self, color: TermColor, identifier: str, has_category: bool = False
    ) -> None:
        """Initialize a formatter instance.

        Parameters
        ----------
        color : `TermColor`
            Terminal color to apply to the formatted message.

        identifier : `str`
            Short identifier used in the formatted message.

        has_category : `bool`, optional
            Whether to include an exception category placeholder in the
            formatted message. By default, `False`.
        """
        self.__count: int = 1

        if has_category:
            # Double formatting: enum creation + runtime formatting.
            category: str = "[{{{{category}}}}]"
        else:
            category: str = ""

        self.__format: str = (
            f"{color}[{identifier}{{code:0{Formatter.__NB_DIGIT}d}}]"
            f"{category} {{message}}{TermColor.CLEAN}"
        )

    def generate_enum(
        self, enum_class: type[enum.Enum], message: str
    ) -> enum.Enum:
        """Generate a formatted enumeration value.

        Parameters
        ----------
        enum_class : `type[enum.Enum]`
            The enumeration class being instantiated.

        message : `str`
            The raw message associated with the enumeration member.

        Returns
        -------
        `enum.Enum`
            A newly created enumeration member with formatted value, message,
            and code attributes.
        """
        formated_message: str = self.__format.format(
            code=self.__count,
            message=message,
        )

        new_object: enum.Enum = str.__new__(enum_class, formated_message)

        new_object.message = message
        new_object.code = self.__count
        new_object._value_ = formated_message

        self.__count += 1

        return new_object
```
{{% /details %}}
### \_\_init\_\_

Initialize a formatter instance.

#### Parameters

**color : `TermColor`**

Terminal color to apply to the formatted message.

**identifier : `str`**

Short identifier used in the formatted message.

**has_category : `bool`, optional**

Whether to include an exception category placeholder in the
formatted message. By default, `False`.

{{% details title="Code block details" open=false %}}
```python
def __init__(
        self, color: TermColor, identifier: str, has_category: bool = False
    ) -> None:
        """Initialize a formatter instance.

        Parameters
        ----------
        color : `TermColor`
            Terminal color to apply to the formatted message.

        identifier : `str`
            Short identifier used in the formatted message.

        has_category : `bool`, optional
            Whether to include an exception category placeholder in the
            formatted message. By default, `False`.
        """
        self.__count: int = 1

        if has_category:
            # Double formatting: enum creation + runtime formatting.
            category: str = "[{{{{category}}}}]"
        else:
            category: str = ""

        self.__format: str = (
            f"{color}[{identifier}{{code:0{Formatter.__NB_DIGIT}d}}]"
            f"{category} {{message}}{TermColor.CLEAN}"
        )
```
{{% /details %}}
### generate\_enum

Generate a formatted enumeration value.

#### Parameters

**enum_class : `type[enum.Enum]`**

The enumeration class being instantiated.

**message : `str`**

The raw message associated with the enumeration member.

#### Returns

**`enum.Enum`**

A newly created enumeration member with formatted value, message,
and code attributes.

{{% details title="Code block details" open=false %}}
```python
def generate_enum(
        self, enum_class: type[enum.Enum], message: str
    ) -> enum.Enum:
        """Generate a formatted enumeration value.

        Parameters
        ----------
        enum_class : `type[enum.Enum]`
            The enumeration class being instantiated.

        message : `str`
            The raw message associated with the enumeration member.

        Returns
        -------
        `enum.Enum`
            A newly created enumeration member with formatted value, message,
            and code attributes.
        """
        formated_message: str = self.__format.format(
            code=self.__count,
            message=message,
        )

        new_object: enum.Enum = str.__new__(enum_class, formated_message)

        new_object.message = message
        new_object.code = self.__count
        new_object._value_ = formated_message

        self.__count += 1

        return new_object
```
{{% /details %}}
## Info

Enumeration of informational messages.

{{% details title="Code block details" open=false %}}
```python
class Info(str, enum.Enum):
    """Enumeration of informational messages."""

    formatter = enum.nonmember(Formatter(TermColor.BLUE, "Inf"))

    def __new__(cls: enum.Enum, message: str):
        """Create a new formatted informational enumeration member.

        Parameters
        ----------
        message : `str`
            Raw message template associated with the enumeration member.

        Returns
        -------
        `Info`
            A formatted enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)

    def __call__(self, **k_w_arg) -> None:
        """Print the formatted informational message.

        Parameters
        ----------
        **k_w_arg
            Keyword arguments used to format the message template.
        """
        print(self.value.format(**k_w_arg), file=sys.stderr)

    COMPUTE_PHARMACOPHORE = "Computing pharmacophore {status}"
    ADD_HYDROGEN = "Adding hydrogen for structure '{file}'."
```
{{% /details %}}
### \_\_new\_\_

Create a new formatted informational enumeration member.

#### Parameters

**message : `str`**

Raw message template associated with the enumeration member.

#### Returns

**`Info`**

A formatted enumeration member.

{{% details title="Code block details" open=false %}}
```python
def __new__(cls: enum.Enum, message: str):
        """Create a new formatted informational enumeration member.

        Parameters
        ----------
        message : `str`
            Raw message template associated with the enumeration member.

        Returns
        -------
        `Info`
            A formatted enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)
```
{{% /details %}}
### \_\_call\_\_

Print the formatted informational message.

#### Parameters

****k_w_arg**

Keyword arguments used to format the message template.

{{% details title="Code block details" open=false %}}
```python
def __call__(self, **k_w_arg) -> None:
        """Print the formatted informational message.

        Parameters
        ----------
        **k_w_arg
            Keyword arguments used to format the message template.
        """
        print(self.value.format(**k_w_arg), file=sys.stderr)
```
{{% /details %}}
## Warn

Enumeration of warning messages.

{{% details title="Code block details" open=false %}}
```python
class Warn(str, enum.Enum):
    """Enumeration of warning messages."""

    default_hook = warnings.formatwarning
    formatter = enum.nonmember(Formatter(TermColor.YELLOW, "Warn", True))

    def __new__(cls: enum.Enum, message):
        """Create a new formatted warning enumeration member.

        Parameters
        ----------
        message : `str`
            Raw warning message template.

        Returns
        -------
        `Warn`
            A formatted warning enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)

    def __call__(self, **k_w_arg) -> None:
        """Emit the warning message.

        Parameters
        ----------
        **k_w_arg
            Keyword arguments used to format the warning message.
        """
        warnings.formatwarning = Warn.format_warning

        try:
            warnings.warn(self.value.format(**k_w_arg))
        finally:
            warnings.formatwarning = Warn.default_hook

    @staticmethod
    def format_warning(
        message: str, category: type, filename, lineno, line=None
    ):
        """Custom formatting function for Python warnings.

        Parameters
        ----------
        message : `str`
            Warning message instance.

        category : `type`
            Warning category.

        filename
            **[NOT USED]**

        lineno
            **[NOT USED]**

        line : optional
            **[NOT USED]**

        Returns
        -------
        `str`
            Formatted warning string.
        """
        del filename, lineno, line

        return str(message).format(category=category.__name__) + "\n"

    SELECTION_NOT_SUPPORTED = (
        "Selection not supported for file extension '{extension}'."
    )
    AROMATICITY = (
        "Checking and assigning aromaticity for input file '{file}', with "
        "selection '{selection}'."
    )
    SKIP_CYCLE = "Skipping cycle of size {length} atoms."
    MDA_GUESS = "MDAnalysis says `{warning}`."
    NO_IMPLICIT = (
        "`NoImplicit` set to `False` for file '{file}' with selection "
        "'{selection}'. Bond orders and charges inferring disable."
    )
```
{{% /details %}}
### \_\_new\_\_

Create a new formatted warning enumeration member.

#### Parameters

**message : `str`**

Raw warning message template.

#### Returns

**`Warn`**

A formatted warning enumeration member.

{{% details title="Code block details" open=false %}}
```python
def __new__(cls: enum.Enum, message):
        """Create a new formatted warning enumeration member.

        Parameters
        ----------
        message : `str`
            Raw warning message template.

        Returns
        -------
        `Warn`
            A formatted warning enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)
```
{{% /details %}}
### \_\_call\_\_

Emit the warning message.

#### Parameters

****k_w_arg**

Keyword arguments used to format the warning message.

{{% details title="Code block details" open=false %}}
```python
def __call__(self, **k_w_arg) -> None:
        """Emit the warning message.

        Parameters
        ----------
        **k_w_arg
            Keyword arguments used to format the warning message.
        """
        warnings.formatwarning = Warn.format_warning

        try:
            warnings.warn(self.value.format(**k_w_arg))
        finally:
            warnings.formatwarning = Warn.default_hook
```
{{% /details %}}
### format\_warning

Custom formatting function for Python warnings.

#### Parameters

**message : `str`**

Warning message instance.

**category : `type`**

Warning category.

**filename**

**[NOT USED]**

**lineno**

**[NOT USED]**

**line : optional**

**[NOT USED]**

#### Returns

**`str`**

Formatted warning string.

{{% details title="Code block details" open=false %}}
```python
def format_warning(
        message: str, category: type, filename, lineno, line=None
    ):
        """Custom formatting function for Python warnings.

        Parameters
        ----------
        message : `str`
            Warning message instance.

        category : `type`
            Warning category.

        filename
            **[NOT USED]**

        lineno
            **[NOT USED]**

        line : optional
            **[NOT USED]**

        Returns
        -------
        `str`
            Formatted warning string.
        """
        del filename, lineno, line

        return str(message).format(category=category.__name__) + "\n"
```
{{% /details %}}
## Error

Enumeration of fatal error messages.

{{% details title="Code block details" open=false %}}
```python
class Error(str, enum.Enum):
    """Enumeration of fatal error messages."""

    default_hook = sys.excepthook
    formatter = enum.nonmember(Formatter(TermColor.RED, "Err", True))

    def __new__(cls: enum.Enum, message) -> enum.Enum:
        """Create a new formatted error enumeration member.

        Parameters
        ----------
        message : `str`
            Raw error message template.

        Returns
        -------
        `Error`
            A formatted error enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)

    def __call__(self, error: Exception, **k_w_arg) -> None:
        """Raise a formatted exception.

        Parameters
        ----------
        error : `Exception`
            Exception class to raise.

        **k_w_arg
            Keyword arguments used to format the error message.

        Raises
        ------
        `Exception`
            The formatted exception.
        """
        sys.excepthook = Error.except_hook
        raise error(self.value.format(**k_w_arg))

    @staticmethod
    def except_hook(exc_type, exc_value, traceback) -> None:
        """Custom exception hook for formatted error output.

        Parameters
        ----------
        exc_type
            Exception type.

        exc_value
            Exception instance.

        traceback
            Traceback object.
        """
        try:
            if "][{category}] " not in str(exc_value):
                sys.excepthook = Error.default_hook
                sys.excepthook(exc_type, exc_value, traceback)
                return None

            print(str(exc_value).format(category=exc_type.__name__))
            return None

        except:
            sys.excepthook = Error.default_hook
            sys.excepthook(exc_type, exc_value, traceback)

        return None

    GENERAL_ARGUMENT_ERROR = "{message}."
    FILE_NOT_FOUND = "File '{file}' does not exist."
    NOT_DIRECTORY = "'{file}' is a directory."
    WRONG_EXTENSION = (
        "File '{file}' extension is wrong. Accepted {type} format:\n"
        "- {format_list}."
    )
    PATH_NOT_FOUND = "Path '{path}' does not exist."
    ADD_HYDROGEN = (
        "Get '{value}' for '-a/--add_hydrogen', except one of these values:"
        "\n{except_value}"
    )
    EXCEPTED_LEN = (
        "Excepte {good_length} arguments for '{command}', got '{length}'."
    )
    SAME_PHARMA_OUTPUT = (
        "Got two times the same output for '--pharmacophore'. Got '{first}' "
        "and '{second}'."
    )
    BUILD_FAIL = (
        "File '{file}' parsed, but no molecule could be built. Please check "
        "the input file."
    )
    PARTIAL_BUILD = (
        "File '{file}' parsed, but the molecule is partially built. Please "
        "check the input file or try to set '-a/--add-hydrogen`."
    )
    MOL_2 = (
        "RDKit failed to parse the input molecule. The issue could be that "
        "the parser expects the atom-typing scheme used by Corina'. Try "
        "changing the atom-typing scheme, else check the input file '{file}'."
    )
    INDEX_OVER_LEN = (
        "Index '{index}' is out of range. Only {length} detected in the "
        "given file '{file}'."
    )
    MDA_FORMAT = "Given file extension '{extension}' is not a valid file type."
    WRONG_SELECTION = (
        "The selection '{selection}' contain unknown selection token."
    )
    EMPTY_SELECTION = (
        "The selection '{selection}' results in the selection of 0 atom."
    )
    FAMILY_NOT_REDEFINE = (
        "Found '{family}' in the feature list, but not found inside "
        "redefinition category."
    )
    WRONG_CATEGORY = (
        "Category '{category}' not found. Could not proceed with "
        "pharmacophore extraction."
    )
    HYDROGEN_ITERATOR = (
        "The hydrogen donor iterator must contain at least one pharmacophore."
    )
    NO_PHARMACOPHORE_DETECTED = (
        "For file '{file}'{message}, no pharmacophore were detected."
    )
    NO_INTERACTION = "No interaction detected."
    WRONG_KEY = "For '{category}', key '{key}' unmatched."
    OVER_CALL = "Object from class '{class_name}' overcalled."
    BAD_MAIN_KEY = (
        "Keys '[{key}]' should not be present inside the given configuration."
    )
    SAVE_PDB_FAIL = (
        "Failed to correct and add hydrogen to file '{file}'. Try protonating "
        "and checking the system."
    )
    WRONG_TYPING = (
        "Receive for field '{field}' type '{wrong_type}'. "
        "Excepted '{good_type}'."
    )
```
{{% /details %}}
### \_\_new\_\_

Create a new formatted error enumeration member.

#### Parameters

**message : `str`**

Raw error message template.

#### Returns

**`Error`**

A formatted error enumeration member.

{{% details title="Code block details" open=false %}}
```python
def __new__(cls: enum.Enum, message) -> enum.Enum:
        """Create a new formatted error enumeration member.

        Parameters
        ----------
        message : `str`
            Raw error message template.

        Returns
        -------
        `Error`
            A formatted error enumeration member.
        """
        return cls.formatter.generate_enum(cls, message)
```
{{% /details %}}
### \_\_call\_\_

Raise a formatted exception.

#### Parameters

**error : `Exception`**

Exception class to raise.

****k_w_arg**

Keyword arguments used to format the error message.

#### Raises

**`Exception`**

The formatted exception.

{{% details title="Code block details" open=false %}}
```python
def __call__(self, error: Exception, **k_w_arg) -> None:
        """Raise a formatted exception.

        Parameters
        ----------
        error : `Exception`
            Exception class to raise.

        **k_w_arg
            Keyword arguments used to format the error message.

        Raises
        ------
        `Exception`
            The formatted exception.
        """
        sys.excepthook = Error.except_hook
        raise error(self.value.format(**k_w_arg))
```
{{% /details %}}
### except\_hook

Custom exception hook for formatted error output.

#### Parameters

**exc_type**

Exception type.

**exc_value**

Exception instance.

**traceback**

Traceback object.

{{% details title="Code block details" open=false %}}
```python
def except_hook(exc_type, exc_value, traceback) -> None:
        """Custom exception hook for formatted error output.

        Parameters
        ----------
        exc_type
            Exception type.

        exc_value
            Exception instance.

        traceback
            Traceback object.
        """
        try:
            if "][{category}] " not in str(exc_value):
                sys.excepthook = Error.default_hook
                sys.excepthook(exc_type, exc_value, traceback)
                return None

            print(str(exc_value).format(category=exc_type.__name__))
            return None

        except:
            sys.excepthook = Error.default_hook
            sys.excepthook(exc_type, exc_value, traceback)

        return None
```
{{% /details %}}
