Skip to content

Commit ba3e6ad

Browse files
committed
Add Clap parser implementations
1 parent 2185e80 commit ba3e6ad

5 files changed

Lines changed: 197 additions & 65 deletions

File tree

Cargo.lock

Lines changed: 121 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
clap = { version = "4.5.40", features = ["derive"] }
78
colored = "3.0.0"
89
poppler-rs = "0.24.1"
910
rust-stemmers = "1.2.0"

src/main.rs

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
use std:: {
2-
env::{self},
3-
fs::{self, File},
4-
io::{self, BufReader, BufWriter, Read},
5-
path::Path,
6-
process::{exit, ExitCode},
7-
str::{self},
8-
sync::{Arc, Mutex},
9-
thread
2+
fs::{self, File}, io::{self, BufReader, BufWriter, Read}, path::Path, process::{exit, ExitCode}, str::{self}, sync::{Arc, Mutex}, thread
103
};
114

5+
6+
use clap::{Parser, Subcommand, ValueEnum};
127
use xml::{self, reader::XmlEvent, EventReader};
138
use xml::common::{TextPosition, Position};
149
use colored::{Colorize};
@@ -224,8 +219,6 @@ fn check_index(index_path: &str) -> Result<(), ()> {
224219
Ok(())
225220
}
226221

227-
const DEFAULT_INDEX_JSON_PATH: &str = "index.json";
228-
229222
/* Fetch the InMemory model from an index file */
230223
fn fetch_model(index_path: &str) -> Result<InMemoryModel, ()> {
231224
let index_file = fs::File::open(&index_path).map_err(|err| {
@@ -238,57 +231,78 @@ fn fetch_model(index_path: &str) -> Result<InMemoryModel, ()> {
238231

239232
return Ok(model);
240233
}
241-
242-
fn usage(program: &String) {
243-
eprintln!("{}: {program} [SUBCOMMAND] [OPTIONS]", "USAGE".bold().cyan(), program = program.bright_blue());
244-
eprintln!("Subcommands:");
245-
eprintln!(" search <index-file> <prompt> Search query within a index file. (Default: Shows top 20 search results)");
246-
eprintln!(" check [index-file] Quickly check how many documents are present in a saved index file (Default: index.json)");
247-
eprintln!(" serve <index-file> [address] Starts an HTTP server with Web Interface based on a pre-built index (Default: localhost:6969)");
234+
235+
#[derive(Parser)]
236+
#[command(name = "DocSense", version, author, about, long_about = None)]
237+
#[command(about = "A fast document indexing and search engine which runs locally on your machine.", long_about = None)]
238+
struct Cli {
239+
#[command(subcommand)]
240+
command: Commands,
248241
}
249242

250-
fn entry() -> Result<(), ()> {
251-
let mut args = env::args();
252-
let program = args.next().expect("path to program is provided");
253-
let subcommand = args.next().ok_or_else(|| {
254-
usage(&program);
255-
eprintln!("{}: no subcommand is provided", "ERROR".red().bold());
256-
})?;
243+
#[derive(ValueEnum, Clone, Debug, PartialEq)]
244+
enum RankMethod {
245+
Tfidf,
246+
Bm25
247+
}
257248

258-
match subcommand.as_str() {
259-
"search" => {
260-
let index_path = args.next().ok_or_else(|| {
261-
usage(&program);
262-
eprintln!("{}: Index file path must provided for {} subcommand.", "ERROR".bold().red(), subcommand.bold().bright_blue());
263-
})?;
249+
#[derive(Subcommand)]
250+
enum Commands {
251+
#[command(
252+
about = "Search for a query string in an indexed file",
253+
long_about = "Search for a prompt string using BM25 (or TF-IDF) ranking algorithm across previously indexed documents."
254+
)]
255+
Search {
256+
#[arg(help = "Path to the .json index file (e.g., index.docsense.json)")]
257+
index_file_path: String,
258+
#[arg(help = "Search prompt string (e.g., 'deep neural networks')")]
259+
prompt: String,
260+
#[arg(short, long, default_value = "tfidf", value_enum, help = "Ranking algorithm to use")]
261+
rank_method: RankMethod,
262+
},
263+
264+
#[command(
265+
about = "Check how many files are indexed",
266+
long_about = "Display number of documents currently indexed in the specified index file. Useful for verifying the index state."
267+
)]
268+
Check {
269+
#[arg(default_value="index.json", help="Path to index file to inspect")]
270+
index_file_path: String,
271+
},
272+
273+
#[command(
274+
about = "Serve directory over HTTP with search interface",
275+
long_about = "Indexes the provided directory recursively and starts a web server for querying indexed files through a UI."
276+
)]
277+
Serve {
278+
#[arg(help = "Path to directory to index and serve")]
279+
dir_path: String,
280+
#[arg(default_value = "127.0.0.1:6969", help = "IP:PORT to bind HTTP server (e.g., 0.0.0.0:8080)")]
281+
address: String,
282+
#[arg(short, long, default_value = "tfidf", value_enum, help = "Ranking algorithm to use")]
283+
rank_method: RankMethod,
284+
}
285+
}
264286

265-
let prompt = args.next().ok_or_else(|| {
266-
usage(&program);
267-
eprintln!("{}: Prompt must be provided for {} subcommand.", "ERROR".bold().red(), subcommand.bold().bright_blue());
268-
})?.chars().collect::<Vec<char>>();
287+
fn entry() -> Result<(), ()> {
288+
let cli = Cli::parse();
269289

270-
let model = fetch_model(&index_path)?;
271-
for (path, rank) in model.search_query(&prompt, &model)?.iter().take(20) {
290+
match cli.command {
291+
Commands::Search {index_file_path, prompt, rank_method} => {
292+
let prompt = prompt.chars().collect::<Vec<char>>();
293+
let model = fetch_model(&index_file_path)?;
294+
for (path, rank) in model.search_query(&prompt, &model, rank_method)?.iter().take(20) {
272295
println!("{path} - {rank}", path = path.display());
273296
}
274297

275298
return Ok(());
276299
}
277300

278-
"check" => {
279-
let index_path = args.next().unwrap_or(DEFAULT_INDEX_JSON_PATH.to_string());
280-
check_index(&index_path).unwrap();
301+
Commands::Check { index_file_path } => {
302+
check_index(&index_file_path).unwrap();
281303
}
282304

283-
"serve" => {
284-
let dir_path = args.next().ok_or_else(|| {
285-
usage(&program);
286-
eprintln!("{}: No directory path is provided for {} subcommand.", "ERROR".bold().red(), subcommand.bold().bright_blue());
287-
})?;
288-
289-
// Default address
290-
let address = args.next().unwrap_or("127.0.0.1:6969".to_string());
291-
305+
Commands::Serve { dir_path, address , rank_method} => {
292306
// IDEATE: Is it fine to place the index file in the folder itself or place in a root dir?
293307
let mut index_path = Path::new(&dir_path).to_path_buf();
294308
index_path.push(".docsense.json");
@@ -320,14 +334,8 @@ fn entry() -> Result<(), ()> {
320334
});
321335
}
322336
// TODO: Print the information of server start at the end of logging
323-
return server::start(&address, Arc::clone(&model));
337+
return server::start(&address, Arc::clone(&model), rank_method);
324338
}
325-
326-
_ => {
327-
usage(&program);
328-
eprintln!("{}: Unknown subcommand {}", "ERROR".bold().red(), subcommand.bold().bright_blue());
329-
return Err(());
330-
}
331339
}
332340
Ok(())
333341
}

src/model.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::default::Default;
99
use super::lexer::*;
1010

1111
pub trait Model {
12-
fn search_query(&self, query: &[char], model: &InMemoryModel) -> Result<Vec<(PathBuf, f32)>, ()>;
12+
fn search_query(&self, query: &[char], model: &InMemoryModel, rank_method: RankMethod) -> Result<Vec<(PathBuf, f32)>, ()>;
1313
fn add_document(&mut self, path: PathBuf, content: &[char], last_modified: SystemTime) -> Result<(), ()>;
1414
fn requires_reindexing(&mut self, path: &Path, last_modified: SystemTime) -> Result<bool, ()>;
1515
}
@@ -100,14 +100,14 @@ impl InMemoryModel {
100100
}
101101
}
102102

103-
static USE_BM25: bool = true;
103+
use crate::RankMethod;
104104
impl Model for InMemoryModel {
105-
fn search_query(&self, query: &[char], model: &InMemoryModel) -> Result<Vec<(PathBuf, f32)>, ()> {
105+
fn search_query(&self, query: &[char], model: &InMemoryModel, rank_method: RankMethod) -> Result<Vec<(PathBuf, f32)>, ()> {
106106
let tokens = Lexer::new(&query).collect::<Vec<_>>();
107107

108108
let mut results = Vec::with_capacity(self.docs.len());
109109
for (path, doc) in &self.docs {
110-
let rank = if USE_BM25 {
110+
let rank = if rank_method == RankMethod::Bm25 {
111111
// BM-25 Ranking
112112
bm25_score(&tokens, doc, model)
113113
} else {

0 commit comments

Comments
 (0)