1616)
1717
1818
19- def valid_file (path_str ):
20- """Check if the file exists, has a .mdu extension, and return its path."""
19+ def valid_file (path_str : str ):
20+ """Validate an .mdu file path and return it as a Path object.
21+
22+ Args:
23+ path_str (str):
24+ The path to the MDU file as provided on the command line.
25+
26+ Returns:
27+ Path: The validated path to the file.
28+
29+ Raises:
30+ argparse.ArgumentTypeError: If the file does not exist or does not have a .mdu extension.
31+ """
2132 path = Path (path_str )
2233 if not str (path ).lower ().endswith (".mdu" ):
2334 raise ArgumentTypeError (f"File must have a .mdu extension: { path } " )
@@ -27,8 +38,21 @@ def valid_file(path_str):
2738 return path
2839
2940
30- def _validator (path_str , extension ):
31- """Validate that the file exists and has the given extension."""
41+ def _validator (path_str : str , extension : str ):
42+ """Validate that a file exists and matches the expected extension.
43+
44+ Args:
45+ path_str (str):
46+ The path to the file to validate.
47+ extension (str):
48+ The required file extension (including the dot), e.g. ".ext".
49+
50+ Returns:
51+ Path: The validated file path.
52+
53+ Raises:
54+ argparse.ArgumentTypeError: If the file does not exist or the extension does not match.
55+ """
3256 path = Path (path_str )
3357 if not path .exists ():
3458 raise ArgumentTypeError (f"File not found: { path } " )
@@ -37,13 +61,36 @@ def _validator(path_str, extension):
3761 return path
3862
3963
40- def valid_file_with_extension (extension ):
41- """Create a validator for files with a specific extension for argparse."""
64+ def valid_file_with_extension (extension : str ):
65+ """Create a validator callable for argparse that enforces a file extension.
66+
67+ Args:
68+ extension (str):
69+ The required file extension (including the dot), e.g. ".mdu".
70+
71+ Returns:
72+ Callable[[str], Path]:
73+ A function that validates a path string and returns a Path when invoked.
74+
75+ Raises:
76+ argparse.ArgumentTypeError: Raised by the returned validator if the provided path does not exist or
77+ does not end with the required extension.
78+ """
4279 return lambda path_str : _validator (path_str , extension )
4380
4481
45- def valid_dir (path_str ):
46- """Validate that the path exists and is a directory."""
82+ def valid_dir (path_str : str ):
83+ """Validate that the given path exists and is a directory.
84+
85+ Args:
86+ path_str (str): The path to validate.
87+
88+ Returns:
89+ Path: The validated directory path.
90+
91+ Raises:
92+ argparse.ArgumentTypeError: If the path does not exist or is not a directory.
93+ """
4794 path = Path (path_str )
4895 if not path .exists ():
4996 raise ArgumentTypeError (f"Directory not found: { path } " )
@@ -53,6 +100,11 @@ def valid_dir(path_str):
53100
54101
55102def _get_parser () -> argparse .ArgumentParser :
103+ """Create and configure the argument parser for the extforce_convert CLI.
104+
105+ Returns:
106+ argparse.ArgumentParser: The configured argument parser.
107+ """
56108 parser = argparse .ArgumentParser (
57109 prog = "extforce_convert" ,
58110 description = "Convert D-Flow FM legacy external forcings files to current external forcings file/initial fields file/structures file." ,
@@ -137,32 +189,63 @@ def _get_parser() -> argparse.ArgumentParser:
137189
138190def main (args = None ):
139191 """
140- Entry point for extforce_convert tool.
192+ Entry point for the extforce_convert command-line tool.
141193
142194 CLI argument combinations:
143195
144196 Required (mutually exclusive, pick one):
145- --mdufile MDUFILE Use MDUFILE to determine input/output files automatically.
146- --extoldfile EXTOLDFILE Convert a specific legacy external forcing file.
147- --dir DIR Recursively find and convert all .mdu files in DIR.
197+ --mdufile, -m MDUFILE Use MDUFILE to determine input/output files automatically.
198+ --extoldfile, -e EXTOLDFILE Convert a specific legacy external forcing file.
199+ --dir, -d DIR Recursively find and convert all .mdu files in DIR.
148200
149201 Optional:
150- --outfiles EXTFILE INIFIELDFILE STRUCTUREFILE
202+ --outfiles, -o EXTFILE INIFIELDFILE STRUCTUREFILE
151203 Specify output filenames for forcings, initial fields, and structures (only with --mdufile or --extoldfile).
152- --no-backup Do not create a backup of overwritten files (mutually exclusive).
153- --remove-legacy-files -r Remove legacy/old files (e.g. .tim) after conversion.
154- --verbose, -v Print diagnostic information.
155- --version Print version and exit.
204+ Note: requires exactly three paths and is only valid for single-file conversions
205+ (i.e., when using --mdufile or --extoldfile). Using --outfiles with --dir is invalid
206+ and will result in an error.
207+ --no-backup Do not create a backup of overwritten files.
208+ --remove-legacy-files, -r Remove legacy/old files (e.g. .tim) after conversion.
209+ --debug-mode Convert only supported quantities; leave unsupported quantities in the legacy external forcing file (default: False).
210+ --verbose, -v Print diagnostic information.
211+ --version Print version and exit.
156212 --path-style {unix,windows}
157- Handle absolute paths in input/output files according to the specified style (unix/windows).
158- Use this when converting models that use unix paths but you are running the converter
159- on a Windows machine and the opposite.
160-
161- Example usages:
162- extforce_convert --mdufile model.mdu
163- extforce_convert --extoldfile old.ext --outfiles new.ext new.ini new.str
164- extforce_convert --dir ./models --no-backup --remove-legacy-files
165- extforce_convert --mdufile model.mdu --path-style unix
213+ Handle absolute paths in input/output files according to the specified style (unix/windows).
214+ Use this when converting models that use unix paths but you are running the converter
215+ on a Windows machine, or the opposite.
216+
217+ Args:
218+ args (Optional[List[str]]): Optional list of argument strings to parse instead of sys.argv. Useful for testing.
219+
220+ Notes:
221+ - `--outfiles` cannot be combined with `--dir`.
222+ - `--outfiles` applies only to a single conversion target (from --mdufile or --extoldfile) and must provide three
223+ filenames, in this order: EXTFILE INIFIELDFILE STRUCTUREFILE.
224+ - When `--debug-mode` is provided, only supported quantities are converted; unsupported quantities remain in the
225+ legacy external forcing file. Without this flag, encountering unsupported quantities results in a failure.
226+
227+ Examples (valid):
228+ - Use an MDU file and let the tool determine I/O automatically
229+ ```shell
230+ >>> extforce_convert --mdufile model.mdu # doctest: +SKIP
231+ ```
232+ - Convert a specific legacy .ext and explicitly set output filenames
233+ ```shell
234+ >>> extforce_convert --extoldfile old.ext --outfiles new.ext new.ini new.str # doctest: +SKIP
235+ ```
236+ - Recursively convert all models in a directory (no --outfiles here)
237+ ```shell
238+ >>> extforce_convert --dir ./models --no-backup --remove-legacy-files # doctest: +SKIP
239+ ```
240+ - Convert with explicit path style handling
241+ ```shell
242+ >>> extforce_convert --mdufile model.mdu --path-style unix # doctest: +SKIP
243+ ```
244+ - invalid flags combinations that will raise an error:
245+ --outfiles only works with single-file modes, not with --dir
246+ ```shell
247+ >>> extforce_convert --dir ./models --outfiles a.ext b.ini c.str # doctest: +SKIP
248+ ```
166249 """
167250 parser = _get_parser ()
168251 args = parser .parse_args (args )
0 commit comments