|
2 | 2 | The PyGMT alias system to convert PyGMT's long-form arguments to GMT's short-form.
|
3 | 3 | """
|
4 | 4 |
|
| 5 | +import warnings |
| 6 | +from collections import UserDict |
5 | 7 | from collections.abc import Mapping, Sequence
|
6 | 8 | from typing import Any, Literal
|
7 | 9 |
|
8 |
| -from pygmt.exceptions import GMTValueError |
| 10 | +from pygmt.exceptions import GMTInvalidInput, GMTValueError |
9 | 11 | from pygmt.helpers.utils import is_nonstr_iter, sequence_join
|
10 | 12 |
|
11 | 13 |
|
@@ -192,3 +194,120 @@ def __init__(
|
192 | 194 | size=size,
|
193 | 195 | ndim=ndim,
|
194 | 196 | )
|
| 197 | + |
| 198 | + |
| 199 | +class AliasSystem(UserDict): |
| 200 | + """ |
| 201 | + Alias system for mapping PyGMT's long-form parameters to GMT's short-form options. |
| 202 | +
|
| 203 | + This class is initialized with keyword arguments, where each key is a GMT option |
| 204 | + flag, and the corresponding value is an ``Alias`` object or a list of ``Alias`` |
| 205 | + objects. |
| 206 | +
|
| 207 | + This class inherits from ``UserDict``, which allows it to behave like a dictionary |
| 208 | + and can be passed to the ``build_arg_list`` function. It also provides the ``merge`` |
| 209 | + method to update the alias dictionary with additional keyword arguments. |
| 210 | +
|
| 211 | + Examples |
| 212 | + -------- |
| 213 | + >>> from pygmt.alias import Alias, AliasSystem |
| 214 | + >>> from pygmt.helpers import build_arg_list |
| 215 | + >>> |
| 216 | + >>> def func( |
| 217 | + ... par0, par1=None, par2=None, frame=False, repeat=None, panel=None, **kwargs |
| 218 | + ... ): |
| 219 | + ... aliasdict = AliasSystem( |
| 220 | + ... A=[ |
| 221 | + ... Alias(par1, name="par1"), |
| 222 | + ... Alias(par2, name="par2", prefix="+o", separator="/"), |
| 223 | + ... ], |
| 224 | + ... B=Alias(frame, name="frame"), |
| 225 | + ... D=Alias(repeat, name="repeat"), |
| 226 | + ... c=Alias(panel, name="panel", separator=","), |
| 227 | + ... ).merge(kwargs) |
| 228 | + ... return build_arg_list(aliasdict) |
| 229 | + >>> func( |
| 230 | + ... "infile", |
| 231 | + ... par1="mytext", |
| 232 | + ... par2=(12, 12), |
| 233 | + ... frame=True, |
| 234 | + ... repeat=[1, 2, 3], |
| 235 | + ... panel=(1, 2), |
| 236 | + ... J="X10c/10c", |
| 237 | + ... ) |
| 238 | + ['-Amytext+o12/12', '-B', '-D1', '-D2', '-D3', '-JX10c/10c', '-c1,2'] |
| 239 | + """ |
| 240 | + |
| 241 | + def __init__(self, **kwargs): |
| 242 | + """ |
| 243 | + Initialize the alias system as a dictionary with current parameter values. |
| 244 | + """ |
| 245 | + # Store the aliases in a dictionary, to be used in the merge() method. |
| 246 | + self.aliasdict = kwargs |
| 247 | + |
| 248 | + # The value of each key in kwargs is an Alias object or a sequence of Alias |
| 249 | + # objects. If it is a single Alias object, we will use its _value property. If |
| 250 | + # it is a sequence of Alias objects, we will concatenate their _value properties |
| 251 | + # into a single string. |
| 252 | + # |
| 253 | + # Note that alias._value is converted by the _to_string method and can only be |
| 254 | + # None, string or sequence of strings. |
| 255 | + # - None means the parameter is not specified. |
| 256 | + # - Sequence of strings means this is a repeatable option, so it can only have |
| 257 | + # one long-form parameter. |
| 258 | + kwdict = {} |
| 259 | + for option, aliases in kwargs.items(): |
| 260 | + if isinstance(aliases, Sequence): # A sequence of Alias objects. |
| 261 | + values = [alias._value for alias in aliases if alias._value is not None] |
| 262 | + if values: |
| 263 | + kwdict[option] = "".join(values) |
| 264 | + elif aliases._value is not None: # A single Alias object and not None. |
| 265 | + kwdict[option] = aliases._value |
| 266 | + super().__init__(kwdict) |
| 267 | + |
| 268 | + def merge(self, kwargs: Mapping[str, Any]): |
| 269 | + """ |
| 270 | + Update the dictionary with additional keyword arguments. |
| 271 | +
|
| 272 | + This method is necessary to allow users to use the single-letter parameters for |
| 273 | + option flags that are not aliased. |
| 274 | + """ |
| 275 | + # Loop over short-form parameters passed via kwargs. |
| 276 | + for short_param, value in kwargs.items(): |
| 277 | + # Check if long-form parameters exist and given. |
| 278 | + long_param_exists = short_param in self.aliasdict |
| 279 | + long_param_given = short_param in self |
| 280 | + |
| 281 | + # Update the dictionary with the short-form parameter anyway. |
| 282 | + self[short_param] = value |
| 283 | + |
| 284 | + # Long-form parameters do not exist. |
| 285 | + if not long_param_exists: |
| 286 | + continue |
| 287 | + |
| 288 | + # Long-form parameters exist. |
| 289 | + aliases = self.aliasdict.get(short_param) |
| 290 | + if not isinstance(aliases, Sequence): # Single Alias object. |
| 291 | + _msg_long = f"Use long-form parameter {aliases.name!r} instead." |
| 292 | + else: # Sequence of Alias objects. |
| 293 | + _params = [f"{v.name!r}" for v in aliases if not v.prefix] |
| 294 | + _modifiers = [f"{v.name!r} ({v.prefix})" for v in aliases if v.prefix] |
| 295 | + _msg_long = ( |
| 296 | + f"Use long-form parameters {', '.join(_params)}, " |
| 297 | + f"with optional parameters {', '.join(_modifiers)} instead." |
| 298 | + ) |
| 299 | + |
| 300 | + # Long-form parameters are already specified. |
| 301 | + if long_param_given: |
| 302 | + msg = ( |
| 303 | + f"Short-form parameter {short_param!r} conflicts with long-form " |
| 304 | + f"parameters and is not recommended. {_msg_long}" |
| 305 | + ) |
| 306 | + raise GMTInvalidInput(msg) |
| 307 | + |
| 308 | + # Long-form parameters are not specified. |
| 309 | + msg = ( |
| 310 | + f"Short-form parameter {short_param!r} is not recommended. {_msg_long}" |
| 311 | + ) |
| 312 | + warnings.warn(msg, category=SyntaxWarning, stacklevel=2) |
| 313 | + return self |
0 commit comments