jumake/
create_files.rs

1// src/create_files.rs
2use std::fs::{self, File};
3use std::io::{Write, BufRead, BufReader};
4use indoc::indoc;
5use crate::context::Context;
6use std::path::PathBuf;
7use std::error::Error;
8
9pub fn create_source_files(context: &Context) -> Result<(), Box<dyn std::error::Error>> {
10    let src_path = context.project_path.join("src");
11    fs::create_dir(&src_path)?;
12
13    match context.template_name.as_deref() {
14        Some("GuiApplication") => {
15            // Create GUI application files
16            create_file_from_template(&src_path, "Main.cpp", MAIN_CPP_TEMPLATE)?;
17            create_file_from_template(
18                &src_path,
19                "MainComponent.cpp",
20                MAIN_COMPONENT_CPP_TEMPLATE,
21            )?;
22            create_file_from_template(&src_path, "MainComponent.h", MAIN_COMPONENT_H_TEMPLATE)?;
23            create_file_from_template(&src_path, "CMakeLists.txt", GUI_APP_CMAKE_TEMPLATE)?;
24        }
25        Some("AudioPlugin") => {
26            // Create audio plugin files
27            create_file_from_template(
28                &src_path,
29                "PluginProcessor.cpp",
30                PLUGIN_PROCESSOR_CPP_TEMPLATE,
31            )?;
32            create_file_from_template(
33                &src_path,
34                "PluginProcessor.h",
35                PLUGIN_PROCESSOR_H_TEMPLATE,
36            )?;
37            create_file_from_template(&src_path, "PluginEditor.cpp", PLUGIN_EDITOR_CPP_TEMPLATE)?;
38            create_file_from_template(&src_path, "PluginEditor.h", PLUGIN_EDITOR_H_TEMPLATE)?;
39            create_file_from_template(&src_path, "CMakeLists.txt", AUDIO_PLUGIN_CMAKE_TEMPLATE)?;
40        }
41        Some("ConsoleApp") => {
42            // Create console application files
43            create_file_from_template(&src_path, "Main.cpp", CONSOLE_APP_MAIN_CPP_TEMPLATE)?;
44            create_file_from_template(&src_path, "CMakeLists.txt", CONSOLE_APP_CMAKE_TEMPLATE)?;
45        }
46        _ => {
47            return Err(format!("Unknown template: {:?}", context.template_name).into());
48        }
49    }
50    Ok(())
51}
52
53// Function to add a class or component file to a project based on the given context and element type.
54pub fn add_class(context: &Context, element_type: &str, element_name: &str) -> Result<(), Box<dyn Error>> {
55    // Construct the source directory path from the context's project path.
56    let src_path = context.project_path.join("src");
57
58    // Determine the correct templates for the header and source files based on the element type.
59    let mut adjusted_element_name = element_name.to_string();
60    let (header_template, cpp_template) = match element_type {
61        "class" => (CLASS_H_TEMPLATE, CLASS_CPP_TEMPLATE),
62        "component" => {
63            // Append "Component" to the name for component types to differentiate from regular classes.
64            adjusted_element_name.push_str("Component");
65            (COMPONENT_H_TEMPLATE, COMPONENT_CPP_TEMPLATE)
66        },
67        _ => return Err(format!("Invalid element type: {}", element_type).into()),
68    };
69
70    // Construct file names for the header and source files.
71    let header_file_name = format!("{}.h", adjusted_element_name);
72    let cpp_file_name = format!("{}.cpp", adjusted_element_name);
73
74    // Check if the header or source file already exists
75    let header_path = src_path.join(&header_file_name);
76    let cpp_path = src_path.join(&cpp_file_name);
77
78    if header_path.exists() || cpp_path.exists() {
79        return Err(format!("{} '{}' already exists in the project.", element_type, adjusted_element_name).into());
80    }
81
82    // Construct file names for the header and source files.
83    let header_file_name = format!("{}.h", adjusted_element_name);
84    let cpp_file_name = format!("{}.cpp", adjusted_element_name);
85
86    // Create files from templates with the adjusted names.
87    create_classfile_from_template(&src_path, &header_file_name, header_template, &adjusted_element_name)?;
88    create_classfile_from_template(&src_path, &cpp_file_name, cpp_template, &adjusted_element_name)?;
89
90    // Attempt to add the newly created cpp file to the CMakeLists.txt
91    let cmakelists_path = src_path.join("CMakeLists.txt");
92    let file = File::open(&cmakelists_path)?;
93    let reader = BufReader::new(file);
94    let lines: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
95    let mut new_lines = Vec::new();
96    let mut found_target_sources = false;
97    let mut added = false;
98
99    // Iterate through each line of CMakeLists.txt to find the target_sources block.
100    for line in lines.iter() {
101        new_lines.push(line.to_string());
102        if line.trim_start().starts_with("target_sources(${PROJECT_NAME}") {
103            found_target_sources = true;
104        }
105        if found_target_sources && line.trim_start().starts_with("PRIVATE") && !added {
106            // Calculate indentation and insert the cpp file name under the PRIVATE specifier.
107            let indentation = line.chars().take_while(|c| c.is_whitespace()).count() + 4;
108            let new_cpp_line = format!("{:indent$}{}", "", cpp_file_name, indent = indentation);
109            new_lines.push(new_cpp_line);
110            added = true;
111        }
112    }
113
114    // Handle the case where the PRIVATE specifier was not found after target_sources.
115    if !added {
116        return Err("Could not find 'PRIVATE' after 'target_sources' in CMakeLists.txt".into());
117    }
118
119    // Write the updated content back to CMakeLists.txt.
120    let new_content = new_lines.join("\n");
121    let mut cmakelists_file = File::create(&cmakelists_path)?;
122    cmakelists_file.write_all(new_content.as_bytes())?;
123
124    // Confirm the addition of the new class or component.
125    println!("{} '{}' added successfully!", element_type, adjusted_element_name);
126    Ok(())
127}
128
129fn create_classfile_from_template(
130    src_path: &PathBuf,
131    file_name: &str,
132    template: &[u8],
133    element_name: &str,
134) -> Result<(), Box<dyn std::error::Error>> {
135    let mut file = File::create(src_path.join(file_name))?;
136    let content = String::from_utf8_lossy(template).replace("Template", element_name);
137    file.write_all(content.as_bytes())?;
138    println!("Created file: {}", src_path.join(file_name).display());
139    Ok(())
140}
141
142fn create_file_from_template(
143    src_path: &PathBuf,
144    file_name: &str,
145    template: &[u8],
146) -> Result<(), Box<dyn std::error::Error>> {
147    let mut file = File::create(src_path.join(file_name))?;
148    file.write_all(template)?;
149    println!("Created file: {}", src_path.join(file_name).display());
150    Ok(())
151}
152// Define the templates as byte slices
153const MAIN_CPP_TEMPLATE: &[u8] = include_bytes!("../templates/GuiApplicationTemplate/Main.cpp.template");
154const MAIN_COMPONENT_CPP_TEMPLATE: &[u8] =
155    include_bytes!("../templates/GuiApplicationTemplate/MainComponent.cpp.template");
156const MAIN_COMPONENT_H_TEMPLATE: &[u8] =
157    include_bytes!("../templates/GuiApplicationTemplate/MainComponent.h.template");
158const GUI_APP_CMAKE_TEMPLATE: &[u8] =
159    include_bytes!("../templates/GuiApplicationTemplate/CMakeLists.txt.template");
160
161const PLUGIN_PROCESSOR_CPP_TEMPLATE: &[u8] =
162    include_bytes!("../templates/AudioPluginTemplate/PluginProcessor.cpp.template");
163const PLUGIN_PROCESSOR_H_TEMPLATE: &[u8] =
164    include_bytes!("../templates/AudioPluginTemplate/PluginProcessor.h.template");
165const PLUGIN_EDITOR_CPP_TEMPLATE: &[u8] =
166    include_bytes!("../templates/AudioPluginTemplate/PluginEditor.cpp.template");
167const PLUGIN_EDITOR_H_TEMPLATE: &[u8] =
168    include_bytes!("../templates/AudioPluginTemplate/PluginEditor.h.template");
169const AUDIO_PLUGIN_CMAKE_TEMPLATE: &[u8] =
170    include_bytes!("../templates/AudioPluginTemplate/CMakeLists.txt.template");
171
172const CONSOLE_APP_CMAKE_TEMPLATE: &[u8] =
173    include_bytes!("../templates/ConsoleAppTemplate/CMakeLists.txt.template");
174
175const CONSOLE_APP_MAIN_CPP_TEMPLATE: &[u8] =
176    include_bytes!("../templates/ConsoleAppTemplate/Main.cpp.template");
177
178const CLASS_H_TEMPLATE: &[u8] = include_bytes!("../templates/ClassTemplates/Class.h.template");
179const CLASS_CPP_TEMPLATE: &[u8] = include_bytes!("../templates/ClassTemplates/Class.cpp.template");
180const COMPONENT_H_TEMPLATE: &[u8] = include_bytes!("../templates/ClassTemplates/Component.h.template");
181const COMPONENT_CPP_TEMPLATE: &[u8] = include_bytes!("../templates/ClassTemplates/Component.cpp.template");
182
183pub fn create_cmakelists(context: &Context) -> Result<(), Box<dyn std::error::Error>> {
184    let cmakelists_path = context.project_path.join("CMakeLists.txt");
185    let mut cmakelists_file = File::create(cmakelists_path)?;
186
187    let cmake_content = format!(
188        indoc! {
189            "cmake_minimum_required(VERSION 3.24)
190             project({} VERSION 0.0.1)
191             add_subdirectory(modules/JUCE)
192             add_subdirectory(src)"
193        },
194        context.project_name
195    );
196
197    cmakelists_file
198        .write_all(cmake_content.as_bytes())?;
199    Ok(())
200}
201