summaryrefslogtreecommitdiff
path: root/scripts/spellcheck.sh
blob: caf08f48634322de0f9ca111e98d7ef9440900ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env bash
# Spell-check Markdown sources using hunspell with ru_RU + en_US dictionaries.
# Words listed in .spellcheck-allow.txt are accepted as correct.
#
# Usage: scripts/spellcheck.sh [paths...]
# Default scope when no args given: content/, docs/, README.md, AGENTS.md.

set -euo pipefail

ROOT=$(cd "$(dirname "$0")/.." && pwd)
ALLOW="${ROOT}/.spellcheck-allow.txt"
[ -f "${ALLOW}" ] || { echo "missing allow list: ${ALLOW}" >&2; exit 1; }

cd "${ROOT}"

PATHS=("$@")
if [ "${#PATHS[@]}" -eq 0 ]; then
  PATHS=(content docs README.md AGENTS.md)
fi

# Build the file list. Avoid `mapfile`/`readarray` so the script runs on
# Bash 3.2 (macOS default) as well as Linux's modern bash.
files=()
while IFS= read -r line; do
  [ -n "${line}" ] && files+=("${line}")
done < <(
  for p in "${PATHS[@]}"; do
    [ -e "${p}" ] || continue
    if [ -d "${p}" ]; then
      find "${p}" -type f -name '*.md'
    else
      printf '%s\n' "${p}"
    fi
  done | sort -u
)

if [ "${#files[@]}" -eq 0 ]; then
  echo "no markdown files found"
  exit 0
fi

# Sanity check: confirm hunspell can actually load both dictionaries.
# (On some environments — e.g. GitHub Actions ubuntu-24.04 runner image —
# Ubuntu's hunspell-ru / hunspell-en-us packages can install successfully
# but leave /usr/share/hunspell/ stripped of the .aff/.dic files. Set
# DICPATH to a directory containing ru_RU.{aff,dic} and en_US.{aff,dic} as
# a workaround.)
if echo 'архитектура' | hunspell -d ru_RU,en_US -l | grep -qx 'архитектура'; then
  echo "::error::hunspell ru_RU dictionary not loaded properly"
  echo "DICPATH=${DICPATH:-(unset)}"
  hunspell -D 2>&1 | head -30
  exit 1
fi
if echo 'architecture' | hunspell -d ru_RU,en_US -l | grep -qx 'architecture'; then
  echo "::error::hunspell en_US dictionary not loaded properly"
  echo "DICPATH=${DICPATH:-(unset)}"
  hunspell -D 2>&1 | head -30
  exit 1
fi

# Explicit template — bare `mktemp` errors on macOS/BSD (`too few X's in template`).
unknown=$(mktemp "${TMPDIR:-/tmp}/landau-spellcheck.XXXXXX")
trap 'rm -f "${unknown}"' EXIT

for f in "${files[@]}"; do
  awk '
    BEGIN { fm = 0; code = 0 }
    NR == 1 && /^---[[:space:]]*$/ { fm = 1; next }
    fm && /^---[[:space:]]*$/      { fm = 0; next }
    fm                             { next }
    /^[[:space:]]*```/             { code = !code; next }
    code                           { next }
    {
      gsub(/<[^>]*>/, " ")                              # HTML tags
      gsub(/`[^`]*`/, " ")                              # inline code spans
      gsub(/https?:\/\/[^[:space:])]+/, " ")            # URLs
      gsub(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+/, " ")     # emails
      gsub(/[][(){}|*_~#]/, " ")                        # markdown punctuation
      # Strip invisible Unicode chars that confuse hunspell tokenizer.
      # Byte sequences below are the UTF-8 encodings of:
      gsub(/\xef\xb8\x8f/, " ")                         # U+FE0F variation selector-16
      gsub(/\xef\xb8\x8e/, " ")                         # U+FE0E variation selector-15
      gsub(/\xe2\x80\x8d/, " ")                         # U+200D zero-width joiner
      gsub(/\xe2\x80\x8c/, " ")                         # U+200C zero-width non-joiner
      gsub(/\xe2\x80\x8b/, " ")                         # U+200B zero-width space
      print
    }
  ' "$f" \
  | hunspell -d ru_RU,en_US -l \
  >> "${unknown}"
done

# Empty unknown list = nothing to filter; happy exit.
if [ ! -s "${unknown}" ]; then
  echo "Spell check OK across ${#files[@]} files."
  exit 0
fi

# Filter accumulated unknowns against the allow list (exact, fixed strings).
sort -u "${unknown}" -o "${unknown}"
filtered=$(grep -vxFf "${ALLOW}" "${unknown}" || true)

if [ -n "${filtered}" ]; then
  echo "::error::Unknown words found in markdown sources:"
  printf '%s\n' "${filtered}" | sed 's/^/  /'
  echo
  echo "If these are intentional (proper nouns, technical terms),"
  echo "add them to .spellcheck-allow.txt. Otherwise, fix the spelling."
  exit 1
fi

echo "Spell check OK across ${#files[@]} files."