1import string
2from typing import Callable, List
3
4from markdown_it import MarkdownIt
5from markdown_it.rules_core import StateCore
6
7
8def basic_count(text: str) -> int:
9 """Split the string and ignore punctuation only elements."""
10 return sum([el.strip(string.punctuation).isalpha() for el in text.split()])
11
12
13def wordcount_plugin(
14 md: MarkdownIt,
15 *,
16 per_minute: int = 200,
17 count_func: Callable[[str], int] = basic_count,
18 store_text: bool = False,
19) -> None:
20 """Plugin for computing and storing the word count.
21
22 Stores in the ``env`` e.g.::
23
24 env["wordcount"] = {
25 "words": 200
26 "minutes": 1,
27 }
28
29 If "wordcount" is already in the env, it will update it.
30
31 :param per_minute: Words per minute reading speed
32 :param store_text: store all text under a "text" key, as a list of strings
33 """
34
35 def _word_count_rule(state: StateCore) -> None:
36 text: List[str] = []
37 words = 0
38 for token in state.tokens:
39 if token.type == "text":
40 words += count_func(token.content)
41 if store_text:
42 text.append(token.content)
43 elif token.type == "inline":
44 for child in token.children or ():
45 if child.type == "text":
46 words += count_func(child.content)
47 if store_text:
48 text.append(child.content)
49
50 data = state.env.setdefault("wordcount", {})
51 if store_text:
52 data.setdefault("text", [])
53 data["text"] += text
54 data.setdefault("words", 0)
55 data["words"] += words
56 data["minutes"] = int(round(data["words"] / per_minute))
57
58 md.core.ruler.push("wordcount", _word_count_rule)