diff --git a/Cargo.toml b/Cargo.toml index a14b7a8..203c7a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.4", features = ["derive"] } colored = "3.0.0" rand = "0.9.0" diff --git a/src/main.rs b/src/main.rs index 70c69e2..c7dfac5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ #![deny(clippy::all)] +use clap::Parser; use colored::*; use rand::Rng; use std::collections::{HashMap, HashSet}; -use std::env; +use std::fs; use std::io; use std::io::Write; use std::iter::FromIterator; @@ -15,18 +16,42 @@ type Progress = HashMap; const REPORT_EVERY: Count = 100; -/// Helper function to read search needles from the command-line arguments. -fn gather_needles() -> Needles { +/// Babel search: find words in a random stream of letters +#[derive(Parser)] +#[command(name = "babel-search")] +#[command(about = "A Library of Babel search implementation")] +struct Args { + /// Path to a dictionary file containing needles (one per line) + #[arg(long)] + dictionary: Option, + + /// Words to search for (alternative to dictionary file) + needles: Vec, +} + +/// Helper function to read search needles from command-line arguments or dictionary file. +fn gather_needles(args: &Args) -> Result { + let mut needles: Vec = Vec::new(); + + // If a dictionary file is provided, read from it + if let Some(dict_path) = &args.dictionary { + let contents = fs::read_to_string(dict_path)?; + needles.extend(contents.lines().map(|line| line.to_string())); + } + + // Also include command-line needles for backward compatibility + needles.extend(args.needles.iter().cloned()); + // (1) Convert to lowercase. - let mut needles: Vec = env::args().skip(1).map(|s| s.to_lowercase()).collect(); + let mut needles: Vec = needles.into_iter().map(|s| s.to_lowercase()).collect(); // (2) Remove whitespace. needles .iter_mut() .for_each(|s| s.retain(|c| !c.is_whitespace())); - // (3) Finally, convert to a set to deduplicate, especially after (1) and (2). - Needles::from_iter(needles) + // (3) Filter out empty strings and convert to a set to deduplicate. + Ok(Needles::from_iter(needles.into_iter().filter(|s| !s.is_empty()))) } /// Helper function to report our progress along the way and at the end. @@ -46,8 +71,23 @@ fn report(&start: &Instant, found: &Progress, partials: &Progress) { } fn main() { + let args = Args::parse(); + // What we're searching for and how much progress we've made. - let needles = gather_needles(); + let needles = match gather_needles(&args) { + Ok(needles) => { + if needles.is_empty() { + eprintln!("Error: No needles provided. Use either --dictionary or provide words as arguments."); + std::process::exit(1); + } + needles + } + Err(e) => { + eprintln!("Error reading dictionary file: {}", e); + std::process::exit(1); + } + }; + let mut found: Progress = HashMap::new(); let mut partials: Progress = HashMap::new();