7个版本 (4个破坏性更新)

0.5.0 2023年3月28日
0.4.2 2023年2月15日
0.3.0 2023年2月7日
0.2.0 2023年2月3日
0.1.0 2023年1月13日

#47 in 过程宏

Download history 476/week @ 2024-03-13 1226/week @ 2024-03-20 825/week @ 2024-03-27 1034/week @ 2024-04-03 1109/week @ 2024-04-10 1086/week @ 2024-04-17 939/week @ 2024-04-24 1217/week @ 2024-05-01 1006/week @ 2024-05-08 829/week @ 2024-05-15 809/week @ 2024-05-22 702/week @ 2024-05-29 719/week @ 2024-06-05 686/week @ 2024-06-12 947/week @ 2024-06-19 795/week @ 2024-06-26

3,266 每月下载量
46 个crate中使用 (直接使用18个)

MIT 许可证

245KB
4.5K SLoC

Deluxe — 最新版本 文档 构建状态

Rust过程宏属性解析器。

摘要

这个crate提供了一个类似于C#属性设计的属性解析器。它有一个类似于serde的接口。属性被写成普通的Rust结构体或枚举,然后自动生成解析器。它们可以包含任意表达式,并且可以使用扁平化机制从其他属性继承。

这个crate中的解析器直接使用syn解析标记流。因此,大多数内置的Rust类型和syn类型可以直接用作字段。

详细信息

这个crate的功能围绕三个特质及其各自的 derive 宏展开

  • ExtractAttributes

    从包含一个syn::Attribute列表的对象中提取属性,并将它们解析为Rust类型。应该为将从一组匹配的属性直接解析出来的顶层结构体实现。

  • ParseAttributes

    从任何包含一个syn::Attribute列表的对象中解析Rust类型。如果一组匹配的属性可能在这个类型和其他类型之间共享,则应使用。

  • ParseMetaItem

    syn::parse::ParseStream中解析Rust类型。应为任何可以嵌套在属性内部的类型实现。

使用此crate中的派生宏的基本用法是简单地派生一个(或几个)这些特质,然后调用extract_attributesparse_attributes。对于更高级的功能,支持在结构体、枚举、变体和字段上使用几个#[deluxe(...)]属性。请参见下面的示例和每个派生宏的文档,以获得支持的属性的完整描述。

默认支持的字段类型列表可以在提供的ParseMetaItem实现列表中查看。对于更复杂的用法,可以提供这些特质的手动实现。请参阅各个特质的文档,以获取有关如何手动实现自己的解析器的更多详细信息。

Deluxe从darlingcrate汲取灵感,但对其提供了一些增强。Darling围绕预解析的syn::Meta对象构建,因此仅限于元语法。Deluxe直接从属性的TokenStream对象中解析类型,因此能够使用任何解析为有效令牌树的语法。Deluxe也不提供用于解析特殊syn对象(如DeriveInputField)的额外特质。相反,Deluxe使用一个泛型特质来解析包含Vec<syn::Attribute>的任何类型。

示例

基本派生宏

要创建一个可以从属性添加一些简单元数据的Rust类型派生宏,首先定义一个派生ExtractAttributes的结构体。然后,在派生宏中调用extract_attributes以创建结构体的实例

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_desc))] // Match only `my_desc` attributes
struct MyDescription {
    name: String,
    version: String,
}

#[proc_macro_derive(MyDescription, attributes(my_desc))]
pub fn derive_my_description(item: TokenStream) -> TokenStream {
    let mut input = syn::parse::<syn::DeriveInput>(item).unwrap();

    // Extract a description, modifying `input.attrs` to remove the matched attributes.
    let MyDescription { name, version } = match deluxe::extract_attributes(&mut input) {
        Ok(desc) => desc,
        Err(e) => return e.into_compile_error().into()
    };

    let ident = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    let tokens = quote::quote! {
        impl #impl_generics #ident #type_generics #where_clause {
            fn my_desc() -> &'static str {
                concat!("Name: ", #name, ", Version: ", #version)
            }
        }
    };
    tokens.into()
}

然后,尝试在某个使用你的宏的代码中添加属性

#[derive(MyDescription)]
#[my_desc(name = "hello world", version = "0.2")]
struct Hello(String);

let hello = Hello("Moon".into());
assert_eq!(hello.my_desc(), "Name: hello world, Version: 0.2");

基本属性宏

该crate中包含的parseparse2函数也可以用作属性宏的简单辅助工具

#[derive(deluxe::ParseMetaItem)]
struct MyDescription {
    name: String,
    version: String,
}

#[proc_macro_attribute]
pub fn my_desc(
    attr: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let MyDescription { name, version } = match deluxe::parse::<MyDescription>(attr) {
        Ok(desc) => desc,
        Err(e) => return e.into_compile_error().into()
    };

    let tokens = quote::quote! {
        fn my_desc() -> &'static str {
            concat!("Name: ", #name, ", Version: ", #version)
        }
        #item
    };
    tokens.into()
}
// In your normal code

#[my_desc(name = "hello world", version = "0.2")]
fn nothing() {}

assert_eq!(my_desc(), "Name: hello world, Version: 0.2");

字段属性

支持aliasdefaultrenameskip属性,其行为与Serde中相同。可以在Vec字段上使用append属性以聚合键的所有重复项。可以使用rest属性来对任何未知键进行自定义处理。

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_object))]
struct MyObject {
    // Can be specified with key `id` or `object_id`
    #[deluxe(alias = object_id)]
    id: u64,

    // Field is optional, defaults to `Default::default` if not present
    #[deluxe(default)]
    count: u64,

    // Defaults to "Empty" if not present
    #[deluxe(default = String::from("Empty"))]
    contents: String,

    // Can be specified only with key `name`
    #[deluxe(rename = name)]
    s: String,

    // Skipped during parsing entirely
    #[deluxe(skip)]
    internal_flag: bool,

    // Appends any extra fields with the key `expr` to the Vec
    #[deluxe(append, rename = expr)]
    exprs: Vec<syn::Expr>,

    // Adds any unknown keys to the hash map
    #[deluxe(rest)]
    rest: std::collections::HashMap<syn::Path, syn::Expr>,
}
// Omitted fields will be set to defaults
#[derive(MyObject)]
#[my_object(id = 1, name = "First", expr = 1 + 2, count = 3)]
struct FirstObject;

// `expr` can be specified multiple times because of the `append` attribute
#[derive(MyObject)]
#[my_object(object_id = 2, name = "Second", expr = 1 + 2, expr = 3 + 4)]
struct SecondObject;

// `unknown` and `extra` will be stored in the `rest` hashmap
#[derive(MyObject)]
#[my_object(id = 3, name = "Third", unknown = 1 + 2, extra = 3 + 4)]
struct ThirdObject;

继承

可以使用flatten属性从另一个结构中的结构解析键

#[derive(deluxe::ParseMetaItem)]
struct A {
    id: u64,
}

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(b))]
struct B {
    #[deluxe(flatten)]
    a: A,
    name: String,
}

然后,在派生 B 时,可以使用来自 AB 的字段。

#[derive(B)]
#[b(id = 123, name = "object")]
struct Object;

嵌套代码中的属性

可以从附加到宏的代码块中提取额外的属性。当在属性宏中使用时,应消耗这些属性,以便在输出令牌时不会产生“未知属性”错误。

#[derive(Default, deluxe::ParseMetaItem, deluxe::ExtractAttributes)]
struct MyDescription {
    name: String,
    version: String,
}

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(author))]
struct Authors(#[deluxe(flatten)] Vec<String>);

#[proc_macro_derive(MyDescription, attributes(my_desc))]
pub fn derive_my_description(item: TokenStream) -> TokenStream {
    let mut input = syn::parse::<syn::DeriveInput>(item).unwrap();

    // Parsing functions suffixed with `_optional` can be used to continue
    // parsing after an error. Any errors will get accumulated into an `Errors`
    // structure, which can then be manually included in the token output to
    // produce compile errors.
    let errors = deluxe::Errors::new();
    let MyDescription { name, version } = deluxe::extract_attributes_optional(&mut input, &errors);

    let mut authors = Vec::new();
    if let syn::Data::Struct(s) = &mut input.data {
        // Look through all fields in the struct for `author` attributes
        for field in s.fields.iter_mut() {
            // Aggregate any errors to avoid exiting the loop early
            match deluxe::extract_attributes(field) {
                Ok(Authors(a)) => authors.extend(a),
                Err(e) => errors.push_syn(e),
            }
        }
    }

    let ident = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    // Make sure to include the errors in the output
    let tokens = quote::quote! {
        #errors
        impl #impl_generics #ident #type_generics #where_clause {
            fn my_desc() -> &'static str {
                concat!("Name: ", #name, ", Version: ", #version #(, ", Author: ", #authors)*)
            }
        }
    };
    tokens.into()
}

#[proc_macro_attribute]
pub fn my_desc_mod(
    attr: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let mut module = syn::parse::<syn::ItemMod>(item) {
        Ok(module) => module,
        Err(e) => return e.into_compile_error().into()
    };

    let errors = deluxe::Errors::new();
    let MyDescription { name, version } = deluxe::parse_optional(attr, &errors);

    let (_, items) = module.content.as_mut().unwrap();

    let mut authors = Vec::new();
    // Look through all items in the module for `author` attributes
    for i in items.iter_mut() {
        // Extract the attributes to remove them from the final output
        match deluxe::extract_attributes(i) {
            Ok(Authors(a)) => authors.extend(a),
            Err(e) => errors.push_syn(e),
        }
    }

    // Place a new function inside the module
    items.push(syn::parse_quote! {
        fn my_desc() -> &'static str {
            concat!("Name: ", #name, ", Version: ", #version #(, ", Author: ", #authors)*)
        }
    });

    // Make sure to include the errors in the output
    let tokens = quote::quote! { #module #errors };
    tokens.into()
}
// In your normal code

#[derive(MyDescription, Default)]
#[my_desc(name = "hello world", version = "0.2")]
struct Hello {
    #[author("Alice")]
    a: i32,
    #[author("Bob")]
    b: String
}

let hello: Hello = Default::default();
assert_eq!(hello.my_desc(), "Name: hello world, Version: 0.2, Author: Alice, Author: Bob");

#[my_desc_mod(name = "hello world", version = "0.2")]
mod abc {
    #[author("Alice", "Bob")]
    fn func1() {}

    #[author("Carol")]
    #[author("Dave")]
    fn func2() {}
}

assert_eq!(
    abc::my_desc(),
    "Name: hello world, Version: 0.2, Author: Alice, Author: Bob, Author: Carol, Author: Dave"
);

元组结构体、元组和 Vec

Deluxe 还支持解析到具有未命名字段的复杂数据结构。

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_tuple))]
struct MyTuple(u64, String);

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_idents))]
struct MyIdents {
    id: u64,
    names: (String, String),
    idents: Vec<syn::Ident>
}

当指定 Vec 类型时,可以使用带括号的标准属性语法。还可以使用类似的数组字面量外观的替代语法 key = [...]

#[derive(MyTuple)]
#[my_tuple(123, "object")]
struct Object;

#[derive(MyIdents)]
#[my_idents(id = 7, names("hello", "world"), idents(a, b, c))]
struct ABC;

// `idents` contains same values as above
#[derive(MyIdents)]
#[my_idents(id = 7, names("hello", "world"), idents = [a, b, c])]
struct ABC2;

C# 风格的属性

C# 中的属性可以首先支持位置参数,然后是命名参数。可以通过使用带有末尾扁平化正常结构的元组结构体来模仿此样式。将 #[deluxe(default)] 放在结构体上,与 Serde 的行为相同,通过用 Default 的值填充所有字段,允许每个命名参数都是可选的。

#[derive(deluxe::ParseMetaItem, Default)]
#[deluxe(default)]
struct Flags {
    native: bool,
}

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(a))]
struct A(u64, String, #[deluxe(flatten)] Flags);
#[derive(A)]
#[a(123, "object")]
struct Object;

#[derive(A)]
#[a(123, "native-object", native = true)]
struct NativeObject;

枚举

枚举通过使用单键的变体名称(蛇形命名)来支持。可以像字段一样重命名、别名和跳过变体。

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_enum))]
enum MyEnum {
    A,
    B,
    C,
    #[deluxe(alias = d)]
    AnotherOne,
    #[deluxe(rename = e)]
    AnotherTwo,
    #[deluxe(skip)]
    SkipMe
}
#[derive(MyEnum)]
#[my_enum(b)]
struct ObjectB;

#[derive(MyEnum)]
#[my_enum(another_one)]
struct ObjectD;

复杂枚举

也支持具有结构体和元组变体的枚举。数据用作属性的参数。也支持在变体内部使用结构体的所有字段属性。

此外,具有命名字段的枚举变体可以扁平化。扁平化变体的行为类似于 Serde 的 untagged 模式。在扁平化变体中,将忽略变体的名称。相反,Deluxe 将尝试使用每个变体中的唯一键来确定是否指定了该变体。如果无法在两个变体之间确定唯一的、明确的键,将抛出编译错误。

#[derive(deluxe::ExtractAttributes)]
#[deluxe(attributes(my_enum))]
enum MyEnum {
    A,
    B(u64, String),
    C { id: u64, name: String },
    #[deluxe(flatten)]
    D { d: u64, name: String },
}
#[derive(MyEnum)]
#[my_enum(a)]
struct ObjectA;

#[derive(MyEnum)]
#[my_enum(b(1, "hello"))]
struct ObjectB;

#[derive(MyEnum)]
#[my_enum(c(id = 2, name = "world"))]
struct ObjectC;

// No inner parenthesis needed here due to flattening
#[derive(MyEnum)]
#[my_enum(d = 3, name = "moon")]
struct ObjectD;

存储容器

在解析过程中,Deluxe 可以存储对包含属性以进行更轻松访问的容器类型的引用。容器字段在属性解析期间被跳过。

#[derive(deluxe::ParseAttributes)]
#[deluxe(attributes(my_object))]
struct MyObject<'t> {
    id: u64,
    // Fill `container` in using the parsed type. Note this restricts the
    // derived `ParseAttributes` impl so it can only be used on `DeriveInput`.
    #[deluxe(container)]
    container: &'t syn::DeriveInput,
}

#[proc_macro_derive(MyObject, attributes(my_desc))]
pub fn derive_my_object(item: TokenStream) -> TokenStream {
    let input = syn::parse::<syn::DeriveInput>(item).unwrap();

    // `obj.container` now holds a reference to `input`
    let obj: MyObject = match deluxe::parse_attributes(&input) {
        Ok(obj) => obj,
        Err(e) => return e.into_compile_error().into()
    };

    let tokens = quote::quote! { /* ... generate some code here ... */ };

    tokens.into()
}

为了同时支持提取和解析,容器字段也可以是值类型。在这种情况下,容器将被克隆到结构体中。

依赖关系

~3MB
~61K SLoC