#![expect(
clippy::print_stderr,
reason = "bywind-cli writes user-facing messages to stderr"
)]
use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
mod convert;
mod display;
mod error;
mod fetch;
mod info;
mod inspect;
mod parsing;
mod search;
mod tune;
mod tune_trial;
#[derive(Parser, Debug)]
#[command(
name = "bywind-cli",
version,
about = "Native CLI for the bywind sailing-search library",
long_about = "Load wind maps (GRIB2 / wind_av1), set bounds and \
endpoints, run the sailing PSO search, and inspect saved \
solutions. See docs/bywind-cli-plan.md for the development \
roadmap."
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
#[expect(
clippy::large_enum_variant,
reason = "clap Subcommand parse target — allocated once in main, never \
duplicated or stored. The size imbalance is a one-shot stack \
cost paid only at startup."
)]
enum Command {
Convert {
input: PathBuf,
#[arg(long, short = 'o')]
out: PathBuf,
#[arg(long, value_name = "N")]
grib_stride: Option<usize>,
#[arg(long, value_name = "LAT0,LON0,LAT1,LON1")]
grib_bbox: Option<String>,
},
Info {
map: PathBuf,
},
Search(search::SearchArgs),
Fetch(fetch::FetchArgs),
Inspect {
solution: PathBuf,
#[arg(long)]
map: Option<PathBuf>,
},
TuneTrial,
Tune {
#[arg(long, default_value_t = 50)]
trials: usize,
#[arg(
long,
value_name = "PATH",
default_value = "profiling/tuning/baseline.json"
)]
baseline: PathBuf,
#[arg(long, value_name = "PATH", default_value = "profiling/tuning")]
routes_dir: PathBuf,
#[arg(
long,
value_name = "PATH",
default_value = "profiling/tuning/study.jsonl"
)]
journal: PathBuf,
#[arg(long, default_value_t = 0)]
sampler_seed: u64,
},
}
fn main() -> ExitCode {
let cli = Cli::parse();
let result: Result<(), error::AppError> = match cli.command {
Command::Convert {
input,
out,
grib_stride,
grib_bbox,
} => convert::run(&input, &out, grib_stride, grib_bbox.as_deref()),
Command::Fetch(args) => fetch::run(&args),
Command::Info { map } => info::run(&map),
Command::Search(args) => search::run(&args),
Command::Inspect { solution, map } => inspect::run(&solution, map.as_deref()),
Command::TuneTrial => tune_trial::run(),
Command::Tune {
trials,
baseline,
routes_dir,
journal,
sampler_seed,
} => tune::run(&tune::TuneArgs {
trials,
baseline,
routes_dir,
journal,
sampler_seed,
..tune::TuneArgs::defaults()
}),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e}");
e.exit_code()
}
}
}