espforge_lib/
export.rs

1use crate::config::{EspforgeConfiguration, PlatformConfig};
2use crate::template_utils::{copy_verbatim, find_template_path, get_templates, write_template};
3use anyhow::{Context, Result, anyhow};
4use std::path::Path;
5
6pub struct ExportOptions {
7    pub example_name: String,
8    pub override_project_name: Option<String>,
9    pub override_platform: Option<String>,
10}
11
12pub fn export_example(options: ExportOptions, target_dir: &Path) -> Result<String> {
13    let root = get_templates();
14
15    let template_path_str = find_template_path(&options.example_name)
16        .ok_or_else(|| anyhow!("Example '{}' not found", options.example_name))?;
17
18    let template_dir = root.get_dir(&template_path_str).ok_or_else(|| {
19        anyhow!(
20            "Template directory structure error for '{}'",
21            options.example_name
22        )
23    })?;
24
25    let example_file = template_dir.path().join("example.yaml");
26    // Use root to get file by full path to ensure correct lookup
27    let yaml_file = root
28        .get_file(&example_file)
29        .ok_or_else(|| anyhow!("Template is missing example.yaml"))?;
30
31    let raw_yaml = yaml_file
32        .contents_utf8()
33        .context("Invalid UTF-8 in example.yaml")?;
34
35    let mut config: EspforgeConfiguration = serde_yaml_ng::from_str(raw_yaml)
36        .context("Failed to parse example.yaml into configuration")?;
37
38    if let Some(name) = options.override_project_name {
39        config.espforge.name = name;
40    }
41
42    if let Some(platform_str) = options.override_platform {
43        let platform_enum: PlatformConfig = serde_yaml_ng::from_str(&platform_str)
44            .map_err(|_| anyhow!("Invalid platform: {}", platform_str))?;
45        config.espforge.platform = platform_enum;
46    }
47
48    let project_name = config.get_name().to_string();
49
50    let yaml_filename = format!("{}.yaml", project_name);
51    let yaml_dest = target_dir.join(&yaml_filename);
52
53    let modified_yaml = serde_yaml_ng::to_string(&config)?;
54    write_template(&yaml_dest, &modified_yaml)?;
55    println!("Created config: {}", yaml_filename);
56
57    let project_path_str = target_dir.to_string_lossy();
58    let template_root_path = template_dir.path();
59
60    for entry in template_dir.find("**/*")? {
61        if let Some(file) = entry.as_file() {
62            let file_path = file.path();
63            let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
64            if file_name == "example.yaml" {
65                continue;
66            }
67
68            if file_path
69                .components()
70                .any(|c| c.as_os_str() == ".zig-cache" || c.as_os_str() == "zig-out")
71            {
72                continue;
73            }
74
75            // We do not render templates here; that happens during 'compile'.
76            copy_verbatim(file, template_root_path, &project_path_str)?;
77            let relative_path = file_path
78                .strip_prefix(template_root_path)
79                .unwrap_or(file_path);
80            println!("Created file: {}", relative_path.display());
81        }
82    }
83
84    let target_cargo_tera = target_dir.join("Cargo.toml.tera");
85    if !target_cargo_tera.exists() {
86        if let Some(dynamic_cargo) = root.get_file("_dynamic/Cargo.toml.tera") {
87             copy_verbatim(dynamic_cargo, Path::new("_dynamic"), &project_path_str)?;
88             println!("Created file: Cargo.toml.tera (from _dynamic)");
89        }
90    }
91
92    Ok(project_name)
93}