1mod templates;
7
8use miette::{bail, IntoDiagnostic, Result};
9use std::fs;
10use std::path::Path;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ProjectType {
15 Binary,
17 Library,
19 FfiWrapper,
21}
22
23impl std::str::FromStr for ProjectType {
24 type Err = String;
25
26 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
27 match s.to_lowercase().as_str() {
28 "binary" | "bin" => Ok(ProjectType::Binary),
29 "library" | "lib" => Ok(ProjectType::Library),
30 "ffi-wrapper" | "ffi" => Ok(ProjectType::FfiWrapper),
31 _ => Err(format!(
32 "Unknown project type: '{}'. Use 'binary', 'library', or 'ffi-wrapper'.",
33 s
34 )),
35 }
36 }
37}
38
39impl std::fmt::Display for ProjectType {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 ProjectType::Binary => write!(f, "binary"),
43 ProjectType::Library => write!(f, "library"),
44 ProjectType::FfiWrapper => write!(f, "ffi-wrapper"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum BuildTemplate {
52 Make,
54 CMake,
56 Meson,
58}
59
60impl std::str::FromStr for BuildTemplate {
61 type Err = String;
62
63 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
64 match s.to_lowercase().as_str() {
65 "make" => Ok(BuildTemplate::Make),
66 "cmake" => Ok(BuildTemplate::CMake),
67 "meson" => Ok(BuildTemplate::Meson),
68 _ => Err(format!(
69 "Unknown build template: '{}'. Use 'make', 'cmake', or 'meson'.",
70 s
71 )),
72 }
73 }
74}
75
76impl std::fmt::Display for BuildTemplate {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 BuildTemplate::Make => write!(f, "make"),
80 BuildTemplate::CMake => write!(f, "cmake"),
81 BuildTemplate::Meson => write!(f, "meson"),
82 }
83 }
84}
85
86pub fn create_project(
88 name: &str,
89 path: &Path,
90 project_type: ProjectType,
91 build_template: BuildTemplate,
92) -> Result<()> {
93 if name.is_empty() {
95 bail!("Project name cannot be empty");
96 }
97 if !name
98 .chars()
99 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
100 {
101 bail!("Project name must contain only alphanumeric characters, underscores, and hyphens");
102 }
103
104 let project_dir = path.join(name);
106 if project_dir.exists() {
107 bail!("Directory '{}' already exists", project_dir.display());
108 }
109
110 fs::create_dir_all(&project_dir).into_diagnostic()?;
111
112 fs::create_dir_all(project_dir.join("src")).into_diagnostic()?;
114
115 if project_type == ProjectType::FfiWrapper {
116 fs::create_dir_all(project_dir.join("include")).into_diagnostic()?;
117 }
118
119 write_manifest(&project_dir, name, project_type)?;
121 write_source_files(&project_dir, name, project_type)?;
122 write_build_files(&project_dir, name, project_type, build_template)?;
123 write_gitignore(&project_dir)?;
124 write_readme(&project_dir, name, project_type, build_template)?;
125
126 eprintln!("Created {} project '{}' at {}", project_type, name, project_dir.display());
127 eprintln!();
128 eprintln!("To get started:");
129 eprintln!(" cd {}", name);
130 match build_template {
131 BuildTemplate::Make => eprintln!(" make"),
132 BuildTemplate::CMake => {
133 eprintln!(" mkdir build && cd build");
134 eprintln!(" cmake ..");
135 eprintln!(" make");
136 }
137 BuildTemplate::Meson => {
138 eprintln!(" meson setup build");
139 eprintln!(" meson compile -C build");
140 }
141 }
142
143 Ok(())
144}
145
146pub fn init_project(
148 path: &Path,
149 project_type: ProjectType,
150 build_template: BuildTemplate,
151) -> Result<()> {
152 let name = path
153 .file_name()
154 .and_then(|n| n.to_str())
155 .unwrap_or("project");
156
157 if path.join("fastc.toml").exists() {
159 bail!("Directory already contains a fastc.toml file");
160 }
161
162 let src_dir = path.join("src");
164 if !src_dir.exists() {
165 fs::create_dir_all(&src_dir).into_diagnostic()?;
166 }
167
168 if project_type == ProjectType::FfiWrapper {
169 let include_dir = path.join("include");
170 if !include_dir.exists() {
171 fs::create_dir_all(&include_dir).into_diagnostic()?;
172 }
173 }
174
175 write_manifest(path, name, project_type)?;
177
178 let main_file = match project_type {
179 ProjectType::Binary => src_dir.join("main.fc"),
180 ProjectType::Library | ProjectType::FfiWrapper => src_dir.join("lib.fc"),
181 };
182 if !main_file.exists() {
183 write_source_files(path, name, project_type)?;
184 }
185
186 write_build_files(path, name, project_type, build_template)?;
188
189 if !path.join(".gitignore").exists() {
191 write_gitignore(path)?;
192 }
193
194 if !path.join("README.md").exists() {
196 write_readme(path, name, project_type, build_template)?;
197 }
198
199 eprintln!("Initialized {} project in {}", project_type, path.display());
200
201 Ok(())
202}
203
204fn write_manifest(project_dir: &Path, name: &str, project_type: ProjectType) -> Result<()> {
205 let content = templates::manifest(name, project_type);
206 fs::write(project_dir.join("fastc.toml"), content).into_diagnostic()
207}
208
209fn write_source_files(project_dir: &Path, name: &str, project_type: ProjectType) -> Result<()> {
210 let (filename, content) = match project_type {
211 ProjectType::Binary => ("main.fc", templates::main_fc(name)),
212 ProjectType::Library => ("lib.fc", templates::lib_fc(name)),
213 ProjectType::FfiWrapper => ("lib.fc", templates::ffi_lib_fc(name)),
214 };
215
216 fs::write(project_dir.join("src").join(filename), content).into_diagnostic()
217}
218
219fn write_build_files(
220 project_dir: &Path,
221 name: &str,
222 project_type: ProjectType,
223 build_template: BuildTemplate,
224) -> Result<()> {
225 match build_template {
226 BuildTemplate::Make => {
227 let content = templates::makefile(name, project_type);
228 fs::write(project_dir.join("Makefile"), content).into_diagnostic()
229 }
230 BuildTemplate::CMake => {
231 let content = templates::cmakelists(name, project_type);
232 fs::write(project_dir.join("CMakeLists.txt"), content).into_diagnostic()
233 }
234 BuildTemplate::Meson => {
235 let content = templates::meson_build(name, project_type);
236 fs::write(project_dir.join("meson.build"), content).into_diagnostic()
237 }
238 }
239}
240
241fn write_gitignore(project_dir: &Path) -> Result<()> {
242 let content = templates::gitignore();
243 fs::write(project_dir.join(".gitignore"), content).into_diagnostic()
244}
245
246fn write_readme(
247 project_dir: &Path,
248 name: &str,
249 project_type: ProjectType,
250 build_template: BuildTemplate,
251) -> Result<()> {
252 let content = templates::readme(name, project_type, build_template);
253 fs::write(project_dir.join("README.md"), content).into_diagnostic()
254}