clap-config-fallback-derive 0.1.2

Macro implementation of #[derive(ConfigParser)]
Documentation
use syn::{Expr, GenericArgument, Ident, PathArguments, Type, parse_quote};

/// Extension trait for `syn::Type`.
pub trait TypeExt {
    /// Extracts the identifier from a type.
    fn ident(&self) -> Option<&Ident>;
    /// Converts the type to an `Option` type if it is not already an `Option`.
    fn to_option(&self) -> Type;
    /// Checks if the type is an `Option` of a specific identifier.
    fn is_option_of(&self, ident: &str) -> bool;
    /// Checks if the type is a specific identifier.
    fn is(&self, ident: &str) -> bool;
}

impl TypeExt for Type {
    fn ident(&self) -> Option<&Ident> {
        match self {
            Type::Path(type_path) => type_path.path.segments.last().map(|segment| &segment.ident),
            _ => None,
        }
    }

    fn to_option(&self) -> Type {
        if self.is("Option") {
            self.clone()
        } else {
            parse_quote! { Option<#self> }
        }
    }

    fn is_option_of(&self, ident: &str) -> bool {
        if let Type::Path(type_path) = self
            && let Some(segment) = type_path.path.segments.last()
            && segment.ident == "Option"
            && let PathArguments::AngleBracketed(args) = &segment.arguments
            && let Some(GenericArgument::Type(ty)) = args.args.first()
        {
            ty.is(ident)
        } else {
            false
        }
    }

    fn is(&self, ident: &str) -> bool {
        if let Type::Path(type_path) = self
            && let Some(segment) = type_path.path.segments.last()
            && segment.ident == ident
        {
            true
        } else {
            false
        }
    }
}

/// Extension trait for `syn::Expr`.
pub trait ExprExt {
    /// Attempts to extract an identifier from the expression.
    fn ident(&self) -> Option<&Ident>;
}

impl ExprExt for Expr {
    fn ident(&self) -> Option<&Ident> {
        match self {
            Expr::Path(p) => p.path.get_ident(),
            Expr::Assign(a) => match &*a.left {
                Expr::Path(p) => p.path.get_ident(),
                _ => None,
            },
            Expr::Call(c) => match &*c.func {
                Expr::Path(p) => p.path.get_ident(),
                _ => None,
            },
            _ => None,
        }
    }
}