zerodds-idlc 1.0.0-rc.5

ZeroDDS IDL4 compiler CLI — Backends fuer C, C++, C#, Java, Python, Rust ueber zerodds-idl-* Codegen-Crates.
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors
//! Projekt-Scaffolding — die „extra Meile".
//!
//! RTI (`-example`) und Fast DDS (`-example`) erzeugen optional ein
//! Build-Projekt; Cyclone gar nicht. `zerodds-idlc` bietet mit
//! `--scaffold` pro Backend die idiomatische Build-Datei an, sodass die
//! generierten Stubs ohne Handarbeit kompilierbar sind:
//!
//! * Rust   → `Cargo.toml`
//! * C/C++  → `CMakeLists.txt`
//! * C#     → `<base>.csproj`
//! * Java   → `pom.xml`
//! * Python → `pyproject.toml`
//! * TS     → `package.json` + `tsconfig.json`
//!
//! Opt-in: ohne `--scaffold` wird nichts davon geschrieben. Bestehende
//! Build-Dateien werden nicht ueberschrieben (der Aufrufer prueft das).

use crate::Backend;

/// Liefert die Scaffolding-Build-Dateien fuer ein Backend als
/// `(Dateiname, Inhalt)`-Paare. `base` ist der IDL-Basisname.
#[must_use]
pub fn scaffold_files(backend: Backend, base: &str) -> Vec<(String, String)> {
    match backend {
        Backend::Rust => vec![("Cargo.toml".to_string(), rust_cargo_toml(base))],
        Backend::C => vec![("CMakeLists.txt".to_string(), cmake(base, "C"))],
        Backend::Cpp => vec![("CMakeLists.txt".to_string(), cmake(base, "CXX"))],
        Backend::CSharp => vec![(format!("{base}.csproj"), csproj())],
        Backend::Java => vec![("pom.xml".to_string(), pom_xml(base))],
        Backend::Python => vec![("pyproject.toml".to_string(), pyproject(base))],
        Backend::Ts => vec![
            ("package.json".to_string(), package_json(base)),
            ("tsconfig.json".to_string(), tsconfig()),
        ],
    }
}

fn rust_cargo_toml(base: &str) -> String {
    format!(
        "# Generated by zerodds-idlc --scaffold.\n\
         [package]\n\
         name = \"{base}\"\n\
         version = \"0.1.0\"\n\
         edition = \"2021\"\n\n\
         [lib]\n\
         path = \"{base}.rs\"\n\n\
         [dependencies]\n\
         zerodds-cdr = \"1\"\n"
    )
}

fn cmake(base: &str, lang: &str) -> String {
    // Beide Backends emittieren einen Header (C: <base>.h, C++:
    // <base>.hpp) — eine INTERFACE-Library reicht.
    format!(
        "# Generated by zerodds-idlc --scaffold.\n\
         cmake_minimum_required(VERSION 3.16)\n\
         project({base} {lang})\n\n\
         add_library({base} INTERFACE)\n\
         target_include_directories({base} INTERFACE ${{CMAKE_CURRENT_SOURCE_DIR}})\n"
    )
}

fn csproj() -> String {
    "<!-- Generated by zerodds-idlc --scaffold. -->\n\
     <Project Sdk=\"Microsoft.NET.Sdk\">\n\
     \x20 <PropertyGroup>\n\
     \x20   <TargetFramework>net8.0</TargetFramework>\n\
     \x20   <Nullable>enable</Nullable>\n\
     \x20   <LangVersion>latest</LangVersion>\n\
     \x20 </PropertyGroup>\n\
     </Project>\n"
        .to_string()
}

fn pom_xml(base: &str) -> String {
    format!(
        "<!-- Generated by zerodds-idlc --scaffold. -->\n\
         <project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n\
         \x20        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\
         \x20        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 \
         http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\
         \x20 <modelVersion>4.0.0</modelVersion>\n\
         \x20 <groupId>org.zerodds.generated</groupId>\n\
         \x20 <artifactId>{base}</artifactId>\n\
         \x20 <version>0.1.0</version>\n\
         \x20 <properties>\n\
         \x20   <maven.compiler.release>17</maven.compiler.release>\n\
         \x20   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\
         \x20 </properties>\n\
         </project>\n"
    )
}

fn pyproject(base: &str) -> String {
    format!(
        "# Generated by zerodds-idlc --scaffold.\n\
         [build-system]\n\
         requires = [\"setuptools>=64\"]\n\
         build-backend = \"setuptools.build_meta\"\n\n\
         [project]\n\
         name = \"{base}\"\n\
         version = \"0.1.0\"\n\
         requires-python = \">=3.10\"\n"
    )
}

fn package_json(base: &str) -> String {
    format!(
        "{{\n\
         \x20 \"name\": \"{base}\",\n\
         \x20 \"version\": \"0.1.0\",\n\
         \x20 \"type\": \"module\",\n\
         \x20 \"main\": \"{base}.ts\",\n\
         \x20 \"dependencies\": {{\n\
         \x20   \"@zerodds/types\": \"^1.0.0\"\n\
         \x20 }},\n\
         \x20 \"devDependencies\": {{\n\
         \x20   \"typescript\": \"^5.4.0\"\n\
         \x20 }}\n\
         }}\n"
    )
}

fn tsconfig() -> String {
    "{\n\
     \x20 \"compilerOptions\": {\n\
     \x20   \"target\": \"ES2022\",\n\
     \x20   \"module\": \"ES2022\",\n\
     \x20   \"moduleResolution\": \"bundler\",\n\
     \x20   \"strict\": true,\n\
     \x20   \"declaration\": true\n\
     \x20 }\n\
     }\n"
    .to_string()
}

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

    #[test]
    fn rust_scaffold_is_cargo_toml() {
        let files = scaffold_files(Backend::Rust, "robot");
        assert_eq!(files.len(), 1);
        assert_eq!(files[0].0, "Cargo.toml");
        assert!(files[0].1.contains("name = \"robot\""));
        assert!(files[0].1.contains("path = \"robot.rs\""));
        assert!(files[0].1.contains("zerodds-cdr"));
    }

    #[test]
    fn ts_scaffold_has_two_files() {
        let files = scaffold_files(Backend::Ts, "chat");
        assert_eq!(files.len(), 2);
        let names: Vec<&str> = files.iter().map(|(n, _)| n.as_str()).collect();
        assert!(names.contains(&"package.json"));
        assert!(names.contains(&"tsconfig.json"));
    }

    #[test]
    fn cpp_and_c_use_cmake() {
        assert_eq!(scaffold_files(Backend::C, "x")[0].0, "CMakeLists.txt");
        assert_eq!(scaffold_files(Backend::Cpp, "x")[0].0, "CMakeLists.txt");
        assert!(
            scaffold_files(Backend::Cpp, "x")[0]
                .1
                .contains("project(x CXX)")
        );
        assert!(
            scaffold_files(Backend::C, "x")[0]
                .1
                .contains("project(x C)")
        );
    }

    #[test]
    fn csharp_scaffold_named_after_base() {
        let files = scaffold_files(Backend::CSharp, "robot");
        assert_eq!(files[0].0, "robot.csproj");
        assert!(files[0].1.contains("Microsoft.NET.Sdk"));
    }

    #[test]
    fn java_scaffold_is_pom() {
        let files = scaffold_files(Backend::Java, "robot");
        assert_eq!(files[0].0, "pom.xml");
        assert!(files[0].1.contains("<artifactId>robot</artifactId>"));
    }

    #[test]
    fn python_scaffold_is_pyproject() {
        let files = scaffold_files(Backend::Python, "robot");
        assert_eq!(files[0].0, "pyproject.toml");
        assert!(files[0].1.contains("name = \"robot\""));
    }
}