# configuration

Parse user configuration.

{{< katex />}}

## Configuration

Configuration manager for parsing and organizing application settings.

This class dynamically loads and validates configuration data from
supported file formats. The `Configuration` class extends `dict` to allow
dictionary-style access to configuration categories.

{{% details title="Code block details" open=false %}}
```python
class Configuration(dict):
    """Configuration manager for parsing and organizing application settings.

    This class dynamically loads and validates configuration data from
    supported file formats. The `Configuration` class extends `dict` to allow
    dictionary-style access to configuration categories.
    """

    REGISTRY_EXTENSION: dict[str, typing.Callable] = {
        ".json": json.load,
        ".toml": tomllib.load,
        ".yml": yaml.safe_load,
        ".yaml": yaml.safe_load,
    }

    def __init__(self, path: pathlib.Path | None = None) -> None:
        """Initialize a configuration object.

        Parameters
        ----------
        path : `pathlib.Path` | `None`
            The path to the configuration file. If `None`, only default
            configuration values are used.
        """
        if path is not None:
            self.parsed_configuration: dict[str, typing.Any] = (
                self.__read_configuration(path)
            )
        else:
            self.parsed_configuration = {}

        # Here, we need to add to `self` and not replace it.
        self.update(
            {
                "miscellaneous": create_miscellaneous_configuration(),
                "interaction_configuration": InteractionConfiguration,
                "visualization": create_drawning_configuration(),
                "vdw_radius": VanDerWaalsRadius,
            }
        )

        self.__configure()

    def __read_configuration(
        self, path: pathlib.Path
    ) -> dict[str, typing.Any]:
        """Read and parse a configuration file based on its extension.

        Parameters
        ----------
        path : `pathlib.Path`
            The path to the configuration file to be parsed.

        Returns
        -------
        `dict[str, typing.Any]`
            A dictionary representation of the parsed configuration file.
        """
        with open(path, "rb") as file:
            parsed_configuration: dict[str, typing.Any] = (
                self.REGISTRY_EXTENSION[path.suffix.lower()](file)
            )

            return parsed_configuration

    def __try_configure(self, category) -> None:
        """Attempt to instantiate a configuration category with parsed values.

        Parameters
        ----------
        category : `str`
            The configuration category to initialize.

        Raises
        ------
        `ValueError`
            - If a provided configuration key does not match the expected
              parameters of the category.
        """
        try:
            self[category] = self[category](
                **self.parsed_configuration[category]
            )
        except TypeError as error:
            regex: re.Pattern = re.compile(r"'(.+)'$")
            key: str = regex.search(str(error)).group(1)

            Error.WRONG_KEY(ValueError, category=category, key=key)

    def __configure(self) -> None:
        """Initialize and validate all configuration categories.

        Raises
        ------
        `ValueError`
            - If a configuration section contains invalid keys.
            - If unknown top-level categories are found in the configuration
              file.
        """
        for key, value in self.items():
            if key not in self.parsed_configuration:
                self[key] = value()
                continue

            self.__try_configure(key)

        bad_key: set[str] = set(self.parsed_configuration.keys()) - set(
            self.keys()
        )

        if len(bad_key) > 0:
            Error.BAD_MAIN_KEY(ValueError, key=", ".join(bad_key))
```
{{% /details %}}
### \_\_init\_\_

Initialize a configuration object.

#### Parameters

**path : `pathlib.Path` | `None`**

The path to the configuration file. If `None`, only default
configuration values are used.

{{% details title="Code block details" open=false %}}
```python
def __init__(self, path: pathlib.Path | None = None) -> None:
        """Initialize a configuration object.

        Parameters
        ----------
        path : `pathlib.Path` | `None`
            The path to the configuration file. If `None`, only default
            configuration values are used.
        """
        if path is not None:
            self.parsed_configuration: dict[str, typing.Any] = (
                self.__read_configuration(path)
            )
        else:
            self.parsed_configuration = {}

        # Here, we need to add to `self` and not replace it.
        self.update(
            {
                "miscellaneous": create_miscellaneous_configuration(),
                "interaction_configuration": InteractionConfiguration,
                "visualization": create_drawning_configuration(),
                "vdw_radius": VanDerWaalsRadius,
            }
        )

        self.__configure()
```
{{% /details %}}
### \_\_read\_configuration

Read and parse a configuration file based on its extension.

#### Parameters

**path : `pathlib.Path`**

The path to the configuration file to be parsed.

#### Returns

**`dict[str, typing.Any]`**

A dictionary representation of the parsed configuration file.

{{% details title="Code block details" open=false %}}
```python
def __read_configuration(
        self, path: pathlib.Path
    ) -> dict[str, typing.Any]:
        """Read and parse a configuration file based on its extension.

        Parameters
        ----------
        path : `pathlib.Path`
            The path to the configuration file to be parsed.

        Returns
        -------
        `dict[str, typing.Any]`
            A dictionary representation of the parsed configuration file.
        """
        with open(path, "rb") as file:
            parsed_configuration: dict[str, typing.Any] = (
                self.REGISTRY_EXTENSION[path.suffix.lower()](file)
            )

            return parsed_configuration
```
{{% /details %}}
### \_\_try\_configure

Attempt to instantiate a configuration category with parsed values.

#### Parameters

**category : `str`**

The configuration category to initialize.

#### Raises

**`ValueError`**

- If a provided configuration key does not match the expected
  parameters of the category.

{{% details title="Code block details" open=false %}}
```python
def __try_configure(self, category) -> None:
        """Attempt to instantiate a configuration category with parsed values.

        Parameters
        ----------
        category : `str`
            The configuration category to initialize.

        Raises
        ------
        `ValueError`
            - If a provided configuration key does not match the expected
              parameters of the category.
        """
        try:
            self[category] = self[category](
                **self.parsed_configuration[category]
            )
        except TypeError as error:
            regex: re.Pattern = re.compile(r"'(.+)'$")
            key: str = regex.search(str(error)).group(1)

            Error.WRONG_KEY(ValueError, category=category, key=key)
```
{{% /details %}}
### \_\_configure

Initialize and validate all configuration categories.

#### Raises

**`ValueError`**

- If a configuration section contains invalid keys.
- If unknown top-level categories are found in the configuration
  file.

{{% details title="Code block details" open=false %}}
```python
def __configure(self) -> None:
        """Initialize and validate all configuration categories.

        Raises
        ------
        `ValueError`
            - If a configuration section contains invalid keys.
            - If unknown top-level categories are found in the configuration
              file.
        """
        for key, value in self.items():
            if key not in self.parsed_configuration:
                self[key] = value()
                continue

            self.__try_configure(key)

        bad_key: set[str] = set(self.parsed_configuration.keys()) - set(
            self.keys()
        )

        if len(bad_key) > 0:
            Error.BAD_MAIN_KEY(ValueError, key=", ".join(bad_key))
```
{{% /details %}}
