Files
anvil/src/main.rs
2026-02-19 10:23:16 -06:00

250 lines
7.5 KiB
Rust

use clap::{Parser, Subcommand};
use colored::*;
use anyhow::Result;
use anvil::version::ANVIL_VERSION;
mod commands {
pub use anvil::commands::*;
}
#[derive(Parser)]
#[command(name = "anvil")]
#[command(author = "Eric Ratliff <eric@nxlearn.net>")]
#[command(version = ANVIL_VERSION)]
#[command(
about = "Arduino project generator and build tool - forges clean embedded projects",
long_about = None
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Create a new Arduino project
New {
/// Project name
name: Option<String>,
/// Template to use (basic)
#[arg(long, short = 't', value_name = "TEMPLATE")]
template: Option<String>,
/// Board preset (uno, mega, nano, leonardo, micro)
#[arg(long, short = 'b', value_name = "BOARD")]
board: Option<String>,
/// List available templates
#[arg(long, conflicts_with = "name")]
list_templates: bool,
/// List available board presets
#[arg(long, conflicts_with = "name")]
list_boards: bool,
},
/// Check system health and diagnose issues
Doctor {
/// Automatically install missing tools
#[arg(long)]
fix: bool,
},
/// Install arduino-cli and required cores
Setup,
/// List connected boards and serial ports
Devices {
/// Save a port to .anvil.local for this project
#[arg(long, conflicts_with_all = ["get", "clear"])]
set: bool,
/// Show the saved port for this project
#[arg(long, conflicts_with_all = ["set", "clear"])]
get: bool,
/// Remove .anvil.local (revert to auto-detect)
#[arg(long, conflicts_with_all = ["set", "get"])]
clear: bool,
/// Port name (e.g. COM3, /dev/ttyUSB0). Auto-detects if omitted with --set.
port_or_dir: Option<String>,
/// Path to project directory (defaults to current directory)
#[arg(long, short = 'd', value_name = "DIR")]
dir: Option<String>,
},
/// Update project scripts to the latest version
Refresh {
/// Path to project directory (defaults to current directory)
dir: Option<String>,
/// Overwrite scripts even if they have been modified
#[arg(long)]
force: bool,
},
/// Manage board profiles in .anvil.toml
Board {
/// Board name (e.g. mega, nano)
name: Option<String>,
/// Add a board to the project
#[arg(long, conflicts_with_all = ["remove", "listall", "default"])]
add: bool,
/// Remove a board from the project
#[arg(long, conflicts_with_all = ["add", "listall", "default"])]
remove: bool,
/// Set the default board
#[arg(long, conflicts_with_all = ["add", "remove", "listall"])]
default: bool,
/// Browse all available boards
#[arg(long, conflicts_with_all = ["add", "remove", "default"])]
listall: bool,
/// Board identifier (from anvil board --listall)
#[arg(long, value_name = "ID")]
id: Option<String>,
/// Baud rate override
#[arg(long, value_name = "RATE")]
baud: Option<u32>,
/// Path to project directory (defaults to current directory)
#[arg(long, short = 'd', value_name = "DIR")]
dir: Option<String>,
},
}
fn main() -> Result<()> {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).ok();
let cli = Cli::parse();
print_banner();
match cli.command {
Commands::New { name, template, board, list_templates, list_boards } => {
if list_boards {
commands::new::list_boards()
} else if list_templates {
commands::new::list_templates()
} else if let Some(project_name) = name {
commands::new::create_project(
&project_name,
template.as_deref(),
board.as_deref(),
)
} else {
anyhow::bail!(
"Project name required.\n\
Usage: anvil new <name>\n\
Usage: anvil new <name> --board mega\n\
List boards: anvil new --list-boards\n\
List templates: anvil new --list-templates"
);
}
}
Commands::Doctor { fix } => {
commands::doctor::run_diagnostics(fix)
}
Commands::Setup => {
commands::setup::run_setup()
}
Commands::Devices { set, get, clear, port_or_dir, dir } => {
if set {
commands::devices::set_port(
port_or_dir.as_deref(),
dir.as_deref(),
)
} else if get {
commands::devices::get_port(
dir.as_deref().or(port_or_dir.as_deref()),
)
} else if clear {
commands::devices::clear_port(
dir.as_deref().or(port_or_dir.as_deref()),
)
} else {
commands::devices::scan_devices()
}
}
Commands::Refresh { dir, force } => {
commands::refresh::run_refresh(
dir.as_deref(),
force,
)
}
Commands::Board { name, add, remove, default, listall, id, baud, dir } => {
if listall {
commands::board::listall_boards(name.as_deref())
} else if add {
let board_name = name.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"Board name required.\n\
Usage: anvil board --add mega\n\
Browse available boards: anvil board --listall"
)
})?;
commands::board::add_board(
board_name,
id.as_deref(),
baud,
dir.as_deref(),
)
} else if remove {
let board_name = name.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"Board name required.\n\
Usage: anvil board --remove mega"
)
})?;
commands::board::remove_board(
board_name,
dir.as_deref(),
)
} else if default {
let board_name = name.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"Board name required.\n\
Usage: anvil board --default uno\n\
List boards: anvil board"
)
})?;
commands::board::set_default_board(
board_name,
dir.as_deref(),
)
} else {
commands::board::list_boards(dir.as_deref())
}
}
}
}
fn print_banner() {
println!(
"{}",
"================================================================"
.bright_cyan()
);
println!(
"{}",
format!(" Anvil - Arduino Build Tool v{}", ANVIL_VERSION)
.bright_cyan()
.bold()
);
println!("{}", " Nexus Workshops LLC".bright_cyan());
println!(
"{}",
"================================================================"
.bright_cyan()
);
println!();
}