minigrep_npm/
lib.rs

1use colored::*;
2use std::error::Error;
3use std::{fs, vec};
4use std::collections::HashMap;
5
6pub struct Arguments {
7    pub query: String,      // Word to be searched for in the file
8    pub file_name: String,  // Name of file to be searched
9    pub help_option: bool,  // Option set to see the help menu
10    pub view_version: bool, // Option set to view minigrep version
11    pub case_ignore: bool,  // Option set to ignore cases
12    pub line_number: bool,  // Option to view line numbers
13    pub query_count: bool,  // Option to view count of query occurences
14    pub non_match: bool,    // Option to view non matching lines
15    pub line_count: bool,   // Option to view total line count containing query
16    pub view_stats: bool,   // Option to view file stats
17}
18
19pub struct LineInfo<'a> {
20    pub line_number: usize,
21    pub line_content: &'a str,
22}
23
24pub struct SearchResult<'a> {
25    pub line_info: Vec<LineInfo<'a>>,
26    pub count: usize,
27    pub line_count: usize,
28}
29
30impl Default for Arguments {
31    fn default() -> Self {
32        Arguments {
33            query: Default::default(),
34            file_name: Default::default(),
35            help_option: false,
36            view_version: false,
37            case_ignore: false,
38            line_number: false,
39            query_count: false,
40            non_match: false,
41            line_count: false,
42            view_stats: false,
43        }
44    }
45}
46
47impl Arguments {
48    // new() constructor for struct Arguments
49    pub fn new(args: &[String]) -> Result<Arguments, &'static str> {
50        // if it contains only two arguments
51
52        let error_message = Err("Unknown command, run 'cargo new minigrep_help' to learn more\n");
53
54        if args.len() == 2 {
55            // Possible command: cargo run minigrep_help
56            if args[1].eq("minigrep_help") {
57                return Ok(Arguments {
58                    help_option: true,
59                    ..Default::default()
60                });
61            }
62        } else if args.len() == 3 {
63            if args[1].eq("minigrep") {
64                // possible command cargo run minigrep -v or
65                // cargo run minigrep --version
66                if args[2].eq("-v") || args[2].eq("--version") {
67                    return Ok(Arguments {
68                        view_version: true,
69                        ..Default::default()
70                    });
71                }
72            } 
73            else if args[2].eq("-S") || args[2].eq("--stats") {
74                // possible command:
75                // cargo run war_and_peace.txt -S
76                return Ok(Arguments {
77                    file_name: args[1].clone(),
78                    view_stats: true,
79                    ..Default::default()  
80                });
81            }
82            else {
83                //possible command:
84                // cargo run hello hello_world.txt
85                return Ok(Arguments {
86                    query: args[1].clone(),
87                    file_name: args[2].clone(),
88                    ..Default::default()
89                });
90            }
91        } else if args.len() >= 4 {
92            // possible command:
93            // cargo run hello hello_world.txt -i -n
94
95            // get the list of options
96            let option_list = &args[3..];
97
98            let mut argument = Arguments {
99                query: args[1].clone(),
100                file_name: args[2].clone(),
101                ..Default::default()
102            };
103
104            if option_list.contains(&"-i".to_string())
105                || option_list.contains(&"--ignore-case".to_string())
106            {
107                argument.case_ignore = true;
108            }
109
110            if option_list.contains(&"-n".to_string())
111                || option_list.contains(&"--line-number".to_string())
112            {
113                argument.line_number = true;
114            }
115
116            if option_list.contains(&"-c".to_string())
117                || option_list.contains(&"--query-count".to_string())
118            {
119                argument.query_count = true;
120            }
121
122            if option_list.contains(&"-lc".to_string())
123                || option_list.contains(&"--line-count".to_string())
124            {
125                argument.line_count = true;
126            }
127
128            if option_list.contains(&"-I".to_string())
129                || option_list.contains(&"--invert-match".to_string())
130            {
131                argument.non_match = true;
132            }
133
134            return Ok(argument);
135        }
136
137        return error_message;
138    }
139}
140
141// Logic behind the whole program
142pub fn run(arguments: Arguments) -> Result<(), Box<dyn Error>> {
143    // if the user wants to view the help menu
144    if arguments.help_option {
145        view_help_menu();
146        return Ok(());
147    }
148
149    // if the user wants to view the version
150    if arguments.view_version {
151        view_version();
152        return Ok(());
153    }
154
155    // if the user wants to view file stats
156    if arguments.view_stats {
157        view_file_stats(arguments);
158        return Ok(());
159    }
160
161    let contents = fs::read_to_string(&arguments.file_name)?;
162    println!("\n{}", format!("{}:", &arguments.file_name).yellow());
163
164    let search_result = if arguments.case_ignore {
165        search_case_insensitive(&arguments.query, &contents)
166    } 
167    else if arguments.non_match {
168        search_invert_match(&arguments.query, &contents)
169    }
170    else {
171        search(&arguments.query, &contents)
172    };
173
174    let lines = search_result.line_info;
175
176    if lines.len() == 0 {
177        println!("No results");
178    }
179
180    // Printing the output with line numbers
181    if arguments.line_number {
182        for line in lines {
183            println!("{}:  {}", format!("{}", line.line_number).cyan(), line.line_content);
184        }
185    } else {
186        for line in lines {
187            println!("{}", line.line_content);
188        }
189    }
190
191    // Printing query occurence count
192    if arguments.query_count {
193        println!("{}", format!("\nCount: {}", search_result.count).green().bold());
194    }
195    else {
196        println!("");
197    }
198
199    // Printing line count
200    if arguments.line_count {
201        println!("{}", format!("Line count: {}\n", search_result.line_count).green().bold());
202    }
203    Ok(())
204}
205
206pub fn search<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
207    let mut line_info: Vec<LineInfo> = vec![];
208    let mut count = 0;
209    let mut line_count = 0;
210
211    // Iterating through each line of contents
212    for (line_no, line) in contents.lines().into_iter().enumerate() {
213        // checking if the line contains our query
214        if line.contains(query) {
215            line_info.push(LineInfo {
216                line_number: line_no,
217                line_content: line,
218            });
219            count += line.matches(query).count();
220            line_count += 1;
221        }
222    }
223
224    SearchResult { line_info, count, line_count}
225}
226
227pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
228    let query = query.to_lowercase();
229    let mut line_info: Vec<LineInfo> = vec![];
230    let mut count = 0;
231    let mut line_count = 0;
232
233    // Iterating through each line of contents
234    for (line_no, line) in contents.lines().into_iter().enumerate() {
235        // checking if the line contains our query
236        if line.to_lowercase().contains(&query) {
237            line_info.push(LineInfo {
238                line_number: line_no,
239                line_content: line,
240            });
241            count += line.to_ascii_lowercase().matches(&query).count();
242            line_count += 1;
243        }
244    }
245
246    SearchResult { line_info, count, line_count}
247}
248
249pub fn search_invert_match<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
250    let mut line_info: Vec<LineInfo> = vec![];
251    let mut line_count = 0;
252
253    // Iterating through each line of contents
254    for (line_no, line) in contents.lines().into_iter().enumerate() {
255        // checking if the line contains our query
256        if !line.contains(query) {
257            line_info.push(LineInfo {
258                line_number: line_no,
259                line_content: line,
260            });
261            line_count += 1;
262        }
263    }
264
265    SearchResult { line_info, count: 0, line_count}
266}
267
268pub fn view_file_stats(arguments: Arguments) {
269    let contents = fs::read_to_string(&arguments.file_name).expect("Unknown File");
270    let contents = contents.to_lowercase();
271    let words_list: Vec<&str> = contents.split_whitespace().collect();
272    let mut word_count = HashMap::new();
273    let mut char_count = 0;
274    let mut line_count = 0;
275
276    for _line in contents.lines().into_iter() {
277        line_count += 1;
278    }
279
280    println!("\n{}\n", format!("{}", arguments.file_name).yellow().bold());
281
282    for word in words_list {
283        let count = word_count.entry(word).or_insert(0);
284        *count += 1;
285    }
286
287    println!("{}", format!("{0: <10} | {1: <10}", "Sequence", "Frequency").bold());
288    println!("{}", format!("----------------------").bold());
289
290    for (word, frequency) in word_count {
291        println!("{0: <10} {1} {2: <10}", word, format!("|").bold(), frequency);
292        char_count += word.len();
293    }
294
295    println!("\n{}", format!("{}: {}", "Character count", char_count).bold());
296    println!("{}\n", format!("{}: {}", "Line count", line_count).bold());
297}
298
299pub fn view_help_menu() {
300    println!("\nRust {} help menu\n", format!("minigrep").yellow().bold());
301
302    println!(
303        "{}: cargo run [QUERY] [FILE_NAME] [OPTION]",
304        format!("Usage").cyan().bold()
305    );
306    println!(
307        "{}: Search for QUERY i.e a word in the FILE_NAME provided",
308        format!("Description").cyan().bold()
309    );
310    println!(
311        "{}: 'cargo run hello hello_world.txt -i'\n",
312        format!("Example").cyan().bold()
313    );
314
315    // ---- PATTERN SELECTION ----
316
317    println!("{}", format!("Pattern Selection:").yellow().bold());
318    println!(
319        "{}\tignore case distinctions\t\t'cargo run hello hello_world.txt -i'",
320        format!("   -i, --ignore-case").blue().bold()
321    );
322
323    // ---- OUTPUT CONTROL ----
324
325    println!("{}", format!("\nOutput Control:").yellow().bold());
326
327    // Line Number
328    println!(
329        "{}\tprint line numbers with output lines \t'cargo run hello hello_world.txt -n'",
330        format!("   -n, --line-number").blue().bold()
331    );
332
333    // Query Occurence Count
334    println!(
335        "{}\toutput total occurences of query \t'cargo run butter recipe.txt -c'",
336        format!("   -c, --query-count").blue().bold()
337    );
338
339    // Line Occurence Count
340    println!(
341        "{}\toutput total lines containing query \t'cargo run phone devices.txt -lc'",
342        format!("   -lc, --line-count").blue().bold()
343    );
344
345    // Invert Match
346    println!(
347        "{}\toutput non-matching lines \t\t'cargo run puppy pets.txt -I'",
348        format!("   -I, --invert-match").blue().bold()
349    );
350
351    // ---- MISCELLANEOUS ----
352
353    println!("{}", format!("\nMiscellaneous:").yellow().bold());
354
355    // Display file stats
356    println!(
357        "{}\t\tdisplays file statistics\t\t'cargo run war_and_peace.txt -S'",
358        format!("   -S, --stats").blue().bold()
359    );
360
361
362    // Display version
363    println!(
364        "{}\tdisplays version information\t\t'cargo run minigrep -v'",
365        format!("   -v, --version").blue().bold()
366    );
367
368    // Display help menu
369    println!(
370        "{}\tdisplays help menu\t\t\t'cargo run minigrep_help'",
371        format!("   minigrep_help").blue().bold()
372    );
373}
374
375pub fn view_version() {
376    println!("\n{}", format!("Minigrep v1.0.0").yellow().bold());
377    println!("Made by Nithin for learning purposes only");
378    println!("https://github.com/nithinmanoj10/minigrep")
379}