zebflow 0.6.1

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
//! Compile-stage processor pipeline for RWE templates.
//!
//! Processors are optional features that transform template HTML before final
//! compile artifacts are produced.
//!
//! Current processors:
//!
//! - `tailwind`: Tailwind-like utility token compiler
//! - `markdown`: Markdown block conversion (`<markdown>...</markdown>`)

use crate::rwe::model::{ReactiveWebDiagnostic, ReactiveWebOptions, StyleEngineMode};
use markdown::{process_markdown, process_rwe_md_placeholders};
use tailwind::{collect_source_tokens, process_tailwind};

pub mod markdown;
pub mod tailwind;

/// Apply enabled compile processors.
///
/// Resolution rules:
///
/// - if `options.processors` is empty, default behavior applies: Tailwind-like
///   processing is driven by `options.style_engine`
/// - if `options.processors` is non-empty, only listed processors run, in the
///   listed order
pub fn apply_compile_processors(
    html: &str,
    source: &str,
    options: &ReactiveWebOptions,
    diagnostics: &mut Vec<ReactiveWebDiagnostic>,
) -> String {
    let normalized = normalize_processor_list(&options.processors);
    if normalized.is_empty() {
        return match options.style_engine {
            StyleEngineMode::TailwindLike => {
                let extra = collect_source_tokens(source);
                process_tailwind(html, &extra)
            }
            StyleEngineMode::Off => html.to_string(),
        };
    }

    let extra = collect_source_tokens(source);
    let mut out = html.to_string();
    for processor in normalized {
        match processor.as_str() {
            "tailwind" => {
                out = process_tailwind(&out, &extra);
            }
            "markdown" => {
                out = process_markdown(&out);
            }
            other => diagnostics.push(ReactiveWebDiagnostic {
                code: "RWE_PROCESSOR_UNKNOWN".to_string(),
                message: format!("unknown processor '{other}' (ignored)"),
            }),
        }
    }
    // Always process <Markdown> component placeholders (data-rwe-md attributes)
    // regardless of explicit processor list — this is a core RWE feature.
    out = process_rwe_md_placeholders(&out);
    out
}

fn normalize_processor_list(raw: &[String]) -> Vec<String> {
    let mut out = Vec::new();
    for item in raw {
        let lowered = item.trim().to_ascii_lowercase();
        if lowered.is_empty() {
            continue;
        }
        if out.iter().any(|existing| existing == &lowered) {
            continue;
        }
        out.push(lowered);
    }
    out
}