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]]
name = "filecaster"
version = "0.1.0"
dependencies = [
"filecaster-derive",
"merge",
"serde",
"serde_json",
"tempfile",
"toml",
]
[[package]]
name = "filecaster-derive"
version = "0.1.0"
dependencies = [
"claims",
"merge",
@ -58,10 +70,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn",
"tempfile",
"toml",
]
[[package]]

View File

@ -1,40 +1,17 @@
[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"
[workspace]
resolver = "2"
members = ["filecaster", "filecaster-derive"]
[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"] }
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
merge = { version = "0.2", optional = true }
[dev-dependencies]
merge = "0.2.0"
# dev-dependencies
claims = "0.8"
serde_json = "1.0"
tempfile = "3.20"
tempfile = "3.10"
toml = "0.9"
[lib]
proc-macro = true
[lints.clippy]
[workspace.lints.clippy]
pedantic = "warn"
nursery = "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 quote::{format_ident, quote};
use syn::{
@ -78,6 +80,15 @@ fn is_from_file_struct(ty: &Type) -> bool {
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
fn build_file_field(field: &Field) -> Result<(TokenStream, TokenStream, Option<TokenStream>)> {
let ident = field
@ -87,38 +98,23 @@ fn build_file_field(field: &Field) -> Result<(TokenStream, TokenStream, Option<T
let ty = &field.ty;
let field_attrs = if WITH_MERGE {
quote! {
#[merge(strategy = merge::option::overwrite_none)]
}
quote! { #[merge(strategy = merge::option::overwrite_none)] }
} else {
quote! {}
};
if is_from_file_struct(ty) {
// 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)?;
// Nested struct -> delegate to its own `FromFile` impl
let field_decl = quote! {
#field_attrs
pub #ident: Option<#ty>
};
let assign = default_expr.map_or_else(
|| quote! { #ident: file.#ident.unwrap_or_default() },
|expr| quote! { #ident: file.#ident.unwrap_or_else(|| #expr) },
);
let default = quote! { #ty: Default };
let assign = quote! {
#ident: <#ty as filecaster::FromFile>::from_file(file.#ident)
};
Ok((field_decl, assign, Some(default)))
let default_bound = Some(quote! { #ty: Default });
Ok((field_decl, assign, default_bound))
}
/// Process all fields

View File

@ -76,10 +76,9 @@
//!
//! MIT OR Apache-2.0
mod derive_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_error2::proc_macro_error;
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};
/// 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,
}