refactor(workspace): make a workspace

This commit is contained in:
Kristofers Solo 2025-07-15 14:33:51 +03:00
parent 6a973db003
commit 337491b37f
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
10 changed files with 106 additions and 59 deletions

15
Cargo.lock generated
View File

@ -51,6 +51,18 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "filecaster" name = "filecaster"
version = "0.1.0" version = "0.1.0"
dependencies = [
"filecaster-derive",
"merge",
"serde",
"serde_json",
"tempfile",
"toml",
]
[[package]]
name = "filecaster-derive"
version = "0.1.0"
dependencies = [ dependencies = [
"claims", "claims",
"merge", "merge",
@ -58,10 +70,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"serde_json",
"syn", "syn",
"tempfile",
"toml",
] ]
[[package]] [[package]]

View File

@ -1,40 +1,17 @@
[package] [workspace]
name = "filecaster" resolver = "2"
version = "0.1.0" members = ["filecaster", "filecaster-derive"]
edition = "2024"
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
description = "Procedural macro to derive configuration from files, with optional merging capabilities."
repository = "https://github.com/kristoferssolo/filecaster"
documentation = "https://docs.rs/filecaster"
homepage = "https://github.com/kristoferssolo/filecaster"
license = "MIT OR Apache-2.0"
keywords = ["proc-macro", "derive", "configuration", "file-parsing"]
categories = ["rust-patterns", "parsing", "config"]
exclude = ["/.github", "/.gitignore", "/tests", "*.png", "*.md"]
readme = "README.md"
[features] [workspace.dependencies]
default = []
merge = ["dep:merge"]
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
proc-macro-error2 = "2.0"
syn = { version = "2.0", features = ["extra-traits", "parsing"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
merge = { version = "0.2", optional = true } merge = "0.2.0"
# dev-dependencies
[dev-dependencies]
claims = "0.8" claims = "0.8"
serde_json = "1.0" serde_json = "1.0"
tempfile = "3.20" tempfile = "3.10"
toml = "0.9" toml = "0.9"
[lib] [workspace.lints.clippy]
proc-macro = true
[lints.clippy]
pedantic = "warn" pedantic = "warn"
nursery = "warn" nursery = "warn"
unwrap_used = "warn" unwrap_used = "warn"

View File

@ -0,0 +1,22 @@
[package]
name = "filecaster-derive"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[features]
default = []
merge = ["dep:merge"]
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
proc-macro-error2 = "2.0"
syn = { version = "2.0", features = ["extra-traits", "parsing"] }
serde.workspace = true
merge = { workspace = true, optional = true }
[dev-dependencies]
claims.workspace = true

View File

@ -1,3 +1,5 @@
use std::path::PathBuf;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{ use syn::{
@ -78,6 +80,15 @@ fn is_from_file_struct(ty: &Type) -> bool {
false false
} }
/// Extract the last identifier from a [`TypePath`].
fn last_path_ident(ty: &Type) -> Result<String> {
if let Type::Path(TypePath { qself: None, path }) = ty {
return Ok(path.segments.last().unwrap().ident.to_string());
}
Err(Error::new_spanned(ty, "expected a plain struct name"))
}
/// Build the shadow field + assignment for one original field /// Build the shadow field + assignment for one original field
fn build_file_field(field: &Field) -> Result<(TokenStream, TokenStream, Option<TokenStream>)> { fn build_file_field(field: &Field) -> Result<(TokenStream, TokenStream, Option<TokenStream>)> {
let ident = field let ident = field
@ -87,38 +98,23 @@ fn build_file_field(field: &Field) -> Result<(TokenStream, TokenStream, Option<T
let ty = &field.ty; let ty = &field.ty;
let field_attrs = if WITH_MERGE { let field_attrs = if WITH_MERGE {
quote! { quote! { #[merge(strategy = merge::option::overwrite_none)] }
#[merge(strategy = merge::option::overwrite_none)]
}
} else { } else {
quote! {} quote! {}
}; };
if is_from_file_struct(ty) { // Nested struct -> delegate to its own `FromFile` impl
// Nested FromFile struct
let field_decl = quote! {
#field_attrs
pub #ident: Option<#ty>
};
let assign = quote! {
#ident: <#ty>::from_file(file.#ident)
};
return Ok((field_decl, assign, None));
}
// Primitive / leaf field
let default_expr = parse_from_file_default_attr(&field.attrs)?;
let field_decl = quote! { let field_decl = quote! {
#field_attrs #field_attrs
pub #ident: Option<#ty> pub #ident: Option<#ty>
}; };
let assign = default_expr.map_or_else( let assign = quote! {
|| quote! { #ident: file.#ident.unwrap_or_default() }, #ident: <#ty as filecaster::FromFile>::from_file(file.#ident)
|expr| quote! { #ident: file.#ident.unwrap_or_else(|| #expr) }, };
);
let default = quote! { #ty: Default };
Ok((field_decl, assign, Some(default))) let default_bound = Some(quote! { #ty: Default });
Ok((field_decl, assign, default_bound))
} }
/// Process all fields /// Process all fields

View File

@ -76,10 +76,9 @@
//! //!
//! MIT OR Apache-2.0 //! MIT OR Apache-2.0
mod derive_from_file;
mod from_file; mod from_file;
pub(crate) use derive_from_file::impl_from_file; pub(crate) use from_file::impl_from_file;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro_error2::proc_macro_error; use proc_macro_error2::proc_macro_error;
use syn::{DeriveInput, parse_macro_input}; use syn::{DeriveInput, parse_macro_input};

29
filecaster/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "filecaster"
version = "0.1.0"
edition = "2024"
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
description = "Procedural macro to derive configuration from files, with optional merging capabilities."
repository = "https://github.com/kristoferssolo/filecaster"
documentation = "https://docs.rs/filecaster"
homepage = "https://github.com/kristoferssolo/filecaster"
license = "MIT OR Apache-2.0"
keywords = ["proc-macro", "derive", "configuration", "file-parsing"]
categories = ["rust-patterns", "parsing", "config"]
exclude = ["/.github", "/.gitignore", "/tests", "*.png", "*.md"]
readme = "README.md"
[features]
default = ["derive"]
derive = ["dep:filecaster-derive"]
merge = ["dep:merge", "filecaster-derive/merge"]
[dependencies]
filecaster-derive = { path = "../filecaster-derive", optional = true }
serde.workspace = true
merge = { workspace = true, optional = true }
[dev-dependencies]
serde_json.workspace = true
tempfile.workspace = true
toml.workspace = true

View File

@ -1,3 +1,4 @@
pub use filecaster_derive::FromFile;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Marker for types that can be built from an `Option<Shadow>` produced by the macro. /// Marker for types that can be built from an `Option<Shadow>` produced by the macro.

View File

@ -0,0 +1,14 @@
use filecaster::FromFile;
#[derive(Debug, Default, Clone, PartialEq, FromFile)]
pub struct Coordinates {
x: i32,
y: i32,
}
#[derive(Debug, Clone, PartialEq, FromFile)]
pub struct Parent {
#[from_file(default = "Foo")]
name: String,
coordinates: Coordinates,
}