HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/anaconda3/Lib/site-packages/anaconda_navigator/config/structures.py
# -*- coding: utf-8 -*-

"""Common configuration structures to use in :mod:`~anaconda_navigator.config.preferences`."""

from __future__ import annotations

__all__ = ['ApplicationSource', 'ApplicationSources', 'SidebarLink', 'SidebarSocial', 'Interval', 'Intervals']

import bisect
import collections
import itertools
import os
import typing

import attr

T = typing.TypeVar('T')
T_co = typing.TypeVar('T_co', covariant=True)

ApplicationGroup = typing.Literal['base', 'overrides']


class ApplicationSource(typing.NamedTuple):
    """
    Description of a remote application source.

    :param path: Path to file on a local machine.
    :param url: Remote resource from which file should be downloaded.
    """

    path: str
    url: str


class ApplicationSources:
    """
    Collection of web-based application tile configurations.

    :param root: where to put configuration files after they are downloaded.
    """

    __slots__ = ('__content', '__locked', '__root')

    def __init__(self, root: str) -> None:
        """Initialize new :class:`~ApplicationSources` instance."""
        self.__content: typing.Final[typing.Mapping[ApplicationGroup, typing.List[ApplicationSource]]] = (
            collections.defaultdict(list)
        )
        self.__locked: bool = False
        self.__root: typing.Final[str] = root

    @property
    def root(self) -> str:  # noqa: D401
        """Directory with downloaded configuration files."""
        return self.__root

    def iter(self, group: typing.Optional['ApplicationGroup'] = None) -> typing.Iterator[ApplicationSource]:
        """
        Iterate through registered application sources.

        :param group: Limit iteration to only single group.
        """
        if group is None:
            return itertools.chain.from_iterable(self.__content.values())
        return iter(self.__content[group])

    def push(self, group: 'ApplicationGroup', url: str) -> None:
        """
        Add new source to the collection.

        :param group: Group to associate this record with.

                      Group defines the stage on which record will be fetched. Check out source code of
                      :func:`~anaconda_navigator.api.external_apps.config_utils.load` for more details.
        :param url: URL of the remote resource.
        """
        from anaconda_navigator.utils import url_utils  # pylint: disable=import-outside-toplevel

        if self.__locked:
            raise TypeError()
        self.__content[group].append(ApplicationSource(
            path=os.path.join(self.__root, url_utils.safe_file_name(url)),
            url=url
        ))

    def lock(self) -> None:
        """Prevent collection from further changes."""
        self.__locked = True


class SidebarLink(typing.NamedTuple):
    """Description of a single link in a sidebar."""

    text: str
    url: str
    utm_medium: str


class SidebarSocial(typing.NamedTuple):
    """Description of a single social button in a sidebar."""

    text: str
    url: str


@attr.s(auto_attribs=True, eq=False, frozen=True, slots=True)
class Interval(typing.Generic[T_co]):  # pylint: disable=too-few-public-methods
    """
    Value that repeats `count` times.

    :param count: how many times the value should be repeated.
    :param value: value that should be repeated.
    """

    count: int
    value: T_co


class Intervals(typing.Generic[T]):
    """
    Sequence of repeating values.

    :param args: values to store in this sequence.
    :param offset: index of the first value in the sequence. This allows creating custom sequences, which may start
                   from any custom index (e.g. create sequence with indices from -5 to 15, or from 100 to 123).
    """

    __slots__ = ('__keys', '__offset', '__values')

    def __init__(self, *args: Interval[T], offset: int = 0) -> None:
        """Initialize new :class:`~Intervals` instance."""
        if not args:
            raise TypeError(f'at least single {Interval.__name__} must be provided')

        self.__keys: typing.Final[typing.List[int]] = []
        self.__offset: typing.Final[int] = offset
        self.__values: typing.Final[typing.List[T]] = []

        offset -= 1

        arg: Interval[T]
        for arg in args:
            if arg.count <= 0:
                raise ValueError(f'each item may repeat only a positive number of times, not {arg.count}')

            offset += arg.count
            self.__keys.append(offset)
            self.__values.append(arg.value)

    @property
    def first(self) -> int:  # noqa: D401
        """Index of the first element in the sequence."""
        return self.__offset

    @property
    def last(self) -> int:  # noqa: D401
        """Index of the last element in the sequence."""
        return self.__keys[-1]

    def get(self, index: int, *, strict: bool = False) -> T:
        """
        Retrieve element at `index`.

        If `strict` is set - method may raise an :exc:`~IndexError` if invalid item is requested. Otherwise - method
        will return value closest to the requested index.
        """
        if strict:
            if index < self.__offset:
                raise IndexError(f'index must be at least {self.__offset!r}')
            if index > self.__keys[-1]:
                raise IndexError(f'index must be at most {self.__offset!r}')

        index = bisect.bisect_left(self.__keys, index, 0, len(self.__keys) - 1)
        return self.__values[index]