2424from __future__ import annotations
2525
2626import argparse
27- from os import PathLike
27+ import os
2828from pathlib import Path
29+ import re
2930
3031from ansys .dpf import core as dpf
3132from ansys .dpf .core .changelog import Changelog
@@ -51,7 +52,7 @@ def __init__(
5152
5253
5354def initialize_server (
54- ansys_path : str | PathLike = None ,
55+ ansys_path : str | os . PathLike = None ,
5556 include_composites : bool = False ,
5657 include_sound : bool = False ,
5758 verbose : bool = False ,
@@ -105,6 +106,98 @@ def initialize_server(
105106 return server
106107
107108
109+ def extract_operator_description_update (content : str ) -> str :
110+ """Extract the updated description to use for an operator.
111+
112+ Parameters
113+ ----------
114+ content:
115+ The contents of the '*_upd.md' file.
116+
117+ Returns
118+ -------
119+ description_update:
120+ The updated description to use for the operator.
121+ """
122+ match = re .search (r"## Description\s*(.*?)\s*(?=## |\Z)" , content , re .DOTALL )
123+ return match .group (0 ) + os .linesep if match else None
124+
125+
126+ def replace_operator_description (original_documentation : str , new_description : str ):
127+ """Replace the original operator description with a new one in the operator documentation file.
128+
129+ Parameters
130+ ----------
131+ original_documentation:
132+ Original operator documentation.
133+ new_description:
134+ New operator description
135+
136+ Returns
137+ -------
138+ updated_documentation:
139+ The updated operator documentation content
140+
141+ """
142+ return re .sub (
143+ r"## Description\s*.*?(?=## |\Z)" , new_description , original_documentation , flags = re .DOTALL
144+ )
145+
146+
147+ def update_operator_descriptions (
148+ docs_path : Path ,
149+ verbose : bool = False ,
150+ ):
151+ """Update operator descriptions based on '*_upd.md' files in DPF documentation sources.
152+
153+ Parameters
154+ ----------
155+ docs_path:
156+ Root path of the DPF documentation to update operator descriptions for.
157+ verbose:
158+ Whether to print progress information.
159+
160+ """
161+ all_md_files = {}
162+ specs_path = docs_path / Path ("operator-specifications" )
163+ # Walk through the target directory and all subdirectories
164+ for root , _ , files in os .walk (specs_path ):
165+ for file in files :
166+ if file .endswith (".md" ):
167+ full_path = Path (root ) / Path (file )
168+ all_md_files [str (full_path )] = file # Store full path and just filename
169+
170+ for base_path , file_name in all_md_files .items ():
171+ if file_name .endswith ("_upd.md" ):
172+ continue # Skip update files
173+
174+ # Construct the expected update file name and path
175+ upd_file_name = f"{ file_name [:- 3 ]} _upd.md"
176+
177+ # Look for the update file in the same folder
178+ upd_path = Path (base_path ).parent / Path (upd_file_name )
179+ if not upd_path .exists ():
180+ continue
181+
182+ # Load contents
183+ with Path (base_path ).open (mode = "r" , encoding = "utf-8" ) as bf :
184+ base_content = bf .read ()
185+ with Path (upd_path ).open (mode = "r" , encoding = "utf-8" ) as uf :
186+ upd_content = uf .read ()
187+
188+ # Extract and replace description
189+ new_description = extract_operator_description_update (upd_content )
190+ if new_description :
191+ updated_content = replace_operator_description (base_content , new_description )
192+ with Path (base_path ).open (mode = "w" , encoding = "utf-8" ) as bf :
193+ bf .write (updated_content )
194+ if verbose :
195+ print (f"Updated description for: { file_name } " )
196+ else :
197+ if verbose :
198+ print (f"No operator description found in: { upd_path } " )
199+
200+
108201def fetch_doc_info (server : dpf .AnyServerType , operator_name : str ) -> dict :
109202 """Fetch information about the specifications of a given operator.
110203
@@ -265,14 +358,14 @@ def generate_operator_doc(
265358 if not include_private and operator_info ["exposure" ] == "private" :
266359 return
267360 template_path = Path (__file__ ).parent / "operator_doc_template.md"
268- spec_folder = output_path / "operator-specifications"
361+ spec_folder = output_path / Path ( "operator-specifications" )
269362 category_dir = spec_folder / category
270363 spec_folder .mkdir (parents = True , exist_ok = True )
271364 if category is not None :
272365 category_dir .mkdir (parents = True , exist_ok = True ) # Ensure all parent directories are created
273366 file_dir = category_dir
274367 else :
275- file_dir = output_path / "operator-specifications"
368+ file_dir = spec_folder
276369 with Path .open (template_path , "r" ) as file :
277370 template = jinja2 .Template (file .read ())
278371
@@ -281,8 +374,8 @@ def generate_operator_doc(
281374 file .write (output )
282375
283376
284- def generate_toc_tree (docs_path : Path ):
285- """Write the global toc.yml file for the DPF documentation based on the operators found.
377+ def update_toc_tree (docs_path : Path ):
378+ """Update the global toc.yml file for the DPF documentation based on the operators found.
286379
287380 Parameters
288381 ----------
@@ -291,14 +384,14 @@ def generate_toc_tree(docs_path: Path):
291384
292385 """
293386 data = []
294- specs_path = docs_path / "operator-specifications"
387+ specs_path = docs_path / Path ( "operator-specifications" )
295388 for folder in specs_path .iterdir ():
296389 if folder .is_dir (): # Ensure 'folder' is a directory
297390 category = folder .name
298391 operators = [] # Reset operators for each category
299392 for file in folder .iterdir ():
300393 if (
301- file .is_file () and file .suffix == ".md"
394+ file .is_file () and file .suffix == ".md" and not file . name . endswith ( "_upd.md" )
302395 ): # Ensure 'file' is a file with .md extension
303396 file_name = file .name
304397 file_path = f"{ category } /{ file_name } "
@@ -308,18 +401,27 @@ def generate_toc_tree(docs_path: Path):
308401
309402 # Render the Jinja2 template
310403 template_path = Path (__file__ ).parent / "toc_template.j2"
311- with Path .open (template_path , "r" ) as template_file :
404+ with template_path .open (mode = "r" ) as template_file :
312405 template = jinja2 .Template (template_file .read ())
313406 output = template .render (data = data ) # Pass 'data' as a named argument
314407
315- # Write the rendered output to toc.yml at the operators_doc level
316- with Path .open (docs_path / "toc.yml" , "w" ) as file :
317- file .write (output )
408+ # Update the original toc.yml file with the rendered output for operator_specifications
409+ toc_path = docs_path / Path ("toc.yml" )
410+ with toc_path .open (mode = "r" ) as file :
411+ original_toc = file .read ()
412+ new_toc = re .sub (
413+ pattern = r"- name: Operator specifications\s*.*?(?=- name: Changelog|\Z)" ,
414+ repl = output ,
415+ string = original_toc ,
416+ flags = re .DOTALL ,
417+ )
418+ with toc_path .open (mode = "w" ) as file :
419+ file .write (new_toc )
318420
319421
320422def generate_operators_doc (
321- ansys_path : Path ,
322423 output_path : Path ,
424+ ansys_path : Path = None ,
323425 include_composites : bool = False ,
324426 include_sound : bool = False ,
325427 include_private : bool = False ,
@@ -334,10 +436,10 @@ def generate_operators_doc(
334436
335437 Parameters
336438 ----------
337- ansys_path:
338- Path to an Ansys/DPF installation.
339439 output_path:
340440 Path to write the output files at.
441+ ansys_path:
442+ Path to an Ansys/DPF installation.
341443 include_composites:
342444 Whether to include operators of the Composites plugin.
343445 include_sound:
@@ -357,7 +459,10 @@ def generate_operators_doc(
357459 operators = get_plugin_operators (server , desired_plugin )
358460 for operator_name in operators :
359461 generate_operator_doc (server , operator_name , include_private , output_path )
360- generate_toc_tree (output_path )
462+ # Generate the toc tree
463+ update_toc_tree (output_path )
464+ # Use update files in output_path
465+ update_operator_descriptions (output_path )
361466
362467
363468def run_with_args (): # pragma: nocover
@@ -368,9 +473,7 @@ def run_with_args(): # pragma: nocover
368473 parser .add_argument (
369474 "--ansys_path" , default = None , help = "Path to Ansys DPF Server installation directory"
370475 )
371- parser .add_argument (
372- "--output_path" , default = None , help = "Path to output directory" , required = True
373- )
476+ parser .add_argument ("--output_path" , default = "." , help = "Path to output directory" )
374477 parser .add_argument ("--include_private" , action = "store_true" , help = "Include private operators" )
375478 parser .add_argument (
376479 "--include_composites" , action = "store_true" , help = "Include Composites operators"
@@ -389,8 +492,8 @@ def run_with_args(): # pragma: nocover
389492 args = parser .parse_args ()
390493
391494 generate_operators_doc (
392- ansys_path = args .ansys_path ,
393495 output_path = args .output_path ,
496+ ansys_path = args .ansys_path ,
394497 include_composites = args .include_composites ,
395498 include_sound = args .include_sound ,
396499 include_private = args .include_private ,
0 commit comments