#![warn(clippy::pedantic, clippy::nursery, clippy::cargo)]
mod cache;
use anyhow::anyhow;
use cache::Cache;
use clap::{Arg, ArgAction, command};
use directories::ProjectDirs;
use gloss_word::{
LookupMode, compile_results, get_response_text, get_sections, pandoc_fallback, pandoc_primary,
take_chunk,
};
use indicatif::{ProgressBar, ProgressStyle};
use scraper::{ElementRef, Selector};
#[allow(clippy::too_many_lines)]
fn main() -> Result<(), anyhow::Error> {
let matches = command!()
.arg(
Arg::new("clear-cache")
.long("clear-cache")
.help("Delete cache directory and its contents")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("etymology")
.short('e')
.long("etymology")
.help("Search for etymology instead of definition")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("fetch-update")
.short('f')
.long("fetch-update")
.help("Fetch new data; update cache if applicable")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("INPUT")
.help("The word or phrase to look up")
.required_unless_present("clear-cache"),
)
.get_matches();
let clear_cache = matches.get_flag("clear-cache");
let lookup_mode = if matches.get_flag("etymology") {
LookupMode::Etymology
} else {
LookupMode::Definition
};
let force_fetch = matches.get_flag("fetch-update");
let desired_word = if clear_cache {
String::new() } else {
let input_word: &String = matches.get_one("INPUT").unwrap(); input_word.to_lowercase()
};
let mut cache = None;
if let Some(proj_dirs) = ProjectDirs::from("com", "theobeers", "gloss-word") {
let cache_dir = proj_dirs.cache_dir();
if clear_cache {
if !cache_dir.exists() {
return Err(anyhow!("Cache directory not found"));
}
trash::delete(cache_dir)?;
eprintln!("Cache directory deleted");
return Ok(());
}
if !cache_dir.exists() {
let _dir = std::fs::create_dir_all(cache_dir);
}
cache = Cache::open(&cache_dir.join("entries.sqlite")).ok();
}
if let Some(cache) = &cache {
if let Ok(entry) = cache.get(&desired_word, lookup_mode)
&& !force_fetch
{
print!("{entry}");
return Ok(());
}
}
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(core::time::Duration::from_millis(80));
pb.set_style(
ProgressStyle::default_spinner()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
.template("{spinner} {msg}")
.unwrap(),
);
pb.set_message("Fetching...");
let lookup_url = lookup_mode.lookup_url(&desired_word);
let response_text = get_response_text(&lookup_url)?;
let parsed_chunk = take_chunk(&response_text);
let sections = get_sections(lookup_mode, &parsed_chunk);
if !sections.is_empty() {
let results = compile_results(lookup_mode, sections);
let final_output = pandoc_primary(&results, lookup_mode)?;
if let Some(cache) = &cache {
let _update = cache.put(&desired_word, lookup_mode, &final_output);
}
pb.finish_and_clear();
print!("{final_output}");
return Ok(());
}
if lookup_mode == LookupMode::Etymology {
pb.finish_and_clear();
return Err(anyhow!("Etymology not found"));
}
let suggestions_selector = Selector::parse("ul.suggestions li").unwrap();
let suggestions_vec: Vec<ElementRef> = parsed_chunk.select(&suggestions_selector).collect();
if !suggestions_vec.is_empty() {
let mut results = String::new();
for element in &suggestions_vec {
results.push_str(&element.html());
}
let pandoc_output = pandoc_fallback(&results)?;
pb.finish_and_clear();
println!("Did you mean:\n");
print!("{pandoc_output}");
return Ok(());
}
pb.finish_and_clear();
Err(anyhow!("Definition not found"))
}