espforge_lib/examples/
template.rs1use super::config::ConfigFile;
2use super::fs::OutputDirectory;
3use crate::cli::model::{ExampleConfig, ExportResult};
4use anyhow::{Context, Result, anyhow};
5use espforge_examples::EXAMPLES_DIR;
6use std::fs;
7use std::path::Path;
8
9pub struct ExampleExporter;
10
11impl Default for ExampleExporter {
12 fn default() -> Self {
13 Self::new()
14 }
15}
16
17impl ExampleExporter {
18 pub fn new() -> Self {
19 Self
20 }
21
22 pub fn export(&self, config: &ExampleConfig, output: &OutputDirectory) -> Result<ExportResult> {
23 let template = ExampleTemplate::find(&config.template_name)?;
24 template.extract_to(output.path())?;
25
26 let app_rust_dir = output.path().join("app").join("rust");
27 let src_dir = output.path().join("src");
28
29 if app_rust_dir.exists() {
30 if !src_dir.exists() {
31 fs::create_dir_all(&src_dir).context("Failed to create src directory")?;
32 }
33
34 for entry in fs::read_dir(&app_rust_dir).context("Failed to read app/rust directory")? {
35 let entry = entry?;
36 let path = entry.path();
37 if path.is_file()
38 && let Some(file_name) = path.file_name()
39 {
40 let dest = src_dir.join(file_name);
41 fs::rename(&path, &dest).context("Failed to move file to src")?;
42 }
43 }
44
45 let app_dir = output.path().join("app");
46 if app_dir.exists() {
47 let _ = fs::remove_dir_all(&app_dir);
48 }
49 }
50
51 let config_file = ConfigFile::locate(output.path())?;
52 config_file.update(config)?;
53
54 let final_name = config_file.rename_to(&config.project_name)?;
55
56 Ok(ExportResult {
57 project_name: config.project_name.clone(),
58 output_file: format!("{}.yaml", final_name),
59 })
60 }
61}
62
63pub(crate) struct ExampleTemplate {
64 dir: &'static include_dir::Dir<'static>,
65}
66
67impl ExampleTemplate {
68 pub(crate) fn find(name: &str) -> Result<Self> {
69 let dir = Self::search_in_catalog(name)
70 .ok_or_else(|| anyhow!("Example template '{}' not found", name))?;
71
72 Ok(Self { dir })
73 }
74
75 fn search_in_catalog(name: &str) -> Option<&'static include_dir::Dir<'static>> {
76 EXAMPLES_DIR
77 .dirs()
78 .flat_map(|category| category.dirs())
79 .find(|example| example.path().file_name().and_then(|n| n.to_str()) == Some(name))
80 }
81
82 fn extract_to(&self, target: &Path) -> Result<()> {
83 extract_recursive(self.dir, target, self.dir.path())
84 .context("Failed to extract example files to disk")
85 }
86
87 pub fn read_chip_from_yaml(&self) -> Option<String> {
88 let yaml_file = self
89 .dir
90 .files()
91 .find(|f| f.path().file_name().and_then(|n| n.to_str()) == Some("example.yaml"))?;
92 let content = std::str::from_utf8(yaml_file.contents()).ok()?;
93 let doc: serde_yaml_ng::Value = serde_yaml_ng::from_str(content).ok()?;
94 doc.get("espforge")
95 .and_then(|e| e.get("platform").or_else(|| e.get("chip")))
96 .and_then(|v| v.as_str())
97 .map(|s| s.to_string())
98 }
99}
100
101fn extract_recursive(
102 dir: &include_dir::Dir,
103 base_path: &Path,
104 root_prefix: &Path,
105) -> std::io::Result<()> {
106 let dir_path = dir.path();
108 let relative_dir_path = dir_path
109 .strip_prefix(root_prefix)
110 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
111
112 let dest_dir = base_path.join(relative_dir_path);
113 if !dest_dir.exists() {
114 fs::create_dir_all(&dest_dir)?;
115 }
116
117 for file in dir.files() {
118 let path = file.path();
119 let relative_path = path
120 .strip_prefix(root_prefix)
121 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
122
123 let dest_path = base_path.join(relative_path);
124
125 if let Some(parent) = dest_path.parent() {
126 fs::create_dir_all(parent)?;
127 }
128
129 fs::write(dest_path, file.contents())?;
130 }
131
132 for subdir in dir.dirs() {
133 extract_recursive(subdir, base_path, root_prefix)?;
134 }
135
136 Ok(())
137}