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
@@ -291,7 +384,7 @@ 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
@@ -308,18 +401,19 @@ 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
315408 # Write the rendered output to toc.yml at the operators_doc level
316- with Path .open (docs_path / "toc.yml" , "w" ) as file :
409+ toc_path = docs_path / Path ("toc.yml" )
410+ with toc_path .open (mode = "w" ) as file :
317411 file .write (output )
318412
319413
320414def generate_operators_doc (
321- ansys_path : Path ,
322415 output_path : Path ,
416+ ansys_path : Path = None ,
323417 include_composites : bool = False ,
324418 include_sound : bool = False ,
325419 include_private : bool = False ,
@@ -334,10 +428,10 @@ def generate_operators_doc(
334428
335429 Parameters
336430 ----------
337- ansys_path:
338- Path to an Ansys/DPF installation.
339431 output_path:
340432 Path to write the output files at.
433+ ansys_path:
434+ Path to an Ansys/DPF installation.
341435 include_composites:
342436 Whether to include operators of the Composites plugin.
343437 include_sound:
@@ -357,7 +451,10 @@ def generate_operators_doc(
357451 operators = get_plugin_operators (server , desired_plugin )
358452 for operator_name in operators :
359453 generate_operator_doc (server , operator_name , include_private , output_path )
454+ # Generate the toc tree
360455 generate_toc_tree (output_path )
456+ # Use update files in output_path
457+ update_operator_descriptions (output_path )
361458
362459
363460def run_with_args (): # pragma: nocover
@@ -389,8 +486,8 @@ def run_with_args(): # pragma: nocover
389486 args = parser .parse_args ()
390487
391488 generate_operators_doc (
392- ansys_path = args .ansys_path ,
393489 output_path = args .output_path ,
490+ ansys_path = args .ansys_path ,
394491 include_composites = args .include_composites ,
395492 include_sound = args .include_sound ,
396493 include_private = args .include_private ,
0 commit comments