mirror of
https://github.com/kristoferssolo/filecaster.git
synced 2025-10-21 19:00:34 +00:00
feat: add FromFile implementation
This commit is contained in:
parent
bed2a26476
commit
62f48cf3cd
169
Cargo.lock
generated
Normal file
169
Cargo.lock
generated
Normal file
@ -0,0 +1,169 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "from_file"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"merge",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e520ba58faea3487f75df198b1d079644ec226ea3b0507d002c6fa4b8cf93a"
|
||||
dependencies = [
|
||||
"merge_derive",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge_derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8f8ce6efff81cbc83caf4af0905c46e58cb46892f63ad3835e81b47eaf7968"
|
||||
dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
13
Cargo.toml
13
Cargo.toml
@ -3,4 +3,17 @@ name = "from_file"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
merge = ["dep:merge"]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro-error = "1.0"
|
||||
syn = { version = "2.0", features = ["extra-traits", "parsing"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
merge = { version = "0.2", optional = true }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
151
src/from_file.rs
Normal file
151
src/from_file.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
Attribute, Data, DeriveInput, Error, Expr, Fields, GenericParam, Generics, Meta, Result,
|
||||
parse_quote,
|
||||
};
|
||||
|
||||
const WITH_MERGE: bool = cfg!(feature = "merge");
|
||||
|
||||
pub fn impl_from_file(input: &DeriveInput) -> Result<TokenStream> {
|
||||
let name = &input.ident;
|
||||
let vis = &input.vis;
|
||||
let generics = add_trait_bouts(input.generics.clone());
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let file_ident = format_ident!("{name}File");
|
||||
|
||||
let fields = match &input.data {
|
||||
Data::Struct(ds) => match &ds.fields {
|
||||
Fields::Named(fields) => &fields.named,
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
&input.ident,
|
||||
"FromFile can only be derived for structs with named fields",
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
&input.ident,
|
||||
"FromFile can only be derived for structs",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut field_assignments = Vec::new();
|
||||
let mut file_fields = Vec::new();
|
||||
let mut default_bounds = Vec::new();
|
||||
|
||||
for field in fields {
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
let ty = &field.ty;
|
||||
|
||||
let mut default_expr = None;
|
||||
for attr in &field.attrs {
|
||||
if let Some(expr) = parse_default_attr(attr)? {
|
||||
default_expr = Some(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let field_attrs = if WITH_MERGE {
|
||||
quote! {
|
||||
#[merge(strategy = merge::option::overwrite_none)]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
file_fields.push(quote! {
|
||||
#field_attrs
|
||||
pub #ident: Option<#ty>
|
||||
});
|
||||
|
||||
if let Some(expr) = default_expr {
|
||||
field_assignments.push(quote! {
|
||||
#ident: file.#ident.unwrap_or_else(|| #expr)
|
||||
});
|
||||
} else {
|
||||
default_bounds.push(quote! { #ty: Default });
|
||||
field_assignments.push(quote! {
|
||||
#ident: file.#ident.unwrap_or_default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let where_clause = if default_bounds.is_empty() {
|
||||
where_clause.cloned()
|
||||
} else {
|
||||
let mut where_clause = where_clause.cloned();
|
||||
if let Some(wc) = &mut where_clause {
|
||||
wc.predicates
|
||||
.extend(default_bounds.into_iter().map(|bound| parse_quote!(#bound)));
|
||||
} else {
|
||||
where_clause = Some(parse_quote!(where #(#default_bounds),*));
|
||||
}
|
||||
where_clause
|
||||
};
|
||||
|
||||
// Conditionally include Merge derive based on feature
|
||||
let derive_clause = if WITH_MERGE {
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize, merge::Merge)]
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#derive_clause
|
||||
#vis struct #file_ident {
|
||||
#(#file_fields),*
|
||||
}
|
||||
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
pub fn from_file(file: Option<#file_ident #ty_generics>) -> Self {
|
||||
let file = file.unwrap_or_default();
|
||||
Self {
|
||||
#(#field_assignments),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics From<Option<#file_ident #ty_generics>> for #name #ty_generics #where_clause {
|
||||
fn from(value: Option<#file_ident #ty_generics>) -> Self {
|
||||
Self::from_file(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
fn add_trait_bouts(mut generics: Generics) -> Generics {
|
||||
for param in &mut generics.params {
|
||||
if let GenericParam::Type(type_param) = param {
|
||||
type_param.bounds.push(parse_quote!(Default));
|
||||
}
|
||||
}
|
||||
generics
|
||||
}
|
||||
|
||||
fn parse_default_attr(attr: &Attribute) -> Result<Option<Expr>> {
|
||||
if !attr.path().is_ident("default") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let meta = attr.parse_args::<Meta>()?;
|
||||
let name_value = match meta {
|
||||
Meta::NameValue(nv) => nv,
|
||||
_ => return Err(Error::new_spanned(attr, "Expected #[default = \"value\"]")),
|
||||
};
|
||||
|
||||
match name_value.value {
|
||||
Expr::Lit(expr_lit) => Ok(Some(Expr::Lit(expr_lit))),
|
||||
_ => Err(Error::new_spanned(
|
||||
&name_value.value,
|
||||
"Default value must be a literal",
|
||||
)),
|
||||
}
|
||||
}
|
||||
22
src/lib.rs
22
src/lib.rs
@ -1,14 +1,16 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
mod from_file;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use from_file::impl_from_file;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_derive(FromFile, attributes(from_file))]
|
||||
pub fn derive_from_file(input: TokenStream) -> TokenStream {
|
||||
let inp = parse_macro_input!(input as DeriveInput);
|
||||
match impl_from_file(&inp) {
|
||||
Ok(ts) => ts.into(),
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user