cpp_oop_macros 0.1.9

helper crate for 'oop_cpp' crate
Documentation
use proc_macro2::TokenTree;
use quote::quote;
use syn::{parse::Parser, Attribute, Field, Fields, FieldsNamed, Ident, Meta, MetaList};

pub fn has_attr(attrs: &[Attribute], attr_name: &str) -> bool {
    attrs
        .iter()
        .any(|attr| attr.path().segments[0].ident == attr_name)
}

pub fn get_attr(attrs: &[Attribute], attr_name: &str) -> Option<Attribute> {
    attrs
        .iter()
        .filter(|attr| attr.path().segments[0].ident == attr_name)
        .cloned()
        .collect::<Vec<_>>()
        .first()
        .cloned()
}

pub fn remove_attr(attrs: &mut Vec<Attribute>, attr_name: &str) {
    attrs.retain(|attr| attr.path().segments[0].ident != attr_name);
}

pub fn has_repr_c(attrs: &[Attribute]) -> bool {
    // Look for existing #[repr(C)] variants, e.g.,
    // #[repr(C)]
    // #[repr(C, packed(4))]

    let has = |meta: &Meta, ident: &str| meta.path().get_ident().map_or(false, |i| i == ident);

    attrs
        .iter()
        .filter(|a| has(&a.meta, "repr"))
        .filter_map(|a| match &a.meta {
            Meta::List(MetaList { tokens, .. }) => {
                Some(tokens.clone().into_iter().collect::<Vec<_>>())
            }
            _ => None,
        })
        .flat_map(IntoIterator::into_iter)
        .map(|tree| match tree {
            TokenTree::Ident(ident) => ident,
            _ => todo!(),
        })
        .any(|n| n == "C")
}

pub fn add_repr_c(attrs: &mut Vec<Attribute>) {
    if has_repr_c(attrs) {
        return;
    }

    let mut repr_c = Attribute::parse_outer
        .parse2(quote! { #[repr(C)] })
        .expect("internal macro error with ill-formed #[repr(C)]");

    attrs.append(&mut repr_c);
}

pub fn add_field(fields: &mut Fields, field_name: &Ident, field_ty_name: &Ident) {
    match fields {
        Fields::Named(FieldsNamed { named: fields, .. }) => {
            // ensure they don't already have this field
            if fields.first().map_or(true, |first_field| {
                first_field
                    .ident
                    .as_ref()
                    .map_or(true, |ident| ident != field_name)
            }) {
                fields.insert(
                    0,
                    Field::parse_named
                        .parse2(quote! { pub #field_name: #field_ty_name })
                        .expect("Failed to create field in struct"),
                );
            }
        }
        _ => panic!("expected named fields"),
    }
}