11use 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 } ;
127use xml:: { self , reader:: XmlEvent , EventReader } ;
138use xml:: common:: { TextPosition , Position } ;
149use 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 */
230223fn 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}
0 commit comments