use std::env;
use std::fs;
use std::path::PathBuf;
use super::utils::{ensure_dir, print_info, print_step, print_success, print_warning};
use crate::cli::InitArgs;
use crate::config::ProjectConfig;
use crate::error::{CliError, CliResult};
pub fn execute(args: InitArgs) -> CliResult<()> {
let current_dir = env::current_dir()?;
let project_name = args.name.unwrap_or_else(|| {
current_dir
.file_name()
.and_then(|n| n.to_str())
.map_or_else(|| "axonml-project".to_string(), String::from)
});
let config_path = current_dir.join("axonml.toml");
if config_path.exists() && !args.force {
return Err(CliError::ProjectExists(
"axonml.toml already exists. Use --force to overwrite.".to_string(),
));
}
println!();
print_info(&format!("Initializing Axonml project: {project_name}"));
println!();
print_step(1, 4, "Creating directory structure...");
create_directories(¤t_dir)?;
print_step(2, 4, "Creating configuration...");
create_config(¤t_dir, &project_name)?;
print_step(3, 4, "Setting up source structure...");
create_source_structure(¤t_dir, &project_name)?;
if args.no_git {
print_step(4, 4, "Skipping git setup...");
} else {
print_step(4, 4, "Setting up git...");
setup_git(¤t_dir)?;
}
println!();
print_success(&format!(
"Initialized Axonml project '{project_name}' in current directory"
));
println!();
print_info("Get started with:");
println!(" axonml train");
println!();
Ok(())
}
fn create_directories(base_path: &PathBuf) -> CliResult<()> {
let dirs = [
"src",
"src/models",
"src/data",
"configs",
"checkpoints",
"logs",
"outputs",
];
for dir in &dirs {
let dir_path = base_path.join(dir);
if !dir_path.exists() {
ensure_dir(&dir_path)?;
}
}
let data_dirs = ["data", "data/train", "data/val", "data/test"];
for dir in &data_dirs {
let dir_path = base_path.join(dir);
if !dir_path.exists() {
ensure_dir(&dir_path)?;
}
}
Ok(())
}
fn create_config(base_path: &PathBuf, project_name: &str) -> CliResult<()> {
let config_path = base_path.join("axonml.toml");
let config = ProjectConfig::new(project_name);
config.save(&config_path)?;
let gitignore_path = base_path.join(".gitignore");
if gitignore_path.exists() {
let existing = fs::read_to_string(&gitignore_path)?;
if !existing.contains("# Axonml") {
let axonml_entries = "\n# Axonml\n/checkpoints/\n/logs/\n/outputs/\n*.axonml\n";
fs::write(&gitignore_path, format!("{existing}{axonml_entries}"))?;
print_warning("Appended Axonml entries to existing .gitignore");
}
} else {
let gitignore = create_gitignore();
fs::write(&gitignore_path, gitignore)?;
}
let train_config_path = base_path.join("configs/train.toml");
if !train_config_path.exists() {
let train_config = create_train_config();
fs::write(&train_config_path, train_config)?;
}
Ok(())
}
fn create_gitignore() -> &'static str {
r"# Axonml project gitignore
# Output directories
/checkpoints/
/logs/
/outputs/
# Data (typically large)
/data/train/
/data/val/
/data/test/
# Python virtual environment
venv/
.venv/
__pycache__/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Model files (typically large)
*.axonml
*.onnx
*.pt
*.pth
*.safetensors
# Temporary files
*.tmp
*.log
"
}
fn create_train_config() -> &'static str {
r#"# Training Configuration
[training]
epochs = 10
batch_size = 32
learning_rate = 0.001
device = "cpu"
[training.optimizer]
name = "adam"
weight_decay = 0.0001
[model]
architecture = "custom"
[data]
train_path = "data/train"
val_path = "data/val"
shuffle = true
"#
}
fn create_source_structure(base_path: &PathBuf, project_name: &str) -> CliResult<()> {
let main_path = base_path.join("src/main.rs");
if !main_path.exists() {
let main_content = format!(
r#"//! {project_name} - Main Entry Point
use axonml::prelude::*;
mod models;
mod data;
fn main() -> Result<(), Box<dyn std::error::Error>> {{
println!("Starting {project_name} training...");
// Your training code here
Ok(())
}}
"#
);
fs::write(&main_path, main_content)?;
}
let models_path = base_path.join("src/models/mod.rs");
if !models_path.exists() {
let models_content = format!(
r"//! Model definitions for {project_name}
use axonml::prelude::*;
// Define your models here
"
);
fs::write(&models_path, models_content)?;
}
let data_path = base_path.join("src/data/mod.rs");
if !data_path.exists() {
let data_content = format!(
r"//! Data loading for {project_name}
use axonml::prelude::*;
// Define your data loaders here
"
);
fs::write(&data_path, data_content)?;
}
let gitkeep_dirs = ["checkpoints/.gitkeep", "logs/.gitkeep", "outputs/.gitkeep"];
for path in &gitkeep_dirs {
let full_path = base_path.join(path);
if !full_path.exists() {
fs::write(&full_path, "")?;
}
}
Ok(())
}
fn setup_git(base_path: &PathBuf) -> CliResult<()> {
use std::process::Command;
let git_dir = base_path.join(".git");
if git_dir.exists() {
return Ok(());
}
let output = Command::new("git")
.arg("init")
.current_dir(base_path)
.output();
if let Ok(result) = output {
if result.status.success() {
let _ = Command::new("git")
.args(["add", "."])
.current_dir(base_path)
.output();
let _ = Command::new("git")
.args(["commit", "-m", "Initialize Axonml project"])
.current_dir(base_path)
.output();
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_create_directories() {
let temp = tempdir().unwrap();
let base = temp.path().to_path_buf();
create_directories(&base).unwrap();
assert!(base.join("src").exists());
assert!(base.join("configs").exists());
assert!(base.join("checkpoints").exists());
}
#[test]
fn test_create_config() {
let temp = tempdir().unwrap();
let base = temp.path().to_path_buf();
std::fs::create_dir_all(base.join("configs")).unwrap();
create_config(&base, "test-project").unwrap();
assert!(base.join("axonml.toml").exists());
assert!(base.join(".gitignore").exists());
}
}