11import csv
2- from pathlib import Path
2+ import difflib
3+ import io
34import re
5+ import sys
6+ from pathlib import Path
7+
8+
9+ def write_if_changed (path : Path , content : str , * , original_content = None , encoding : str = "utf-8" , label : str | None = None ) -> bool :
10+ if original_content is None :
11+ if path .exists ():
12+ original_content = path .read_text (encoding = encoding )
13+ else :
14+ original_content = ""
15+ old_content = original_content
16+ if old_content == content :
17+ return False
18+ display_label = str (label or path )
19+ old_lines = old_content .splitlines (keepends = True )
20+ new_lines = content .splitlines (keepends = True )
21+ diff = difflib .unified_diff (
22+ old_lines ,
23+ new_lines ,
24+ fromfile = f"a/{ display_label } " ,
25+ tofile = f"b/{ display_label } " ,
26+ )
27+ diff_output = "" .join (diff )
28+ if diff_output :
29+ print (diff_output , end = "" if diff_output .endswith ("\n " ) else "\n " )
30+ path .write_text (content , encoding = encoding )
31+ return True
432
533
634def update_heading_numbers_and_generate_toc (directory : Path ):
@@ -15,6 +43,13 @@ def update_heading_numbers_and_generate_toc(directory: Path):
1543 all_learning_objectives = []
1644 Path ().as_posix ()
1745 chapters = {}
46+ modified_paths = set ()
47+
48+ def rel_label (path : Path ) -> str :
49+ try :
50+ return str (path .relative_to (directory ))
51+ except ValueError :
52+ return str (path )
1853
1954 for file in sorted (directory .glob ("website/docs/**/*.md" )):
2055 file_path = file .relative_to (Path ('website/docs' ).resolve ())
@@ -144,30 +179,39 @@ def update_heading_numbers_and_generate_toc(directory: Path):
144179 print (f"Duplicate anchor '{ heading_anchor } ' in '{ file_path } ' and '{ heading_catalog [heading_anchor ][0 ]} '" )
145180 heading_catalog [heading_anchor ] = (file_path , * heading )
146181
147- with file .open ("w" , encoding = "utf-8" ) as md_file :
148- md_file .writelines (updated_lines )
182+ original_content = "" .join (lines )
183+ updated_content = "" .join (updated_lines )
184+ if write_if_changed (file , updated_content , original_content = original_content , label = rel_label (file )):
185+ modified_paths .add (file .resolve ())
149186
150187 all_learning_objectives .extend (learning_objectives )
151188
152189 sorted_lo = sorted (all_learning_objectives , key = lambda x : x [0 ])
153190
154- with (directory / "LOs.csv" ).open ("w" , encoding = "utf-8" ) as csv_file :
155- writer = csv .writer (csv_file )
156- writer .writerow (["LO ID" , "K Level" , "Content" , "Slide Number" , "Done" , "Notes" ])
157- writer .writerows ([(f"LO-{ lo_id } " , f"({ k_level } )" , lo_content , "" , "" , "" ) for lo_id , k_level , lo_content in sorted_lo ])
191+ lo_csv_buffer = io .StringIO ()
192+ writer = csv .writer (lo_csv_buffer )
193+ writer .writerow (["LO ID" , "K Level" , "Content" , "Slide Number" , "Done" , "Notes" ])
194+ writer .writerows ([(f"LO-{ lo_id } " , f"({ k_level } )" , lo_content , "" , "" , "" ) for lo_id , k_level , lo_content in sorted_lo ])
195+ lo_csv_content = lo_csv_buffer .getvalue ().replace ("\r \n " , "\n " )
196+ lo_csv_buffer .close ()
197+ lo_csv_path = directory / "LOs.csv"
198+ if write_if_changed (lo_csv_path , lo_csv_content , label = rel_label (lo_csv_path )):
199+ modified_paths .add (lo_csv_path .resolve ())
158200
159201 print (f"Total LOs: { len (sorted_lo )} " )
160202 toc_entries .sort (key = lambda x : x [0 ])
161203
162204 readme_path = directory / "README.md"
163- with readme_path .open ("w" , encoding = "utf-8" ) as readme_file :
164- readme_file .write ("# Table of Contents\n \n " )
165- for _ , toc_entry in toc_entries :
166- toc_entry = toc_entry .replace ("`](" , "`](website/docs/" )
167- readme_file .write (toc_entry + "\n " )
205+ readme_lines = ["# Table of Contents\n \n " ]
206+ for _ , toc_entry in toc_entries :
207+ toc_entry = toc_entry .replace ("`](" , "`](website/docs/" )
208+ readme_lines .append (toc_entry + "\n " )
209+ readme_content = "" .join (readme_lines )
210+ if write_if_changed (readme_path , readme_content , label = rel_label (readme_path )):
211+ modified_paths .add (readme_path .resolve ())
168212
169213 for file in sorted (directory .glob ("website/docs/**/*.md" )):
170- file_path = file .relative_to (Path ('website/docs' ).resolve ())
214+ docs_path = file .relative_to (Path ('website/docs' ).resolve ())
171215 match = chapter_file_pattern .fullmatch (file .as_posix ())
172216 if not match :
173217 continue
@@ -178,7 +222,7 @@ def update_heading_numbers_and_generate_toc(directory: Path):
178222 for lineno , line in enumerate (lines ):
179223 def replace_link (match ):
180224 description , link_file , anchor_name = match .groups ()
181- link_file = link_file or file_path
225+ link_file = link_file or docs_path
182226 if anchor_name in heading_catalog :
183227 file_path , numbering , title , anchor = heading_catalog [anchor_name ]
184228 if title .strip () == anchor_name .strip () or anchor .endswith (anchor_name ):
@@ -193,24 +237,29 @@ def replace_link(match):
193237 updated_line = re .sub (internal_link_pattern , replace_link , line )
194238 updated_lines .append (updated_line )
195239
196- with file .open ("w" , encoding = "utf-8" ) as md_file :
197- md_file .writelines (updated_lines )
240+ original_content = "" .join (lines )
241+ updated_content = "" .join (updated_lines )
242+ if write_if_changed (file , updated_content , original_content = original_content , label = rel_label (file )):
243+ modified_paths .add (file .resolve ())
198244
199- with Path ("website" , "docs" , "learning_objectives.md" ).open ("w" , encoding = "utf-8" ) as lo_file :
200- anchor = {}
201- for k , v in heading_catalog .items ():
202- numbering = v [1 ]
203- if len (numbering ) <= 3 :
204- anchor [v [1 ]] = str (v [0 ])
205- else :
206- anchor [v [1 ]] = f'{ v [0 ]} #{ v [- 1 ]} '
207- lo_file .write ("# Learning Objectives\n " )
208- lo_file .write (f'| ID | K-Level | Content |\n ' )
209- lo_file .write (f'| --- | --- | --- |\n ' )
210- for lo_id , k_level , lo_content in sorted_lo :
211- lo_file .write (f'| [`LO-{ lo_id } `]({ anchor .get (lo_id .split ("-" )[0 ])} ) | { k_level } | { lo_content .replace ('|' , '\\ |' )} |\n ' )
245+ learning_objectives_path = directory / "website" / "docs" / "learning_objectives.md"
246+ anchor = {}
247+ for k , v in heading_catalog .items ():
248+ numbering = v [1 ]
249+ if len (numbering ) <= 3 :
250+ anchor [v [1 ]] = str (v [0 ])
251+ else :
252+ anchor [v [1 ]] = f'{ v [0 ]} #{ v [- 1 ]} '
253+ lo_md_lines = ["# Learning Objectives\n " , '| ID | K-Level | Content |\n ' , '| --- | --- | --- |\n ' ]
254+ for lo_id , k_level , lo_content in sorted_lo :
255+ lo_md_lines .append (f'| [`LO-{ lo_id } `]({ anchor .get (lo_id .split ("-" )[0 ])} ) | { k_level } | { lo_content .replace ("|" , "\\ |" )} |\n ' )
256+ lo_md_content = "" .join (lo_md_lines )
257+ if write_if_changed (learning_objectives_path , lo_md_content , label = rel_label (learning_objectives_path )):
258+ modified_paths .add (learning_objectives_path .resolve ())
212259
260+ return len (modified_paths )
213261
214262
215263if __name__ == "__main__" :
216- update_heading_numbers_and_generate_toc (Path .cwd ())
264+ fix_count = update_heading_numbers_and_generate_toc (Path .cwd ())
265+ sys .exit (1 if fix_count > 0 else 0 )
0 commit comments