use std::{ env::set_current_dir, path::{Path, PathBuf}, process::Command, }; use colored::Colorize; use glob::glob; const MAIN_C: &str = include_str!("templates/main.c"); const GITIGNORE: &str = include_str!("templates/gitignoretemplate"); #[derive(clap::Parser)] #[clap(version)] pub struct App { #[clap(subcommand)] command: Subcommand, } #[derive(clap::Subcommand)] enum Subcommand { /// Create a new C project New { /// The name of the new project name: String, }, /// Initialize a new C project Init, /// Run the local project Run { /// The build mode to use mode: Option, /// Arguments to pass to the project binary #[arg(long, short)] args: Option>, }, /// Build the local project Build { /// The build mode to use mode: Option, }, /// Clean all in progress files Clean, } impl App { pub fn run(self) { match self.command { Subcommand::New { name } => match create_project(&name) { Err(e) => { eprintln!("Error creating project '{name}': {e}"); std::process::exit(1); } _ => {} }, Subcommand::Init => match create_project(".") { Err(e) => { eprintln!("Error initializing project: {e}"); std::process::exit(1); } _ => {} }, Subcommand::Run { mode, args } => { match build(&mode) { Err(e) => { eprintln!("Error building project: {e}"); std::process::exit(1); } _ => {} }; if let Err(e) = run(&mode, args) { eprintln!("Error running project: {e}"); std::process::exit(1); } } Subcommand::Build { mode } => match build(&mode) { Err(e) => { eprintln!("Error building project: {e}"); std::process::exit(1); } _ => {} }, Subcommand::Clean => match clean() { Err(e) => { eprintln!("Error cleaning project: {e}"); std::process::exit(1); } _ => {} }, } } } fn create_project>(directory: P) -> std::io::Result<()> { let name = if directory.as_ref().to_string_lossy() == "." { String::new() } else { format!(" '{}'", directory.as_ref().to_string_lossy()) }; println!( " {} binary (application){}", "Creating".green().bold(), name ); let pathdir = directory.as_ref(); if pathdir.exists() && pathdir.to_string_lossy() != "." { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, "specified directory already exists", )); } if !pathdir.exists() && pathdir.to_string_lossy() != "." { std::fs::create_dir(pathdir)?; } set_current_dir(pathdir)?; std::fs::create_dir("src")?; std::fs::write("src/main.c", MAIN_C)?; std::fs::write(".gitignore", GITIGNORE)?; let config = crate::config::Config::new(&pathdir.to_string_lossy()); let serial = toml::to_string_pretty(&config).expect("a valid TOML structure"); std::fs::write("Pallet.toml", &serial)?; Ok(()) } fn get_config() -> Option { let p = PathBuf::from("Pallet.toml"); if !p.exists() { return None; } let raw = std::fs::read_to_string(&p).expect("A valid config file"); toml::from_str(&raw).ok() } fn build(mode: &Option) -> std::io::Result<()> { let conf = match get_config() { Some(conf) => conf, None => { eprintln!("Error opening config file. No Pallet.toml in current directory."); std::process::exit(1); } }; let build_config = conf.get_or_default(mode).ok_or(std::io::Error::new( std::io::ErrorKind::NotFound, "build layout not found", ))?; println!( " {} '{}' profile for project '{}'", "Building".green().bold(), build_config.name, conf.name ); let start = std::time::Instant::now(); std::fs::create_dir_all(format!("target/{}", build_config.name))?; let mut command = Command::new("gcc"); for arg in &build_config.args { command.arg(arg); } for entry in glob("src/*.c") .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}").as_str()))? { if let Ok(path) = entry { command.arg(path.to_string_lossy().to_string()); } } command .arg("-o") .arg(format!("target/{}/{}", build_config.name, conf.name)); let mut child = command.spawn()?; child.wait()?; let stop = start.elapsed(); println!( " {} '{}' profile for project '{}' in {:.2}s", "Finished".green().bold(), build_config.name, conf.name, stop.as_secs_f64() ); Ok(()) } fn run(mode: &Option, args: Option>) -> std::io::Result<()> { let conf = match get_config() { Some(conf) => conf, None => { eprintln!("Error opening config file. No Pallet.toml in current directory."); std::process::exit(1); } }; let build_config = conf.get_or_default(mode).ok_or(std::io::Error::new( std::io::ErrorKind::NotFound, "build layout not found", ))?; println!( " {} '{}' profile for project '{}'", "Running".green().bold(), build_config.name, conf.name ); let mut command = Command::new(format!("target/{}/{}", build_config.name, conf.name)); if let Some(args) = args { for arg in args { command.arg(arg); } } let mut child = command.spawn()?; child.wait()?; Ok(()) } fn clean() -> std::io::Result<()> { if let None = get_config() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, "no Pallet.toml found in local dir", )); } std::fs::remove_dir_all("target/")?; println!( " {} removed files in target/ directory", "Successfully".green().bold() ); Ok(()) }