updated AGENTS.md and pps site works again.
This commit is contained in:
625
docs/part-system.md
Normal file
625
docs/part-system.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# Mize Part System (LLM generated guide)
|
||||
|
||||
## Overview
|
||||
|
||||
Mize is built around a **parts architecture** that allows you to compose functionality modularly. Each part is a self-contained unit that can:
|
||||
|
||||
- Store and manage its own state
|
||||
- Expose methods to other parts
|
||||
- Register itself with the Mize instance
|
||||
- Define configuration options
|
||||
|
||||
Parts are the primary way to extend Mize's functionality. The system is designed so that parts can depend on each other, and users can enable/disable parts as needed.
|
||||
|
||||
---
|
||||
|
||||
## 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(())
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user