@@ -8,10 +8,12 @@ EXIT_CODE=0
88VERBOSE=" ${VERBOSE:- 0} "
99
1010log_verbose () {
11- [[ " $VERBOSE " == " 1" ]] && echo " Info: $* "
11+ if [[ " $VERBOSE " == " 1" ]]; then
12+ echo " Info: $* "
13+ fi
1214}
1315
14- ASSET_EXTENSIONS_REGEX=' png|jpg|jpeg|svg|gif|webp|avif|ico|xml|yaml|yml|json|css|js|pdf|zip|tar.gz|woff|woff2|ttf|eot|mp4|webm'
16+ ASSET_EXTENSIONS_REGEX=' png|jpg|jpeg|svg|gif|webp|avif|ico|xml|yaml|yml|json|css|js|pdf|zip|tar\ .gz|woff|woff2|ttf|eot|mp4|webm'
1517SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) " || exit 1
1618REPO_ROOT=" $( cd " $SCRIPT_DIR /.." && pwd) " || exit 1
1719CONTENT_ROOT=" $( cd " $REPO_ROOT /$CONTENT_DIR " && pwd) " || exit 1
@@ -47,15 +49,19 @@ canonicalize_path() {
4749 local path=" $1 "
4850 local result=()
4951 local part
52+ local parts
5053
54+ # Bash 3.2 compatible: use here-string
5155 IFS=' /' read -r -a parts <<< " $path"
5256
5357 for part in " ${parts[@]} " ; do
5458 if [[ -z " $part " || " $part " == " ." ]]; then
5559 continue
5660 elif [[ " $part " == " .." ]]; then
61+ # Bash 3.2 compatible: calculate last index instead of using -1
5762 if [[ ${# result[@]} -gt 0 ]]; then
58- unset ' result[-1]'
63+ local last_idx=$(( ${# result[@]} - 1 ))
64+ unset " result[$last_idx ]"
5965 fi
6066 else
6167 result+=(" $part " )
@@ -105,7 +111,6 @@ check_internal_link() {
105111 return 0
106112 fi
107113
108- # Bash 3.2 compatible lowercase conversion
109114 local clean_lower
110115 clean_lower=" $( printf " %s" " $clean_link " | tr ' [:upper:]' ' [:lower:]' ) "
111116
@@ -139,9 +144,13 @@ check_internal_link() {
139144 target_path=" $REPO_ROOT /static${clean_link} "
140145
141146 elif [[ " $clean_link " == /* ]]; then
142- echo " Error: Unknown absolute path"
143- echo " File: $file :$line_no "
147+ location=" $file "
148+ [[ -n " $line_no " ]] && location=" $file :$line_no "
149+
150+ echo " Error: Unsupported absolute internal path (cannot validate deterministically)"
151+ echo " File: $location "
144152 echo " Link: $link "
153+
145154 EXIT_CODE=1
146155 return
147156
@@ -199,14 +208,21 @@ check_internal_link() {
199208
200209echo " Starting link validation..."
201210
202- while read -r FILE; do
211+ while IFS= read -r FILE; do
212+
203213 CODE_LINES=" "
204214 in_fence=false
205215 line_no=0
206216
207- while IFS= read -r line; do
208- (( line_no++ ))
209-
217+ while IFS= read -r line || [[ -n " $line " ]]; do
218+ (( ++ line_no))
219+ # NOTE:
220+ # Code fence detection is heuristic and does not validate proper pairing.
221+ # The logic simply toggles state when encountering ``` or ~~~ markers.
222+ # If a Markdown file contains an unclosed fence or mismatched fence types,
223+ # all subsequent lines may be treated as code and skipped from validation.
224+ # This behavior is intentional to keep the validator lightweight and
225+ # avoids implementing a full Markdown parser. Such cases require manual review.
210226 if [[ " $line " =~ ^[[:space:]]* (\`\`\` | ~~~) ]]; then
211227 # NOTE:
212228 # Code fence detection assumes fences are properly paired.
@@ -229,20 +245,22 @@ while read -r FILE; do
229245 fi
230246
231247 # NOTE:
232- # Inline code detection is heuristic.
233- # It assumes backticks are paired on the same line.
234- # Escaped backticks are ignored, but complex or malformed
235- # Markdown inline code spans may still be misdetected.
236-
248+ # Inline code detection is heuristic and intentionally simplistic.
249+ # The logic assumes backticks are properly paired within a single line
250+ # after removing escaped backticks. Malformed Markdown, complex inline
251+ # constructs, or unusual escaping patterns may cause false positives
252+ # or false negatives. This validator does not implement a full Markdown
253+ # parser and therefore cannot guarantee perfect inline code detection.
237254 escaped_line=" ${line// \\\` / } "
238- inline_count=$( grep -o " \` " <<< " $escaped_line" | wc -l)
255+ inline_count=$( printf " %s\n" " $escaped_line " | grep -o " \` " || true)
256+ inline_count=$( printf " %s\n" " $inline_count " | wc -l)
239257 if (( inline_count % 2 == 1 )) ; then
240258 CODE_LINES=" $CODE_LINES $line_no "
241259 fi
242260
243261 done < " $FILE "
244262
245- while read -r MATCH; do
263+ while read -r MATCH || [[ -n " $MATCH " ]] ; do
246264 [[ -z " $MATCH " ]] && continue
247265
248266 LINE_NO=" ${MATCH%%:* } "
@@ -254,10 +272,10 @@ while read -r FILE; do
254272 LINK=" ${LINK% )} "
255273
256274 check_internal_link " $LINK " " $FILE " " $LINE_NO "
257- done < <( grep -n -oE ' \]\([^)]+\)' " $FILE " )
275+ done < <( grep -n -oE ' \]\([^)]+\)' " $FILE " || true )
258276
259277 unset CODE_LINES
260- done < <( find " $CONTENT_ROOT " -type f -name " *.md" )
278+ done < <( find " $CONTENT_ROOT " -type f -name " *.md" 2> /dev/null || true )
261279
262280if [[ $EXIT_CODE -eq 0 ]]; then
263281 echo " Link validation passed!"
0 commit comments