aethellib 0.10.1

Composable text generation primitives over target-specific TOML corpora with provenance tracking.
Documentation
//! custom rule example showing the Rule trait and dependency-aware custom rules.

use aethellib::{engine::combinators, prelude::*};
use rand::{Rng, RngExt};

struct BladeCodeRule {
    pool: PoolRef,
}

impl BladeCodeRule {
    fn new(pool: PoolRef) -> Self {
        Self { pool }
    }
}

impl Rule for BladeCodeRule {
    fn pool_refs(&self) -> Vec<PoolRef> {
        vec![self.pool.clone()]
    }

    fn execute<'a>(
        &self,
        ctx: &GenerationContext<'a>,
        rng: &mut dyn Rng,
    ) -> Result<ComposedValue, AethelError> {
        let values = ctx
            .corpus
            .pooled_values_for_field_section(self.pool.field(), self.pool.section())
            .ok_or_else(|| AethelError::PoolNotFound {
                section: self.pool.section().to_string(),
                field: self.pool.field().to_string(),
            })?;

        if values.is_empty() {
            return Err(AethelError::Custom("pool is empty".to_string()));
        }

        let left = &values[rng.random_range(0..values.len())];
        let right = &values[rng.random_range(0..values.len())];

        Ok(ComposedValue {
            value: format!(
                "{}-{}",
                left.value.to_uppercase(),
                right.value.to_uppercase()
            ),
            provenance: left
                .provenance
                .iter()
                .chain(&right.provenance)
                .cloned()
                .collect(),
        })
    }
}

fn main() {
    let corpus = Corpus::from_files(
        &["examples/data/weapon_test_data.toml"],
        "weapon",
        None,
        None,
    )
    .expect("failed to load weapon corpus");

    let blade_code = RuleKey::new("blade_code").expect("rule key should be valid");
    let weapon_kind = RuleKey::new("weapon_kind").expect("rule key should be valid");
    let display = RuleKey::new("display").expect("rule key should be valid");

    let primitives = PoolRef::new("name", "primitives").expect("pool ref should be valid");
    let kind = PoolRef::new("type", "type").expect("pool ref should be valid");

    let compiled = PlanBuilder::new(&corpus)
        .rule(&blade_code, BladeCodeRule::new(primitives))
        .rule(&weapon_kind, combinators::pick(kind))
        .rule(
            &display,
            combinators::custom(
                [blade_code.clone(), weapon_kind.clone()],
                |ctx, _rng, deps| {
                    let code = ctx.require(&deps[0])?;
                    let kind = ctx.require(&deps[1])?;

                    Ok(ComposedValue {
                        value: format!("{} {}", code.value, kind.value),
                        provenance: code
                            .provenance
                            .iter()
                            .chain(&kind.provenance)
                            .cloned()
                            .collect(),
                    })
                },
            ),
        )
        .validate()
        .expect("plan validation failed")
        .compile()
        .expect("plan compilation failed");

    let mut rng = rand::rng();
    let ctx = compiled.generate(&mut rng).expect("generation failed");

    println!(
        "Generated custom weapon: {}",
        ctx.require(&display).expect("display should exist").value,
    );
}