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"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
merge = ["dep:merge"]
|
||||||
|
|
||||||
[dependencies]
|
[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 {
|
mod from_file;
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
use from_file::impl_from_file;
|
||||||
mod tests {
|
use proc_macro::TokenStream;
|
||||||
use super::*;
|
use proc_macro_error::proc_macro_error;
|
||||||
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
#[test]
|
#[proc_macro_error]
|
||||||
fn it_works() {
|
#[proc_macro_derive(FromFile, attributes(from_file))]
|
||||||
let result = add(2, 2);
|
pub fn derive_from_file(input: TokenStream) -> TokenStream {
|
||||||
assert_eq!(result, 4);
|
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