init
Some checks failed
ci/woodpecker/push/test Pipeline failed

This commit is contained in:
JMARyA 2025-04-28 18:53:21 +02:00
commit 6c54873ca2
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
34 changed files with 5502 additions and 0 deletions

70
owl_macro/Cargo.lock generated Normal file
View file

@ -0,0 +1,70 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "owl_macro"
version = "0.1.0"
dependencies = [
"convert_case",
"heck",
"proc-macro2",
"quote",
"syn",
]
[[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 = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
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 = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"

14
owl_macro/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "owl_macro"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1"
syn = { version = "2", features = ["full"] }
proc-macro2 = "1"
convert_case = "0.6"
heck = "0.5.0"

215
owl_macro/src/lib.rs Normal file
View file

@ -0,0 +1,215 @@
extern crate proc_macro;
use convert_case::{Case, Casing};
use heck::ToSnakeCase;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::{Parse, ParseStream}, parse_macro_input, ItemStruct, LitStr, Path, Token};
#[proc_macro_attribute]
/// Create a relation.
///
/// Syntax: `#[relation("TopName", Top, "SubName", Sub, RelationKind::*)]`
///
/// # Examples
///
/// ```ignore
/// #[relation("Lover", Person, "Loved", Person, RelationKind::Bidirectional)]
/// pub struct LoveRelation {}
/// ```
pub fn relation(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as RelationArgs);
let input_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &input_struct.ident;
let role1 = &args.role1;
let role1_fn = format_ident!("get_{}_of", &args.role2);
let ty1 = &args.ty1;
let role2 = &args.role2;
let role2_fn = format_ident!("get_{}_of", &args.role1);
let ty2 = &args.ty2;
let kind = &args.kind;
let struct_ref_name = format_ident!("{}Reference", struct_name);
let ref_name = format!(
"{}-refs",
format_ident!("{}", struct_name).to_string().to_snake_case()
);
let fns = match quote!(#kind).to_string().as_str() {
"RelationKind :: Bidirectional" => { quote! {
pub fn #role1_fn(#role1: &owl::db::Model<#ty1>, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_top(#role1, &*db.storage)
}
pub fn #role2_fn(#role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_sub(#role2, &*db.storage)
}
} }
"RelationKind :: Unidirectional" => { quote! {
pub fn #role1_fn<X: Into<owl::db::relation::IdRef<#ty1>>>(#role1: X, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_with_t(#role1, &*db.storage)
}
pub fn #role2_fn<X: Into<owl::db::relation::IdRef<#ty2>>>(#role2: X, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_with_s(#role2, &*db.storage)
}
} }
_ => { unimplemented!() }
};
let relation_name = struct_name.to_string().to_case(Case::Snake);
let expanded = quote! {
#[derive(owl::Serialize, owl::Deserialize, Default)]
#input_struct
#[derive(owl::Deserialize, owl::Serialize, Debug)]
pub struct #struct_ref_name {
pub id: Id,
pub inner: owl::db::relation::RelationReference
}
impl owl::Identifiable for #struct_ref_name {
fn id(&self) -> Id {
Id::String(format!("[{}->{}]", self.inner.top.id, self.inner.sub.id))
}
fn model_id() -> String {
#ref_name.to_string()
}
}
impl #struct_ref_name {
pub fn alter_meta<F: Fn(&mut #struct_name)>(
&mut self,
u: F
) {
self.inner.alter_meta(u);
}
}
impl owl::db::relation::RelationRef for #struct_ref_name {
fn top(&self) -> IdRef<serde_json::Value> {
self.inner.top.clone()
}
fn sub(&self) -> IdRef<serde_json::Value> {
self.inner.sub.clone()
}
fn from_ref(r: owl::db::relation::RelationReference) -> Self {
Self {
id: owl::db::id::Id::String(r.id()),
inner: r
}
}
fn update_meta(&mut self, weight: Option<f64>, meta: Option<serde_json::Value>) {
self.inner.weight = weight;
self.inner.meta = meta;
}
}
impl #struct_name {
pub fn relation() -> Relation<#ty1, #ty2, #struct_ref_name, #kind> {
owl::db::relation::Relation::new(#relation_name)
}
#fns
pub fn meta(reference: &Model<#struct_ref_name>) -> Option<Self> {
if let Some(meta) = reference.read().inner.meta.clone() {
return Some(serde_json::from_value(meta).unwrap());
}
None
}
pub fn #role1(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty1> {
unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::top(reference, &db) }
}
pub fn #role2(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty2> {
unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::sub(reference, &db) }
}
pub fn get(#role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Option<Model<#struct_ref_name>> {
unsafe { Self::relation().get(#role1, #role2, &db) }
}
pub fn add(&self, #role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, weight: Option<f64>, db: &owl::db::Database) {
let meta = serde_json::to_value(self.clone()).ok();
Self::relation().add(#role1, #role2, weight, meta, &db)
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
/// Create a model.
///
/// # Examples
///
/// ```ignore
/// #[model]
/// pub struct MyTing {
/// pub id: Id
/// }
/// ```
pub fn model(_: TokenStream, input: TokenStream) -> TokenStream {
let input_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &input_struct.ident;
let relation_name = struct_name.to_string().to_case(Case::Snake);
let expanded = quote! {
#[derive(serde::Serialize, serde::Deserialize)]
#input_struct
impl owl::db::store::Saveable for #struct_name {}
impl owl::Identifiable for #struct_name {
fn id(&self) -> owl::db::id::Id {
self.id.clone()
}
fn model_id() -> String {
#relation_name.to_string()
}
}
};
TokenStream::from(expanded)
}
struct RelationArgs {
role1: syn::Ident,
ty1: syn::Path,
role2: syn::Ident,
ty2: syn::Path,
kind: syn::Path,
}
impl Parse for RelationArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let role1: LitStr = input.parse()?;
input.parse::<Token![,]>()?;
let ty1: Path = input.parse()?;
input.parse::<Token![,]>()?;
let role2: LitStr = input.parse()?;
input.parse::<Token![,]>()?;
let ty2: Path = input.parse()?;
input.parse::<Token![,]>()?;
let kind: Path = input.parse()?;
Ok(Self {
role1: syn::Ident::new(&role1.value(), role1.span()),
ty1,
role2: syn::Ident::new(&role2.value(), role2.span()),
ty2,
kind,
})
}
}