use std::fs;
use std::path::Path;
use anyhow::{Context, Result, bail};
use crate::cli::NewArgs;
use crate::{generator, naming, templates, timestamp};
pub fn run(args: &NewArgs) -> Result<()> {
let name = args.name.as_str();
naming::validate_project_name(name)?;
let framework_dependency = framework_dependency(args.framework_path.as_deref())?;
let target = std::env::current_dir()
.context("failed to read the current directory")?
.join(name);
if target.exists() {
bail!("destination `{}` already exists", target.display());
}
fs::create_dir(&target)
.with_context(|| format!("failed to create directory {}", target.display()))?;
if let Err(err) = scaffold(&target, name, &framework_dependency) {
let _ = fs::remove_dir_all(&target);
return Err(err);
}
println!("\nApplication `{name}` created successfully.\n");
println!("Next steps:");
println!(" cd {name}");
println!(" slave migrate # create the users, roles and auth_tokens tables");
println!(" slave serve # then POST /auth/register; the first account is admin");
Ok(())
}
fn scaffold(target: &Path, name: &str, framework_dependency: &str) -> Result<()> {
let [ts_users, ts_roles, ts_tokens]: [String; 3] =
timestamp::utc_now_sequence(3)?
.try_into()
.map_err(|_| anyhow::anyhow!("failed to generate migration timestamps"))?;
let vars = [
("name", name),
("slave_version", env!("CARGO_PKG_VERSION")),
("framework_dependency", framework_dependency),
("ts_users", ts_users.as_str()),
("ts_roles", ts_roles.as_str()),
("ts_tokens", ts_tokens.as_str()),
];
for file in templates::PROJECT {
let path = templates::render(file.path, &vars);
let contents = templates::render(file.contents, &vars);
generator::write_file(&target.join(&path), &contents, false)?;
println!(" created {name}/{path}");
}
let env_example = templates::PROJECT
.iter()
.find(|file| file.path == ".env.example")
.map(|file| templates::render(file.contents, &vars))
.unwrap_or_default();
generator::write_file(&target.join(".env"), &env_example, false)?;
println!(" created {name}/.env");
Ok(())
}
fn framework_dependency(local_path: Option<&Path>) -> Result<String> {
let Some(path) = local_path else {
return Ok(format!(
"slave-framework = \"{}\"",
templates::FRAMEWORK_VERSION
));
};
let path = path
.canonicalize()
.with_context(|| format!("framework path {} does not exist", path.display()))?;
let manifest = fs::read_to_string(path.join("Cargo.toml")).unwrap_or_default();
if !manifest.contains("name = \"slave-framework\"") {
bail!(
"{} does not contain the slave-framework crate",
path.display()
);
}
let path = path
.to_string_lossy()
.replace('\\', "\\\\")
.replace('"', "\\\"");
Ok(format!("slave-framework = {{ path = \"{path}\" }}"))
}