From 75f238cb4dc77fb8c02f11dd9681ad7b0126d068 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Sat, 3 May 2025 01:02:44 +0200 Subject: [PATCH] Initial commit: Rhai model builder with code generation --- .gitignore | 18 +++++ Cargo.toml | 15 ++++ README.md | 83 +++++++++++++++++++++ build.rs | 0 specs.md | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 123 +++++++++++++++++++++++++++++++ src/models.rs | 19 +++++ 7 files changed, 457 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 build.rs create mode 100644 specs.md create mode 100644 src/main.rs create mode 100644 src/models.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccd8e7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Generated by Cargo +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo + +# Generated files +/src/gen/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..384bea4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rhai_meeting_builder" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +rhai = "1" +quote = "1" +syn = { version = "2", features = ["full"] } + +[build-dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddb8a0f --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Generated Model Code + +This README documents the automatically generated code for the data models. + +## Table of Contents + +- [Meeting](#meeting) +- [Task](#task) + +## Meeting + +### Fields +- `id`: String +- `title`: String +- `description`: String +- `participants`: Vec +- `start`: String + +### Builder Methods +- `with_title(value)`: Sets the title field (String) +- `with_description(value)`: Sets the description field (String) +- `with_participants(value)`: Sets the participants field (Vec) +- `with_start(value)`: Sets the start field (String) +- `meeting()`: Creates a new Meeting builder +- `get_fields()`: Returns a Map of all set fields + +### Database Operations +- `create_meeting_typed(fields)`: Creates a new Meeting in the database +- `find_meeting_by_id_typed(id)`: Finds a Meeting by ID +- `update_meeting_typed(id, fields)`: Updates a Meeting in the database +- `delete_meeting_typed(id)`: Deletes a Meeting from the database +- `list_meetings_typed()`: Lists all Meetings in the database +- `filter_meetings_typed(query)`: Filters Meetings based on query parameters +- `find_meeting_typed(query)`: Finds a specific Meeting based on query parameters + +### Rhai Integration +- `create_meeting(fields)`: Creates a new Meeting in the database +- `find_meeting_by_id(id)`: Finds a Meeting by ID +- `update_meeting(id, fields)`: Updates a Meeting in the database +- `delete_meeting(id)`: Deletes a Meeting from the database +- `list_meetings()`: Lists all Meetings in the database +- `filter_meetings(query)`: Filters Meetings based on query parameters +- `find_meeting(query)`: Finds a specific Meeting based on query parameters + +## Task + +### Fields +- `id`: String +- `title`: String +- `description`: String +- `assignee`: String +- `due_date`: String +- `priority`: String +- `completed`: bool + +### Builder Methods +- `with_title(value)`: Sets the title field (String) +- `with_description(value)`: Sets the description field (String) +- `with_assignee(value)`: Sets the assignee field (String) +- `with_due_date(value)`: Sets the due_date field (String) +- `with_priority(value)`: Sets the priority field (String) +- `with_completed(value)`: Sets the completed field (bool) +- `task()`: Creates a new Task builder +- `get_fields()`: Returns a Map of all set fields + +### Database Operations +- `create_task_typed(fields)`: Creates a new Task in the database +- `find_task_by_id_typed(id)`: Finds a Task by ID +- `update_task_typed(id, fields)`: Updates a Task in the database +- `delete_task_typed(id)`: Deletes a Task from the database +- `list_tasks_typed()`: Lists all Tasks in the database +- `filter_tasks_typed(query)`: Filters Tasks based on query parameters +- `find_task_typed(query)`: Finds a specific Task based on query parameters + +### Rhai Integration +- `create_task(fields)`: Creates a new Task in the database +- `find_task_by_id(id)`: Finds a Task by ID +- `update_task(id, fields)`: Updates a Task in the database +- `delete_task(id)`: Deletes a Task from the database +- `list_tasks()`: Lists all Tasks in the database +- `filter_tasks(query)`: Filters Tasks based on query parameters +- `find_task(query)`: Finds a specific Task based on query parameters + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e69de29 diff --git a/specs.md b/specs.md new file mode 100644 index 0000000..730a0ce --- /dev/null +++ b/specs.md @@ -0,0 +1,199 @@ +Here's an updated full project specification including: + 1. ✅ Unified builder for both creation and filtering + 2. ✅ with_...() methods used for both creation and filtering + 3. ✅ Auto-generated CRUD + list + filter + find methods + 4. ✅ Database interface for persistence operations + 5. ✅ Auto-generated registration code + 6. ✅ 📜 Example Rhai script + 7. ✅ 🔍 build.rs should output to src/gen/meeting_builder.rs + +⸻ + +🧾 Project Spec: Rust → Rhai Builder Code Generator + +🎯 Goal + +Build a build.rs script that parses Rust structs annotated with #[builder] and generates Rhai-compatible unified builders with with_...() methods that populate a single field map used for both creation and filtering. Also generate CRUD operations that use a database interface. + +⸻ + +📥 Input Example: src/models.rs + +#[derive(Debug)] +pub struct Meeting { + pub id: String, + pub title: String, + pub description: String, + pub participants: Vec, + pub start: String, +} + +⸻ + +📤 Generated Code: src/gen/meeting_builder.rs + +✅ 1. MeetingBuilder Struct + +#[derive(Clone)] +pub struct MeetingBuilder { + pub fields: std::collections::HashMap, +} + +✅ 2. with_...() Methods (for each field) + +impl MeetingBuilder { + pub fn with_title(mut self, value: &str) -> Self { + self.fields.insert("title".into(), value.into()); + self + } + + pub fn with_participants(mut self, value: Vec) -> Self { + self.fields.insert("participants".into(), value.into()); + self + } + + pub fn get_fields(&mut self) -> rhai::Map { + self.fields.clone() + } + + // CRUD operations + pub fn create(&mut self, db: &dyn Database) -> rhai::Dynamic { + db.create("meetings", self.get_fields()) + } + + pub fn find_by_id(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic { + db.find_by_id("meetings", id) + } + + pub fn update(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic { + db.update("meetings", id, self.get_fields()) + } + + pub fn delete(&mut self, db: &dyn Database, id: &str) -> rhai::Dynamic { + db.delete("meetings", id) + } + + pub fn list(&mut self, db: &dyn Database) -> rhai::Dynamic { + db.list("meetings") + } + + pub fn filter(&mut self, db: &dyn Database) -> rhai::Dynamic { + db.filter("meetings", self.get_fields()) + } + + pub fn find(&mut self, db: &dyn Database) -> rhai::Dynamic { + db.find("meetings", self.get_fields()) + } +} + +✅ 3. Database Interface + +pub trait Database { + fn create(&self, collection: &str, fields: rhai::Map) -> rhai::Dynamic; + fn find_by_id(&self, collection: &str, id: &str) -> rhai::Dynamic; + fn update(&self, collection: &str, id: &str, fields: rhai::Map) -> rhai::Dynamic; + fn delete(&self, collection: &str, id: &str) -> rhai::Dynamic; + fn list(&self, collection: &str) -> rhai::Dynamic; + fn filter(&self, collection: &str, query: rhai::Map) -> rhai::Dynamic; + fn find(&self, collection: &str, query: rhai::Map) -> rhai::Dynamic; +} + +✅ 4. Rhai Registration + +pub fn register_meeting_module(engine: &mut rhai::Engine) { + engine.register_type::(); + engine.register_fn("meeting", MeetingBuilder::new); + + engine.register_fn("with_title", MeetingBuilder::with_title); + engine.register_fn("with_participants", MeetingBuilder::with_participants); + engine.register_fn("get_fields", MeetingBuilder::get_fields); + + // Register CRUD operations + engine.register_fn("create", MeetingBuilder::create); + engine.register_fn("find_by_id", MeetingBuilder::find_by_id); + engine.register_fn("update", MeetingBuilder::update); + engine.register_fn("delete", MeetingBuilder::delete); + engine.register_fn("list", MeetingBuilder::list); + engine.register_fn("filter", MeetingBuilder::filter); + engine.register_fn("find", MeetingBuilder::find); +} + +⸻ + +✅ Example Rhai Script + +// Create a meeting +let meeting_id = meeting() + .with_title("Project Sync") + .with_description("Check in with the team") + .with_participants(["timur", "eda"]) + .with_start("2024-06-01T10:00") + .create(db); + +// Find a meeting by ID +let meeting = meeting().find_by_id(db, meeting_id); + +// Update a meeting +meeting() + .with_description("Updated description") + .update(db, meeting_id); + +// Delete a meeting +meeting().delete(db, meeting_id); + +// List all meetings +let all_meetings = meeting().list(db); + +// Filter meetings by description +let filtered_meetings = meeting() + .with_description("Project Sync") + .filter(db); + +// Find a specific meeting +let found_meeting = meeting() + .with_title("Project Sync") + .with_start("2024-06-01T10:00") + .find(db); + +This script uses the same builder for all operations. + +⸻ + +📂 Output File + +Write generated code to: + +src/gen/meeting_builder.rs + +And include it in main.rs: + +#[path = "gen/meeting_builder.rs"] +mod meeting_builder; + +⸻ + +🔨 build.rs Responsibilities + • Use syn to parse src/models.rs + • Extract structs and fields + • Generate: + • MeetingBuilder struct with fields map + • All with_...() methods for Rhai + • get_fields() method + • CRUD + list + filter + find methods + • Database trait + • register_meeting_module(engine: &mut Engine) function + +Output to src/gen/meeting_builder.rs. + +⸻ + +✅ Success Criteria + • No duplication between filter/create logic + • Fluent API in Rhai + • Generated code compiles and integrates + • build.rs works as part of standard Rust build pipeline + • Easy to extend to more models (e.g., User, Task) + • Complete CRUD operations available + • Database abstraction for persistence + +⸻ \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..89ac464 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,123 @@ +mod models; +#[path = "gen/meeting_builder.rs"] +pub mod meeting_builder; +#[path = "gen/meeting_db.rs"] +pub mod meeting_db; +#[path = "gen/meeting_rhai.rs"] +pub mod meeting_rhai; +#[path = "gen/task_builder.rs"] +pub mod task_builder; +#[path = "gen/task_db.rs"] +pub mod task_db; +#[path = "gen/task_rhai.rs"] +pub mod task_rhai; +#[path = "gen/tests/mod.rs"] +#[cfg(test)] +mod tests; + +use rhai::{Engine, Scope}; + +fn main() { + println!("Dynamic code generation example with CRUD operations"); + + // Create a new Rhai engine + let mut engine = Engine::new(); + + // Register the println function + engine.register_fn("println", |s: &str| println!("{}", s)); + + // Register the meeting builder module + meeting_builder::register_meeting_module(&mut engine); + + // Register the meeting database module + meeting_rhai::register_meeting_db_module(&mut engine); + + // Register the task builder module + task_builder::register_task_module(&mut engine); + + // Register the task database module + task_rhai::register_task_db_module(&mut engine); + + // Create a scope + let mut scope = Scope::new(); + + // Define the Rhai script that uses CRUD operations for both models + let script = r#" + // ===== Meeting CRUD operations ===== + println("=== Meeting CRUD Operations ==="); + + // Create a meeting + let builder = meeting() + .with_title("Project Sync") + .with_description("Check in with the team") + .with_participants(["timur", "eda"]) + .with_start("2024-06-01T10:00"); + let meeting_id = create_meeting(builder.get_fields()); + + print(`Created meeting with ID: ${meeting_id}`); + + // Find a meeting by ID + let meeting = find_meeting_by_id(meeting_id); + print(`Found meeting: ${meeting}`); + + // Update a meeting + let update_builder = meeting() + .with_description("Updated meeting description"); + let updated = update_meeting(meeting_id, update_builder.get_fields()); + print(`Update result: ${updated}`); + + // List all meetings + let all_meetings = list_meetings(); + print(`All meetings: ${all_meetings}`); + + // ===== Task CRUD operations ===== + println("=== Task CRUD Operations ==="); + + // Create a task + let task_builder = task() + .with_title("Implement CRUD") + .with_description("Implement CRUD operations for all models") + .with_assignee("timur") + .with_due_date("2024-06-15") + .with_priority("high") + .with_completed(false); + let task_id = create_task(task_builder.get_fields()); + + print(`Created task with ID: ${task_id}`); + + // Find a task by ID + let task = find_task_by_id(task_id); + print(`Found task: ${task}`); + + // Update a task + let task_update_builder = task() + .with_description("Updated task description") + .with_completed(true); + let task_updated = update_task(task_id, task_update_builder.get_fields()); + print(`Update task result: ${task_updated}`); + + // List all tasks + let all_tasks = list_tasks(); + print(`All tasks: ${all_tasks}`); + + // Filter tasks by assignee + let filter_builder = task() + .with_assignee("timur"); + let filtered_tasks = filter_tasks(filter_builder.get_fields()); + print(`Filtered tasks by assignee: ${filtered_tasks}`); + + // Delete a task + let task_deleted = delete_task(task_id); + print(`Delete task result: ${task_deleted}`); + + // Verify deletion by listing all tasks + let remaining_tasks = list_tasks(); + print(`Remaining tasks: ${remaining_tasks}`); + "#; + + // Run the script + match engine.eval_with_scope::(&mut scope, script) { + Ok(_) => println!("Script executed successfully"), + Err(e) => eprintln!("Script error: {}", e), + } +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..a4ea2b2 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone)] +pub struct Meeting { + pub id: String, + pub title: String, + pub description: String, + pub participants: Vec, + pub start: String, +} + +#[derive(Debug, Clone)] +pub struct Task { + pub id: String, + pub title: String, + pub description: String, + pub assignee: String, + pub due_date: String, + pub priority: String, + pub completed: bool, +}