chore(repo): prune dead code and duplicate abstractions (#3413)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-12 13:09:42 +02:00
1 parent c17091768c
commit c9fdf8fead
2937 files changed
+42233 -176793

No files matched your search

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EnvSnapshot {
pub hunspell_dict_dir: Option<String>,
pub xdg_data_home: Option<String>,
pub home: Option<String>,
pub xdg_data_dirs: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SystemDictionary {
pub tag: String,
pub aff_path: PathBuf,
pub dic_path: PathBuf,
}
pub fn canonicalize_tag(raw: &str) -> String {
raw.chars()
.map(|ch| match ch {
'_' | '-' => '-',
_ => ch.to_ascii_lowercase(),
})
.collect()
}
pub fn build_search_path(env: &EnvSnapshot) -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(raw) = &env.hunspell_dict_dir {
dirs.extend(
raw.split(':')
.filter(|part| !part.is_empty())
.map(PathBuf::from),
);
}
if let Some(xdg_home) = &env.xdg_data_home {
dirs.push(Path::new(xdg_home).join("hunspell"));
} else if let Some(home) = &env.home {
dirs.push(Path::new(home).join(".local/share/hunspell"));
}
if let Some(xdg_dirs) = &env.xdg_data_dirs {
dirs.extend(
xdg_dirs
.split(':')
.filter(|part| !part.is_empty())
.map(|part| Path::new(part).join("hunspell")),
);
}
dirs.push(PathBuf::from("/usr/local/share/hunspell"));
dirs.push(PathBuf::from("/usr/share/hunspell"));
dirs.push(PathBuf::from("/usr/share/myspell/dicts"));
dirs.push(PathBuf::from("/usr/share/myspell"));
dirs
}
pub fn discover_dictionaries(env: &EnvSnapshot) -> Vec<SystemDictionary> {
let mut seen = HashSet::new();
let mut out = Vec::new();
for dir in build_search_path(env) {
scan_dir(&dir, &mut seen, &mut out);
}
out
}
fn scan_dir(dir: &Path, seen: &mut HashSet<String>, out: &mut Vec<SystemDictionary>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|ext| ext.to_str()) != Some("dic") {
continue;
}
let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
continue;
};
if stem.is_empty() {
continue;
}
let aff_path = dir.join(format!("{stem}.aff"));
if !aff_path.is_file() {
continue;
}
let tag = canonicalize_tag(stem);
if !seen.insert(tag.clone()) {
continue;
}
out.push(SystemDictionary {
tag,
aff_path,
dic_path: path,
});
}
}
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pub fn is_utf8_encoding(encoding: &str) -> bool {
let bytes = encoding.as_bytes();
if bytes.is_empty() || bytes.len() > 16 {
return false;
}
bytes.eq_ignore_ascii_case(b"utf-8") || bytes.eq_ignore_ascii_case(b"utf8")
}
@@ -1,16 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
mod dictionaries;
mod encoding;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::path::Path;
use std::ptr;
use std::sync::{Mutex, MutexGuard};
use fluxer_desktop_native::system_hunspell::dictionaries::{EnvSnapshot, discover_dictionaries};
use fluxer_desktop_native::system_hunspell::encoding::is_utf8_encoding;
use fluxer_desktop_native::system_hunspell::hashing::hash_file_to_hex;
use napi::Task;
use napi::bindgen_prelude::{AsyncTask, Env, Error, Result, Status};
use crate::dictionaries::{EnvSnapshot, discover_dictionaries};
use crate::encoding::is_utf8_encoding;
use napi::bindgen_prelude::{Error, Result, Status};
use napi_derive::napi;
static HUNSPELL_LOCK: Mutex<()> = Mutex::new(());
@@ -99,29 +100,6 @@ pub fn discover_system_dictionaries() -> Vec<SystemDictionary> {
.collect()
}
pub struct HashFileTask {
path: String,
}
#[napi(js_name = "hashFile")]
pub fn hash_file(path: String) -> Result<AsyncTask<HashFileTask>> {
validate_path_arg(&path)?;
Ok(AsyncTask::new(HashFileTask { path }))
}
impl Task for HashFileTask {
type Output = String;
type JsValue = String;
fn compute(&mut self) -> Result<Self::Output> {
hash_file_to_hex(&self.path).map_err(hash_error)
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
#[derive(Debug)]
struct Dictionary {
handle: *mut hunspell_sys::Hunhandle,
@@ -297,81 +275,9 @@ fn hunspell_load_error(reason: &str) -> Error {
)
}
fn hash_error(error: std::io::Error) -> Error {
let message = match error.kind() {
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {
"could not open file for hashing"
}
_ => "read failed while hashing file",
};
Error::new(Status::GenericFailure, message)
}
#[cfg(test)]
mod tests {
use std::io::Write;
use super::*;
#[test]
fn suggestion_limit_defaults_and_bounds_match_legacy_contract() {
assert_eq!(8, normalize_suggestion_limit(None));
assert_eq!(8, normalize_suggestion_limit(Some(0)));
assert_eq!(1, normalize_suggestion_limit(Some(1)));
assert_eq!(64, normalize_suggestion_limit(Some(64)));
assert_eq!(8, normalize_suggestion_limit(Some(65)));
}
#[test]
fn path_validation_rejects_empty_and_nul_paths() {
assert_eq!(
"path must be non-empty",
validate_path_arg("")
.expect_err("empty path should fail")
.reason
);
assert_eq!(
"path must not contain NUL bytes",
validate_path_arg("/tmp/a\0b")
.expect_err("NUL path should fail")
.reason
);
}
#[test]
fn text_validation_rejects_nul_words() {
assert_eq!(
"word must not contain NUL bytes",
validate_text_arg("a\0b", "word")
.expect_err("NUL word should fail")
.reason
);
}
#[test]
fn hash_error_maps_open_failures_to_existing_js_message() {
let err = hash_file_to_hex("/nonexistent/path/that/should/not/exist.bin")
.map_err(hash_error)
.expect_err("missing file should fail");
assert_eq!(Status::GenericFailure, err.status);
assert_eq!("could not open file for hashing", err.reason);
}
#[test]
fn hash_file_task_streams_file_to_expected_hex() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("payload");
let mut file = File::create(&path).unwrap();
file.write_all(b"abc").unwrap();
let mut task = HashFileTask {
path: path.display().to_string(),
};
assert_eq!(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
task.compute().unwrap()
);
}
use super::Dictionary;
#[test]
fn dictionary_fixture_spells_and_accepts_runtime_words() {
@@ -390,15 +296,6 @@ mod tests {
drop(dir);
}
#[test]
fn dictionary_rejects_non_utf8_dictionaries() {
let (_dir, aff_path, dic_path) = write_fixture_dictionary("ISO-8859-1");
let err = Dictionary::load(&aff_path, &dic_path)
.expect_err("non-UTF-8 dictionary should be rejected");
assert_eq!(Status::GenericFailure, err.status);
assert_eq!("Hunspell load failed: DictionaryNotUtf8", err.reason);
}
fn write_fixture_dictionary(encoding: &str) -> (tempfile::TempDir, String, String) {
let dir = tempfile::tempdir().unwrap();
let aff_path = dir.path().join("fixture.aff");