use std::path::Path;
use anyhow::{Result, bail};
use crate::cli::MakeArgs;
use crate::{generator, naming, project, templates};
const MODULES_DIR: &str = "src/modules";
#[derive(Debug, Clone, Copy)]
pub enum Artifact {
Model,
Controller,
}
impl Artifact {
fn label(self) -> &'static str {
match self {
Self::Model => "model",
Self::Controller => "controller",
}
}
fn directory(self) -> &'static str {
match self {
Self::Model => "models",
Self::Controller => "controllers",
}
}
fn template(self) -> &'static str {
match self {
Self::Model => templates::MODEL,
Self::Controller => templates::CONTROLLER,
}
}
fn default_module(self, name: &str) -> Result<String> {
let stem = match self {
Self::Model => name,
Self::Controller => name.strip_suffix("Controller").unwrap_or(name),
};
if stem.is_empty() {
bail!("cannot derive a module from `{name}`; pass one with --module");
}
Ok(naming::to_snake_case(stem))
}
}
pub fn run(artifact: Artifact, args: &MakeArgs) -> Result<()> {
naming::validate_type_name(&args.name, artifact.label())?;
let module = match &args.module {
Some(module) => module.clone(),
None => artifact.default_module(&args.name)?,
};
naming::validate_module_name(&module)?;
let root = project::find_root()?;
generate(&root, artifact, &args.name, &module, args.force)
}
fn generate(root: &Path, artifact: Artifact, name: &str, module: &str, force: bool) -> Result<()> {
let modules_dir = root.join(MODULES_DIR);
let modules_mod = modules_dir.join("mod.rs");
if !modules_mod.is_file() {
bail!(
"{MODULES_DIR}/mod.rs not found; generators expect the domain-based layout created by `slave new`"
);
}
let domain_dir = modules_dir.join(module);
let domain_mod = domain_dir.join("mod.rs");
let rel = |file: &str| format!("{MODULES_DIR}/{module}/{file}");
let kind_dir = artifact.directory();
let stem = naming::to_snake_case(name);
let file_name = format!("{kind_dir}/{stem}.rs");
let vars = [
("name", name),
("module", module),
("controller", &stem),
("route", &module.replace('_', "-")),
];
let contents = templates::render(artifact.template(), &vars);
generator::write_file(&domain_dir.join(&file_name), &contents, force)?;
println!(" created {}", rel(&file_name));
if !domain_mod.exists() {
generator::write_file(&domain_mod, &format!("//! The `{module}` domain.\n"), false)?;
println!(" created {}", rel("mod.rs"));
}
let kind_mod = domain_dir.join(kind_dir).join("mod.rs");
generator::ensure_mod_decl(&kind_mod, &format!("pub mod {stem};"))?;
generator::ensure_mod_decl(&domain_mod, &format!("mod {kind_dir};"))?;
generator::ensure_mod_decl(&modules_mod, &format!("pub mod {module};"))?;
if let Artifact::Controller = artifact {
add_routes(&domain_dir, &modules_mod, module, &vars)?;
}
println!(
"{} {name} [{}] created successfully.",
capitalize(artifact.label()),
rel(&file_name)
);
Ok(())
}
fn add_routes(
domain_dir: &Path,
modules_mod: &Path,
module: &str,
vars: &[(&str, &str)],
) -> Result<()> {
let routes = domain_dir.join("routes.rs");
if routes.exists() {
println!(
" note: {MODULES_DIR}/{module}/routes.rs already exists; register the new handlers there"
);
return Ok(());
}
generator::write_file(&routes, &templates::render(templates::ROUTES, vars), false)?;
println!(" created {MODULES_DIR}/{module}/routes.rs");
let domain_mod = domain_dir.join("mod.rs");
generator::ensure_mod_decl(&domain_mod, "mod routes;")?;
generator::ensure_trailing_line(&domain_mod, "pub use routes::routes;")?;
let entry = format!(" {module}::routes(),");
if !generator::insert_before_marker(modules_mod, templates::ROUTES_MARKER, &entry)? {
println!(
" note: `{}` marker not found; add `{module}::routes()` to the list in {MODULES_DIR}/mod.rs",
templates::ROUTES_MARKER
);
}
Ok(())
}
fn capitalize(word: &str) -> String {
let mut chars = word.chars();
chars
.next()
.map(|first| first.to_ascii_uppercase().to_string() + chars.as_str())
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_modules() {
let model = |n| Artifact::Model.default_module(n).unwrap();
let controller = |n| Artifact::Controller.default_module(n).unwrap();
assert_eq!(model("User"), "user");
assert_eq!(model("BlogPost"), "blog_post");
assert_eq!(controller("UserController"), "user");
assert_eq!(controller("BlogPostController"), "blog_post");
assert_eq!(controller("Admin"), "admin");
assert!(Artifact::Controller.default_module("Controller").is_err());
}
}