Replace manual CLI parsing with clap derive
- Add clap dependency with derive feature for structured CLI argument parsing - Replace env::args() manual matching with clap subcommands (fetch, best, missing, wishlist, trash, details, track) - Change print_best_car() signature from (&String, &String, &String) to (&str, &str, u8) for idiomatic Rust - Add TODO.md tracking known tech debt and planned features - Update CHANGELOG.md with clap migration details and set v0.2.0 release date
This commit is contained in:
parent
89fa69e3c8
commit
08c96d9981
5 changed files with 227 additions and 99 deletions
189
src/records.rs
189
src/records.rs
|
|
@ -28,10 +28,10 @@
|
|||
//! - `futures`: Future utilities for handling async operations
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::task;
|
||||
|
||||
|
|
@ -48,22 +48,45 @@ use top_drives::{
|
|||
/// Used as the upper bound when iterating RQ ranges and as a fallback for parse errors.
|
||||
const MAX_RQ: u8 = 113;
|
||||
|
||||
/// Command-line interface for the Top Drives Records Analyzer.
|
||||
#[derive(Parser)]
|
||||
#[command(name = "records", about = "Top Drives Records Analyzer", version)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
/// Available subcommands for car performance analysis.
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Download latest car and track data from repository
|
||||
Fetch,
|
||||
/// Show best performing cars in your collection
|
||||
Best,
|
||||
/// Show best cars not owned by you (prefix + if not on wishlist)
|
||||
Missing,
|
||||
/// Show wishlist cars that are no longer among the best
|
||||
Wishlist,
|
||||
/// Identify cars that can be safely removed from collection
|
||||
Trash,
|
||||
/// Show detailed timing data for all tracks
|
||||
Details,
|
||||
/// Show best car for a specific track/condition/RQ combination
|
||||
Track {
|
||||
/// Track name (e.g. "Laguna Seca")
|
||||
track: String,
|
||||
/// Track condition (e.g. "Dry", "Wet")
|
||||
condition: String,
|
||||
/// RQ limit (e.g. 85)
|
||||
rq: u8,
|
||||
},
|
||||
}
|
||||
|
||||
/// Main entry point for the Top Drives Records Analyzer
|
||||
///
|
||||
/// Parses command line arguments and executes the requested analysis operation.
|
||||
/// Parses command line arguments using `clap` and executes the requested analysis operation.
|
||||
/// Supports multiple subcommands for different types of car performance analysis.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// The first argument specifies the operation to perform:
|
||||
/// - `fetch`: Download latest car and track data from repository
|
||||
/// - `best`: Show best performing cars in user's collection
|
||||
/// - `missing`: Show best cars not owned by the user (prefix `+` if not on wishlist)
|
||||
/// - `trash`: Identify cars that can be safely removed from collection
|
||||
/// - `wishlist`: Show wishlist cars that are no longer among the best
|
||||
/// - `details`: Show detailed timing data for all tracks
|
||||
/// - `track`: Show best car for specific track/condition/RQ combination
|
||||
///
|
||||
/// # Example Usage
|
||||
///
|
||||
/// ```bash
|
||||
|
|
@ -72,85 +95,63 @@ const MAX_RQ: u8 = 113;
|
|||
/// ```
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args: Vec<_> = env::args().collect();
|
||||
let cli = Cli::parse();
|
||||
|
||||
match args.get(1) {
|
||||
None => {
|
||||
println!("Please provide a command.")
|
||||
match cli.command {
|
||||
Commands::Fetch => {
|
||||
// Download phase: Get fresh data from all sources
|
||||
// This is the foundation for all other operations
|
||||
get_cars_file().await;
|
||||
get_tracks_file().await;
|
||||
fetch_times().await;
|
||||
}
|
||||
Some(argument) => {
|
||||
let command = argument.as_str();
|
||||
match command {
|
||||
"fetch" => {
|
||||
// Download phase: Get fresh data from all sources
|
||||
// This is the foundation for all other operations
|
||||
get_cars_file().await;
|
||||
get_tracks_file().await;
|
||||
fetch_times().await;
|
||||
}
|
||||
"best" => {
|
||||
// Standard best cars analysis
|
||||
// Shows cars you own that perform well across tracks
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let _car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_cars(&ctx, false, &my_data.wishlist); // false = include owned cars
|
||||
}
|
||||
"missing" => {
|
||||
// Missing cars analysis
|
||||
// Shows high-performing cars you don't own
|
||||
// Cars not yet on the wishlist are prefixed with "+"
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let _car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_cars(&ctx, true, &my_data.wishlist); // true = filter out owned cars
|
||||
}
|
||||
"wishlist" => {
|
||||
// Wishlist review
|
||||
// Shows cars on the wishlist that are no longer among the best
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_wishlist_stale(&ctx, &my_data.wishlist, &car_list);
|
||||
}
|
||||
"trash" => {
|
||||
// Collection optimization
|
||||
// Identifies cars that can be safely removed
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_trash(&ctx, &car_list, &my_data.owned);
|
||||
}
|
||||
"details" => {
|
||||
// Comprehensive data export
|
||||
// Outputs all timing data for external analysis
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_times(&ctx);
|
||||
}
|
||||
"track" => {
|
||||
// Track-specific analysis
|
||||
// Requires exactly 3 additional arguments
|
||||
if args.get(4).is_none() {
|
||||
println!("Track name, condition and RQ limit required.");
|
||||
} else {
|
||||
// Safe to unwrap here due to above check
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
parse_all_times(&my_data.owned, &ctx).await;
|
||||
let track = args.get(2).unwrap();
|
||||
let condition = args.get(3).unwrap();
|
||||
let rq = args.get(4).unwrap();
|
||||
print_best_car(&ctx, track, condition, rq);
|
||||
}
|
||||
}
|
||||
&_ => {
|
||||
// Unknown command handler
|
||||
// Provides feedback for typos or outdated commands
|
||||
println!("Unknown command '{}'.", argument)
|
||||
}
|
||||
}
|
||||
Commands::Best => {
|
||||
// Standard best cars analysis
|
||||
// Shows cars you own that perform well across tracks
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let _car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_cars(&ctx, false, &my_data.wishlist); // false = include owned cars
|
||||
}
|
||||
Commands::Missing => {
|
||||
// Missing cars analysis
|
||||
// Shows high-performing cars you don't own
|
||||
// Cars not yet on the wishlist are prefixed with "+"
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let _car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_cars(&ctx, true, &my_data.wishlist); // true = filter out owned cars
|
||||
}
|
||||
Commands::Wishlist => {
|
||||
// Wishlist review
|
||||
// Shows cars on the wishlist that are no longer among the best
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_wishlist_stale(&ctx, &my_data.wishlist, &car_list);
|
||||
}
|
||||
Commands::Trash => {
|
||||
// Collection optimization
|
||||
// Identifies cars that can be safely removed
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
let car_list = parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_trash(&ctx, &car_list, &my_data.owned);
|
||||
}
|
||||
Commands::Details => {
|
||||
// Comprehensive data export
|
||||
// Outputs all timing data for external analysis
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_times(&ctx);
|
||||
}
|
||||
Commands::Track { track, condition, rq } => {
|
||||
// Track-specific analysis
|
||||
let my_data = load_my_cars_data();
|
||||
let ctx = AnalysisContext::new();
|
||||
parse_all_times(&my_data.owned, &ctx).await;
|
||||
print_best_car(&ctx, &track, &condition, rq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -392,12 +393,12 @@ fn print_best_cars(ctx: &AnalysisContext, filter_out_owned: bool, wishlist: &Has
|
|||
/// # Note
|
||||
/// If no car is found for the specified combination, it may indicate that no data
|
||||
/// is available for that specific track/condition/RQ combination.
|
||||
fn print_best_car(ctx: &AnalysisContext, track: &String, condition: &String, rq: &String) {
|
||||
fn print_best_car(ctx: &AnalysisContext, track: &str, condition: &str, rq: u8) {
|
||||
let best_cars = ctx.best_cars.lock().unwrap();
|
||||
let key = (
|
||||
track.clone(),
|
||||
rq.parse::<u8>().unwrap_or(MAX_RQ), // Default to max RQ if parsing fails
|
||||
condition.clone(),
|
||||
track.to_string(),
|
||||
rq,
|
||||
condition.to_string(),
|
||||
);
|
||||
|
||||
if let Some(best_list) = best_cars.get(&key) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue