616 lines
14 KiB
Markdown
616 lines
14 KiB
Markdown
# Mize Part System (LLM generated guide)
|
|
|
|
|
|
---
|
|
|
|
## Core Concepts
|
|
|
|
### The MizePart Trait
|
|
|
|
All parts implement the `MizePart` trait, which provides:
|
|
|
|
```rust
|
|
pub trait MizePart: MizePartGenerated {
|
|
fn name(&self) -> &'static str; // Part identifier
|
|
fn get_mize(&mut self) -> &mut Mize; // Access to the Mize instance
|
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()>; // Initialization
|
|
fn run(&mut self, mize: &mut Mize) -> MizeResult<()>; // Execution
|
|
fn deps(&self) -> &'static [&'static str]; // Dependencies
|
|
fn opts(&self, mize: &mut Mize); // Config options
|
|
// ... async variants
|
|
}
|
|
```
|
|
|
|
### Part Registration
|
|
|
|
Parts register themselves with the Mize instance using:
|
|
|
|
- **`mize.register_part(Box::new(MyPart { ... }))`** - For parts with state and methods
|
|
- **`mize.add_name_only_part("name")`** - For simple parts that only need to be known by name (and have no state)
|
|
|
|
### Part Access
|
|
|
|
Parts can access other parts using:
|
|
|
|
```rust
|
|
// Get a part by name (returns a guard that ensures the part is returned)
|
|
let mut part = mize.get_part("part_name")?;
|
|
|
|
// Get a typed part (for parts with structs)
|
|
let mut typed_part = mize.get_part_native::<OtherPart>("other_part")?;
|
|
```
|
|
|
|
**Important**: Users must ensure that dependent parts are enabled before the parts that depend on them. There's no automatic dependency resolution.
|
|
|
|
---
|
|
|
|
## Current Implementation (Compiled-in Parts)
|
|
|
|
Currently, all parts are compiled into the `marts` crate and registered via the `compile_in_parts!` macro in the application's `main.rs`.
|
|
|
|
### Example: `apps/os/src/main.rs`
|
|
|
|
```rust
|
|
compile_in_parts! {
|
|
mize,
|
|
"cli": marts::cli,
|
|
"auth": marts::part_client_auth,
|
|
"spacetime": marts::spacetime,
|
|
"ppc.server": marts::ppc::server::part_ppc_server,
|
|
"c2vi": marts::part_c2vi,
|
|
"sci": marts::sci::part_sci,
|
|
"sci.timetrack": marts::sci::timetrack::part_timetrack,
|
|
"sci.inbox": marts::sci::inbox::part_sci_inbox,
|
|
};
|
|
```
|
|
|
|
Each entry maps a part name to a function that registers the part with the Mize instance.
|
|
|
|
### How It Works
|
|
|
|
1. The macro reads the `enable` config option (a list of part names)
|
|
2. For each enabled part, it calls the corresponding registration function
|
|
3. Each registration function creates the part and calls `mize.register_part()`
|
|
|
|
---
|
|
|
|
## Creating a New Part
|
|
|
|
### Option 1: Simple Name-Only Part
|
|
|
|
For parts that don't need state or methods:
|
|
|
|
```rust
|
|
// In your part module
|
|
pub fn part_my_simple_part(mize: &mut Mize) -> MizeResult<()> {
|
|
// Just register the name
|
|
mize.add_name_only_part("my_simple_part");
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Option 2: Part with State and Methods (Using `mize_part` Macro)
|
|
|
|
For parts that need to store state and expose methods:
|
|
|
|
```rust
|
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
|
|
|
#[mize_part] // or #[mize_part("custom_name")] for custom name
|
|
#[derive(Default)]
|
|
pub struct MyPart {
|
|
mize: Mize, // Required field
|
|
counter: u32, // Your custom state
|
|
api_key: String, // More state
|
|
}
|
|
|
|
// Implement MizePartCreate for parts that need custom initialization
|
|
impl MizePartCreate for MyPart {
|
|
// Optional: override create_generated for custom initialization
|
|
}
|
|
|
|
impl MizePart for MyPart {
|
|
// Optional: override name if not using the struct name
|
|
fn name(&self) -> &'static str {
|
|
"my_part" // Default would be "mypart" from struct name
|
|
}
|
|
|
|
// Optional: define config options this part needs
|
|
fn opts(&self, mize: &mut Mize) {
|
|
mize.new_opt("my_part.api_key");
|
|
mize.new_opt("my_part.counter_initial");
|
|
}
|
|
|
|
// Optional: initialization logic
|
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
|
self.counter = mize.get_config("my_part.counter_initial")?.value_u32()?;
|
|
self.api_key = mize.get_config("my_part.api_key")?.value_string()?;
|
|
Ok(())
|
|
}
|
|
|
|
// Optional: define dependencies on other parts
|
|
fn deps(&self) -> &'static [&'static str] {
|
|
&["auth"] // This part requires "auth" to be loaded first
|
|
}
|
|
}
|
|
|
|
// The registration function
|
|
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
|
mize.register_part(Box::new(MyPart {
|
|
mize: mize.clone(),
|
|
counter: 0,
|
|
api_key: String::new(),
|
|
}))?;
|
|
Ok(())
|
|
}
|
|
|
|
// You can add methods to your part struct
|
|
impl MyPart {
|
|
pub fn increment(&mut self) -> u32 {
|
|
self.counter += 1;
|
|
self.counter
|
|
}
|
|
|
|
pub fn get_api_key(&self) -> &str {
|
|
&self.api_key
|
|
}
|
|
}
|
|
```
|
|
|
|
### Using the Part from Another Part
|
|
|
|
```rust
|
|
pub fn part_another_part(mize: &mut Mize) -> MizeResult<()> {
|
|
// Get the MyPart instance
|
|
let mut my_part = mize.get_part_native::<MyPart>("my_part")?;
|
|
|
|
// Call its methods
|
|
let count = my_part.increment();
|
|
let api_key = my_part.get_api_key();
|
|
|
|
// ... your logic
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## The `mize_part` Macro
|
|
|
|
The `#[mize_part]` procedural macro simplifies part creation by automatically implementing the required traits.
|
|
|
|
### What It Generates
|
|
|
|
For a struct like:
|
|
|
|
```rust
|
|
#[mize_part]
|
|
#[derive(Default)]
|
|
pub struct MyPart {
|
|
mize: Mize,
|
|
// ... your fields
|
|
}
|
|
```
|
|
|
|
The macro generates:
|
|
|
|
1. **`MizePartGenerated` implementation** - Provides default implementations for the generated trait methods
|
|
2. **`MizePartCreateGenerated` implementation** - Provides a default `create_generated` method
|
|
3. **`MizePartCreateThisSucks` implementation** - Compatibility trait
|
|
4. **Extern "C" function** - Named `make_mize_part_<name>` for dynamic loading
|
|
|
|
### Custom Part Name
|
|
|
|
```rust
|
|
#[mize_part("custom_name")]
|
|
pub struct MyPart {
|
|
mize: Mize,
|
|
}
|
|
```
|
|
|
|
This will use "custom_name" instead of the derived "mypart".
|
|
|
|
### Requirements
|
|
|
|
- The struct **must** have a field named `mize: Mize`
|
|
- The struct should derive `Default` or implement it
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### Defining Config Options
|
|
(about to be deprecad, does nothing)
|
|
|
|
Parts declare what config options they need in their `opts()` method:
|
|
|
|
```rust
|
|
impl MizePart for MyPart {
|
|
fn opts(&self, mize: &mut Mize) {
|
|
mize.new_opt("my_part.api_url");
|
|
mize.new_opt("my_part.timeout");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Using Config Options
|
|
|
|
Parts retrieve config using `mize.get_config()`:
|
|
|
|
```rust
|
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
|
let api_url = mize.get_config("my_part.api_url")?.value_string()?;
|
|
let timeout = mize.get_config("my_part.timeout")?.value_u64()?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### User Configuration
|
|
|
|
Users configure parts via TOML files or environment variables. Example `config.toml`:
|
|
|
|
```toml
|
|
[my_part]
|
|
api_url = "https://api.example.com"
|
|
timeout = 30
|
|
|
|
[enable]
|
|
# List of parts to enable
|
|
my_part = true
|
|
auth = true
|
|
spacetime = true
|
|
```
|
|
|
|
Or via command line:
|
|
|
|
```bash
|
|
mize --opt my_part.api_url=https://api.example.com run
|
|
```
|
|
|
|
---
|
|
|
|
## Part Dependencies
|
|
|
|
### Declaring Dependencies
|
|
(about to be deprecad, does nothing)
|
|
|
|
Parts declare their dependencies in the `deps()` method:
|
|
|
|
```rust
|
|
impl MizePart for MyPart {
|
|
fn deps(&self) -> &'static [&'static str] {
|
|
&["auth", "spacetime"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note**: This is currently informational only. Users must manually ensure dependencies are enabled.
|
|
|
|
### Accessing Dependencies
|
|
|
|
Use `get_part_native()` to access other parts:
|
|
|
|
```rust
|
|
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
|
// Get the auth part
|
|
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
|
|
|
|
// Use it
|
|
let token = auth.id_token()?;
|
|
|
|
// Get spacetime
|
|
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Future Vision: Separate Crates
|
|
|
|
The goal is to move each part to its own crate following the naming convention **`mize-part-<name>`**. when creating a part make a new Cargo project like this already.
|
|
|
|
### Structure of a Part Crate
|
|
|
|
```
|
|
mize-part-discord/
|
|
├── Cargo.toml
|
|
└── src/
|
|
└── lib.rs
|
|
```
|
|
|
|
### Example: `mize-part-discord/Cargo.toml`
|
|
|
|
```toml
|
|
[package]
|
|
name = "mize-part-discord"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
|
|
[lib]
|
|
crate-type = ["cdylib", "rlib"]
|
|
|
|
[dependencies]
|
|
mize = { path = "../mize" }
|
|
serenity = "0.12"
|
|
```
|
|
|
|
### Example: `mize-part-discord/src/lib.rs`
|
|
|
|
```rust
|
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
|
use serenity::Client;
|
|
|
|
#[mize_part]
|
|
#[derive(Default)]
|
|
pub struct DiscordPart {
|
|
mize: Mize,
|
|
client: Option<Client>,
|
|
}
|
|
|
|
impl MizePartCreate for DiscordPart {}
|
|
|
|
impl MizePart for DiscordPart {
|
|
fn name(&self) -> &'static str {
|
|
"discord"
|
|
}
|
|
|
|
fn opts(&self, mize: &mut Mize) {
|
|
mize.new_opt("discord.token");
|
|
}
|
|
|
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
|
let token = mize.get_config("discord.token")?.value_string()?;
|
|
// Initialize serenity client...
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// The registration function - THIS IS WHAT MATTERS
|
|
pub fn part_discord(mize: &mut Mize) -> MizeResult<()> {
|
|
mize.register_part(Box::new(DiscordPart {
|
|
mize: mize.clone(),
|
|
client: None,
|
|
}))?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Using Separate Part Crates
|
|
|
|
In your application's `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
mize = { path = "../mize" }
|
|
mize-part-discord = { path = "../mize-part-discord" }
|
|
mize-part-auth = { path = "../mize-part-auth" }
|
|
```
|
|
|
|
In your application's `main.rs`:
|
|
|
|
```rust
|
|
compile_in_parts! {
|
|
mize,
|
|
"discord": mize_part_discord::part_discord,
|
|
"auth": mize_part_auth::part_auth,
|
|
};
|
|
```
|
|
|
|
## Complete Example: Creating a New Part
|
|
|
|
Let's create a complete example of a new part called `mize-part-greeting`.
|
|
|
|
### Step 1: Create the Crate
|
|
|
|
```bash
|
|
cargo new --lib mize-part-greeting
|
|
cd mize-part-greeting
|
|
```
|
|
|
|
### Step 2: Edit `Cargo.toml`
|
|
|
|
```toml
|
|
[package]
|
|
name = "mize-part-greeting"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
|
|
[lib]
|
|
crate-type = ["cdylib", "rlib"]
|
|
|
|
[dependencies]
|
|
mize = { path = "../mize" }
|
|
```
|
|
|
|
### Step 3: Create the Part (`src/lib.rs`)
|
|
|
|
```rust
|
|
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
|
|
|
|
/// A simple greeting part that stores a name and can generate greetings
|
|
#[mize_part]
|
|
#[derive(Default)]
|
|
pub struct GreetingPart {
|
|
mize: Mize,
|
|
name: String,
|
|
}
|
|
|
|
impl MizePartCreate for GreetingPart {}
|
|
|
|
impl MizePart for GreetingPart {
|
|
fn name(&self) -> &'static str {
|
|
"greeting"
|
|
}
|
|
|
|
fn opts(&self, mize: &mut Mize) {
|
|
mize.new_opt("greeting.name");
|
|
mize.new_opt("greeting.formal");
|
|
}
|
|
|
|
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
|
self.name = mize
|
|
.get_config("greeting.name")?
|
|
.value_string()
|
|
.unwrap_or_else(|_| "World".to_string());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl GreetingPart {
|
|
/// Generate a greeting message
|
|
pub fn greet(&self) -> String {
|
|
let formal = self
|
|
.mize
|
|
.get_config("greeting.formal")
|
|
.map(|v| v.value_bool().unwrap_or(false))
|
|
.unwrap_or(false);
|
|
|
|
if formal {
|
|
format!("Good day, {}!", self.name)
|
|
} else {
|
|
format!("Hello, {}!", self.name)
|
|
}
|
|
}
|
|
|
|
/// Set the name
|
|
pub fn set_name(&mut self, name: String) {
|
|
self.name = name;
|
|
}
|
|
|
|
/// Get the current name
|
|
pub fn get_name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
/// Registration function - this is what the compile_in_parts! macro will call
|
|
pub fn part_greeting(mize: &mut Mize) -> MizeResult<()> {
|
|
mize.register_part(Box::new(GreetingPart {
|
|
mize: mize.clone(),
|
|
name: String::new(),
|
|
}))?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Step 4: Add to Your Application
|
|
|
|
In your application's `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
mize = { path = "../mize" }
|
|
mize-part-greeting = { path = "../mize-part-greeting" }
|
|
```
|
|
|
|
In your application's `main.rs`:
|
|
|
|
```rust
|
|
compile_in_parts! {
|
|
mize,
|
|
"greeting": mize_part_greeting::part_greeting,
|
|
};
|
|
```
|
|
|
|
### Step 5: Use the Part
|
|
|
|
```rust
|
|
// In another part or in your CLI
|
|
let mut greeting = mize.get_part_native::<GreetingPart>("greeting")?;
|
|
println!("{}", greeting.greet());
|
|
|
|
// Change the name
|
|
greeting.set_name("Alice".to_string());
|
|
println!("{}", greeting.greet());
|
|
```
|
|
|
|
### Step 6: Configure It
|
|
|
|
Create a `config.toml`:
|
|
|
|
```toml
|
|
[greeting]
|
|
name = "Bob"
|
|
formal = true
|
|
|
|
[enable]
|
|
greeting = true
|
|
```
|
|
|
|
Run with:
|
|
|
|
```bash
|
|
mize --config config.toml run
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Naming**: Use `mize-part-<name>` for crate names and `part_<name>` for registration functions
|
|
2. **State**: Store part-specific state in the part struct
|
|
3. **Dependencies**: Declare dependencies in `deps()` and document them
|
|
4. **Config**: Declare all config options in `opts()`
|
|
5. **Error Handling**: Use `MizeResult` and `mize_err!` for errors
|
|
6. **Documentation**: Document your part's purpose, config options, and methods
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
- Parts are modular units of functionality in Mize
|
|
- Each part implements the `MizePart` trait
|
|
- Use `#[mize_part]` macro for parts with state and methods
|
|
- Register parts with `mize.register_part()` or `mize.add_name_only_part()`
|
|
- Parts access config via `mize.get_config()`
|
|
- Parts access other parts via `mize.get_part_native::<T>()`
|
|
- Users enable parts via the `enable` config option
|
|
- Future: Each part will be its own crate named `mize-part-<name>`
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Common Patterns
|
|
|
|
**Register a part:**
|
|
```rust
|
|
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
|
```
|
|
|
|
**Get a part:**
|
|
```rust
|
|
let mut part = mize.get_part_native::<MyPart>("my_part")?;
|
|
```
|
|
|
|
**Get config:**
|
|
```rust
|
|
let value = mize.get_config("my_part.setting")?.value_string()?;
|
|
```
|
|
|
|
**Declare config option:**
|
|
```rust
|
|
mize.new_opt("my_part.setting");
|
|
```
|
|
|
|
**Define a part struct:**
|
|
```rust
|
|
#[mize_part]
|
|
#[derive(Default)]
|
|
pub struct MyPart {
|
|
mize: Mize,
|
|
// your fields...
|
|
}
|
|
```
|
|
|
|
**Registration function:**
|
|
```rust
|
|
pub fn part_my_part(mize: &mut Mize) -> MizeResult<()> {
|
|
mize.register_part(Box::new(MyPart { mize: mize.clone(), ... }))?;
|
|
Ok(())
|
|
}
|
|
```
|