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 过程宏
3,266 每月下载量
在 46 个crate中使用 (直接使用18个)
245KB
4.5K SLoC
Deluxe —
Rust过程宏属性解析器。
摘要
这个crate提供了一个类似于C#属性设计的属性解析器。它有一个类似于serde的接口。属性被写成普通的Rust结构体或枚举,然后自动生成解析器。它们可以包含任意表达式,并且可以使用扁平化机制从其他属性继承。
这个crate中的解析器直接使用syn
解析标记流。因此,大多数内置的Rust类型和syn
类型可以直接用作字段。
详细信息
这个crate的功能围绕三个特质及其各自的 derive 宏展开
-
从包含一个
syn::Attribute
列表的对象中提取属性,并将它们解析为Rust类型。应该为将从一组匹配的属性直接解析出来的顶层结构体实现。 -
从任何包含一个
syn::Attribute
列表的对象中解析Rust类型。如果一组匹配的属性可能在这个类型和其他类型之间共享,则应使用。 -
从
syn::parse::ParseStream
中解析Rust类型。应为任何可以嵌套在属性内部的类型实现。
使用此crate中的派生宏的基本用法是简单地派生一个(或几个)这些特质,然后调用extract_attributes
或parse_attributes
。对于更高级的功能,支持在结构体、枚举、变体和字段上使用几个#[deluxe(...)]
属性。请参见下面的示例和每个派生宏的文档,以获得支持的属性的完整描述。
默认支持的字段类型列表可以在提供的ParseMetaItem
实现列表中查看。对于更复杂的用法,可以提供这些特质的手动实现。请参阅各个特质的文档,以获取有关如何手动实现自己的解析器的更多详细信息。
相关crate
Deluxe从darlingcrate汲取灵感,但对其提供了一些增强。Darling围绕预解析的syn::Meta
对象构建,因此仅限于元语法。Deluxe直接从属性的TokenStream
对象中解析类型,因此能够使用任何解析为有效令牌树的语法。Deluxe也不提供用于解析特殊syn
对象(如DeriveInput
和Field
)的额外特质。相反,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中包含的parse
和parse2
函数也可以用作属性宏的简单辅助工具
#[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");
字段属性
支持alias
、default
、rename
和skip
属性,其行为与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
时,可以使用来自 A
和 B
的字段。
#[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