Skip to content

Commit

Permalink
builder patterns :D
Browse files Browse the repository at this point in the history
  • Loading branch information
Billy-Sheppard committed Sep 25, 2024
1 parent 1f2ec50 commit 503091e
Show file tree
Hide file tree
Showing 19 changed files with 3,801 additions and 1,119 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules/
# Added by cargo

target
build_logs.txt
27 changes: 19 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ js-sys = "0.3.64"

[workspace]
members = ["examples"]

[build-dependencies]
heck = "*"
itertools = "*"
quote = "*"
proc-macro2 = "*"
syn = { features = ["parsing", "full"], version = "*" }
270 changes: 270 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
#![allow(clippy::ptr_arg, clippy::collapsible_if)]

use std::{fmt::Debug, io::Write, ops::Not};

use heck::ToSnakeCase;
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{Field, ItemStruct};

fn main() {
std::fs::write("build_logs.txt", "").unwrap();

let objects = std::fs::read_to_string("./src/objects/chart_objects.rs").unwrap();

let symbols = syn::parse_file(&objects).unwrap();

let use_ = quote! {
#![allow(clippy::default_constructed_unit_structs)]
'\n'
use {
super::{
chart_objects::*,
helper_objects::*
},
crate::{Annotation, FnWithArgs},
std::collections::*
};
};

let impl_blocks = symbols
.items
.into_iter()
.filter_map(|item| match item {
syn::Item::Struct(item_struct) => Some(item_struct),
_ => None,
})
.map(|s| {
let s_name = &s.ident;

let generics = &s.generics;

let type_param = s
.generics
.type_params()
.map(|t| t.ident.clone())
.collect_vec();

let type_params = if type_param.is_empty().not() {
quote! { < #(#type_param),* > }
} else {
quote! {}
};

let new = quote! {
pub fn new() -> Self {
Self::default()
}
'\n'
};

let methods = s.clone()
.fields
.into_iter()
.map(|field| {
let name = &field.ident;
let get_name = syn::Ident::new(
&format!("get_{}", name.clone().unwrap().to_string().to_snake_case()),
proc_macro2::Span::call_site(),
);
let set_name = ident(
&name.clone().unwrap().to_string().to_snake_case(),
).unwrap();
let type_ = field.ty.clone();

let (type_segments, type_seperators) = type_segments(&type_);

let default_set_fn = if type_segments[0].0 == "Option" {
let mut seps = type_seperators.iter().skip(1).rev().skip(1).rev();
let mut type_vec = Vec::new();
for (seg, _) in type_segments.iter().skip(1) {
type_vec.push(seg);
if let Some(s) = seps.next() {
type_vec.push(s);
}
}
append_log(type_vec.clone().into_iter().join(""));
let type_ = ident(&type_vec.into_iter().join("")).unwrap().to_token_stream();

quote!{
pub fn #set_name(mut self, value: impl Into<#type_>) -> #s_name #type_params {
self.#name = Some(value.into());
self
}
}
}
else {
quote!{
pub fn #set_name(mut self, value: impl Into<#type_>) -> #s_name #type_params {
self.#name = value.into();
self
}
}
};

let override_fn = override_set_fn(&s, &field);

let set_fn = override_fn.unwrap_or(default_set_fn);

quote! {
pub fn #get_name(&mut self) -> &mut #type_ {
&mut self.#name
}
#set_fn
'\n'
}
.to_token_stream()
})
.collect_vec();

quote! {
'\n'
impl #generics #s_name #type_params {
#new

#(#methods)*
}
}
})
.collect();

let code = [Vec::from([use_]), impl_blocks]
.concat()
.into_iter()
.map(|token| token.to_string())
.collect_vec()
.join("\n")
.replace("'\\n'", "\n\n");

let mut formatted_code = std::process::Command::new("rustfmt")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap();
formatted_code
.stdin
.take()
.map(|mut s| s.write_all(code.as_bytes()));

std::fs::write(
"./src/objects/methods.rs",
String::from_utf8_lossy(&formatted_code.wait_with_output().unwrap().stdout).to_string(),
)
.unwrap();
}

fn override_set_fn(s: &ItemStruct, field: &Field) -> Option<TokenStream> {
let s_name = &s.ident;
let name = &field.ident;
let type_ = &field.ty;
let type_param = s
.generics
.type_params()
.map(|t| t.ident.clone())
.collect_vec();
let type_params = if type_param.is_empty().not() {
quote! { < #(#type_param),* > }
} else {
quote! {}
};
let set_name = ident(&name.clone().unwrap().to_string().to_snake_case()).unwrap();

let (type_segments, _type_seperators) = type_segments(type_);

// append_log(type_.to_token_stream().to_string());
// append_log(
// type_segments
// .clone()
// .into_iter()
// .map(|(seg, _)| seg)
// .collect_vec()
// .join(" | "),
// );

if let Some((seg, _type_)) = type_segments.first() {
// for Vec<T>
if seg == "Vec" {
let inner_t = &type_segments[1].1;
let iterator_set_fn = quote! {
pub fn #set_name<T: Into<#inner_t>>(mut self, value: impl IntoIterator<Item = T>) -> #s_name #type_params {
self.#name = value.into_iter().map(Into::into).collect();
self
}
};
return Some(iterator_set_fn);
}

if seg == "Option" {
// for Option<HashMap<T, U>>
if type_segments[1].0 == "HashMap" {
let inner_t = &type_segments[2].1;
let inner_u = &type_segments[3].1;
let iterator_set_fn = quote! {
pub fn #set_name<T: Into<#inner_t>, U: IntoIterator<Item = (T, #inner_u)>>(mut self, value: U) -> #s_name #type_params {
self.#name = Some(value.into_iter().map(|(k, v)| (k.into(), v)).collect());
self
}
};
return Some(iterator_set_fn);
}
}
}

None
}

fn type_segments(type_: &syn::Type) -> (Vec<(String, syn::Type)>, Vec<String>) {
let type_segments = type_.to_token_stream().to_string();
append_log(&type_segments);
let segs = type_segments
.split("<")
.flat_map(|seg| seg.split(","))
.flat_map(|seg| seg.split(">"))
.map(|seg| seg.trim().to_string())
.filter_map(|seg| Some((seg.clone(), ident(seg.trim()).ok()?)))
.collect_vec();

let mut chars = type_segments
.chars()
.filter(|c| [':', '<', '>', ','].contains(c))
.collect_vec();
if chars.len() % 2 != 0 {
chars.push(' ');
}
let seps = chars
.into_iter()
.tuples()
.fold(Vec::new(), |mut acc, (c1, c2)| {
if c1 == ':' && c2 == ':' {
acc.push("::".to_string());
}

if ['<', '>', ','].contains(&c1) {
acc.push(c1.to_string());
}
if ['<', '>', ','].contains(&c2) {
acc.push(c2.to_string());
}

acc
});

append_log(&seps);

(segs, seps)
}

fn ident(i: &str) -> Result<syn::Type, syn::Error> {
syn::parse_str(i)
}

fn append_log(s: impl Debug) {
let mut file = std::fs::OpenOptions::new()
.append(true)
.open("build_logs.txt")
.unwrap();

file.write_all(format!("{:#?}", s).as_bytes()).unwrap();
file.write_all(b"\n").unwrap();
}
Loading

0 comments on commit 503091e

Please sign in to comment.