iocaine 2.0.0

The deadliest poison known to AI
Documentation
// SPDX-FileCopyrightText: 2025 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: MIT

use anyhow::Result;
use minijinja::{value::Kwargs, Environment, Error, State};
use rand::{seq::IndexedRandom, Rng};
use regex::Regex;
use serde::Serialize;
use std::collections::BTreeMap;

use crate::{
    config::Config,
    garglebargle::GargleBargle,
    gobbledygook::GobbledyGook,
    qr_journey,
    wurstsalat_generator_pro::{join_words, WurstsalatGeneratorPro},
};

#[derive(Debug)]
pub struct AssembledStatisticalSequences {
    pub config: Config,
    pub engine: Environment<'static>,
}

#[derive(Debug, Serialize)]
struct PageContext<'a> {
    pub content_type: &'a str,
    pub static_seed: String,
    pub request_host: &'a str,
    pub request_uri: &'a str,
    pub params: BTreeMap<String, String>,
}

impl AssembledStatisticalSequences {
    pub fn new(config: &Config) -> Self {
        tracing::info!("Loading the markov chain corpus");
        let chain = WurstsalatGeneratorPro::learn_from_files(&config.sources.markov).unwrap();
        tracing::info!("Corpus loaded");

        tracing::info!("Loading word salad");
        let words = GargleBargle::load_words(&config.sources.words).unwrap();
        tracing::info!("Word salad loaded");

        let mut engine = Environment::new();

        if let Some(dir) = &config.templates.directory {
            engine.set_loader(minijinja::path_loader(dir));
        } else if let Some(main) = &config.templates.main {
            engine
                .add_template_owned("main.jinja", main.clone())
                .unwrap();
        } else {
            minijinja_embed::load_templates!(&mut engine);
        }

        let rand = move |state: &State, options: Kwargs| -> Result<u32, Error> {
            let mut rng = GobbledyGook::from(state, &options, "default")?;
            let result = rng.gen.random_range(rng.min..=rng.max);

            Ok(result)
        };
        engine.add_function("rand", rand);

        let regex_gen =
            move |state: &State, pattern: &str, options: Kwargs| -> Result<String, Error> {
                let mut rng = GobbledyGook::from(state, &options, "regex")?;
                let gen = rand_regex::Regex::compile(pattern, options.get("max")?).unwrap();

                Ok(rng.gen.sample(&gen))
            };
        engine.add_function("regex_gen", regex_gen);

        let markov_gen = move |state: &State, options: Kwargs| -> Result<String, Error> {
            let mut rng = GobbledyGook::from(state, &options, "markov")?;
            let words = options
                .get::<Option<usize>>("words")?
                .unwrap_or_else(|| rng.gen.random_range(rng.min..=rng.max) as usize);

            let result = chain.generate(rng.gen).take(words);
            Ok(join_words(result))
        };
        engine.add_function("markov_gen", markov_gen);

        let href_gen = move |state: &State, options: Kwargs| -> Result<String, Error> {
            let mut rng = GobbledyGook::from(state, &options, "href")?;
            let count = options
                .get::<Option<usize>>("words")?
                .unwrap_or_else(|| rng.gen.random_range(rng.min..=rng.max) as usize);

            let result = (1..=count)
                .map(|_| words.0.choose(&mut rng.gen).unwrap().as_str())
                .collect::<Vec<_>>()
                .join("-");
            Ok(result)
        };
        engine.add_function("href_gen", href_gen);

        engine.add_function(
            "qr",
            move |state: &State, content: String, options: Kwargs| -> Result<String, Error> {
                qr_journey::generate(state, content, options)
            },
        );

        engine.add_filter(
            "matches",
            move |haystack: String, needle: String| -> Result<bool, Error> {
                let re = Regex::new(&needle).unwrap();
                Ok(re.is_match(&haystack))
            },
        );
        engine.add_filter(
            "sanitize_filename",
            |filename: String, replacement: Option<&str>| -> String {
                sanitize_filename::sanitize_with_options(
                    filename,
                    sanitize_filename::Options {
                        replacement: replacement.unwrap_or(""),
                        ..Default::default()
                    },
                )
            },
        );

        Self {
            config: config.clone(),
            engine,
        }
    }

    pub fn generate(
        &self,
        host: &str,
        path: &str,
        params: &BTreeMap<String, String>,
    ) -> Result<(String, String)> {
        let initial_seed = &self.config.generator.initial_seed;
        let serialized_params = params
            .iter()
            .map(|(k, v)| format!("{}={}", k, v))
            .collect::<Vec<_>>()
            .join("-");
        let static_seed = format!("{}/{}#{}{}", host, path, initial_seed, serialized_params);

        let template = self.engine.get_template("main.jinja")?;

        let context = PageContext {
            content_type: "text/html",
            static_seed,
            request_host: host,
            request_uri: path,
            params: params.clone(),
        };

        let (garbage, state) = template.render_and_return_state(context)?;
        let content_type = state.lookup("content_type").unwrap_or("text/html".into());

        Ok((content_type.to_string(), garbage))
    }
}