Skip to content

Latest commit

 

History

270 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Part of Ferramenta, a family of Rust tools.

ferrolex

Powered by Sebastian Software crates.io docs.rs CI License: MIT OR Apache-2.0 MSRV: 1.94 Coverage gate: ≥ 79%

Spell checking for text and code.

ferrolex is an independent Rust engine that safely loads existing Hunspell dictionaries and provides fast, deterministic word checks and suggestions without linking to the native Hunspell library. A verified dictionary catalog and downloader cover the complete path from an upstream dictionary to a local, caller-controlled cache.

The engine intentionally does not parse Markdown, programming languages, or other document formats. Format-aware tools extract prose or identifiers and call ferrolex through its Rust or Node.js API. This keeps language ownership in projects designed for format-aware integrations, such as ferromark for Markdown, ferrocat for PO catalogs, and OXC for TypeScript, instead of turning ferrolex into a general analysis framework. No sibling integration is part of the current support tier yet; see ADR-0010.

See the documentation index for product contracts, compatibility evidence, and retained prototype history.

Status

The project is pre-1.0. The Rust engine, Hunspell compatibility, suggestions, and managed dictionary acquisition are the current product focus. The Node.js binding is the first direct runtime integration and requires Node.js 22.13 or newer. Its @ferrolex/node package contract and eight-package prebuilt matrix are release-gated in CI, but the package has not yet been published to npm. The C ABI, Python, LSP, and VS Code implementations remain evaluation prototypes without a PyPI, language-server, or extension release path. Public APIs may change in minor releases before 1.0; breaking changes are recorded in the changelog.

Reviewed dictionary compatibility

This concise status is generated from the digest-pinned real-world fixture catalog. CI checks it in the relevant-change compatibility gate and in the weekly, manual, and release differential scorecard runs.

  • ✅ Ready for the tested core: the exact pinned dictionary imports without recognition errors and its reviewed word forms work.
  • 🟡 In progress: pinned probes and cache roundtrips pass, while the exact strict-import blockers remain review-gated.
  • 🔴 Blocked: ferrolex cannot reliably import that exact dictionary yet.

This is deliberately not a “100% Hunspell compatible” claim. The full locale matrix records the boundaries, and the oracle scorecard artifact contains the current differential evidence.

Dictionary locale Status What this means
en_US ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
de_DE ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
es_ES ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
fr_FR ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
it_IT ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
pt_BR ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
pt_PT ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
nl_NL ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
hu_HU 🟡 In progress Pinned probes and cache roundtrips pass; exact strict-import blockers are review-gated.
ar ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
tr_TR ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.
pl_PL ✅ Ready for the tested core The pinned dictionary imports strictly and its reviewed word forms work.

Contributing

Start with CONTRIBUTING.md for the local validation gates, commit conventions, release workflow, and provenance policy. The architecture overview explains the product boundaries, and the ADR index records the decisions behind them.

For a small first contribution, browse the open good-first issues. Please read the linked context in each issue before changing a public contract or compatibility boundary.

Install

Install the command-line tool from crates.io, or build it from a checkout:

cargo install ferrolex-cli
# or, from this repository:
cargo build -p ferrolex-cli

The build places the binary at target/debug/ferrolex; cargo install adds ferrolex to Cargo's bin directory.

Try it

Create a UTF-8 word-list file with one word per line, then check either one word or a plain-text file:

ferrolex check --dictionary words.txt Straße
ferrolex check --dictionary words.txt --file README.md
ferrolex check --dictionary words.txt --file README.md CHANGELOG.md
printf 'text from stdin' | ferrolex check --dictionary words.txt --file -
ferrolex check --dictionary words.txt -- --hyphenated-word
ferrolex check --format json --dictionary words.txt --file README.md
ferrolex suggest --dictionary words.txt Strase
ferrolex validate --strict dictionary.aff dictionary.dic
ferrolex check --hunspell dictionary.aff derived-form
ferrolex dictionary list
ferrolex dictionary install pl_PL --cache .ferrolex-dictionaries
ferrolex check --hunspell .ferrolex-dictionaries/pl_PL/pl_PL.aff słowami
ferrolex suggest --hunspell .ferrolex-dictionaries/pl_PL/pl_PL.aff slowami

Plain-word-list files ignore blank lines, leading or trailing whitespace, and lines beginning with #. Exact matching, including casing, is the default for plain word-list and compiled-dictionary checks; Hunspell imports apply Hunspell-style capitalization fallback for initial-capital and all-uppercase input. Library users can opt into NFC or NFKC normalization explicitly; case folding remains a separate future policy. A complete tab-separated list in the form word<TAB>unsigned-frequency is also accepted by compile --dictionary and check --dictionary; the frequency is used for suggestion ranking and the word portion is used for recognition. Directly loaded lists use only the word portion, so frequency-ranked suggestions require the compiled artifact. Format detection ignores blank lines and comments, including comments containing tabs. A file with plain data or a trailing tab remains a plain word list.

validate imports a Hunspell-style pair under ferrolex's documented compatibility subset and reports structured diagnostics. It decodes UTF-8, ISO-8859-1, and ISO-8859-2 source pairs from their SET declaration; reviewed mixed-encoding catalog pairs are handled by dictionary install. It never invokes an external spell-checking engine; see the import contract and affix semantics.

--hunspell accepts an ordinary .aff path and derives the adjacent .dic. It verifies and uses an installed runtime cache when present; otherwise it strictly imports the sources with a slower-path notice and does not write next to them. Importer errors fail closed. For frequent use or read-only source directories, compile the pair to a writable standalone artifact and pass it with --compiled; catalog-specific encoding overrides require dictionary install and are never inferred from a filename alone.

suggest exposes bounded, deterministic edit-distance suggestions across any number of layered plain-word-list dictionaries, installed Hunspell runtime caches, and compiled artifacts. Each source flag is repeatable, just as it is for check and analyze. It reports when its configured work limits prevent a complete search, still returns any stable partial results, and prints a scaled retry hint when budget exhaustion produced no result. Hunspell suggestions enumerate stored stems and additionally derive bounded affixed and compound forms near the query; they never pre-expand the dictionary. UserDictionary project overlays can be used through the library API. The CLI automatically layers .ferrolex/words.txt and the global ferrolex user word list into check, suggest, and analyze when those files exist. The comparison and ranking contract is documented in Suggestions.

Rust library quick start

use ferrolex::{import, Dictionary, ImportMode, SuggestConfig};

let imported = import(
    "example.aff",
    "SET UTF-8\n",
    "example.dic",
    "1\nferrolex\n",
    ImportMode::Strict,
)?;
let dictionary = imported.dictionary();
assert!(dictionary.contains("ferrolex"));
assert_eq!(
    dictionary
        .suggester(SuggestConfig::default())
        .suggest("ferolex")
        .suggestions()[0]
        .word(),
    "ferrolex"
);
# Ok::<(), ferrolex::ImportError>(())

The ferrolex umbrella package re-exports the supported product crates as ferrolex::hunspell, ferrolex::suggest, and ferrolex::dictionaries. The common importer, dictionary, suggestion, and catalog types are also available at the crate root. Depending only on ferrolex keeps these APIs on the same version-locked release line.

The Node.js binding is the supported pre-1.0 foreign-runtime integration and remains in default-members because its Rust build is part of the npm release gate, although the Cargo crate itself is publish = false. The C ABI and Python binding are evaluation prototypes; the LSP and VS Code work are retained editor prototypes outside the current product scope. These prototype paths are excluded from the default release compilation and test gates, then checked when their paths change or a maintainer starts the prototype workflow manually. See Native integrations.

The optional, digest-pinned LibreOffice installer is documented in Dictionary fetching. It fetches reviewed upstream sources into a cache you select; ferrolex neither bundles nor redistributes dictionary content, and normal commands never download or update dictionaries implicitly. The catalog provides a reviewed per-locale SPDX expression and upstream notice for English, German, Spanish, French, Italian, Portuguese, Dutch, Polish, Russian, Turkish, Arabic, Ukrainian, Swedish, Indonesian, Hindi, and Bengali. Urdu requires a separately reviewed source because it has no pair in the pinned LibreOffice collection. CJK is intentionally deferred until text segmentation has its own contract. The locale compatibility matrix separates safe acquisition from strict import and recognition evidence. Successful strict installs also create a versioned Hunspell runtime cache.

compile --dictionary turns the same word-list syntax used by check into a deterministic native artifact. compile <AFF> <DIC> produces a standalone Hunspell artifact that retains ferrolex's supported affix semantics and can be copied to a machine without the source pair. check --compiled loads either artifact type and can be layered with plain or installed Hunspell dictionaries. validate --compiled verifies its format before use; native artifacts additionally receive the full offset, UTF-8 payload, and sort-order check. The binary format and Hunspell runtime cache document the formats and compatibility policy.

inspect makes the compatibility boundary visible before deployment. It prints the artifact format and version, source metadata where the format records it, and the capabilities supported by the artifact format. For FLXHSP, this is the format-wide reader contract rather than a claim about which optional directives a particular source pair used. This gives release and locale-matrix automation a stable, human-readable artifact report.

Product boundaries

ferrolex owns dictionary acquisition, import, recognition, and suggestions. It does not own document parsing, editor protocols, or language semantics. The Node.js binding is the supported direct runtime integration; the C ABI and Python binding are evaluation prototypes, while the LSP and VS Code work are retained editor prototypes outside the product scope. Existing experimental code may remain while the workspace is simplified, but it must not drive the public API, release matrix, or future release gates. See ADR-0010 for the tier definitions.

Benchmarks

The core lookup benchmark is a local characterization harness, not a published performance claim. Run it on a quiet machine with:

cargo bench -p ferrolex-core

See Performance for the measured contract.

Robustness testing

The regular test suite contains deterministic adversarial corpora for untrusted Hunspell input, compiled artifacts, and bounded suggestions. See Robustness testing for the covered boundaries and focused command.

Security

Please report vulnerabilities privately as described in the security policy. Dictionary and artifact inputs are treated as untrusted throughout the supported import and loading paths.

MSRV

ferrolex supports the Rust version declared in the workspace Cargo.toml; see the MSRV policy.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Dictionary data is not bundled with the engine and has separate licensing.

Ferramenta — A family of Rust tools.

The content pipeline

Tool Job
ferroni Oniguruma-compatible regex engine
ferriki Shiki-compatible syntax highlighting
ferromark Markdown to HTML with a secure default and every GFM extension included.

The language workshop

Tool Job
ferrocat Translation catalog engine
palamedes Internationalization for TypeScript applications

On the workbench

Tool Job
ferrovia SVGO-compatible SVG optimizer
ferralk Glob matching and parallel filesystem walking
ferrugo PDF previews for untrusted files

Sebastian Software
TypeScript, React & Rust consulting
Experts in Agentic Software Development
Work with us · More open source

Copyright © 2026 Sebastian Software GmbH

About

Spell checking for text and code. An independent Rust engine that safely loads existing Hunspell dictionaries.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages