Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ license = "Apache-2.0"
name = "scaffolding-core"
readme = "README.md"
repository = "https://github.com/dsietz/scaffolding-core"
version = "0.0.3"
version = "0.1.0"

[badges]
maintenance = {status = "experimental"}
Expand All @@ -26,7 +26,7 @@ path = "src/lib.rs"

[dependencies]
chrono = "0.4.35"
scaffolding-macros = {path = "./scaffolding-macros", version = "0.0.3"}
scaffolding-macros = {path = "./scaffolding-macros", version = "0.1.0"}

[dependencies.uuid]
features = ["v4"]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ For software development teams who appreciate a kick-start to their object orien

## What's New

This crate is in an `Initial` release phase and is only intended as a PoC.
!!! This crate is in an `Initial` release phase and is only intended as a PoC. !!!

**0.0.1**
+ [Initial Release](https://github.com/dsietz/scaffolding-core/issues/1)
**0.1.0**
+ [Added Metadata feature](https://github.com/dsietz/scaffolding-core/issues/2)

## How to Contribute

Expand Down
8 changes: 1 addition & 7 deletions scaffolding-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scaffolding-macros"
version = "0.0.3"
version = "0.1.0"
authors = ["dsietz <davidsietz@yahoo.com>"]
edition = "2021"
readme = "README.md"
Expand Down Expand Up @@ -29,11 +29,5 @@ proc-macro = true
quote = "1.0.35"
syn = {version = "2.0.53", features = ["full", "extra-traits"]}

[dependencies.uuid]
version = "1.5.0"
features = [
"v4",
]

[dev-dependencies]
serde_yaml = "0.9.27"
166 changes: 136 additions & 30 deletions scaffolding-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
use proc_macro::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Parser, Result};
use syn::{parse, parse_macro_input, punctuated::Punctuated, ItemStruct, LitStr, Token};
use syn::Expr::Struct;
use syn::FieldValue;
use syn::Member;
use syn::{parse_macro_input, parse_quote, punctuated::Punctuated, ItemStruct, LitStr, Token};

static METADATA: &str = "metadata";
static CORE_ATTRS: [&str; 5] = [
"id",
"created_dtm",
"modified_dtm",
"inactive_dtm",
"expired_dtm",
];

///
/// Modifying a struct
///
#[proc_macro_attribute]
pub fn scaffolding_entity(args: TokenStream, input: TokenStream) -> TokenStream {
// println!("attr: \"{}\"", args.to_string());

let mut item_struct = parse_macro_input!(input as ItemStruct);
pub fn scaffolding_struct(args: TokenStream, input: TokenStream) -> TokenStream {
let mut item_struct: ItemStruct = parse_macro_input!(input as ItemStruct);
let attrs = parse_macro_input!(args as Args)
.vars
.iter()
.map(|a| a.value())
.collect::<Vec<_>>();
// println!("metadata: {:?}", attrs.contains(&"metadata".to_string()));

if let syn::Fields::Named(ref mut fields) = item_struct.fields {
// The unique identifier of the object
Expand Down Expand Up @@ -49,6 +59,7 @@ pub fn scaffolding_entity(args: TokenStream, input: TokenStream) -> TokenStream
.unwrap(),
);

// optional attributes
match attrs.contains(&METADATA.to_string()) {
true => {
// The metadata handler
Expand All @@ -68,26 +79,6 @@ pub fn scaffolding_entity(args: TokenStream, input: TokenStream) -> TokenStream
.into();
}

// #[proc_macro_attribute]
// pub fn scaffolding_metadata(args: TokenStream, input: TokenStream) -> TokenStream {
// let mut item_struct = parse_macro_input!(input as ItemStruct);
// let _ = parse_macro_input!(args as parse::Nothing);

// if let syn::Fields::Named(ref mut fields) = item_struct.fields {
// // The unique identifier of the object
// fields.named.push(
// syn::Field::parse_named
// .parse2(quote! { metadata: BTreeMap::new() })
// .unwrap(),
// );
// }

// return quote! {
// #item_struct
// }
// .into();
// }

#[derive(Debug)]
struct Args {
pub vars: Vec<LitStr>,
Expand All @@ -102,6 +93,9 @@ impl Parse for Args {
}
}

///
/// Implementing the Traits
///
#[proc_macro_derive(Scaffolding)]
pub fn scaffolding_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
Expand All @@ -116,11 +110,123 @@ fn impl_scaffolding(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl Scaffolding for #name {
// fn hello(&self) {
// println!("Hello, My name is {}!", stringify!(#name));
// println!("My id is {}", self.id);
// fn set_id(mut &self, value: String) {
// self.id = value;
// }
}
};
gen.into()
}

#[proc_macro_attribute]
pub fn scaffolding_fn(args: TokenStream, input: TokenStream) -> TokenStream {
let mut item: syn::Item = syn::parse(input).unwrap();
let fn_item = match &mut item {
syn::Item::Fn(fn_item) => fn_item,
_ => panic!("expected fn"),
};
let attrs = parse_macro_input!(args as Args)
.vars
.iter()
.map(|a| a.value())
.collect::<Vec<_>>();

// get the name of the method
let name = &fn_item.sig.ident.to_string();

match name.as_ref() {
"new" => {
print!("Modifying function {} ...", name);
// find the line that sets the id attribute
for s in 0..fn_item.block.stmts.len() {
match &mut fn_item.block.stmts[s] {
syn::Stmt::Expr(expr, None) => match expr {
Struct(expr_struct) => {
// println!("Found a Struct!");
let mut modify_attr_list = vec![
"id",
"created_dtm",
"modified_dtm",
"inactive_dtm",
"expired_dtm",
];

match attrs.contains(&METADATA.to_string()) {
true => {
modify_attr_list.push(&METADATA);
}
_ => {}
}
// first determine if the attributes already exist
for f in 0..expr_struct.fields.len() {
match &expr_struct.fields[f].member {
Member::Named(mbr) => {
match CORE_ATTRS.contains(&mbr.to_string().as_str()) {
true => {
// core attribute already set, so don't need to add it
// println!("Ignoring attribute {}", mbr.to_string());
modify_attr_list
.retain_mut(|a| *a != mbr.to_string().as_str());
}
false => {}
}
}
_ => {}
}
}

// then, add the missing attributes
for attr in modify_attr_list.iter() {
// println!("Adding attribute {}", attr);
match *attr {
"id" => {
let line: FieldValue = parse_quote! {id: defaults::id()};
expr_struct.fields.insert(0, line);
}
"created_dtm" => {
let line: FieldValue =
parse_quote! {created_dtm: defaults::now()};
expr_struct.fields.insert(0, line);
}
"modified_dtm" => {
let line: FieldValue =
parse_quote! {modified_dtm: defaults::now()};
expr_struct.fields.insert(0, line);
}
"inactive_dtm" => {
let line: FieldValue = parse_quote! {inactive_dtm: defaults::add_days(defaults::now(), 90)};
expr_struct.fields.insert(0, line);
}
"expired_dtm" => {
let line: FieldValue = parse_quote! {expired_dtm: defaults::add_years(defaults::now(), 3)};
expr_struct.fields.insert(0, line);
}
"metadata" => {
let line: FieldValue =
parse_quote! {metadata: BTreeMap::new()};
expr_struct.fields.insert(0, line);
}
_ => {}
}
}
}
_ => {
// println!("Not an Struct!");
}
},
_ => {
// println!("Not an Expr!");
}
}
}
}
_ => {
print!(
"Function {} is unsupported. Nothing to add to function ",
name
);
}
}

item.into_token_stream().into()
}
38 changes: 35 additions & 3 deletions scaffolding-macros/tests/macro_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,58 @@ mod tests {
use scaffolding_macros::*;
use std::collections::BTreeMap;

#[scaffolding_entity("metadata")]
#[scaffolding_struct("metadata")]
#[derive(Debug, Clone)]
struct MyEntity {
a: String,
b: bool,
}

impl MyEntity {
#[scaffolding_fn("metadata")]
pub fn new(param1: String, param2: bool) -> Self {
let x = format!("a_{}", param1);
Self {
id: "lorem ipsum".to_string(),
created_dtm: 1711281509,
modified_dtm: 1711281509,
inactive_dtm: 1711281509,
expired_dtm: 1711281509,
a: x,
b: param2,
}
}

#[scaffolding_fn]
pub fn hello(&self) -> String {
self.a.clone()
}
}

#[test]
fn test_core_struct() {
let entity = MyEntity {
let mut entity = MyEntity {
id: "lorem ipsum".to_string(),
created_dtm: 1711281509,
modified_dtm: 1711281509,
inactive_dtm: 1711281509,
expired_dtm: 1711281509,
metadata: BTreeMap::new(),
a: "hello".to_string(),
b: true,
};
println!("struct is {:?}", entity);

assert_eq!(entity.id, "lorem ipsum");
assert_eq!(entity.b, true);
assert_eq!(entity.hello(), "hello");
}

#[test]
fn test_core_impl() {
let mut entity = MyEntity::new("hello".to_string(), true);

assert_eq!(entity.id, "lorem ipsum");
assert_eq!(entity.b, true);
assert_eq!(entity.hello(), "a_hello");
}
}
40 changes: 27 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,29 @@
//! This can be done by calling one of the `Scaffolding` trait's many datetime related methods, (e.g.: `never()`)
//!
//! ### Example
//!
//! Add Scaffolding to a `struct` and `impl` using macros and defaults
//! ```rust
//! extern crate scaffolding_core;
//!
//! use scaffolding_core::{defaults, Scaffolding};
//! use scaffolding_core::{defaults};
//! use scaffolding_macros::*;
//! // Required for scaffolding metadata functionality
//! use std::collections::BTreeMap;
//!
//! #[scaffolding_entity]
//! #[derive(Debug, Clone, Scaffolding)]
//! #[scaffolding_struct("metadata")]
//! #[derive(Debug, Clone)]
//! struct MyEntity {
//! b: bool,
//! a: bool,
//! b: String,
//! }
//!
//! impl MyEntity {
//! #[scaffolding_fn("metadata")]
//! fn new(arg: bool) -> Self {
//! let msg = format!("You said it is {}", arg);
//! Self {
//! id: defaults::id(),
//! created_dtm: defaults::now(),
//! modified_dtm: defaults::now(),
//! inactive_dtm: defaults::add_months(defaults::now(), 12),
//! expired_dtm: defaults::add_years(defaults::now(), 3),
//! b: arg,
//! a: arg,
//! b: msg
//! }
//! }
//!
Expand All @@ -60,11 +61,24 @@
//! }
//! }
//!
//! let entity = MyEntity::new(true);
//! let mut entity = MyEntity::new(true);
//! println!("{:?}", entity);
//!
//! // scaffolding attributes
//! assert_eq!(entity.id.len(), "54324f57-9e6b-4142-b68d-1d4c86572d0a".len());
//! assert_eq!(entity.created_dtm, defaults::now());
//! assert_eq!(entity.modified_dtm, defaults::now());
//! // becomes inactive in 90 days
//! assert_eq!(entity.inactive_dtm, defaults::add_days(defaults::now(), 90));
//! // expires in 3 years
//! assert_eq!(entity.expired_dtm, defaults::add_years(defaults::now(), 3));
//! // use the metadata functionality
//! entity.metadata.insert("field_1".to_string(), "myvalue".to_string());
//! assert_eq!(entity.metadata.len(), 1);
//!
//! // extended attributes
//! assert_eq!(entity.b, true);
//! assert_eq!(entity.a, true);
//! assert_eq!(entity.b, "You said it is true");
//!
//! // extended behavior
//! assert_eq!(entity.my_func(), "my function");
Expand Down
Loading