Skip to content

Commit 7d369c0

Browse files
authored
Add GitHub Actions CI and refresh the README. (#2)
Replace dead Travis/AppVeyor with a multi-OS workflow and document this as a maintained fork with Linux hunspell requirements.
1 parent 652a59e commit 7d369c0

8 files changed

Lines changed: 152 additions & 67 deletions

File tree

.github/workflows/ci.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
RUSTFLAGS: -Dwarnings
11+
12+
jobs:
13+
test:
14+
name: test (${{ matrix.os }})
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
os: [ubuntu-latest, macos-latest, windows-latest]
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Install Rust
25+
uses: dtolnay/rust-toolchain@stable
26+
27+
- name: Cache cargo
28+
uses: Swatinem/rust-cache@v2
29+
30+
- name: Install hunspell (Linux)
31+
if: runner.os == 'Linux'
32+
run: |
33+
sudo apt-get update
34+
sudo apt-get install -y \
35+
libhunspell-dev \
36+
hunspell-en-us \
37+
libclang-dev
38+
39+
- name: Test
40+
run: cargo test --all-targets
41+
42+
fmt:
43+
name: fmt
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@v4
47+
- uses: dtolnay/rust-toolchain@stable
48+
with:
49+
components: clippy
50+
- uses: Swatinem/rust-cache@v2
51+
- name: Install hunspell
52+
run: |
53+
sudo apt-get update
54+
sudo apt-get install -y libhunspell-dev hunspell-en-us libclang-dev
55+
- run: cargo clippy --all-targets -- -D warnings

.travis.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
11
# spellbound
2-
[![travis-ci Build Status](https://travis-ci.com/euclio/spellbound.svg?branch=master)](https://travis-ci.com/euclio/spellbound)
3-
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/euclio/spellbound?svg=true)](https://ci.appveyor.com/project/euclio/spellbound)
42

5-
`spellbound` is a small crate that binds to the native platform's spell checking
6-
APIs and wraps them in a friendlier, rustic interface.
3+
[![CI](https://github.com/rtmongold/spellbound/actions/workflows/ci.yml/badge.svg)](https://github.com/rtmongold/spellbound/actions/workflows/ci.yml)
74

8-
Supported platforms and corresponding APIs:
5+
Native spell checking with a small Rust API.
6+
7+
This is a **maintained fork** of [euclio/spellbound](https://github.com/euclio/spellbound)
8+
(last upstream commit 2020). API 0.2 returns `Result` from `Checker::new` and exposes
9+
UTF-8 byte ranges on spelling errors.
910

1011
| Platform | API |
1112
| -------- | ------------------ |
12-
| MacOS | [`NSSpellChecker`] |
13+
| macOS | [`NSSpellChecker`] |
1314
| Windows | [`ISpellChecker`] |
14-
| *nix | [`hunspell`]
15+
| *nix | [`hunspell`] |
1516

1617
[`ISpellChecker`]: https://docs.microsoft.com/en-us/windows/desktop/api/spellcheck/nn-spellcheck-ispellchecker
1718
[`NSSpellChecker`]: https://developer.apple.com/documentation/appkit/nsspellchecker
1819
[`hunspell`]: https://hunspell.github.io/
20+
21+
## Example
22+
23+
```rust
24+
use spellbound::Checker;
25+
26+
fn main() -> Result<(), spellbound::Error> {
27+
let mut checker = Checker::new()?;
28+
for err in checker.check("I beleeve I can fly") {
29+
println!("{} @ {}..{}", err.text(), err.start(), err.end());
30+
}
31+
Ok(())
32+
}
33+
```
34+
35+
## Linux
36+
37+
Needs a hunspell dictionary on disk (default search includes `/usr/share/hunspell`). Example:
38+
39+
- Arch: `pacman -S hunspell hunspell-en_us`
40+
- Debian/Ubuntu: `apt install libhunspell-dev hunspell-en-us`
41+
42+
Without a dictionary, `Checker::new()` returns `Error::Unavailable`.
43+
44+
## License
45+
MIT OR Apache-2.0

appveyor.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/lib.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,15 @@ pub struct SpellingError(imp::SpellingError);
8585

8686
impl SpellingError {
8787
/// Returns the text of the misspelled word.
88-
pub fn text(&self) -> &str { self.0.text() }
89-
pub fn start(&self) -> usize { self.0.start() }
90-
pub fn end(&self) -> usize { self.0.end() }
88+
pub fn text(&self) -> &str {
89+
self.0.text()
90+
}
91+
pub fn start(&self) -> usize {
92+
self.0.start()
93+
}
94+
pub fn end(&self) -> usize {
95+
self.0.end()
96+
}
9197
}
9298

9399
#[cfg(test)]
@@ -98,14 +104,14 @@ mod tests {
98104
fn no_errors() {
99105
let text = "I'm happy that this sentence has no errors.";
100106
let mut checker = Checker::new().unwrap();
101-
assert_eq!(checker.check(&text).count(), 0);
107+
assert_eq!(checker.check(text).count(), 0);
102108
}
103109

104110
#[test]
105111
fn single_error() {
106112
let text = "asdf";
107113
let mut checker = Checker::new().unwrap();
108-
let errors = checker.check(&text).collect::<Vec<_>>();
114+
let errors = checker.check(text).collect::<Vec<_>>();
109115
assert_eq!(errors.len(), 1);
110116
assert_eq!(errors[0].text(), "asdf");
111117
assert_eq!(&text[errors[0].start()..errors[0].end()], "asdf");
@@ -115,7 +121,7 @@ mod tests {
115121
fn multiple_errors() {
116122
let text = "asdf hjkl qwer uiop";
117123
let mut checker = Checker::new().unwrap();
118-
let errors = checker.check(&text).collect::<Vec<_>>();
124+
let errors = checker.check(text).collect::<Vec<_>>();
119125
assert_eq!(errors.len(), 4);
120126
assert_eq!(errors[0].text(), "asdf");
121127
assert_eq!(errors[1].text(), "hjkl");

src/mac.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl Checker {
6969
pub fn check(&mut self, text: &str) -> impl Iterator<Item = SpellingError> {
7070
SpellcheckIter {
7171
document_tag: self.document_tag,
72-
ns_text: unsafe { NSString::alloc(nil).init_str(text)},
72+
ns_text: unsafe { NSString::alloc(nil).init_str(text) },
7373
ns_offset: 0,
7474
original: text.to_owned(),
7575
byte_cursor: 0,
@@ -85,9 +85,15 @@ pub struct SpellingError {
8585
}
8686

8787
impl SpellingError {
88-
pub fn text(&self) -> &str { &self.text }
89-
pub fn start(&self) -> usize { self.start }
90-
pub fn end(&self) -> usize { self.end }
88+
pub fn text(&self) -> &str {
89+
&self.text
90+
}
91+
pub fn start(&self) -> usize {
92+
self.start
93+
}
94+
pub fn end(&self) -> usize {
95+
self.end
96+
}
9197
}
9298

9399
struct SpellcheckIter {
@@ -129,7 +135,7 @@ impl Iterator for SpellcheckIter {
129135
let end = start + misspelling.len();
130136
self.byte_cursor = end;
131137
self.ns_offset = range.location + range.length;
132-
138+
133139
Some(SpellingError {
134140
text: misspelling.to_owned(),
135141
start,

src/unix.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ impl Checker {
5555

5656
words(text).filter_map(move |(start, end, word)| {
5757
let cstr = CString::new(word).ok()?;
58-
let ok = unsafe { Hunspell_spell(hunspell, cstr.as_bytes_with_nul().as_ptr() as *const i8) } != 0;
58+
let ok =
59+
unsafe { Hunspell_spell(hunspell, cstr.as_bytes_with_nul().as_ptr() as *const i8) }
60+
!= 0;
5961
if ok {
6062
None
6163
} else {
@@ -71,7 +73,12 @@ impl Checker {
7173
pub fn ignore(&mut self, word: &str) {
7274
let cstr = CString::new(word).unwrap();
7375

74-
unsafe { Hunspell_add(self.hunspell, cstr.as_bytes_with_nul().as_ptr() as *const i8) };
76+
unsafe {
77+
Hunspell_add(
78+
self.hunspell,
79+
cstr.as_bytes_with_nul().as_ptr() as *const i8,
80+
)
81+
};
7582
}
7683
}
7784

@@ -90,9 +97,15 @@ pub struct SpellingError {
9097
}
9198

9299
impl SpellingError {
93-
pub fn text(&self) -> &str { &self.text }
94-
pub fn start(&self) -> usize { self.start }
95-
pub fn end(&self) -> usize { self.end }
100+
pub fn text(&self) -> &str {
101+
&self.text
102+
}
103+
pub fn start(&self) -> usize {
104+
self.start
105+
}
106+
pub fn end(&self) -> usize {
107+
self.end
108+
}
96109
}
97110

98111
fn is_word_char(c: char) -> bool {
@@ -102,12 +115,12 @@ fn is_word_char(c: char) -> bool {
102115
fn words(text: &str) -> impl Iterator<Item = (usize, usize, &str)> {
103116
let mut words = Vec::new();
104117
let mut chars = text.char_indices().peekable();
105-
118+
106119
while let Some((start, c)) = chars.next() {
107120
if !is_word_char(c) {
108121
continue;
109122
}
110-
123+
111124
let mut end = start + c.len_utf8();
112125
while let Some(&(i, next)) = chars.peek() {
113126
if !is_word_char(next) {

src/win.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,28 @@ use std::os::windows::ffi::OsStrExt;
88
use std::ptr::{self, NonNull};
99

1010
use winapi::{
11-
Class,
12-
Interface,
1311
shared::{
1412
winerror::{SUCCEEDED, S_FALSE, S_OK},
1513
wtypesbase::CLSCTX_INPROC_SERVER,
1614
},
1715
um::{
18-
combaseapi::{CoInitializeEx, CoCreateInstance},
16+
combaseapi::{CoCreateInstance, CoInitializeEx},
1917
objbase::COINIT_MULTITHREADED,
20-
spellcheck::{IEnumSpellingError, SpellCheckerFactory, ISpellChecker, ISpellCheckerFactory},
18+
spellcheck::{
19+
IEnumSpellingError, ISpellChecker, ISpellCheckerFactory, SpellCheckerFactory,
20+
},
2121
unknwnbase::IUnknown,
2222
},
23+
Class, Interface,
2324
};
2425

2526
struct ComPtr<T>(NonNull<T>);
2627

2728
impl<T> ComPtr<T> {
28-
fn new(p: *mut T) -> ComPtr<T> where T: Interface {
29+
fn new(p: *mut T) -> ComPtr<T>
30+
where
31+
T: Interface,
32+
{
2933
ComPtr(NonNull::new(p).unwrap())
3034
}
3135
}
@@ -178,7 +182,8 @@ impl Iterator for ErrorIter {
178182
let utf16_start = start as usize;
179183
let utf16_len = length as usize;
180184

181-
let err_text = String::from_utf16(&self.text[utf16_start..utf16_start + utf16_len]).ok()?;
185+
let err_text =
186+
String::from_utf16(&self.text[utf16_start..utf16_start + utf16_len]).ok()?;
182187

183188
let byte_start = utf16_offset_to_utf8(&self.original, utf16_start);
184189
let byte_end = utf16_offset_to_utf8(&self.original, utf16_start + utf16_len);
@@ -201,7 +206,13 @@ pub struct SpellingError {
201206
}
202207

203208
impl SpellingError {
204-
pub fn text(&self) -> &str { &self.text }
205-
pub fn start(&self) -> usize { self.start }
206-
pub fn end(&self) -> usize { self.end }
209+
pub fn text(&self) -> &str {
210+
&self.text
211+
}
212+
pub fn start(&self) -> usize {
213+
self.start
214+
}
215+
pub fn end(&self) -> usize {
216+
self.end
217+
}
207218
}

0 commit comments

Comments
 (0)