_core/
lib.rs

1use pyo3::prelude::*;
2use pyo3::types::PyDict;
3use std::path::PathBuf;
4
5use crate::config::ProjectConfig;
6
7mod config;
8mod engine;
9
10// Macro to extract config values from PyDict with default values
11macro_rules! extract_config {
12    // Pattern for required values (no default)
13    ($dict:expr, $key:literal, required) => {{
14        if let Some(value) = $dict.get_item($key)? {
15            value.extract()?
16        } else {
17            return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(concat!(
18                $key,
19                " is required"
20            )));
21        }
22    }};
23
24    // Pattern for optional values with default (handles all types)
25    ($dict:expr, $key:literal, $default:expr) => {{
26        if let Some(value) = $dict.get_item($key)? {
27            value.extract()?
28        } else {
29            $default
30        }
31    }};
32}
33
34// // Create an init function to register the application metadata
35// #[pyfunction]
36// pub fn init() -> PyResult<()> {
37//     // todo: On 'dev_utils' crate, the macro must also return the value as a HashMap!
38//     dev_utils::app_dt!(file!(),
39//         "package" => ["authors", "license", "description"]
40//     );
41//     Ok(())
42// }
43
44#[pyfunction]
45fn get_crate_version() -> String {
46    env!("CARGO_PKG_VERSION").to_string()
47}
48
49// Keep the old function for backwards compatibility but mark it as deprecated
50/// Main function to generate a project from the static branch (deprecated - use run_engine instead)
51#[pyfunction]
52fn generate_project_from_static_branch(config_dict: &Bound<'_, PyDict>) -> PyResult<bool> {
53    // Convert the Python dictionary to a Rust ProjectConfig struct
54    let project_name: String = extract_config!(config_dict, "project_name", required);
55    let destination: String = extract_config!(config_dict, "destination", ".".to_string());
56    let destination_path = PathBuf::from(destination);
57
58    // Extract other configuration options with defaults or Optionals
59    let author_name: String = extract_config!(config_dict, "author_name", "Test User".to_string());
60    let author_email: String =
61        extract_config!(config_dict, "author_email", "test@example.com".to_string());
62
63    let db_name: Option<String> = extract_config!(
64        config_dict,
65        "db_name",
66        Some(project_name.to_lowercase().replace('-', "_"))
67    );
68
69    let db_owner_admin: Option<String> = extract_config!(
70        config_dict,
71        "db_owner_admin",
72        Some(format!(
73            "{}_owner",
74            project_name.to_lowercase().replace('-', "_")
75        ))
76    );
77
78    let db_owner_pword: Option<String> =
79        extract_config!(config_dict, "db_owner_pword", Some("password".to_string()));
80
81    let include_server: bool = extract_config!(config_dict, "include_server", true);
82    let include_frontend: bool = extract_config!(config_dict, "include_frontend", true);
83    let include_tauri_desktop: bool = extract_config!(config_dict, "include_tauri_desktop", true);
84
85    let app_identifier: String = extract_config!(
86        config_dict,
87        "app_identifier",
88        format!(
89            "com.example.{}",
90            project_name.to_lowercase().replace('-', "")
91        )
92    );
93
94    let deno_package_name: String = extract_config!(
95        config_dict,
96        "deno_package_name",
97        "@test/gwa-project".to_string()
98    );
99
100    // Create the ProjectConfig struct
101    let project_config = ProjectConfig {
102        project_name: project_name.clone(),
103        author_name,
104        author_email,
105        app_identifier,
106        db_name,
107        db_owner_admin,
108        db_owner_pword,
109        include_server,
110        include_frontend,
111        include_tauri_desktop,
112        deno_package_name,
113    };
114
115    // Generate the project using the new engine instead of the old generator
116    match engine::run(&project_config, &destination_path) {
117        Ok(_) => Ok(true),
118        Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
119            "Failed to generate project: {}",
120            e
121        ))),
122    }
123}
124
125/// The new engine-based function to generate a project
126#[pyfunction]
127fn run_engine(config_dict: &Bound<'_, PyDict>) -> PyResult<bool> {
128    // Convert the Python dictionary to a Rust ProjectConfig struct
129    let project_name: String = extract_config!(config_dict, "project_name", required);
130    let destination: String = extract_config!(config_dict, "destination", ".".to_string());
131    let destination_path = PathBuf::from(destination);
132
133    // Extract other configuration options with defaults or Optionals
134    let author_name: String = extract_config!(config_dict, "author_name", "Test User".to_string());
135    let author_email: String =
136        extract_config!(config_dict, "author_email", "test@example.com".to_string());
137
138    let db_name: Option<String> = extract_config!(
139        config_dict,
140        "db_name",
141        Some(project_name.to_lowercase().replace('-', "_"))
142    );
143
144    let db_owner_admin: Option<String> = extract_config!(
145        config_dict,
146        "db_owner_admin",
147        Some(format!(
148            "{}_owner",
149            project_name.to_lowercase().replace('-', "_")
150        ))
151    );
152
153    let db_owner_pword: Option<String> =
154        extract_config!(config_dict, "db_owner_pword", Some("password".to_string()));
155
156    let include_server: bool = extract_config!(config_dict, "include_server", true);
157    let include_frontend: bool = extract_config!(config_dict, "include_frontend", true);
158    let include_tauri_desktop: bool = extract_config!(config_dict, "include_tauri_desktop", true);
159
160    let app_identifier: String = extract_config!(
161        config_dict,
162        "app_identifier",
163        format!(
164            "com.example.{}",
165            project_name.to_lowercase().replace('-', "")
166        )
167    );
168
169    let deno_package_name: String = extract_config!(
170        config_dict,
171        "deno_package_name",
172        "@test/gwa-project".to_string()
173    );
174
175    // Create the ProjectConfig struct
176    let project_config = ProjectConfig {
177        project_name: project_name.clone(),
178        author_name,
179        author_email,
180        app_identifier,
181        db_name,
182        db_owner_admin,
183        db_owner_pword,
184        include_server,
185        include_frontend,
186        include_tauri_desktop,
187        deno_package_name,
188    };
189
190    // Generate the project using the new engine
191    match engine::run(&project_config, &destination_path) {
192        Ok(_) => Ok(true),
193        Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
194            "Failed to generate project with engine: {}",
195            e
196        ))),
197    }
198}
199
200/// A Python module implemented in Rust. The name of this function must match
201/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
202/// import the module.
203#[pymodule]
204fn _core(m: &Bound<PyModule>) -> PyResult<()> {
205    // m.add_function(wrap_pyfunction!(hello_from_bin, m)?)?;
206    // m.add_function(wrap_pyfunction!(init, m)?)?;
207    m.add_function(wrap_pyfunction!(get_crate_version, m)?)?;
208    m.add_function(wrap_pyfunction!(generate_project_from_static_branch, m)?)?;
209    m.add_function(wrap_pyfunction!(run_engine, m)?)?;
210    Ok(())
211}