libmake/
generator.rs

1// Copyright notice and licensing information.
2// These lines indicate the copyright of the software and its licensing terms.
3// SPDX-License-Identifier: Apache-2.0 OR MIT indicates dual licensing under Apache 2.0 or MIT licenses.
4// Copyright © 2023-2024 LibMake. All rights reserved.
5
6use crate::{
7    interface::replace_placeholders, macro_generate_from_csv,
8    macro_generate_from_ini, macro_generate_from_json,
9    macro_generate_from_toml, macro_generate_from_yaml,
10    models::model_params::FileGenerationParams,
11    utils::create_directory,
12};
13use std::{fs, io, path::PathBuf};
14
15/// Creates the template folder and downloads necessary template files.
16///
17/// This function attempts to create a template directory in the current working directory
18/// and then downloads a set of predefined template files into this directory.
19///
20/// # Errors
21///
22/// Returns an `io::Error` if the template directory cannot be created, or if there's an error
23/// during the download and writing of the template files. Possible causes include
24/// network issues, file permission errors, or other I/O-related problems.
25///
26pub fn create_template_folder() -> io::Result<()> {
27    let current_dir = std::env::current_dir()?;
28    // println!("Current directory: {:?}", current_dir);
29    let template_dir_path = current_dir.join("template");
30    // println!("Creating template directory: {:?}", template_dir_path);
31    create_directory(&template_dir_path)?;
32    let url = "https://raw.githubusercontent.com/sebastienrousseau/libmake/main/template/";
33    let files = [
34        "AUTHORS.tpl",
35        "build.tpl",
36        "Cargo.tpl",
37        "ci.tpl",
38        "CONTRIBUTING.tpl",
39        "criterion.tpl",
40        "deepsource.tpl",
41        "deny.tpl",
42        "example.tpl",
43        "gitignore.tpl",
44        "lib.tpl",
45        "macros.tpl",
46        "main.tpl",
47        "README.tpl",
48        "rustfmt.tpl",
49        "TEMPLATE.tpl",
50        "test.tpl",
51    ];
52    for file in &files {
53        let file_path = template_dir_path.join(file);
54        // Check if the file already exists
55        if !file_path.exists() {
56            let file_url = format!("{url}{file}");
57            let response =
58                reqwest::blocking::get(&file_url).map_err(|e| {
59                    io::Error::new(
60                        io::ErrorKind::Other,
61                        format!(
62                            "Failed to download template file: {e}"
63                        ),
64                    )
65                })?;
66
67            let file_contents = response.text().map_err(|e| {
68                io::Error::new(
69                    io::ErrorKind::Other,
70                    format!("Failed to read response body: {e}"),
71                )
72            })?;
73
74            // Write the file contents, trimming any leading or trailing newline characters
75            fs::write(
76                &file_path,
77                file_contents
78                    .trim_start_matches('\n')
79                    .trim_end_matches('\n'),
80            )?;
81        }
82    }
83    Ok(())
84}
85
86/// Copies a template file to the output directory and replaces the
87/// placeholders with the given parameters (if any).
88///
89/// # Arguments
90///
91/// * `template_file` - The name of the template file.
92/// * `dest_file` - The name of the destination file.
93/// * `project_directory` - The path to the project directory.
94/// * `params` - The parameters to be used for replacing the placeholders.
95///
96/// # Errors
97///
98/// Returns an `io::Error` in the following cases:
99///
100/// - If the template file cannot be found, read, or copied.
101/// - If the destination file cannot be created or written to.
102/// - If there is an error in replacing placeholders in the destination file.
103///
104#[allow(unused_results)]
105pub fn copy_and_replace_template(
106    template_file: &str,
107    dest_file: &str,
108    project_directory: &PathBuf,
109    params: &FileGenerationParams,
110) -> io::Result<()> {
111    let mut tpl_file = PathBuf::new();
112    tpl_file.push("template");
113    tpl_file.push(template_file);
114
115    let mut dest_path = PathBuf::new();
116    dest_path.push(project_directory);
117    dest_path.push(dest_file);
118
119    fs::copy(&tpl_file, &dest_path)?;
120
121    replace_placeholders(&tpl_file, &dest_path, params)?;
122
123    Ok(())
124}
125
126/// Generates files for a new Rust project based on given arguments.
127///
128/// # Arguments
129///
130/// - `params` - Parameters containing project details and configurations.
131///
132/// # Errors
133///
134/// Returns an `io::Result` error if:
135///
136/// - There are issues creating the project directory or subdirectories.
137/// - There are issues copying or replacing the template files.
138/// - Any other I/O related errors occur during the file generation process.
139///
140pub fn generate_files(params: FileGenerationParams) -> io::Result<()> {
141    let Some(ref output) = params.output else {
142        return Err(io::Error::new(
143            io::ErrorKind::Other,
144            "Output directory is not specified",
145        ));
146    };
147
148    // Get the project directory path from the output parameter,
149    // create a PathBuf from it, and assign it to the project_directory variable
150    let project_directory =
151        PathBuf::from(output.clone().trim_matches('\"'));
152
153    // Creating the project directory
154    create_directory(&project_directory)?;
155
156    // Creating the template directory
157    create_template_folder()?;
158
159    // Define the subdirectories to be created within the project directory
160    let subdirectories = [
161        "src",
162        "benches",
163        "examples",
164        "tests",
165        ".github/",
166        ".github/workflows",
167    ];
168
169    // Iterate over the subdirectories and create them
170    for subdir in &subdirectories {
171        let dir_path = project_directory.join(subdir);
172        create_directory(&dir_path)?;
173    }
174
175    // Copying the template files to the new library directory
176    let templates = [
177        // --- #Start GitHub Actions workflows ---
178        // Add audit GitHub Actions workflows template
179        ("github/workflows/audit.tpl", ".github/workflows/audit.yml"),
180        // Add check GitHub Actions workflows template
181        ("github/workflows/check.tpl", ".github/workflows/check.yml"),
182        // Add coverage GitHub Actions workflows template
183        (
184            "github/workflows/coverage.tpl",
185            ".github/workflows/coverage.yml",
186        ),
187        // Add document GitHub Actions workflows template
188        (
189            "github/workflows/document.tpl",
190            ".github/workflows/document.yml",
191        ),
192        // Add lint GitHub Actions workflows template
193        ("github/workflows/lint.tpl", ".github/workflows/lint.yml"),
194        // Add release GitHub Actions workflows template
195        (
196            "github/workflows/release.tpl",
197            ".github/workflows/release.yml",
198        ),
199        // Add test GitHub Actions workflows template
200        ("github/workflows/test.tpl", ".github/workflows/test.yml"),
201        // --- #End GitHub Actions workflows ---
202        // Add Authors template
203        ("AUTHORS.tpl", "AUTHORS.md"),
204        // Add build template
205        ("build.tpl", "build.rs"),
206        // Add Cargo template
207        ("Cargo.tpl", "Cargo.toml"),
208        // Add Contributing template
209        ("CONTRIBUTING.tpl", "CONTRIBUTING.md"),
210        // Add Criterion template
211        ("criterion.tpl", "benches/criterion.rs"),
212        // Add Deepsource template
213        ("deepsource.tpl", ".deepsource.toml"),
214        // Add Deny template
215        ("deny.tpl", "deny.toml"),
216        // Add Example template
217        ("example.tpl", "examples/example.rs"),
218        // Add Gitignore template
219        ("gitignore.tpl", ".gitignore"),
220        // Add Lib template
221        ("lib.tpl", "src/lib.rs"),
222        // Add Macros template
223        ("macros.tpl", "src/macros.rs"),
224        // Add Main template
225        ("main.tpl", "src/main.rs"),
226        // Add Readme template
227        ("README.tpl", "README.md"),
228        // Add Rustfmt template
229        ("rustfmt.tpl", "rustfmt.toml"),
230        // Add Template template
231        ("TEMPLATE.tpl", "TEMPLATE.md"),
232        // Add Test template
233        ("test_test.tpl", "tests/test_test.rs"),
234    ];
235
236    for (template, target) in templates {
237        copy_and_replace_template(
238            template,
239            target,
240            &project_directory,
241            &params,
242        )?;
243    }
244
245    // Displaying the argument and value pairs
246    println!("{:<15}Value", "Argument");
247    println!("{:<15}{}", "author", params.author.unwrap_or_default());
248    println!("{:<15}{}", "build", params.build.unwrap_or_default());
249    println!(
250        "{:<15}{}",
251        "categories",
252        params.categories.unwrap_or_default()
253    );
254    println!(
255        "{:<15}{}",
256        "description",
257        params.description.unwrap_or_default()
258    );
259    println!(
260        "{:<15}{}",
261        "documentation",
262        params.documentation.unwrap_or_default()
263    );
264    println!("{:<15}{}", "edition", params.edition.unwrap_or_default());
265    println!("{:<15}{}", "email", params.email.unwrap_or_default());
266    println!(
267        "{:<15}{}",
268        "homepage",
269        params.homepage.unwrap_or_default()
270    );
271    println!(
272        "{:<15}{}",
273        "keywords",
274        params.keywords.unwrap_or_default()
275    );
276    println!("{:<15}{}", "license", params.license.unwrap_or_default());
277    println!("{:<15}{}", "name", params.name.unwrap_or_default());
278    println!("{:<15}{}", "output", output.clone());
279    println!("{:<15}{}", "readme", params.readme.unwrap_or_default());
280    println!(
281        "{:<15}{}",
282        "repository",
283        params.repository.unwrap_or_default()
284    );
285    println!(
286        "{:<15}{}",
287        "rustversion",
288        params.rustversion.unwrap_or_default()
289    );
290    println!("{:<15}{}", "version", params.version.unwrap_or_default());
291    println!("{:<15}{}", "website", params.website.unwrap_or_default());
292
293    Ok(())
294}
295
296/// Generates files for a new Rust project based on a configuration file.
297///
298/// # Arguments
299///
300/// - `path` - The path to the configuration file.
301/// - `file_type` - The type of the configuration file (e.g., JSON, YAML, CSV, TOML and INI).
302///
303/// # Errors
304///
305/// Returns an `io::Result` error if:
306///
307/// - The specified configuration file cannot be found, read, or is not in a valid format.
308/// - There are issues parsing the configuration file into the `FileGenerationParams` struct.
309/// - Any errors occur during the file generation process based on the configuration.
310///
311pub fn generate_from_config(
312    path: &str,
313    file_type: &str,
314) -> Result<(), String> {
315    match file_type {
316        "csv" => macro_generate_from_csv!(path)?,
317        "ini" => macro_generate_from_ini!(path)?,
318        "json" => macro_generate_from_json!(path)?,
319        "yaml" => macro_generate_from_yaml!(path)?,
320        "toml" => macro_generate_from_toml!(path)?,
321        _ => {
322            return Err(format!(
323                "Unsupported configuration file type: {}",
324                file_type
325            ))
326        }
327    }
328    Ok(())
329}