1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! Hunspell checker configuration.
use super::{Lang5, SearchDirs, WrappedRegex};
use std::path::{Path, PathBuf};
use crate::errors::*;
use serde::{Deserialize, Serialize};
const fn yes() -> bool {
true
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Quirks {
/// A regular expression, whose capture groups will be checked, instead of
/// the initial token. Only the first one that matches will be used to split
/// the word.
#[serde(default)]
pub transform_regex: Vec<WrappedRegex>,
/// Allow concatenated words instead of dashed connection. Note that this
/// only applies, if one of the suggested replacements has an item that is
/// equivalent except for addition dashes (`-`).
#[serde(default)]
pub allow_concatenation: bool,
/// The counterpart of `allow_concatenation`. Accepts words which have
/// replacement suggestions that contain additional dashes.
#[serde(default)]
pub allow_dashes: bool,
/// Treats sequences of emojis as OK.
#[serde(default = "yes")]
pub allow_emojis: bool,
/// Check the expressions in the footnote references. By default this is
/// turned on to remain backwards compatible but disabling it could be
/// particularly useful when one uses abbreviations instead of numbers as
/// footnote references. For instance by default the fragment `hello[^xyz]`
/// would be spellchecked as `helloxyz` which is obviously a misspelled
/// word, but by turning this check off, it will skip validating the
/// reference altogether and will only check the word `hello`.
#[serde(default = "yes")]
pub check_footnote_references: bool,
}
impl Default for Quirks {
fn default() -> Self {
Self {
transform_regex: Vec::new(),
allow_concatenation: false,
allow_dashes: false,
allow_emojis: true,
check_footnote_references: true,
}
}
}
impl Quirks {
pub(crate) const fn allow_concatenated(&self) -> bool {
self.allow_concatenation
}
pub(crate) const fn allow_dashed(&self) -> bool {
self.allow_dashes
}
pub(crate) const fn allow_emojis(&self) -> bool {
self.allow_emojis
}
pub(crate) fn transform_regex(&self) -> &[WrappedRegex] {
&self.transform_regex
}
pub(crate) fn check_footnote_references(&self) -> bool {
self.check_footnote_references
}
}
fn default_tokenization_splitchars() -> String {
"\",;:.!?#(){}[]|/_-‒'`&@§¶…".to_owned()
}
pub type ZetConfig = HunspellConfig;
pub type SpellbookConfig = HunspellConfig;
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct HunspellConfig {
/// The language we want to check against, used as the dictionary and
/// affixes file name.
#[serde(default)]
pub lang: Lang5,
/// Additional search directories for `.dic` and `.aff` files.
// must be option so it can be omitted in the config
#[serde(default)]
pub search_dirs: SearchDirs,
/// Avoid the OS provided dictionaries and only use the builtin ones,
/// besides those defined in `extra_dictionaries`.
#[serde(default)]
pub skip_os_lookups: bool,
/// Use the builtin dictionaries as last resort. Usually combined with
/// `skip_os_lookups=true` to enforce the `builtin` usage. Does not prevent
/// the usage of `extra_dictionaries`.
#[serde(default)]
pub use_builtin: bool,
#[serde(default = "default_tokenization_splitchars")]
pub tokenization_splitchars: String,
/// Additional dictionaries for topic specific lingo.
#[serde(default)]
pub extra_dictionaries: Vec<PathBuf>,
/// Additional quirks besides dictionary lookups.
#[serde(default)]
pub quirks: Quirks,
}
impl Default for HunspellConfig {
fn default() -> Self {
Self {
lang: Lang5::en_US,
search_dirs: SearchDirs::default(),
extra_dictionaries: Vec::default(),
quirks: Quirks::default(),
tokenization_splitchars: default_tokenization_splitchars(),
skip_os_lookups: false,
use_builtin: true,
}
}
}
impl HunspellConfig {
pub fn lang(&self) -> Lang5 {
self.lang
}
pub fn search_dirs(&self) -> impl Iterator<Item = &PathBuf> {
self.search_dirs.iter(!self.skip_os_lookups)
}
pub fn extra_dictionaries(&self) -> impl Iterator<Item = &PathBuf> {
self.extra_dictionaries.iter()
}
pub fn sanitize_paths(&mut self, base: &Path) -> Result<()> {
self.search_dirs = self
.search_dirs
.iter(!self.skip_os_lookups)
.filter_map(|search_dir| {
let abspath = if !search_dir.is_absolute() {
base.join(search_dir)
} else {
search_dir.to_owned()
};
abspath.canonicalize().ok().inspect(|abspath| {
log::trace!(
"Sanitized ({} + {}) -> {}",
base.display(),
search_dir.display(),
abspath.display()
);
})
})
.collect::<Vec<PathBuf>>()
.into();
// convert all extra dictionaries to absolute paths
'o: for extra_dic in self.extra_dictionaries.iter_mut() {
for search_dir in
self.search_dirs
.iter(!self.skip_os_lookups)
.filter_map(|search_dir| {
if !extra_dic.is_absolute() {
base.join(search_dir).canonicalize().ok()
} else {
Some(search_dir.to_owned())
}
})
{
let abspath = if !extra_dic.is_absolute() {
search_dir.join(&extra_dic)
} else {
continue 'o;
};
if let Ok(abspath) = abspath.canonicalize() {
if abspath.is_file() {
*extra_dic = abspath;
continue 'o;
}
} else {
log::debug!("Failed to canonicalize {}", abspath.display());
}
}
bail!(
"Could not find extra dictionary {} in any of the search paths",
extra_dic.display()
);
}
Ok(())
}
}