Skip to content

Commit

Permalink
Make spellcheck optional. Add documentation for where to find spellch…
Browse files Browse the repository at this point in the history
…eck resources.
willcrichton committed Nov 3, 2023
1 parent 2c26367 commit b2ee6e1
Showing 4 changed files with 51 additions and 35 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -247,4 +247,5 @@ You can configure mdbook-quiz by adding options to the `[preprocessor.quiz]` sec

* `fullscreen` (boolean): If true, then a quiz will take up the web page's full screen during use.
* `cache-answers` (boolean): If true, then the user's answers will be saved in their browser's `localStorage`. Then the quiz will show the user's answers even after they reload the page.
* `more-words` (path): An optional path to a `.dic` file that adds valid words to the spellchecker.
* `spellcheck` (boolean): If true, then run a spellchecker on all Markdown strings.
* `more-words` (path): An optional path to a `.dic` file that adds valid words to the spellchecker. You can find a base dictionary for each language in [wooorm/dictionaries](https://github.com/wooorm/dictionaries/tree/main/dictionaries). You can find documentation about how to write a `.dic` file in [this blog post](https://typethinker.blogspot.com/2008/02/fun-with-aspell-word-lists.html).
60 changes: 31 additions & 29 deletions crates/mdbook-quiz-validate/src/impls/markdown.rs
Original file line number Diff line number Diff line change
@@ -29,36 +29,38 @@ impl Validate for Markdown {
nodes
}

let nodes = collect_nodes(&root);
let dict = crate::spellcheck::dictionary();
let open_quote = &cx.contents()[value.start()..];
let quote_size = if let Some(next) = open_quote.strip_prefix(r#"""""#) {
if next.starts_with('\n') {
4
if cx.spellcheck {
let nodes = collect_nodes(&root);
let dict = crate::spellcheck::dictionary();
let open_quote = &cx.contents()[value.start()..];
let quote_size = if let Some(next) = open_quote.strip_prefix(r#"""""#) {
if next.starts_with('\n') {
4
} else {
3
}
} else {
3
}
} else {
1
};
let base = value.start() + quote_size;
for node in nodes {
if let (Node::Text(text), Some(pos)) = (node, node.position()) {
let errors = dict
.check_indices(&text.value)
.filter(|(_, s)| *s != "-")
.filter(|(_, s)| s.parse::<isize>().is_err())
.filter(|(_, s)| s.parse::<f32>().is_err());
for (idx, substr) in errors {
// base: location of string literal in TOML file
// pos.start.offset: location of markdown text node in the string
// idx: location of error in text node
let span = (base + pos.start.offset + idx, substr.len());
let error = SpellingError {
word: substr.to_string(),
span: span.into(),
};
cx.warning(error);
1
};
let base = value.start() + quote_size;
for node in nodes {
if let (Node::Text(text), Some(pos)) = (node, node.position()) {
let errors = dict
.check_indices(&text.value)
.filter(|(_, s)| *s != "-")
.filter(|(_, s)| s.parse::<isize>().is_err())
.filter(|(_, s)| s.parse::<f32>().is_err());
for (idx, substr) in errors {
// base: location of string literal in TOML file
// pos.start.offset: location of markdown text node in the string
// idx: location of error in text node
let span = (base + pos.start.offset + idx, substr.len());
let error = SpellingError {
word: substr.to_string(),
span: span.into(),
};
cx.warning(error);
}
}
}
}
10 changes: 6 additions & 4 deletions crates/mdbook-quiz-validate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -35,15 +35,17 @@ pub(crate) struct ValidationContext {
path: PathBuf,
contents: String,
ids: IdSet,
spellcheck: bool,
}

impl ValidationContext {
pub fn new(path: &Path, contents: &str, ids: IdSet) -> Self {
pub fn new(path: &Path, contents: &str, ids: IdSet, spellcheck: bool) -> Self {
ValidationContext {
diagnostics: Default::default(),
path: path.to_owned(),
contents: contents.to_owned(),
ids,
spellcheck,
}
}

@@ -147,8 +149,8 @@ struct ParseError {
}

/// Runs validation on a quiz with TOML-format `contents` at `path` under the ID set `ids`.
pub fn validate(path: &Path, contents: &str, ids: &IdSet) -> anyhow::Result<()> {
let mut cx = ValidationContext::new(path, contents, Arc::clone(ids));
pub fn validate(path: &Path, contents: &str, ids: &IdSet, spellcheck: bool) -> anyhow::Result<()> {
let mut cx = ValidationContext::new(path, contents, Arc::clone(ids), spellcheck);

let parse_result = toml::from_str::<Quiz>(contents);
match parse_result {
@@ -179,7 +181,7 @@ pub fn validate(path: &Path, contents: &str, ids: &IdSet) -> anyhow::Result<()>

#[cfg(test)]
pub(crate) fn harness(contents: &str) -> anyhow::Result<()> {
validate(Path::new("dummy.rs"), contents, &IdSet::default())
validate(Path::new("dummy.rs"), contents, &IdSet::default(), true)
}

#[cfg(test)]
13 changes: 12 additions & 1 deletion crates/mdbook-quiz/src/main.rs
Original file line number Diff line number Diff line change
@@ -45,6 +45,11 @@ struct QuizConfig {
/// Sets the default language for syntax highlighting.
default_language: Option<String>,

/// If true, then run a spellchecker on all Markdown strings.
///
/// You can add a custom dictionary via the `more-words` key.
spellcheck: Option<bool>,

/// Path to a .dic file containing words to include in the spellcheck dictionary.
more_words: Option<PathBuf>,

@@ -121,7 +126,12 @@ impl QuizPreprocessor {
let mut content_toml = fs::read_to_string(&quiz_path_abs)
.with_context(|| format!("Failed to read quiz file: {}", quiz_path_abs.display()))?;

mdbook_quiz_validate::validate(&quiz_path_abs, &content_toml, &self.question_ids)?;
mdbook_quiz_validate::validate(
&quiz_path_abs,
&content_toml,
&self.question_ids,
self.config.spellcheck.unwrap_or(false),
)?;

let changed = self.auto_id(&quiz_path_abs, &content_toml)?;
if changed {
@@ -186,6 +196,7 @@ impl SimplePreprocessor for QuizPreprocessor {
more_words: config_toml
.get("more-words")
.map(|value| value.as_str().unwrap().into()),
spellcheck: parse_bool("spellcheck"),
dev_mode,
};

0 comments on commit b2ee6e1

Please sign in to comment.