Theme

alex/volcanic

Automated vulkan bindings

BRANCH main
main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
Download file

root / codegen / src / manifest.rs

pub fn update_features(manifest: &str, features: &[String]) -> String {
    let feature_section = feature_section(features);
    let lines: Vec<_> = manifest.lines().collect();

    let Some(start) = lines.iter().position(|line| line.trim() == "[features]") else {
        let separator = if manifest.ends_with('\n') {
            "\n"
        } else {
            "\n\n"
        };
        return format!("{manifest}{separator}{feature_section}");
    };

    let end = lines
        .iter()
        .enumerate()
        .skip(start + 1)
        .find_map(|(idx, line)| {
            let trimmed = line.trim();
            (trimmed.starts_with('[') && trimmed.ends_with(']')).then_some(idx)
        })
        .unwrap_or(lines.len());

    let mut updated = String::new();
    let prefix_end = lines[..start]
        .iter()
        .rposition(|line| !line.trim().is_empty())
        .map(|idx| idx + 1)
        .unwrap_or(0);
    if prefix_end > 0 {
        updated.push_str(&lines[..prefix_end].join("\n"));
        updated.push_str("\n\n");
    }
    updated.push_str(&feature_section);
    if end < lines.len() {
        updated.push('\n');
        updated.push_str(&lines[end..].join("\n"));
        updated.push('\n');
    }
    updated
}

fn feature_section(features: &[String]) -> String {
    let mut out = String::new();
    out.push_str("[features]\n");
    out.push_str("default = []\n");
    out.push_str("everything = [\n");
    for feature in features {
        out.push_str(&format!("    \"{feature}\",\n"));
    }
    out.push_str("]\n\n");
    out.push_str("loader = [\"dep:libloading\"]\n");
    for feature in features {
        let dependencies = feature_dependencies(feature);
        if dependencies.is_empty() {
            out.push_str(&format!("{feature} = []\n"));
        } else {
            out.push_str(&format!("{feature} = ["));
            for (idx, dependency) in dependencies.iter().enumerate() {
                if idx > 0 {
                    out.push_str(", ");
                }
                out.push_str(&format!("\"{dependency}\""));
            }
            out.push_str("]\n");
        }
    }
    out
}

fn feature_dependencies(feature: &str) -> &'static [&'static str] {
    match feature {
        "core_1_1" => &["core_1_0"],
        "core_1_2" => &["core_1_1"],
        "core_1_3" => &["core_1_2"],
        "core_1_4" => &["core_1_3"],
        _ => &[],
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn preserves_manifest_sections_after_features() {
        let manifest = "[package]\nname = \"example\"\n\n[features]\nold = []\n\n[dev-dependencies]\nexample = \"1\"\n";
        let updated = update_features(manifest, &["core_1_0".into(), "core_1_1".into()]);
        assert!(updated.starts_with("[package]\nname = \"example\"\n"));
        assert!(updated.ends_with("[dev-dependencies]\nexample = \"1\"\n"));
        assert!(!updated.contains("old ="));
        assert!(updated.contains("core_1_1 = [\"core_1_0\"]"));
        assert_eq!(
            update_features(&updated, &["core_1_0".into(), "core_1_1".into()]),
            updated
        );
    }
}