use colored::*;
use std::env;
use std::error::Error;
use std::fs;
pub mod app_structs; pub mod text_analysis;
use crate::text_analysis::{analyze_structure, extract_keywords, search, search_case_insensitive};
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = if config.file_name.ends_with(".pdf") {
println!("{}", "Reading pdf file...".bright_blue().bold());
pdf_extract::extract_text(&config.file_name)?
} else {
fs::read_to_string(&config.file_name)? };
let structural_analysis_results = analyze_structure(&contents);
println!("{}", "Structural Analysis:".cyan().bold());
println!(
" Lines: {}",
structural_analysis_results.lines.to_string().yellow()
);
println!(
" Words: {}",
structural_analysis_results.words.to_string().yellow()
);
println!(
" Characters: {}",
structural_analysis_results.characters.to_string().yellow()
);
println!(
" Stanzas: {}",
structural_analysis_results.stanzas.to_string().yellow()
);
println!(
" Unique words: {}",
structural_analysis_results.unique_words.to_string().yellow()
);
println!(
" Empty lines: {}",
structural_analysis_results.empty_lines.to_string().yellow()
);
let keywords = extract_keywords(&contents, 5);
println!("\n{}", "Keywords (Top 5):".cyan().bold());
for (keyword, count) in keywords {
println!(" {}: {}", keyword.green().bold(), count.to_string().blue());
}
println!("\n{}", "Search Results:".cyan().bold());
let search_results_owned: Vec<String> = if config.case_sensitive {
search(&config.query, &contents)
} else {
search_case_insensitive(&config.query, &contents)
};
if search_results_owned.is_empty() {
println!("{}", " No matches found.".italic());
} else {
let search_results_refs: Vec<&str> =
search_results_owned.iter().map(|s| s.as_str()).collect();
for line_content_str in search_results_refs {
let query_to_highlight = &config.query;
if config.case_sensitive {
let mut last_end = 0;
for (start, part) in line_content_str.match_indices(query_to_highlight) {
print!("{}", &line_content_str[last_end..start]);
print!("{}", part.magenta().bold());
last_end = start + part.len();
}
println!("{}", &line_content_str[last_end..]);
} else {
let lower_query = query_to_highlight.to_lowercase();
let mut last_end = 0;
let mut current_search_pos = 0;
let lower_line_content = line_content_str.to_lowercase();
while let Some(start_in_lower) =
lower_line_content[current_search_pos..].find(&lower_query)
{
let actual_start = current_search_pos + start_in_lower;
let match_len = lower_query.len();
let actual_end = actual_start + match_len;
print!("{}", &line_content_str[last_end..actual_start]);
print!(
"{}",
(&line_content_str[actual_start..actual_end]).red().bold()
);
last_end = actual_end;
current_search_pos = actual_end;
if current_search_pos >= line_content_str.len() {
break;
}
}
println!("{}", &line_content_str[last_end..]);
}
}
}
Ok(())
}
pub struct Config {
pub query: String,
pub file_name: String,
pub case_sensitive: bool,
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("Looks like you passed some wonky doohickeys");
}
let query = args[1].clone();
let file_name = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
file_name,
case_sensitive,
})
}
}