diff --git a/pygmt/helpers/__init__.py b/pygmt/helpers/__init__.py index d0356798d97..128b1e31a18 100644 --- a/pygmt/helpers/__init__.py +++ b/pygmt/helpers/__init__.py @@ -16,6 +16,7 @@ ) from pygmt.helpers.utils import ( args_in_kwargs, + build_arg_list, build_arg_string, data_kind, is_nonstr_iter, diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 28041911d23..c6f19693781 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -622,7 +622,7 @@ def kwargs_to_strings(**conversions): The strings are what GMT expects from command line arguments. Boolean arguments and None are not converted and will be processed in the - ``build_arg_string`` function. + ``build_arg_list`` function. You can also specify other conversions to specific arguments. diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 75514d2077d..204519c3cf2 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -11,7 +11,8 @@ import sys import time import webbrowser -from collections.abc import Iterable +from collections.abc import Iterable, Sequence +from typing import Any import xarray as xr from pygmt.exceptions import GMTInvalidInput @@ -315,6 +316,108 @@ def non_ascii_to_octal(argstr): return argstr.translate(str.maketrans(mapping)) +def build_arg_list( + kwdict: dict[str, Any], + confdict: dict[str, str] | None = None, + infile: str | pathlib.PurePath | Sequence[str | pathlib.PurePath] | None = None, + outfile: str | pathlib.PurePath | None = None, +) -> list[str]: + r""" + Convert keyword dictionaries and input/output files into a list of GMT arguments. + + Make sure all values in ``kwdict`` have been previously converted to a string + representation using the ``kwargs_to_strings`` decorator. The only exceptions are + ``True``, ``False`` and ``None``. + + Any remaining lists or tuples will be interpreted as multiple entries for the same + parameter. For example, the kwargs entry ``"B": ["xa", "yaf"]`` will be + converted to ``["-Bxa", "-Byaf"]``. + + Parameters + ---------- + kwdict + A dictionary containing parsed keyword arguments. + confdict + A dictionary containing configurable GMT parameters. + infile + The input file or a list of input files. + outfile + The output file. + + Returns + ------- + args + The list of command line arguments that will be passed to GMT modules. The + keyword arguments are sorted alphabetically, followed by GMT configuration + key-value pairs, with optional input file(s) at the beginning and optional + output file at the end. + + Examples + -------- + >>> build_arg_list(dict(A=True, B=False, C=None, D=0, E=200, F="", G="1/2/3/4")) + ['-A', '-D0', '-E200', '-F', '-G1/2/3/4'] + >>> build_arg_list(dict(A="1/2/3/4", B=["xaf", "yaf", "WSen"], C=("1p", "2p"))) + ['-A1/2/3/4', '-BWSen', '-Bxaf', '-Byaf', '-C1p', '-C2p'] + >>> print( + ... build_arg_list( + ... dict( + ... B=["af", "WSne+tBlank Space"], + ... F='+t"Empty Spaces"', + ... l="'Void Space'", + ... ) + ... ) + ... ) + ['-BWSne+tBlank Space', '-Baf', '-F+t"Empty Spaces"', "-l'Void Space'"] + >>> print( + ... build_arg_list( + ... dict(A="0", B=True, C="rainbow"), + ... confdict=dict(FORMAT_DATE_MAP="o dd"), + ... infile="input.txt", + ... outfile="output.txt", + ... ) + ... ) + ['input.txt', '-A0', '-B', '-Crainbow', '--FORMAT_DATE_MAP=o dd', '->output.txt'] + >>> print( + ... build_arg_list( + ... dict(A="0", B=True), + ... confdict=dict(FORMAT_DATE_MAP="o dd"), + ... infile=["f1.txt", "f2.txt"], + ... outfile="out.txt", + ... ) + ... ) + ['f1.txt', 'f2.txt', '-A0', '-B', '--FORMAT_DATE_MAP=o dd', '->out.txt'] + >>> print(build_arg_list(dict(R="1/2/3/4", J="X4i", watre=True))) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Unrecognized parameter 'watre'. + """ + gmt_args = [] + for key, value in kwdict.items(): + if len(key) > 2: # Raise an exception for unrecognized options + raise GMTInvalidInput(f"Unrecognized parameter '{key}'.") + if value is None or value is False: # Exclude arguments that are None or False + pass + elif value is True: + gmt_args.append(f"-{key}") + elif is_nonstr_iter(value): + gmt_args.extend(non_ascii_to_octal(f"-{key}{_value}") for _value in value) + else: + gmt_args.append(non_ascii_to_octal(f"-{key}{value}")) + gmt_args = sorted(gmt_args) + + if confdict: + gmt_args.extend(f"--{key}={value}" for key, value in confdict.items()) + + if infile: # infile can be a single file or a list of files + if isinstance(infile, str | pathlib.PurePath): + gmt_args = [str(infile), *gmt_args] + else: + gmt_args = [str(_file) for _file in infile] + gmt_args + if outfile: + gmt_args.append(f"->{outfile}") + return gmt_args + + def build_arg_string(kwdict, confdict=None, infile=None, outfile=None): r""" Convert keyword dictionaries and input/output files into a GMT argument string.