cargo_spellcheck/config/
hunspell.rs1use super::{Lang5, SearchDirs, WrappedRegex};
4use std::path::{Path, PathBuf};
5
6use crate::errors::*;
7
8use serde::{Deserialize, Serialize};
9
10const fn yes() -> bool {
11 true
12}
13
14#[derive(Deserialize, Serialize, Debug, Clone)]
15pub struct Quirks {
16 #[serde(default)]
20 pub transform_regex: Vec<WrappedRegex>,
21 #[serde(default)]
25 pub allow_concatenation: bool,
26 #[serde(default)]
29 pub allow_dashes: bool,
30 #[serde(default = "yes")]
32 pub allow_emojis: bool,
33 #[serde(default = "yes")]
41 pub check_footnote_references: bool,
42}
43
44impl Default for Quirks {
45 fn default() -> Self {
46 Self {
47 transform_regex: Vec::new(),
48 allow_concatenation: false,
49 allow_dashes: false,
50 allow_emojis: true,
51 check_footnote_references: true,
52 }
53 }
54}
55
56impl Quirks {
57 pub(crate) const fn allow_concatenated(&self) -> bool {
58 self.allow_concatenation
59 }
60
61 pub(crate) const fn allow_dashed(&self) -> bool {
62 self.allow_dashes
63 }
64
65 pub(crate) const fn allow_emojis(&self) -> bool {
66 self.allow_emojis
67 }
68
69 pub(crate) fn transform_regex(&self) -> &[WrappedRegex] {
70 &self.transform_regex
71 }
72
73 pub(crate) fn check_footnote_references(&self) -> bool {
74 self.check_footnote_references
75 }
76}
77
78fn default_tokenization_splitchars() -> String {
79 "\",;:.!?#(){}[]|/_-‒'`&@§¶…".to_owned()
80}
81
82pub type ZetConfig = HunspellConfig;
83pub type SpellbookConfig = HunspellConfig;
84
85#[derive(Deserialize, Serialize, Debug, Clone)]
86#[serde(deny_unknown_fields)]
87pub struct HunspellConfig {
88 #[serde(default)]
91 pub lang: Lang5,
92 #[serde(default)]
95 pub search_dirs: SearchDirs,
96
97 #[serde(default)]
100 pub skip_os_lookups: bool,
101
102 #[serde(default)]
106 pub use_builtin: bool,
107
108 #[serde(default = "default_tokenization_splitchars")]
109 pub tokenization_splitchars: String,
110
111 #[serde(default)]
113 pub extra_dictionaries: Vec<PathBuf>,
114 #[serde(default)]
116 pub quirks: Quirks,
117}
118
119impl Default for HunspellConfig {
120 fn default() -> Self {
121 Self {
122 lang: Lang5::en_US,
123 search_dirs: SearchDirs::default(),
124 extra_dictionaries: Vec::default(),
125 quirks: Quirks::default(),
126 tokenization_splitchars: default_tokenization_splitchars(),
127 skip_os_lookups: false,
128 use_builtin: true,
129 }
130 }
131}
132
133impl HunspellConfig {
134 pub fn lang(&self) -> Lang5 {
135 self.lang
136 }
137
138 pub fn search_dirs(&self) -> impl Iterator<Item = &PathBuf> {
139 self.search_dirs.iter(!self.skip_os_lookups)
140 }
141
142 pub fn extra_dictionaries(&self) -> impl Iterator<Item = &PathBuf> {
143 self.extra_dictionaries.iter()
144 }
145
146 pub fn sanitize_paths(&mut self, base: &Path) -> Result<()> {
147 self.search_dirs = self
148 .search_dirs
149 .iter(!self.skip_os_lookups)
150 .filter_map(|search_dir| {
151 let abspath = if !search_dir.is_absolute() {
152 base.join(search_dir)
153 } else {
154 search_dir.to_owned()
155 };
156
157 abspath.canonicalize().ok().inspect(|abspath| {
158 log::trace!(
159 "Sanitized ({} + {}) -> {}",
160 base.display(),
161 search_dir.display(),
162 abspath.display()
163 );
164 })
165 })
166 .collect::<Vec<PathBuf>>()
167 .into();
168
169 'o: for extra_dic in self.extra_dictionaries.iter_mut() {
172 for search_dir in
173 self.search_dirs
174 .iter(!self.skip_os_lookups)
175 .filter_map(|search_dir| {
176 if !extra_dic.is_absolute() {
177 base.join(search_dir).canonicalize().ok()
178 } else {
179 Some(search_dir.to_owned())
180 }
181 })
182 {
183 let abspath = if !extra_dic.is_absolute() {
184 search_dir.join(&extra_dic)
185 } else {
186 continue 'o;
187 };
188 if let Ok(abspath) = abspath.canonicalize() {
189 if abspath.is_file() {
190 *extra_dic = abspath;
191 continue 'o;
192 }
193 } else {
194 log::debug!("Failed to canonicalize {}", abspath.display());
195 }
196 }
197 bail!(
198 "Could not find extra dictionary {} in any of the search paths",
199 extra_dic.display()
200 );
201 }
202
203 Ok(())
204 }
205}