fieldx_aux 0.1.6

Various types and tools useful for fieldx crates and, potentially, to users of fieldx
Documentation
use crate::{FXOrig, FXTriggerHelper};
use darling::{ast::NestedMeta, FromMeta};
use getset::Getters;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{spanned::Spanned, Meta};

#[derive(Debug, Clone, Getters)]
#[getset(get = "pub")]
pub struct FXDefault<const OPTIONAL: bool = false> {
    #[getset(skip)]
    off:   bool,
    value: Option<NestedMeta>,
    orig:  Option<TokenStream>,
}

impl<const OPTIONAL: bool> FXDefault<OPTIONAL> {
    #[allow(dead_code)]
    pub fn is_str(&self) -> bool {
        if let Some(NestedMeta::Lit(syn::Lit::Str(ref _lit))) = self.value {
            true
        }
        else {
            false
        }
    }

    #[allow(dead_code)]
    pub fn has_value(&self) -> bool {
        self.value.is_some()
    }
}

impl<const OPTIONAL: bool> FromMeta for FXDefault<OPTIONAL> {
    fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
        let mut off = false;
        let meta = if items.len() == 2 {
            match &items[0] {
                NestedMeta::Meta(Meta::Path(path)) if path.is_ident("off") && path.segments.len() == 1 => {
                    off = true;
                }
                _ => return Err(darling::Error::custom("The first argument can only be 'off' keyword")),
            }
            Some(items[1].clone())
        }
        else if items.len() == 0 {
            if OPTIONAL {
                None
            }
            else {
                return Err(darling::Error::too_few_items(1));
            }
        }
        else if items.len() > 2 {
            return Err(darling::Error::too_many_items(2));
        }
        else {
            Some(items[0].clone())
        };

        let mut orig = TokenStream::new();
        orig.extend(items.iter().map(|i| i.to_token_stream()));

        Ok(Self {
            off,
            value: meta,
            orig: Some(orig),
        })
    }

    fn from_word() -> darling::Result<Self> {
        if OPTIONAL {
            Ok(Self {
                off:   Default::default(),
                value: None,
                orig:  None,
            })
        }
        else {
            Err(darling::Error::custom("The actual default value is required"))
        }
    }
}

impl<const OPTIONAL: bool> FXTriggerHelper for FXDefault<OPTIONAL> {
    fn is_true(&self) -> bool {
        !self.off
    }
}

impl<const OPTIONAL: bool> FXOrig<TokenStream> for FXDefault<OPTIONAL> {
    fn orig(&self) -> Option<&TokenStream> {
        self.orig.as_ref()
    }
}

impl<const OPTIONAL: bool> TryFrom<&FXDefault<OPTIONAL>> for String {
    type Error = darling::Error;

    fn try_from(dv: &FXDefault<OPTIONAL>) -> darling::Result<Self> {
        if let Some(NestedMeta::Lit(syn::Lit::Str(ref lit))) = dv.value {
            Ok(lit.value())
        }
        else {
            Err(darling::Error::custom("The default value must be a string")
                .with_span(&dv.orig.as_ref().map_or_else(|| Span::call_site(), |o| o.span())))
        }
    }
}