Files
pallet/src/app.rs

315 lines
8.3 KiB
Rust
Raw Normal View History

2026-03-22 11:16:14 -05:00
use std::{
env::set_current_dir,
path::{Path, PathBuf},
2026-03-22 18:04:40 -05:00
process::Command,
2026-03-22 11:16:14 -05:00
};
use clap::CommandFactory;
2026-03-22 18:17:51 -05:00
use colored::Colorize;
2026-03-22 18:04:40 -05:00
use glob::glob;
2026-03-22 11:16:14 -05:00
const MAIN_C: &str = include_str!("templates/main.c");
const GITIGNORE: &str = "target/";
2026-03-22 11:16:14 -05:00
#[derive(clap::Parser)]
#[clap(version, about)]
2026-03-22 11:16:14 -05:00
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<String>,
2026-03-22 18:04:40 -05:00
/// Arguments to pass to the project binary
#[arg(long, short)]
2026-03-22 18:04:40 -05:00
args: Option<Vec<String>>,
2026-03-22 11:16:14 -05:00
},
/// Build the local project
Build {
/// The build mode to use
mode: Option<String>,
},
2026-03-22 18:22:27 -05:00
/// Clean all in progress files
Clean,
/// Utility functions
Utils {
#[clap(subcommand)]
command: UtilSubcommand,
},
}
#[derive(clap::Subcommand)]
enum UtilSubcommand {
/// Generate shell completions
Completions {
/// The shell to generate completions for
#[arg(value_enum, long, short)]
shell: ShellCompletions,
},
}
#[derive(Clone, clap::ValueEnum)]
enum ShellCompletions {
Bash,
Fish,
PowerShell,
Zsh,
2026-03-22 11:16:14 -05:00
}
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);
}
2026-03-22 18:17:51 -05:00
_ => {}
2026-03-22 11:16:14 -05:00
},
Subcommand::Init => match create_project(".") {
Err(e) => {
eprintln!("Error initializing project: {e}");
std::process::exit(1);
}
2026-03-22 18:17:51 -05:00
_ => {}
2026-03-22 11:16:14 -05:00
},
2026-03-22 18:04:40 -05:00
Subcommand::Run { mode, args } => {
match build(&mode) {
Err(e) => {
eprintln!("Error building project: {e}");
std::process::exit(1);
}
2026-03-22 18:17:51 -05:00
_ => {}
2026-03-22 18:04:40 -05:00
};
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);
}
2026-03-22 18:17:51 -05:00
_ => {}
2026-03-22 18:04:40 -05:00
},
2026-03-22 18:22:27 -05:00
Subcommand::Clean => match clean() {
Err(e) => {
eprintln!("Error cleaning project: {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Utils { command } => match command {
UtilSubcommand::Completions { shell } => {
let name = env!("CARGO_PKG_NAME");
let mut command = App::command();
match shell {
ShellCompletions::Bash => clap_complete::generate(
clap_complete::shells::Bash,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::Fish => clap_complete::generate(
clap_complete::shells::Fish,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::PowerShell => clap_complete::generate(
clap_complete::shells::PowerShell,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::Zsh => clap_complete::generate(
clap_complete::shells::Zsh,
&mut command,
name,
&mut std::io::stdout(),
),
}
}
},
2026-03-22 11:16:14 -05:00
}
}
}
fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
2026-03-22 18:17:51 -05:00
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
);
2026-03-22 11:16:14 -05:00
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)?;
2026-03-22 18:22:27 -05:00
std::fs::write(".gitignore", GITIGNORE)?;
2026-03-22 11:16:14 -05:00
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<crate::config::Config> {
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()
}
2026-03-22 18:04:40 -05:00
fn build(mode: &Option<String>) -> std::io::Result<()> {
2026-03-22 11:16:14 -05:00
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",
))?;
2026-03-22 18:17:51 -05:00
println!(
" {} '{}' profile for project '{}'",
"Building".green().bold(),
build_config.name,
conf.name
);
let start = std::time::Instant::now();
2026-03-22 18:04:40 -05:00
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()?;
2026-03-22 18:17:51 -05:00
let stop = start.elapsed();
println!(
" {} '{}' profile for project '{}' in {:.2}s",
"Finished".green().bold(),
build_config.name,
conf.name,
stop.as_secs_f64()
);
2026-03-22 18:04:40 -05:00
Ok(())
}
fn run(mode: &Option<String>, args: Option<Vec<String>>) -> 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",
))?;
2026-03-22 18:17:51 -05:00
println!(
" {} '{}' profile for project '{}'",
"Running".green().bold(),
build_config.name,
conf.name
);
2026-03-22 18:04:40 -05:00
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(())
2026-03-22 11:16:14 -05:00
}
2026-03-22 18:22:27 -05:00
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(())
}