folk-builder 0.1.1

Custom binary builder for Folk — generates and compiles a Folk server with selected plugins
Documentation
use crate::config::{BuildConfig, PluginEntry};

/// Generate the content of the output `Cargo.toml`.
pub fn generate_cargo_toml(cfg: &BuildConfig) -> String {
    let mut deps = String::new();

    // Core dependencies
    deps.push_str(
        r#"folk-core = "0.1.1"
folk-api = "0.1.1"
folk-runtime-pipe = "0.1.1"
serde_json = "1"
toml = "0.8"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
"#,
    );

    for plugin in &cfg.plugin {
        deps.push_str(&generate_plugin_dep(plugin));
    }

    format!(
        r#"[package]
name = "{output}"
version = "0.1.0"
edition = "2024"

[[bin]]
name = "{output}"
path = "src/main.rs"

[dependencies]
{deps}
"#,
        output = cfg.build.output,
        deps = deps
    )
}

fn generate_plugin_dep(p: &PluginEntry) -> String {
    if let Some(path) = &p.path {
        format!(
            r#"{} = {{ path = "{}" }}
"#,
            p.crate_name, path
        )
    } else if let Some(git) = &p.git {
        let ver = p.version.as_deref().unwrap_or("0.1");
        format!(
            r#"{} = {{ git = "{}", version = "{}" }}
"#,
            p.crate_name, git, ver
        )
    } else {
        let ver = p.version.as_deref().unwrap_or("0.1");
        format!(
            r#"{} = "{}"
"#,
            p.crate_name, ver
        )
    }
}

/// Generate the content of the output `main.rs`.
pub fn generate_main_rs(cfg: &BuildConfig) -> String {
    let imports: Vec<String> = cfg
        .plugin
        .iter()
        .map(|p| {
            let ident = p.crate_name.replace('-', "_");
            format!("use {}::folk_plugin_factory as {}_factory;", ident, ident)
        })
        .collect();

    let registrations: Vec<String> = cfg
        .plugin
        .iter()
        .map(|p| {
            let ident = p.crate_name.replace('-', "_");
            let key = if p.config_key.is_empty() {
                &p.crate_name
            } else {
                &p.config_key
            };
            format!(
                r#"    let cfg_{ident} = raw_cfg.get("{key}").cloned().unwrap_or(toml::Value::Table(Default::default()));
    let cfg_{ident}_json = serde_json::to_value(&cfg_{ident}).unwrap_or_default();
    server.register_plugin({ident}_factory().create(cfg_{ident}_json)?);
"#,
                ident = ident,
                key = key
            )
        })
        .collect();

    format!(
        r#"//! Generated by folk-builder. Do not edit.

{imports}

#[tokio::main]
async fn main() -> anyhow::Result<()> {{
    let cfg = folk_core::config::FolkConfig::load()?;
    folk_core::logging::init(&cfg.log)?;
    let raw_cfg: toml::Table = {{
        let content = std::fs::read_to_string("folk.toml").unwrap_or_default();
        content.parse().unwrap_or_default()
    }};

    let runtime = folk_runtime_pipe::runtime::PipeRuntime::new(
        folk_runtime_pipe::runtime::PipeConfig {{
            php:    cfg.workers.php.clone(),
            script: cfg.workers.script.clone(),
        }}
    );

    let mut server = folk_core::server::FolkServer::new(cfg, std::sync::Arc::new(runtime));

{registrations}

    server.run().await
}}
"#,
        imports = imports.join("\n"),
        registrations = registrations.join("")
    )
}