hierconf-core 0.5.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),

        /// Hide a struct field or enum variant from man page generation.
        ///
        /// Usage: `#[facet(hierconf::hide)]`
        Hide,
    }
}

fn get_attr<'a, T>(attrs: &'a [FacetAttr], key: &'static str) -> Result<&'a T, HierConfError>
where
    T: Facet<'static>,
{
    for attr in attrs {
        if attr.ns == Some("hierconf") && attr.key == key {
            return attr
                .get_as::<T>()
                .ok_or(HierConfError::AttributeShapeMismatch {
                    shape: attr.data.shape,
                    key,
                });
        }
    }
    Err(HierConfError::MissingAttribute(key.to_string()))
}

#[cfg(feature = "mangen")]
fn get_attr_opt<'a, T>(
    attrs: &'a [FacetAttr],
    key: &'static str,
) -> Result<Option<&'a T>, HierConfError>
where
    T: Facet<'static>,
{
    match get_attr::<T>(attrs, key) {
        Ok(attr) => Ok(Some(attr)),
        Err(HierConfError::MissingAttribute(_)) => Ok(None),
        Err(e) => Err(e),
    }
}

#[cfg(feature = "mangen")]
fn has_attr(attrs: &[FacetAttr], key: &'static str) -> bool {
    attrs
        .iter()
        .any(|attr| attr.ns == Some("hierconf") && attr.key == key)
}

/// 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>,
{
    Ok(get_attr::<&str>(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>,
{
    Ok(get_attr_opt::<&str>(T::SHAPE.attributes, "synopsis")?.cloned())
}

/// 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>,
{
    Ok(get_attr_opt::<&str>(T::SHAPE.attributes, "date")?.cloned())
}

/// 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> {
    Ok(get_attr_opt::<&str>(attrs, "type_name")?.cloned())
}

/// 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> {
    Ok(get_attr_opt::<&str>(attrs, "type_doc")?.cloned())
}

/// Check if an item (field or variant) should be hidden from man page generation.
///
/// Looks for `#[facet(hierconf::hide)]` attributes.
/// Returns `true` if the attribute is present, `false` otherwise.
#[cfg(feature = "mangen")]
pub fn is_hidden(attrs: &[FacetAttr]) -> bool {
    has_attr(attrs, "hide")
}