- Add normalize_to_final_build() to map tune configs to final builds (323/233/332) - Add normalized_config field to BestCarRecord - Change CarCounts.by_rid key from String to (String, String) for per-config tracking - Change CarCounts.guid_to_rid to HashMap<String, HashSet<(String, String)>> - Extract owned tune configs from my_cars.json (engineMajor/weightMajor/chassisMajor) - Add improve command: owned cars not yet at final build, sorted by track count - Strip version prefixes (v2c_/v1c_) from config keys at parse time - Update dedup check in update_best_car to consider normalized_config - Add atomic_write helper for safe CSV output - Delete completed plans/code-review-v2.md - Apply rustfmt formatting across codebase
238 lines
7.8 KiB
Rust
238 lines
7.8 KiB
Rust
//! # Top Drives Records Analyzer
|
|
//!
|
|
//! A comprehensive Rust-based tool for analyzing car performance data in the Top Drives racing game.
|
|
//! This module provides the CLI entry point and delegates to submodules for parsing, output, and
|
|
//! command handling.
|
|
//!
|
|
//! ## Module Structure
|
|
//!
|
|
//! - [`commands`] — Command handlers (fetch, best, trash, wishlist, details, track)
|
|
//! - [`parsing`] — Core timing data parsing and car database loading
|
|
//! - [`output`] — CSV output utilities and sorting helpers
|
|
//!
|
|
//! ## Key Features
|
|
//!
|
|
//! - Fetches car and track data from GitHub repository
|
|
//! - Parses JSON timing files for performance analysis
|
|
//! - Identifies best performing cars across all tracks and conditions
|
|
//! - Compares user collection against optimal car lists
|
|
//! - Provides track-specific performance recommendations
|
|
//! - Supports multiple track conditions (Dry, Wet, Dirt, Snow, etc.)
|
|
//!
|
|
//! ## Data Flow
|
|
//!
|
|
//! 1. **Fetch Phase**: Downloads car database and track definitions
|
|
//! 2. **Parse Phase**: Processes JSON timing files for each car
|
|
//! 3. **Analysis Phase**: Compares times across RQ ranges and conditions
|
|
//! 4. **Output Phase**: Writes results to CSV files
|
|
//!
|
|
//! ## Dependencies
|
|
//!
|
|
//! - `reqwest`: HTTP client for fetching data from GitHub
|
|
//! - `serde_json`: JSON parsing for car and track data
|
|
//! - `tokio`: Asynchronous runtime for concurrent processing
|
|
//! - `futures`: Future utilities for handling async operations
|
|
//! - `clap`: Command-line argument parsing
|
|
|
|
mod commands;
|
|
mod output;
|
|
mod parsing;
|
|
|
|
use clap::{Parser, Subcommand};
|
|
|
|
use top_drives::AppError;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constants (CLI defaults)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Default path for the user's car collection export.
|
|
const DEFAULT_MY_CARS_PATH: &str = "my_cars.json";
|
|
|
|
/// Default path for the car database file.
|
|
const DEFAULT_CARS_DB_PATH: &str = "cars_repo.json";
|
|
|
|
/// Default directory for per-car timing JSON files.
|
|
const DEFAULT_TIMES_DIR: &str = "times";
|
|
|
|
/// Default path for the tracks database file.
|
|
const DEFAULT_TRACKS_DB_PATH: &str = "tracks_repo.json";
|
|
|
|
/// Default directory for output CSV files.
|
|
const DEFAULT_OUTPUT_DIR: &str = ".";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CLI definition
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Command-line interface for the Top Drives Records Analyzer.
|
|
#[derive(Parser)]
|
|
#[command(name = "records", about = "Top Drives Records Analyzer", version)]
|
|
struct Cli {
|
|
/// Path to the user's car collection export file
|
|
#[arg(long, default_value = DEFAULT_MY_CARS_PATH)]
|
|
my_cars: String,
|
|
|
|
/// Path to the car database file
|
|
#[arg(long, default_value = DEFAULT_CARS_DB_PATH)]
|
|
cars_db: String,
|
|
|
|
/// Directory containing per-car timing JSON files
|
|
#[arg(long, default_value = DEFAULT_TIMES_DIR)]
|
|
times_dir: String,
|
|
|
|
/// Path to the tracks database file
|
|
#[arg(long, default_value = DEFAULT_TRACKS_DB_PATH)]
|
|
tracks_db: String,
|
|
|
|
/// Directory for output CSV files
|
|
#[arg(long, default_value = DEFAULT_OUTPUT_DIR)]
|
|
output_dir: String,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
/// Available subcommands for car performance analysis.
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Download latest car and track data from repository
|
|
Fetch,
|
|
/// Generate best.csv and very_best.csv
|
|
Best {
|
|
/// Minimum number of tracks a car must be best on to appear in very_best.csv (default: 10)
|
|
#[arg(long, default_value_t = 10)]
|
|
min_count: usize,
|
|
},
|
|
/// Generate trash.csv (owned cars not best on any track)
|
|
Trash,
|
|
/// Generate wishlist.csv, add.csv, and remove.csv
|
|
Wishlist {
|
|
/// Minimum number of tracks a car must be best on to qualify (default: 10)
|
|
#[arg(long, default_value_t = 10)]
|
|
min_count: usize,
|
|
/// Maximum number of cars on the wishlist (default: 300)
|
|
#[arg(long, default_value_t = 300)]
|
|
max_wishlist: usize,
|
|
},
|
|
/// Generate all output files in a single pass
|
|
All {
|
|
/// Minimum number of tracks a car must be best on to qualify for very_best/wishlist (default: 10)
|
|
#[arg(long, default_value_t = 10)]
|
|
min_count: usize,
|
|
/// Maximum number of cars on the wishlist (default: 300)
|
|
#[arg(long, default_value_t = 300)]
|
|
max_wishlist: usize,
|
|
},
|
|
/// Generate details.csv with all timing data
|
|
Details,
|
|
/// Show best car for a specific track/condition/RQ combination
|
|
Track { track: String, condition: String, rq: u8 },
|
|
/// Generate improve.csv (owned cars not in final config, sorted by best tracks)
|
|
Improve,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main entry point
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Main entry point for the Top Drives Records Analyzer
|
|
///
|
|
/// Parses command line arguments using `clap` and executes the requested analysis operation.
|
|
/// Supports multiple subcommands for different types of car performance analysis.
|
|
///
|
|
/// # Example Usage
|
|
///
|
|
/// ```bash
|
|
/// cargo run --bin records -- best
|
|
/// cargo run --bin records -- track "Laguna Seca" "Dry" "85"
|
|
/// cargo run --bin records -- --cars-db /data/cars.json --times-dir /data/times best
|
|
/// ```
|
|
#[tokio::main]
|
|
async fn main() {
|
|
if let Err(e) = run().await {
|
|
eprintln!("Error: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
async fn run() -> Result<(), AppError> {
|
|
let cli = Cli::parse();
|
|
|
|
// Ensure the output directory exists before dispatching any command.
|
|
// create_dir_all(".") is a no-op, so no special-casing needed.
|
|
std::fs::create_dir_all(&cli.output_dir)?;
|
|
|
|
match cli.command {
|
|
Commands::Fetch => {
|
|
commands::fetch::run_fetch(&cli.cars_db, &cli.tracks_db, &cli.times_dir).await?;
|
|
}
|
|
Commands::Best { min_count } => {
|
|
commands::best::run_best(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
min_count,
|
|
)
|
|
.await?;
|
|
}
|
|
Commands::Trash => {
|
|
commands::trash::run_trash(&cli.my_cars, &cli.cars_db, &cli.times_dir, &cli.output_dir)
|
|
.await?;
|
|
}
|
|
Commands::Wishlist { min_count, max_wishlist } => {
|
|
commands::wishlist::run_wishlist(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
min_count,
|
|
max_wishlist,
|
|
)
|
|
.await?;
|
|
}
|
|
Commands::All { min_count, max_wishlist } => {
|
|
commands::all::run_all(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
min_count,
|
|
max_wishlist,
|
|
)
|
|
.await?;
|
|
}
|
|
Commands::Details => {
|
|
commands::details::run_details(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
)
|
|
.await?;
|
|
}
|
|
Commands::Track { track, condition, rq } => {
|
|
commands::track::run_track(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
&track,
|
|
&condition,
|
|
rq,
|
|
)
|
|
.await?;
|
|
}
|
|
Commands::Improve => {
|
|
commands::improve::run_improve(
|
|
&cli.my_cars,
|
|
&cli.cars_db,
|
|
&cli.times_dir,
|
|
&cli.output_dir,
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|