use clap::builder::Styles;
use clap::builder::styling::Color::Ansi;
use clap::builder::styling::{
AnsiColor::{Blue, Cyan, Green, Red, Yellow},
Style,
};
use clap::{Parser, Subcommand, ValueEnum};
use std::fmt::Display;
use std::path::PathBuf;
pub const CLAP_STYLE: Styles = Styles::styled()
.header(Style::new().bold().fg_color(Some(Ansi(Green))))
.usage(Style::new().bold().fg_color(Some(Ansi(Green))))
.literal(Style::new().fg_color(Some(Ansi(Blue))).bold())
.placeholder(Style::new().fg_color(Some(Ansi(Cyan))))
.error(Style::new().fg_color(Some(Ansi(Red))).bold())
.valid(Style::new().fg_color(Some(Ansi(Green))))
.invalid(Style::new().fg_color(Some(Ansi(Yellow))));
#[derive(Debug, Clone, Parser)]
#[command(version, about, long_about, styles=CLAP_STYLE)]
pub struct AmazeingArgs {
#[clap(subcommand)]
pub command: ArgCommand,
#[clap(global = true, long, short = 'Z', display_order = 101, default_value_t = 1f32)]
pub zoom: f32,
#[clap(global = true, long, short = 'C', display_order = 102, value_name = "Colors.toml")]
pub colors: Option<PathBuf>,
#[clap(global = true, long, short = 'F', display_order = 103, default_value_t = 60.)]
pub fps: f32,
#[clap(global = true, long, short = 'P', display_order = 103, default_value_t = false)]
pub show_perimeter: bool,
}
#[derive(Debug, Clone, PartialEq, Subcommand)]
pub enum ArgCommand {
#[clap(visible_alias = "C")]
Create(CreateArgs),
#[clap(visible_alias = "V")]
View(ViewArgs),
#[clap(visible_alias = "S")]
Solve(SolveArgs),
}
#[derive(Debug, Clone, PartialEq, Parser)]
pub struct CreateArgs {
#[clap(global = true, long, short, default_value_t = ArgUnitShape::default(), value_name = "UnitShape")]
pub unit_shape: ArgUnitShape,
#[clap(global = true, long, short)]
pub maze: Option<PathBuf>,
#[clap(long, short)]
pub rows: usize,
#[clap(long, short)]
pub cols: usize,
#[clap(global = true, long, short, default_value_t = ArgProcedure::Dfs)]
pub procedure: ArgProcedure,
#[clap(global = true, long, short = 'H', default_value_t = ArgHeuristic::Dijkstra, required_if_eq("procedure", "a-star"))]
pub heuristic_function: ArgHeuristic,
#[clap(global = true, long, short, default_value_t = 2)]
pub jumble_factor: u32,
#[clap(global = true, long, short, default_value_t = ArgWeightDirection::default())]
pub weight_direction: ArgWeightDirection,
#[clap(global = true, long, short, default_value_t = false)]
pub verbose: bool,
}
#[derive(Debug, Clone, PartialEq, Parser)]
pub struct ViewArgs {
#[clap(long, short)]
pub maze: PathBuf,
#[clap(long, short, default_value_t = false)]
pub update: bool,
}
#[derive(Debug, Clone, PartialEq, Parser)]
pub struct SolveArgs {
#[clap(long, short)]
pub maze: PathBuf,
#[clap(long, short, default_value_t = ArgProcedure::Dfs)]
pub procedure: ArgProcedure,
#[clap(long, short = 'H', default_value_t = ArgHeuristic::default(), required_if_eq("procedure", "a-star"))]
pub heuristic_function: ArgHeuristic,
#[clap(long, short, default_value_t = false)]
pub verbose: bool,
}
#[derive(Debug, Clone, PartialEq, ValueEnum, Default)]
pub enum ArgUnitShape {
#[clap(alias = "t")]
Triangle,
#[clap(alias = "s")]
Square,
#[clap(alias = "r")]
Rhombus,
#[default]
#[clap(alias = "h")]
Hexagon,
#[clap(alias = "hr")]
HexagonRectangle,
#[clap(alias = "o")]
Octagon,
#[clap(alias = "os")]
OctagonSquare,
}
impl Display for ArgUnitShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArgUnitShape::Triangle => write!(f, "triangle"),
ArgUnitShape::Square => write!(f, "square"),
ArgUnitShape::Rhombus => write!(f, "rhombus"),
ArgUnitShape::Hexagon => write!(f, "hexagon"),
ArgUnitShape::HexagonRectangle => write!(f, "hexagon-rectangle"),
ArgUnitShape::Octagon => write!(f, "octagon"),
ArgUnitShape::OctagonSquare => write!(f, "octagon-square"),
}
}
}
#[derive(Debug, Clone, PartialEq, ValueEnum, Default)]
pub enum ArgWeightDirection {
#[clap(alias = "f")]
Forward,
#[clap(alias = "b")]
#[default]
Backward,
}
impl Display for ArgWeightDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArgWeightDirection::Forward => write!(f, "forward"),
ArgWeightDirection::Backward => write!(f, "backward"),
}
}
}
#[derive(Debug, Clone, PartialEq, ValueEnum, Default)]
pub enum ArgProcedure {
#[clap(alias = "b")]
Bfs,
#[default]
#[clap(alias = "d")]
Dfs,
#[clap(alias = "p")]
Prim,
#[clap(alias = "i")]
Iddfs,
#[clap(alias = "gbf")]
GreedyBestFirst,
#[clap(alias = "bb")]
BidirectionalBfs,
#[clap(alias = "bs")]
BeamSearch,
#[clap(alias = "bgbf")]
BidirectionalGreedyBestFirst,
#[clap(alias = "sas")]
SimulatedAnnealingSearch,
#[clap(alias = "ab")]
AldousBroder,
#[clap(alias = "ba")]
BidirectionalAStart,
#[clap(alias = "a")]
AStar,
}
impl Display for ArgProcedure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArgProcedure::Bfs => write!(f, "bfs"),
ArgProcedure::Dfs => write!(f, "dfs"),
ArgProcedure::Prim => write!(f, "prim"),
ArgProcedure::Iddfs => write!(f, "iddfs"),
ArgProcedure::GreedyBestFirst => write!(f, "greedy-best-first"),
ArgProcedure::BidirectionalBfs => write!(f, "bidirectional-bfs"),
ArgProcedure::BeamSearch => write!(f, "beam-search"),
ArgProcedure::BidirectionalGreedyBestFirst => write!(f, "bidirectional-greedy-best-first"),
ArgProcedure::SimulatedAnnealingSearch => write!(f, "simulated-annealing-search"),
ArgProcedure::AldousBroder => write!(f, "aldous-broder"),
ArgProcedure::BidirectionalAStart => write!(f, "bidirectional-a-start"),
ArgProcedure::AStar => write!(f, "a-star"),
}
}
}
#[derive(Debug, Clone, PartialEq, ValueEnum, Default)]
pub enum ArgHeuristic {
#[clap(alias = "m")]
Manhattan,
#[clap(alias = "e")]
Euclidean,
#[clap(alias = "c")]
Chebyshev,
#[clap(alias = "o")]
Octile,
#[default]
#[clap(alias = "d")]
Dijkstra,
}
impl Display for ArgHeuristic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArgHeuristic::Manhattan => write!(f, "manhattan"),
ArgHeuristic::Euclidean => write!(f, "euclidean"),
ArgHeuristic::Chebyshev => write!(f, "chebyshev"),
ArgHeuristic::Octile => write!(f, "octile"),
ArgHeuristic::Dijkstra => write!(f, "dijkstra"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
use std::path::Path;
#[test]
fn parse_create_with_defaults() {
let parsed = AmazeingArgs::try_parse_from(["amazeing", "create", "--rows", "9", "--cols", "11"])
.expect("create args should parse");
assert_eq!(parsed.zoom, 1.0);
assert_eq!(parsed.fps, 60.0);
assert!(!parsed.show_perimeter);
match parsed.command {
ArgCommand::Create(create) => {
assert_eq!(create.rows, 9);
assert_eq!(create.cols, 11);
assert_eq!(create.procedure, ArgProcedure::Dfs);
assert_eq!(create.heuristic_function, ArgHeuristic::Dijkstra);
assert!(!create.verbose);
}
_ => panic!("expected create command"),
}
}
#[test]
fn parse_solve_with_global_flags() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"--zoom",
"1.5",
"--fps",
"24",
"solve",
"--maze",
"assets/maze/001_005_005_square.maze",
"--procedure",
"a-star",
"-H",
"manhattan",
])
.expect("solve args should parse");
assert_eq!(parsed.zoom, 1.5);
assert_eq!(parsed.fps, 24.0);
match parsed.command {
ArgCommand::Solve(solve) => {
assert_eq!(solve.procedure, ArgProcedure::AStar);
assert_eq!(solve.heuristic_function, ArgHeuristic::Manhattan);
assert_eq!(solve.maze, Path::new("assets/maze/001_005_005_square.maze"));
}
_ => panic!("expected solve command"),
}
}
#[test]
fn parse_create_with_aldous_broder_procedure() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"create",
"--rows",
"9",
"--cols",
"11",
"--procedure",
"aldous-broder",
])
.expect("create args should parse for aldous-broder");
match parsed.command {
ArgCommand::Create(create) => {
assert_eq!(create.procedure, ArgProcedure::AldousBroder);
}
_ => panic!("expected create command"),
}
}
#[test]
fn parse_solve_with_bidirectional_a_start_procedure() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"solve",
"--maze",
"assets/maze/001_005_005_square.maze",
"--procedure",
"bidirectional-a-start",
])
.expect("solve args should parse for bidirectional-a-start");
match parsed.command {
ArgCommand::Solve(solve) => {
assert_eq!(solve.procedure, ArgProcedure::BidirectionalAStart);
}
_ => panic!("expected solve command"),
}
}
#[test]
fn parse_create_with_prim_procedure() {
let parsed =
AmazeingArgs::try_parse_from(["amazeing", "create", "--rows", "9", "--cols", "11", "--procedure", "prim"])
.expect("create args should parse for prim");
match parsed.command {
ArgCommand::Create(create) => {
assert_eq!(create.procedure, ArgProcedure::Prim);
}
_ => panic!("expected create command"),
}
}
#[test]
fn parse_solve_with_greedy_best_first_procedure() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"solve",
"--maze",
"assets/maze/001_005_005_square.maze",
"--procedure",
"greedy-best-first",
])
.expect("solve args should parse for greedy-best-first");
match parsed.command {
ArgCommand::Solve(solve) => {
assert_eq!(solve.procedure, ArgProcedure::GreedyBestFirst);
}
_ => panic!("expected solve command"),
}
}
#[test]
fn parse_create_with_beam_search_procedure() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"create",
"--rows",
"9",
"--cols",
"11",
"--procedure",
"beam-search",
])
.expect("create args should parse for beam-search");
match parsed.command {
ArgCommand::Create(create) => assert_eq!(create.procedure, ArgProcedure::BeamSearch),
_ => panic!("expected create command"),
}
}
#[test]
fn parse_solve_with_bidirectional_greedy_best_first_procedure() {
let parsed = AmazeingArgs::try_parse_from([
"amazeing",
"solve",
"--maze",
"assets/maze/001_005_005_square.maze",
"--procedure",
"bidirectional-greedy-best-first",
])
.expect("solve args should parse for bidirectional-greedy-best-first");
match parsed.command {
ArgCommand::Solve(solve) => {
assert_eq!(solve.procedure, ArgProcedure::BidirectionalGreedyBestFirst);
}
_ => panic!("expected solve command"),
}
}
}