1111Usage (book.toml):
1212 [output.swift-test]
1313 command = "python3 path/to/mdbook-swift-test.py"
14- supported-platforms = ["linux", "darwin"] # optional; sys.platform values to run on
14+ supported-platforms = ["linux", "darwin", "win32"] # optional; sys.platform values to run on
15+
16+ Do not run this script directly in a terminal: mdbook pipes JSON into stdin.
17+ Use `mdbook build` from the book directory (or `mdbook-swift-test.py --help`).
18+
19+ Debugging (see what mdBook sent):
20+ Set MDBOOK_SWIFT_TEST_DUMP_CONTEXT to a file path, then run `mdbook build`.
21+ The full renderer context JSON is written there; search for "content" or ```swift.
1522"""
1623
1724import json
1825import os
1926import re
27+ import shutil
2028import subprocess
2129import sys
2230import tempfile
@@ -79,11 +87,19 @@ def unhide_line(line: str) -> str:
7987
8088def extract_blocks (content : str , path : str ) -> Iterator [CodeBlock ]:
8189 """Yields Swift code blocks from markdown content."""
90+ # mdBook may pass CRLF on Windows; the fence regex only matches LF after ```info.
91+ if "\r " in content :
92+ content = content .replace ("\r \n " , "\n " ).replace ("\r " , "\n " )
8293 for m in CODE_BLOCK .finditer (content ):
8394 attrs = Attributes .parse (m .group (1 ))
8495 if attrs .language == "swift" :
8596 start_line = content [: m .start ()].count ("\n " ) + 2
86- yield CodeBlock (m .group (2 ).rstrip ("\n " ), start_line , path , attrs )
97+ yield CodeBlock (m .group (2 ).rstrip ("\n \r " ), start_line , path , attrs )
98+
99+
100+ def _book_roots (book : dict ) -> list :
101+ """Top-level spine entries: mdBook 0.5+ uses `items`, older versions use `sections`."""
102+ return book .get ("items" ) or book .get ("sections" , [])
87103
88104
89105def extract_from_chapter (item : dict ) -> Iterator [CodeBlock ]:
@@ -126,7 +142,7 @@ def compile_swift(source: str) -> TestResult:
126142
127143def prepare_source (code : str ) -> str :
128144 """A code block's source code with hidden lines revealed and placeholder bodies expanded."""
129- unhidden = "\n " .join (unhide_line (line ) for line in code .split ( " \n " ))
145+ unhidden = "\n " .join (unhide_line (line ) for line in code .splitlines ( ))
130146 return re .sub (r"\{\s*\.\.\.\s*\}" , "{ fatalError() }" , unhidden )
131147
132148
@@ -135,8 +151,40 @@ def line(char: str, count: int = 60) -> str:
135151 return char * count
136152
137153
138- def main ():
154+ def _usage_stderr () -> None :
155+ print (
156+ "mdbook-swift-test.py is an mdbook backend: it reads the book JSON from stdin.\n "
157+ "Run from the book directory:\n "
158+ " mdbook build\n "
159+ "mdbook will invoke this command when [output.swift-test] is set in book.toml.\n "
160+ "On Windows, use the same `command` as in book.toml (e.g. python path\\ to\\ mdbook-swift-test.py)." ,
161+ file = sys .stderr ,
162+ )
163+
164+
165+ def main () -> None :
166+ if len (sys .argv ) > 1 and sys .argv [1 ] in ("-h" , "--help" ):
167+ print (__doc__ .strip (), file = sys .stderr )
168+ sys .exit (0 )
169+
170+ # Interactive terminal has no piped JSON; json.load would block until EOF (Ctrl+C).
171+ if sys .stdin .isatty ():
172+ _usage_stderr ()
173+ sys .exit (2 )
174+
139175 context = json .load (sys .stdin )
176+
177+ dump_path = os .environ .get ("MDBOOK_SWIFT_TEST_DUMP_CONTEXT" , "" ).strip ()
178+ if dump_path :
179+ # ensure_ascii=True so lone surrogates from mdBook/Rust still serialize (UTF-8 cannot store them).
180+ with open (dump_path , "w" , encoding = "utf-8" ) as df :
181+ json .dump (context , df , indent = 2 , ensure_ascii = True )
182+ print (
183+ f"Wrote mdBook renderer context to { dump_path !r} "
184+ "(MDBOOK_SWIFT_TEST_DUMP_CONTEXT)" ,
185+ file = sys .stderr ,
186+ )
187+
140188 cfg = (
141189 context .get ("config" , {})
142190 .get ("output" , {})
@@ -150,9 +198,17 @@ def main():
150198 )
151199 sys .exit (0 )
152200
201+ # CI and many dev machines have no Swift on Windows; skip instead of failing every block.
202+ if sys .platform == "win32" and not shutil .which ("swiftc" ):
203+ print (
204+ "Skipping Swift code example tests (swiftc not on PATH; install Swift for Windows to enable)" ,
205+ file = sys .stderr ,
206+ )
207+ sys .exit (0 )
208+
153209 book = context .get ("book" , {})
154210 blocks = list (
155- chain .from_iterable (extract_from_chapter (s ) for s in book . get ( "sections" , [] ))
211+ chain .from_iterable (extract_from_chapter (s ) for s in _book_roots ( book ))
156212 )
157213
158214 results = [
0 commit comments