pagetop 0.5.0

Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.
Documentation
//! Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas.

use std::any::Any;

/// Selector para identificar segmentos de la ruta de un tipo.
#[derive(Clone, Copy, Debug)]
pub enum TypeInfo {
    /// Ruta completa tal y como la devuelve [`core::any::type_name`].
    FullName,
    /// Último segmento de la ruta, por ejemplo `Vec<i32>` en lugar de `alloc::vec::Vec<i32>`.
    ShortName,
    /// Conserva todo **desde** `start` inclusive hasta el final.
    NameFrom(isize),
    /// Conserva todo **hasta e incluyendo** `end`.
    NameTo(isize),
    /// Conserva la subruta comprendida entre `start` y `end` (ambos inclusive).
    PartialName(isize, isize),
}

impl TypeInfo {
    /// Devuelve el segmento solicitado de la ruta para el tipo `T`.
    pub fn of<T: ?Sized>(&self) -> &'static str {
        let type_name = std::any::type_name::<T>();
        match self {
            TypeInfo::FullName => type_name,
            TypeInfo::ShortName => Self::partial(type_name, -1, None),
            TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None),
            TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)),
            TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)),
        }
    }

    /// Extrae un rango de segmentos de `type_name` (tokens separados por `::`).
    ///
    /// Los argumentos `start` y `end` identifican los índices de los segmentos teniendo en cuenta:
    ///
    /// * Los índices positivos cuentan **desde la izquierda**, empezando en `0`.
    /// * Los índices negativos cuentan **desde la derecha**, `-1` es el último.
    /// * Si `end` es `None`, el corte llega hasta el último segmento.
    /// * Si la selección resulta vacía por índices desordenados o segmento inexistente, se devuelve
    ///   la cadena vacía.
    ///
    /// Ejemplos (con `type_name = "alloc::vec::Vec<i32>"`):
    ///
    /// | Llamada                      | Resultado                |
    /// |------------------------------|--------------------------|
    /// | `partial(...,  0, None)`     | `"alloc::vec::Vec<i32>"` |
    /// | `partial(...,  1, None)`     | `"vec::Vec<i32>"`        |
    /// | `partial(..., -1, None)`     | `"Vec<i32>"`             |
    /// | `partial(...,  0, Some(-2))` | `"alloc::vec"`           |
    /// | `partial(..., -5, None)`     | `"alloc::vec::Vec<i32>"` |
    ///
    /// La porción devuelta vive tanto como `'static` porque `type_name` es `'static` y sólo se
    /// presta.
    fn partial(type_name: &'static str, start: isize, end: Option<isize>) -> &'static str {
        let maxlen = type_name.len();

        // Localiza los límites de cada segmento a nivel 0 de `<...>`.
        let mut segments = Vec::new();
        let mut segment_start = 0; // Posición inicial del segmento actual.
        let mut angle_brackets = 0; // Profundidad dentro de '<...>'.
        let mut previous_char = '\0'; // Se inicializa a carácter nulo, no hay aún carácter previo.

        for (idx, c) in type_name.char_indices() {
            match c {
                ':' if angle_brackets == 0 => {
                    if previous_char == ':' {
                        if segment_start < idx - 1 {
                            segments.push((segment_start, idx - 1)); // No incluye último '::'.
                        }
                        segment_start = idx + 1; // Nuevo segmento tras '::'.
                    }
                }
                '<' => angle_brackets += 1,
                '>' => angle_brackets -= 1,
                _ => {}
            }
            previous_char = c;
        }

        // Incluye el último segmento si lo hubiese.
        if segment_start < maxlen {
            segments.push((segment_start, maxlen));
        }

        // Calcula la posición inicial.
        let start_pos = segments
            .get(if start >= 0 {
                start as usize
            } else {
                segments.len().saturating_sub(start.unsigned_abs())
            })
            .map_or(0, |&(s, _)| s);

        // Calcula la posición final.
        let end_pos = segments
            .get(if let Some(end) = end {
                if end >= 0 {
                    end as usize
                } else {
                    segments.len().saturating_sub(end.unsigned_abs())
                }
            } else {
                segments.len() - 1
            })
            .map_or(maxlen, |&(_, e)| e);

        // Devuelve la cadena parcial basada en las posiciones calculadas.
        if start_pos >= end_pos {
            return "";
        }
        &type_name[start_pos..end_pos]
    }
}

/// Proporciona información de tipo en tiempo de ejecución y conversión dinámica de tipos.
///
/// Este *trait* se implementa automáticamente para **todos** los tipos que implementen [`Any`], de
/// modo que basta con traer [`AnyInfo`] al ámbito (`use crate::AnyInfo;`) para disponer de estos
/// métodos adicionales, o usar el [`prelude`](crate::prelude) de PageTop.
///
/// # Ejemplo
///
/// ```rust
/// # use pagetop::prelude::*;
/// let n = 3u32;
/// assert_eq!(n.type_name(), "u32");
/// ```
pub trait AnyInfo: Any {
    /// Devuelve el nombre totalmente cualificado del tipo.
    fn type_name(&self) -> &'static str;

    /// Devuelve el nombre corto del tipo (último segmento del nombre).
    fn short_name(&self) -> &'static str;

    /// Devuelve una referencia a `dyn Any` para la conversión dinámica de tipos.
    fn as_any_ref(&self) -> &dyn Any;

    /// Devuelve una referencia mutable a `dyn Any` para la conversión dinámica de tipos.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl<T: Any> AnyInfo for T {
    #[inline]
    fn type_name(&self) -> &'static str {
        TypeInfo::FullName.of::<T>()
    }

    #[inline]
    fn short_name(&self) -> &'static str {
        TypeInfo::ShortName.of::<T>()
    }

    #[inline]
    fn as_any_ref(&self) -> &dyn Any {
        self
    }

    #[inline]
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

/// Extiende [`AnyInfo`] con utilidades de *downcasting* para conversión de tipos.
///
/// Preferible a usar directamente `Any::downcast_ref` porque conserva el *trait bound* [`AnyInfo`],
/// lo que permite seguir llamando a `type_name`, etc.
pub trait AnyCast: AnyInfo {
    /// Comprueba si la instancia subyacente es de tipo `T`.
    #[inline]
    fn is<T>(&self) -> bool
    where
        T: AnyInfo,
    {
        self.as_any_ref().is::<T>()
    }

    /// Intenta hacer *downcast* de un objeto para obtener una referencia de tipo `T`.
    #[inline]
    #[must_use]
    fn downcast_ref<T>(&self) -> Option<&T>
    where
        T: AnyInfo,
    {
        self.as_any_ref().downcast_ref()
    }

    /// Intenta hacer *downcast* de un objeto para obtener una referencia mutable de tipo `T`.
    #[inline]
    #[must_use]
    fn downcast_mut<T>(&mut self) -> Option<&mut T>
    where
        T: AnyInfo,
    {
        self.as_any_mut().downcast_mut()
    }
}

/// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`].
impl<T: ?Sized + AnyInfo> AnyCast for T {}

// API para definir acciones que alteran el comportamiento predeterminado del código.
pub mod action;

// API para construir nuevos componentes.
pub mod component;

// API para añadir nuevas funcionalidades usando extensiones.
pub mod extension;

// API para añadir y gestionar nuevos temas.
pub mod theme;