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,
Tools,
}
impl Artifact {
fn label(self) -> &'static str {
match self {
Self::Model => "model",
Self::Tools => "tools",
}
}
fn template(self) -> &'static str {
match self {
Self::Model => templates::MODEL,
Self::Tools => templates::TOOLS,
}
}
fn stem(self, name: &str) -> &str {
match self {
Self::Model => name,
Self::Tools => name.strip_suffix("Tools").unwrap_or(name),
}
}
fn default_module(self, name: &str) -> Result<String> {
let stem = self.stem(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 (file_name, vars) = match artifact {
Artifact::Model => {
let stem = naming::to_snake_case(name);
(format!("models/{stem}.rs"), vec![("name", name.to_owned())])
}
Artifact::Tools => {
let stem = artifact.stem(name);
if stem.is_empty() {
bail!("`{name}` needs a name before `Tools`, e.g. `PostTools`");
}
let vars = vec![
("name", stem.to_owned()),
("prefix", naming::to_snake_case(stem)),
];
("tools.rs".to_owned(), vars)
}
};
let mut vars: Vec<(&str, &str)> = vars.iter().map(|(k, v)| (*k, v.as_str())).collect();
vars.push(("module", module));
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"));
}
match artifact {
Artifact::Model => {
let stem = naming::to_snake_case(name);
generator::ensure_mod_decl(
&domain_dir.join("models/mod.rs"),
&format!("pub mod {stem};"),
)?;
generator::ensure_mod_decl(&domain_mod, "mod models;")?;
generator::ensure_mod_decl(&modules_mod, &format!("pub mod {module};"))?;
}
Artifact::Tools => {
generator::ensure_mod_decl(&domain_mod, "mod tools;")?;
generator::ensure_trailing_line(&domain_mod, "pub use tools::tools;")?;
generator::ensure_mod_decl(&modules_mod, &format!("pub mod {module};"))?;
register_tools(&modules_mod, module)?;
}
}
println!(
"{} {name} [{}] created successfully.",
capitalize(artifact.label()),
rel(&file_name)
);
Ok(())
}
fn register_tools(modules_mod: &Path, module: &str) -> Result<()> {
let entry = format!(" {module}::tools(),");
if std::fs::read_to_string(modules_mod)?.contains(entry.trim()) {
return Ok(());
}
if !generator::insert_before_marker(modules_mod, templates::TOOLS_MARKER, &entry)? {
println!(
" note: `{}` marker not found; add `{module}::tools()` to the list in {MODULES_DIR}/mod.rs",
templates::TOOLS_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 tools = |n| Artifact::Tools.default_module(n).unwrap();
assert_eq!(model("User"), "user");
assert_eq!(model("BlogPost"), "blog_post");
assert_eq!(tools("UserTools"), "user");
assert_eq!(tools("BlogPostTools"), "blog_post");
assert_eq!(tools("Admin"), "admin");
assert!(Artifact::Tools.default_module("Tools").is_err());
}
}