mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
chore(repo): prune dead code and duplicate abstractions (#3413)
This commit is contained in:
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");
|
||||
|
||||
Reference in new issue
Block a user