use clap::{ App, SubCommand, Arg }; use std::fs::{ self, File }; use std::io::Write; use std::path::Path; use chrono::offset::Local; use chrono::Datelike; use slug::slugify; use std::process::Command; use std::path::PathBuf; fn main() { let app = App::new("Zipity") .version("0.0.1-alpha.2") .author("Eric David Smith") .about("A command-line tool for Zipity") .subcommand( SubCommand::with_name("init") .about("Creates a new Zipity project") .arg(Arg::with_name("project_name").required(true)) ) .subcommand(SubCommand::with_name("build")) .subcommand(SubCommand::with_name("serve")) .subcommand( SubCommand::with_name("add") .about("Adds a new component to the project") .subcommand( SubCommand::with_name("route") .about("Adds a new route with a markdown file") .arg(Arg::with_name("route_name").required(true)) ) .subcommand( SubCommand::with_name("api") .about("Adds a new API endpoint") .arg(Arg::with_name("endpoint_name").required(true)) ) .subcommand( SubCommand::with_name("test") .about("Adds a new test") .arg(Arg::with_name("test_name").required(true)) ) .subcommand( SubCommand::with_name("md") .about("Adds a new markdown file") .arg(Arg::with_name("file_name").required(true)) ) .subcommand( SubCommand::with_name("static") .about("Adds a new static file") .arg(Arg::with_name("file_name").required(true)) ) ) .get_matches(); match app.subcommand() { ("init", Some(init_matches)) => { let project_name = init_matches.value_of("project_name").unwrap(); println!("Creating new Zipity project: {}", project_name); // Create the project directory fs::create_dir(project_name).expect("Failed to create project directory"); // Create the README.md file inside the project directory let readme_path = Path::new(project_name).join("README.md"); let mut readme_file = File::create(&readme_path).expect( "Failed to create README.md file" ); readme_file .write_all( b"# My Zipity Project\n\nWelcome to my Zipity project!\n\n## Usage\n\n```bash\n./cli/zipity [OPTIONS]\n```\n\n## Help\n\n```text\nZipity 0.0.1-alpha.2\nEric David Smith\nA command-line tool for Zipity\n\nUSAGE:\n cli [SUBCOMMAND]\n\nFLAGS:\n -h, --help Prints help information\n -V, --version Prints version information\n\nSUBCOMMANDS:\n add Adds a new component to the project\n build\n help Prints this message or the help of the given subcommand(s)\n init\n serve\n```" ) .expect("Failed to write to README.md"); // Copy static assets to the project directory let static_dir = Path::new("static"); let destination_dir = Path::new(project_name).join("static"); copy_directory(static_dir, destination_dir).expect("Failed to copy static assets"); // Create the .gitignore file let gitignore_path = Path::new(project_name).join(".gitignore"); let mut gitignore_file = File::create(&gitignore_path).expect( "Failed to create .gitignore file" ); gitignore_file.write_all(b"/target\n/out\n").expect("Failed to write to .gitignore"); // Copy the template.html file to the project directory let template_path = Path::new("template.html"); let destination_path = Path::new(project_name).join("template.html"); fs::copy(template_path, destination_path).expect("Failed to copy template.html"); // Run 'git init' in the project directory Command::new("git") .arg("init") .arg(project_name) .output() .expect("Failed to run 'git init'"); println!("New Zipity project created: {}", project_name); // Create the routes directory let routes_dir_path = Path::new(project_name).join("routes"); fs::create_dir(&routes_dir_path).expect("Failed to create routes directory"); // Create the index.md file in the routes directory let current_date = Local::now(); let formatted_date = format!( "{}-{:02}-{:02}", current_date.year(), current_date.month(), current_date.day() ); let route_slug = slugify("Index"); let index_md_path = routes_dir_path.join(format!("{}.md", route_slug)); let mut index_md_file = File::create(&index_md_path).expect( "Failed to create index.md file" ); let index_md_content = format!( r#"--- title: "Index" slug: "{}" date: "{}" description: "This is a description of my post" keywords: "keyword1, keyword2, keyword3" author: "Author Name" --- # Index This is a placeholder for the Index route. "#, route_slug, formatted_date ); index_md_file .write_all(index_md_content.as_bytes()) .expect("Failed to write to index.md file"); println!("Created index.md file at {}", index_md_path.to_string_lossy()); } ("build", Some(_)) => { println!("You ran 'build' command"); } ("serve", Some(_)) => { println!("You ran 'serve' command"); let host = "127.0.0.1"; let port = 8080; println!("Server is running on http://{}:{}", host, port); let output = Command::new("cargo") .args(&["run", "--bin", "zipity"]) .output() .expect("Failed to start server"); if !output.status.success() { eprintln!("Error: {:?}", output); } } ("add", Some(add_matches)) => { match add_matches.subcommand() { ("route", Some(matches)) => { let route_name = matches.value_of("route_name").unwrap(); println!("You ran 'add route' command with route name '{}'", route_name); let current_date = Local::now(); let formatted_date = format!( "{}-{:02}-{:02}", current_date.year(), current_date.month(), current_date.day() ); let route_slug = slugify(route_name); let route_file_path = Path::new("routes").join(format!("{}.md", route_slug)); let mut route_file = File::create(&route_file_path).expect( "Failed to create route file" ); let template = format!( r#"--- title: "{}" slug: "{}" date: "{}" description: "This is a description of my post" keywords: "keyword1, keyword2, keyword3" author: "Author Name" --- # {} This is a placeholder for the {} route. "#, route_name, route_slug, formatted_date, route_name, route_name ); route_file .write_all(template.as_bytes()) .expect("Failed to write to route file"); println!("Created route file at {}", route_file_path.to_string_lossy()); } ("api", Some(matches)) => { let api_name = matches.value_of("endpoint_name").unwrap(); println!("You ran 'add api' command with API name '{}'", api_name); let api_slug = slugify(api_name); // Create the 'api' directory if it doesn't exist fs::create_dir_all("api").expect("Failed to create 'api' directory"); let api_file_path = Path::new("api").join(format!("{}.rs", api_slug)); let mut api_file = File::create(&api_file_path).expect( "Failed to create API file" ); let template = format!( r#"use actix_web::{{web, HttpResponse, Responder}}; pub async fn {}() -> impl Responder {{ HttpResponse::Ok().body("This is the {} API endpoint") }} "#, api_slug, api_name ); api_file.write_all(template.as_bytes()).expect("Failed to write to API file"); println!("Created API file at {}", api_file_path.to_string_lossy()); } ("test", Some(matches)) => { let test_name = matches.value_of("test_name").unwrap(); println!("You ran 'add test' command with test name '{}'", test_name); // Here is where you would add the logic to actually create the new test } ("md", Some(matches)) => { let file_name = matches.value_of("file_name").unwrap(); println!("You ran 'add md' command with file name '{}'", file_name); // Here is where you would add the logic to actually create the new markdown file } ("static", Some(matches)) => { let file_name = matches.value_of("file_name").unwrap(); println!("You ran 'add static' command with file name '{}'", file_name); // Here is where you would add the logic to actually create the new static file } _ => { println!("Unknown 'add' subcommand"); } } } _ => { println!("Unknown command"); } } } // Helper function to copy a directory recursively fn copy_directory(source: &Path, destination: PathBuf) -> std::io::Result<()> { if source.is_dir() { fs::create_dir_all(&destination)?; for entry in fs::read_dir(source)? { let entry = entry?; let entry_path = entry.path(); let dest_path = destination.join(entry.file_name()); if entry_path.is_dir() { copy_directory(&entry_path, dest_path)?; } else { fs::copy(&entry_path, &dest_path)?; } } } Ok(()) }