#![warn(missing_docs)]
#![allow(
clippy::doc_markdown,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::option_if_let_else
)]
mod config;
extern crate serde;
use std::{
fs::{File, OpenOptions},
io::{Read, Write},
path::Path,
};
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
pub use config::{Config, Language, Languages};
use walkdir::WalkDir;
#[derive(Debug, Deserialize)]
pub struct SpellResult {
pub code: u32,
pub col: u32,
pub len: u32,
pub pos: u32,
pub row: u32,
pub s: Vec<String>,
pub word: String,
}
impl SpellResult {
pub fn get_error_name(&self) -> Option<&str> {
match self.code {
1 => Some("ERROR_UNKNOWN_WORD"),
2 => Some("ERROR_REPEAT_WORD"),
3 => Some("ERROR_CAPITALIZATION"),
4 => Some("ERROR_UNKNOWN_WORD"),
_ => None,
}
}
}
pub type SpellResults = Vec<SpellResult>;
pub struct Speller {
pub config: Config,
client: reqwest::blocking::Client,
}
#[derive(Debug, Serialize)]
struct RequestData<'a> {
text: &'a str,
options: u32,
lang: &'a str,
format: &'a str,
}
const FORMAT_PLAIN: &str = "plain";
const FORMAT_HTML: &str = "html";
const API_URL: &str = "https://speller.yandex.net/services/spellservice.json/checkText";
impl Default for Speller {
fn default() -> Self {
Self::new(Config::default())
}
}
impl Speller {
pub fn new(config: Config) -> Speller {
Speller {
config,
client: reqwest::blocking::Client::new(),
}
}
pub fn spell_text(&self, text: &str) -> Result<String> {
self._spell_text(text, self.get_format())
}
pub fn spell_url(&self, url: &str) -> Result<String> {
let content = self.fetch_page(url)?;
self._spell_html_text(&content)
}
fn _spell_html_text(&self, text: &str) -> Result<String> {
self._spell_text(text, FORMAT_HTML)
}
fn _spell_text(&self, text: &str, format: &str) -> Result<String> {
let spell_results: SpellResults = self.call_api(text, format)?;
let mut result_text = text.to_string();
for spell_result in spell_results {
if !spell_result.s.is_empty() {
let word = spell_result.word;
let suggestion = &spell_result.s[0];
result_text = result_text.replace(&word, suggestion);
}
}
Ok(result_text)
}
pub fn spell_path(&self, path: &Path) -> Result<()> {
for entry in WalkDir::new(path) {
if let Err(err) = self._spell_file(entry?.path()) {
println!("Error: {}", err);
}
}
Ok(())
}
fn _spell_file(&self, path: &Path) -> Result<()> {
if !path.is_file() {
return Ok(());
}
let mut content = String::new();
let mut file = File::open(path)?;
if file.read_to_string(&mut content).is_err() {
return Ok(());
}
println!("check {}", path.to_string_lossy());
let result = if let Some(ext) = path.extension() {
if ext == "html" {
self._spell_html_text(&content)
} else {
self.spell_text(&content)
}
} else {
self.spell_text(&content)
};
if let Err(err) = result {
return Err(anyhow!("{}: {}", path.to_string_lossy(), err));
}
let mut file = OpenOptions::new().write(true).open(path)?;
file.write_all(result.unwrap().as_bytes())?;
Ok(())
}
fn get_format(&self) -> &str {
if self.config.is_html {
FORMAT_HTML
} else {
FORMAT_PLAIN
}
}
pub fn check_text(&self, text: &str) -> Result<SpellResults> {
self.call_api(text, self.get_format())
}
fn fetch_page(&self, url: &str) -> Result<String> {
let response = self.client.get(url).send()?;
if !response.status().is_success() {
return Err(anyhow!("Failed to call api {}", response.text()?));
}
Ok(response.text()?)
}
fn api_options(&self) -> u32 {
let mut options = 0u32;
if self.config.ignore_digits {
options |= 2
}
if self.config.ignore_urls {
options |= 4
}
if self.config.find_repeat_words {
options |= 8
}
if self.config.ignore_capitalization {
options |= 512
}
options
}
fn call_api(&self, text: &str, format: &str) -> Result<SpellResults> {
if text.len() >= 10_000 {
return Err(anyhow!("Input text is too long: {} >= 10000", &text.len()));
}
let url = format!(
"{}?text={}&options={}&lang={}&format={}",
API_URL,
text,
self.api_options(),
self.config.languages(),
format,
);
let response = self.client.get(url).send()?;
if !response.status().is_success() {
return Err(anyhow!("Failed to call api {}", response.text()?));
}
let results = response.json::<SpellResults>()?;
Ok(results)
}
}