rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use super::app_template::normalize_identifier;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectTemplateConfig {
    pub project_name: String,
    pub settings_module: String,
    pub include_manage_rs: bool,
    pub include_urls: bool,
}

impl Default for ProjectTemplateConfig {
    fn default() -> Self {
        Self::new("project")
    }
}

impl ProjectTemplateConfig {
    #[must_use]
    pub fn new(project_name: impl Into<String>) -> Self {
        let project_name = canonical_name(project_name.into(), "project");

        Self {
            project_name,
            settings_module: String::from("settings"),
            include_manage_rs: true,
            include_urls: true,
        }
    }

    #[must_use]
    pub fn with_manage_rs(mut self, value: bool) -> Self {
        self.include_manage_rs = value;
        self
    }

    #[must_use]
    pub fn with_urls(mut self, value: bool) -> Self {
        self.include_urls = value;
        self
    }

    #[must_use]
    pub fn crate_name(&self) -> String {
        normalize_identifier(&self.project_name, "project")
    }

    #[must_use]
    pub fn settings_path(&self) -> String {
        let module = normalize_identifier(&self.settings_module, "settings");
        format!("src/{module}.rs")
    }

    #[must_use]
    pub fn scaffold_files(&self) -> Vec<String> {
        let mut files = Vec::new();
        if self.include_manage_rs {
            files.push(String::from("manage.rs"));
        }
        files.push(String::from("Cargo.toml"));
        files.push(String::from("src/main.rs"));
        files.push(self.settings_path());
        if self.include_urls {
            files.push(String::from("src/urls.rs"));
        }
        files
    }
}

fn canonical_name(name: String, fallback: &str) -> String {
    let trimmed = name.trim();
    if trimmed.is_empty() {
        String::from(fallback)
    } else {
        trimmed.to_owned()
    }
}

#[cfg(test)]
mod tests {
    use super::ProjectTemplateConfig;

    #[test]
    fn default_config_matches_current_project_layout() {
        let config = ProjectTemplateConfig::default();

        assert_eq!(config.project_name, "project");
        assert_eq!(config.crate_name(), "project");
        assert_eq!(
            config.scaffold_files(),
            vec![
                String::from("manage.rs"),
                String::from("Cargo.toml"),
                String::from("src/main.rs"),
                String::from("src/settings.rs"),
                String::from("src/urls.rs"),
            ]
        );
    }

    #[test]
    fn crate_name_is_normalized_for_cargo_packages() {
        let config = ProjectTemplateConfig::new("Customer Portal");

        assert_eq!(config.crate_name(), "customer_portal");
    }

    #[test]
    fn settings_path_uses_custom_module_name() {
        let mut config = ProjectTemplateConfig::new("customer-portal");
        config.settings_module = String::from("prod_settings");

        assert_eq!(config.settings_path(), "src/prod_settings.rs");
    }

    #[test]
    fn optional_files_can_be_disabled() {
        let config = ProjectTemplateConfig::new("customer-portal")
            .with_manage_rs(false)
            .with_urls(false);

        assert_eq!(
            config.scaffold_files(),
            vec![
                String::from("Cargo.toml"),
                String::from("src/main.rs"),
                String::from("src/settings.rs"),
            ]
        );
    }
}