hierconf-core 0.3.0

Core functionality for hierconf configuration management
Documentation
//! Extension attributes for hierconf
//!
//! This module defines custom facet attributes for hierconf configuration structs.

use crate::error::HierConfError;
use facet::Facet;

// Type alias to avoid conflict with local Attr enum
type FacetAttr = facet::Attr;

#[cfg(not(feature = "mangen"))]
facet::define_attr_grammar! {
    ns "hierconf";
    crate_path ::hierconf_core::attrs;

    /// Hierconf extension attributes for configuration structs.
    pub enum Attr {
        /// Set application name used for config paths and man pages.
        ///
        /// Usage: `#[facet(hierconf::app_name("myapp"))]`
        AppName(&'static str),
    }
}

#[cfg(feature = "mangen")]
facet::define_attr_grammar! {
    ns "hierconf";
    crate_path ::hierconf_core::attrs;

    /// Hierconf extension attributes for configuration structs.
    pub enum Attr {
        /// Set application name used for config paths and man pages.
        ///
        /// Usage: `#[facet(hierconf::app_name("myapp"))]`
        AppName(&'static str),

        /// Override the synopsis text for the man page. Defaults to the rustdoc comment of the struct.
        ///
        /// Usage: `#[facet(hierconf::synopsis("Configuration format for myapp"))]`
        Synopsis(&'static str),

        /// Override the date for the man page. Defaults to the current date.
        ///
        /// Usage: `#[facet(hierconf::date("2025-01-15"))]`
        Date(&'static str),

        /// Override the type name displayed in man pages. Useful for opaque types or custom type names.
        ///
        /// Usage: `#[facet(hierconf::type_name("MyCustomType"))]`
        TypeName(&'static str),

        /// Override the documentation text for a type in man pages. Overrides rustdoc comments.
        ///
        /// Usage: `#[facet(hierconf::type_doc("Custom documentation for this type."))]`
        TypeDoc(&'static str),
    }
}

fn extract_attr_value(
    attrs: &[FacetAttr],
    key_str: &'static str,
) -> Result<&'static str, HierConfError> {
    for attr in attrs {
        if attr.ns == Some("hierconf") && attr.key == key_str {
            // Try to get as the typed Attr enum first
            if let Some(hierconf_attr) = attr.get_as::<Attr>() {
                match hierconf_attr {
                    Attr::AppName(name) => {
                        return Ok(*name);
                    }
                    #[cfg(feature = "mangen")]
                    Attr::Synopsis(text) => {
                        return Ok(*text);
                    }
                    #[cfg(feature = "mangen")]
                    Attr::Date(date) => {
                        return Ok(*date);
                    }
                    #[cfg(feature = "mangen")]
                    Attr::TypeName(name) => {
                        return Ok(*name);
                    }
                    #[cfg(feature = "mangen")]
                    Attr::TypeDoc(doc) => {
                        return Ok(*doc);
                    }
                }
            }
            // Fallback: try to get as &'static str directly (for compatibility)
            if let Some(value) = attr.get_as::<&'static str>() {
                return Ok(*value);
            }
            return Err(HierConfError::AttributeShapeMismatch {
                shape: attr.data.shape(),
                key: attr.key,
            });
        }
    }

    let attr_name = format!("hierconf::{}", key_str);
    Err(HierConfError::MissingAttribute(format!(
        "{} must be specified via #[facet({}(\"...\"))] attribute",
        key_str, attr_name
    )))
}

#[cfg(feature = "mangen")]
fn extract_optional_attr_value(
    attrs: &[FacetAttr],
    key_str: &'static str,
) -> Result<Option<&'static str>, HierConfError> {
    match extract_attr_value(attrs, key_str) {
        Ok(value) => Ok(Some(value)),
        Err(HierConfError::MissingAttribute(_)) => Ok(None),
        Err(e) => Err(e),
    }
}

/// Extract the application name from a facet type's attributes.
///
/// Looks for `#[facet(hierconf::app_name("..."))]` attributes on the struct.
/// Returns an error if the attribute is not found.
pub fn extract_app_name<T>() -> Result<&'static str, HierConfError>
where
    T: Facet<'static>,
{
    extract_attr_value(T::SHAPE.attributes, "app_name")
}

/// Extract the synopsis from a facet type's attributes.
///
/// Looks for `#[facet(hierconf::synopsis("..."))]` attributes on the struct.
/// Returns `None` if the attribute is not found.
#[cfg(feature = "mangen")]
pub fn extract_synopsis<T>() -> Result<Option<&'static str>, HierConfError>
where
    T: Facet<'static>,
{
    extract_optional_attr_value(T::SHAPE.attributes, "synopsis")
}

/// Extract the date from a facet type's attributes.
///
/// Looks for `#[facet(hierconf::date("..."))]` attributes on the struct.
/// Returns `None` if the attribute is not found.
#[cfg(feature = "mangen")]
pub fn extract_date<T>() -> Result<Option<&'static str>, HierConfError>
where
    T: Facet<'static>,
{
    extract_optional_attr_value(T::SHAPE.attributes, "date")
}

/// Extract the type name override from attributes.
///
/// Looks for `#[facet(hierconf::type_name("..."))]` attributes.
/// Returns `None` if the attribute is not found.
#[cfg(feature = "mangen")]
pub fn extract_type_name(attrs: &[FacetAttr]) -> Result<Option<&'static str>, HierConfError> {
    extract_optional_attr_value(attrs, "type_name")
}

/// Extract the type documentation override from attributes.
///
/// Looks for `#[facet(hierconf::type_doc("..."))]` attributes.
/// Returns `None` if the attribute is not found.
#[cfg(feature = "mangen")]
pub fn extract_type_doc(attrs: &[FacetAttr]) -> Result<Option<&'static str>, HierConfError> {
    extract_optional_attr_value(attrs, "type_doc")
}