@@ -3199,7 +3199,7 @@ def DeleteCase(self, i: int, **kw):
31993199 # Local function to perform deletion
32003200 def del_folder (frun ):
32013201 # Delete the folder using :mod:`shutil`
3202- shutil .rmtree (frun )
3202+ shutil .rmtree (frun , ignore_errors = True )
32033203 # Status update
32043204 print (" Deleted folder '%s'" % frun )
32053205 # Get the case name and go there.
@@ -4497,17 +4497,51 @@ def abspath(self, fname: str) -> str:
44974497 return os .path .join (self .RootDir , fname_sys )
44984498
44994499 # Copy files
4500- @run_rootdir
45014500 def copy_files (self , i : int ):
4501+ r"""Copy files from *Mesh* section
4502+
4503+ This applies to both *CopyFiles* and *CopyAsFiles* in the
4504+ *Mesh* section. The former will copy a given file into the run
4505+ folder for case *i* using the base name of the original (source)
4506+ file. Using
4507+
4508+ .. code-block:: javascript
4509+
4510+ "Mesh": {
4511+ "CopyAsFiles": {
4512+ "inputs/mesh-config02.ugrid": "mesh.ugrid"
4513+ }
4514+ }
4515+
4516+ will copy the file ``inputs/mesh-config02.ugrid`` into the run
4517+ folder but name it ``mesh.ugrid`` there.
4518+
4519+ :Call:
4520+ >>> cntl.copy_files(i)
4521+ :Inputs:
4522+ *cntl*: :class:`cape.cfdx.cntl.Cntl`
4523+ Overall CAPE control instance
4524+ *i*: :class:`int`
4525+ Case index
4526+ :Versions:
4527+ * 2025-09-19 ``@ddalle``: v1.0
4528+ """
4529+ # Ensure case index is set
4530+ self .opts .setx_i (i )
4531+ # Create case folder
4532+ self .make_case_folder (i )
4533+ # Two categories
4534+ self ._copy_as_files (i )
4535+ self ._copy_files (i )
4536+
4537+ # Copy files w/o renaming
4538+ @run_rootdir
4539+ def _copy_files (self , i : int ):
45024540 # Get list of files to copy
45034541 files = self .opts .get_CopyFiles ()
45044542 # Check for any
45054543 if files is None or len (files ) == 0 :
45064544 return
4507- # Ensure case index is set
4508- self .opts .setx_i (i )
4509- # Create case folder
4510- self .make_case_folder (i )
45114545 # Name of case folder
45124546 frun = self .x .GetFullFolderNames (i )
45134547 # Loop through files
@@ -4525,18 +4559,76 @@ def copy_files(self, i: int):
45254559 # Copy file
45264560 shutil .copy (fabs , fdest )
45274561
4528- # Link files
4562+ # Copy files with renaming
45294563 @run_rootdir
4530- def link_files (self , i : int ):
4531- # Get list of files to copy
4532- files = self .opts .get_LinkFiles ()
4564+ def _copy_as_files (self , i : int ):
4565+ # Get dict of files to copy
4566+ filedict = self .opts .get_CopyAsFiles ()
45334567 # Check for any
4534- if files is None or len (files ) == 0 :
4568+ if filedict is None or len (filedict ) == 0 :
45354569 return
4570+ # Name of case folder
4571+ frun = self .x .GetFullFolderNames (i )
4572+ # Loop through files
4573+ for src , trg in filedict .items ():
4574+ # Absolutize source
4575+ fabs = self .abspath (src )
4576+ # Destination file
4577+ fdest = os .path .join (self .RootDir , frun , trg )
4578+ # Check for overwrite
4579+ if os .path .isfile (fdest ):
4580+ print (f" Replacing file '{ src } ' -> '{ fdest } '" )
4581+ os .remove (fdest )
4582+ # Copy file
4583+ shutil .copy (fabs , fdest )
4584+
4585+ # Link files
4586+ def link_files (self , i : int ):
4587+ r"""Link files from *Mesh* section
4588+
4589+ This applies to both *LinkFiles* and *LinkAsFiles* in the
4590+ *Mesh* section. The former will copy a given file into the run
4591+ folder for case *i* using the base name of the original (source)
4592+ file. Using
4593+
4594+ .. code-block:: javascript
4595+
4596+ "Mesh": {
4597+ "LinkAsFiles": {
4598+ "inputs/mesh-config02.ugrid": "mesh.ugrid"
4599+ }
4600+ }
4601+
4602+ will create a link (using the absolute path) from
4603+ ``inputs/mesh-config02.ugrid`` to ``mesh.ugrid`` in the case run
4604+ folder.
4605+
4606+ :Call:
4607+ >>> cntl.link_files(i)
4608+ :Inputs:
4609+ *cntl*: :class:`cape.cfdx.cntl.Cntl`
4610+ Overall CAPE control instance
4611+ *i*: :class:`int`
4612+ Case index
4613+ :Versions:
4614+ * 2025-09-19 ``@ddalle``: v1.0
4615+ """
45364616 # Ensure case index is set
45374617 self .opts .setx_i (i )
45384618 # Create case folder
45394619 self .make_case_folder (i )
4620+ # Two parts
4621+ self ._link_as_files (i )
4622+ self ._link_files (i )
4623+
4624+ # Link files w/o renaming
4625+ @run_rootdir
4626+ def _link_files (self , i : int ):
4627+ # Get list of files to copy
4628+ files = self .opts .get_LinkFiles ()
4629+ # Check for any
4630+ if files is None or len (files ) == 0 :
4631+ return
45404632 # Name of case folder
45414633 frun = self .x .GetFullFolderNames (i )
45424634 # Loop through files
@@ -4553,6 +4645,30 @@ def link_files(self, i: int):
45534645 # Copy file
45544646 os .symlink (fabs , fdest )
45554647
4648+ # Link files with renaming
4649+ @run_rootdir
4650+ def _link_as_files (self , i : int ):
4651+ # Get dict of files to copy
4652+ filedict = self .opts .get_LinkAsFiles ()
4653+ # Check for any
4654+ if filedict is None or len (filedict ) == 0 :
4655+ return
4656+ # Name of case folder
4657+ frun = self .x .GetFullFolderNames (i )
4658+ # Loop through files
4659+ for src , trg in filedict .items ():
4660+ # Absolutize source
4661+ fabs = self .abspath (src )
4662+ # Destination file
4663+ fdest = os .path .join (self .RootDir , frun , trg )
4664+ # Check for overwrite
4665+ if os .path .isfile (fdest ):
4666+ raise FileExistsError (
4667+ f" Cannot copy '{ os .path .basename (src )} ' -> "
4668+ f"'{ src } '; file exists" )
4669+ # Copy file
4670+ os .symlink (fabs , fdest )
4671+
45564672 # --- Archiving ---
45574673 # Function to archive results and remove files
45584674 def ArchiveCases (self , ** kw ):
0 commit comments