mod app;
mod components;
mod config;
mod engine;
mod events;
mod import;
mod metrics;
mod persistence;
mod tui;
mod ui;
mod update;
use clap::Parser;
use app::{App, ErrorMode};
use config::{Config, ErrorModeSerde};
use events::setup_event_channel;
use persistence::SavedStats;
use ui::view;
use update::update;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[arg(long)]
target_wpm: Option<u32>,
#[arg(long)]
error_mode: Option<String>,
#[arg(long)]
reset: bool,
#[arg(long)]
data_dir: bool,
#[arg(long, value_name = "FILE")]
import: Option<std::path::PathBuf>,
#[arg(long)]
force: bool,
}
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let cli = Cli::parse();
if cli.data_dir {
match SavedStats::path() {
Some(p) => {
if let Some(dir) = p.parent() {
println!("{}", dir.display());
}
}
None => eprintln!("Could not determine data directory for this platform."),
}
return Ok(());
}
if let Some(ref import_path) = cli.import {
let config = Config::load();
let target_wpm = cli.target_wpm.unwrap_or(config.target_wpm);
let target_cpm = target_wpm as f64 * 5.0;
return import::run_import(import_path, target_cpm, cli.force);
}
if cli.reset {
match SavedStats::delete() {
Ok(true) => println!("Stats reset. Starting fresh."),
Ok(false) => println!("No stats file found. Starting fresh."),
Err(e) => eprintln!("Warning: could not delete stats: {e}"),
}
}
let config = Config::load();
let target_wpm = cli.target_wpm.unwrap_or(config.target_wpm);
let error_mode = if let Some(ref mode_str) = cli.error_mode {
match mode_str.as_str() {
"stop-on-error" | "stop" => ErrorMode::StopOnError,
_ => ErrorMode::ForgiveMistakes,
}
} else {
match config.error_mode {
ErrorModeSerde::StopOnError => ErrorMode::StopOnError,
ErrorModeSerde::ForgiveMistakes => ErrorMode::ForgiveMistakes,
}
};
let saved_stats = if cli.reset { None } else { SavedStats::load() };
let mut terminal = tui::init()?;
let mut app = App::new_with_state(target_wpm, error_mode, saved_stats);
app.fragment_length = config.fragment_length;
app.natural_words = config.natural_words;
app.daily_goal_minutes = config.daily_goal_minutes;
app.alphabet_size = config.alphabet_size;
app.scheduler.alphabet_size = config.alphabet_size;
app.generator.set_natural_words(app.natural_words);
let rx = setup_event_channel();
while app.running {
terminal.draw(|frame| view(&app, frame))?;
match rx.recv() {
Ok(event) => update(&mut app, event),
Err(_) => break,
}
app.flush_pending_saves();
}
tui::restore()?;
Ok(())
}