Skip to content

Commit ddcc7b8

Browse files
committed
Simplify the alias system
1 parent b244f74 commit ddcc7b8

File tree

3 files changed

+63
-48
lines changed

3 files changed

+63
-48
lines changed

pygmt/alias.py

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import dataclasses
66
import warnings
7-
from collections import defaultdict
87
from collections.abc import Mapping, Sequence
98
from typing import Any, Literal
109

@@ -227,8 +226,8 @@ class AliasSystem:
227226
... B=Alias(frame, name="frame"),
228227
... D=Alias(repeat, name="repeat"),
229228
... c=Alias(panel, name="panel", separator=","),
230-
... )
231-
... return build_arg_list(alias.kwdict | kwargs)
229+
... ).update(kwargs)
230+
... return build_arg_list(alias.kwdict)
232231
>>> func(
233232
... "infile",
234233
... par1="mytext",
@@ -241,60 +240,67 @@ class AliasSystem:
241240
['-Amytext+o12/12', '-B', '-D1', '-D2', '-D3', '-JX10c/10c', '-c1,2']
242241
"""
243242

244-
def __init__(self, **kwargs):
243+
def __init__(self, **kwargs: Mapping[str, Alias | Sequence[Alias]]):
245244
"""
246245
Initialize the alias system and create the keyword dictionary that stores the
247246
current parameter values.
248247
"""
249-
# Keyword dictionary with an empty string as default value.
250-
self.kwdict = defaultdict(str)
251-
for option, aliases in kwargs.items():
252-
if not is_nonstr_iter(aliases): # A single alias.
248+
# Storing the aliases as a dictionary for easy access.
249+
self.aliasdict = kwargs
250+
251+
# Storing option-value as a keyword dictionary.
252+
self.kwdict = {}
253+
# Loop over the alias dictionary.
254+
# The value of each key is an Alias object or a sequence of Alias objects.
255+
# If it is a single Alias object, we will use its _value property.
256+
# If it is a sequence of Alias objects, we will concatenate their _value
257+
# properties into a single string.
258+
259+
# Note that alias._value is converted by the _to_string method and can only be
260+
# None, string or sequence of strings.
261+
# - None means the parameter is not specified.
262+
# - Sequence of strings means this is a repeatable option, so it can only have
263+
# one long-form parameter.
264+
for option, aliases in self.aliasdict.items():
265+
if isinstance(aliases, Sequence): # A sequence of Alias objects.
266+
values = [alias._value for alias in aliases if alias._value is not None]
267+
if values:
268+
self.kwdict[option] = "".join(values)
269+
elif aliases._value is not None: # A single Alias object and not None.
253270
self.kwdict[option] = aliases._value
254-
continue
255-
256-
for alias in aliases: # List of aliases.
257-
match alias._value:
258-
case None:
259-
continue
260-
case str():
261-
self.kwdict[option] += alias._value
262-
case list():
263-
# A repeatable option should have only one alias, so break.
264-
self.kwdict[option] = alias._value
265-
break
266-
267-
# Dictionary mapping option flags to their alias objects.
268-
self._aliasdict = {}
269-
for option, aliases in kwargs.items():
270-
if not is_nonstr_iter(aliases):
271-
self._aliasdict[option] = [aliases.name]
272-
else:
273-
self._aliasdict[option] = [alias.name for alias in aliases]
274-
275-
def merge(self, kwargs):
271+
# Now, the dictionary value should be a string or a sequence of strings.
272+
273+
def update(self, kwargs: Mapping[str, Any]):
276274
"""
277-
Merge additional keyword arguments into the existing keyword dictionary.
275+
Update the kwdict dictionary with additional keyword arguments.
278276
279277
This method is necessary to allow users to use the single-letter parameters for
280278
option flags that are not aliased.
281279
"""
282-
# Loop over short-form parameters passed in kwargs.
280+
# Loop over short-form parameters passed via kwargs.
283281
for short_param, value in kwargs.items():
284-
if self._aliasdict.get(short_param):
285-
long_form = ", ".join(repr(r) for r in self._aliasdict.get(short_param))
286-
287-
# Long-form exists and is already given.
288-
if short_param in self.kwdict and self.kwdict[short_param] is not None:
289-
msg = f"Parameters in short-form {short_param!r} and long-form {long_form} can't coexist."
290-
raise GMTInvalidInput(msg)
291-
292-
# Long-form exists, but not given.
293-
if short_param in self._aliasdict:
282+
# Long-form parameters exist.
283+
if aliases := self.aliasdict.get(short_param):
284+
long_params = ", ".join(
285+
[repr(alias.name) for alias in aliases]
286+
if isinstance(aliases, Sequence)
287+
else [repr(aliases.name)]
288+
)
289+
# Long-form parameters are already specified.
290+
if short_param in self.kwdict:
291+
msg = (
292+
f"Parameter in short-form {short_param!r} conflicts with "
293+
f"long-form parameter {long_params}."
294+
)
295+
raise GMTInvalidInput(msg)
296+
297+
# Long-form parameters are not specified.
294298
msg = (
295299
f"Short-form parameter {short_param!r} is not recommended. "
296-
f"Use long-form parameter {long_form} instead."
300+
f"Use long-form parameter {long_params} instead."
297301
)
298302
warnings.warn(msg, category=SyntaxWarning, stacklevel=2)
303+
304+
# Update the kwdict with the short-form parameter anyway.
299305
self.kwdict[short_param] = value
300306
return self

pygmt/src/basemap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def basemap(self, region=None, projection=None, frame=None, **kwargs):
8686
R=Alias(region, name="region", separator="/", size=[4, 6]),
8787
J=Alias(projection, name="projection"),
8888
B=Alias(frame, name="frame"),
89-
).merge(kwargs)
89+
).update(kwargs)
9090

9191
with Session() as lib:
9292
lib.call_module(module="basemap", args=build_arg_list(alias.kwdict))

pygmt/tests/test_alias_system.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def func(projection=None, region=None, frame=None, label=None, text=None, **kwar
1717
R=Alias(region, name="region", separator="/", size=[4, 6]),
1818
B=Alias(frame, name="frame"),
1919
U=[Alias(label, name="label"), Alias(text, name="text", prefix="+t")],
20-
).merge(kwargs)
20+
).update(kwargs)
2121
return build_arg_list(alias.kwdict)
2222

2323

@@ -42,7 +42,10 @@ def test_alias_system_one_alias_short_form():
4242
assert func(J="X10c") == ["-JX10c"]
4343

4444
# Coexistence of long-form and short-form parameters.
45-
with pytest.raises(GMTInvalidInput, match="can't coexist"):
45+
with pytest.raises(
46+
GMTInvalidInput,
47+
match="Parameter in short-form 'J' conflicts with long-form parameter 'projection'.",
48+
):
4649
func(projection="X10c", J="H10c")
4750

4851

@@ -64,8 +67,14 @@ def test_alias_system_multiple_aliases_short_form():
6467
):
6568
assert func(U="abcd+tefg") == ["-Uabcd+tefg"]
6669

67-
with pytest.raises(GMTInvalidInput, match="can't coexist"):
70+
with pytest.raises(
71+
GMTInvalidInput,
72+
match="Parameter in short-form 'U' conflicts with long-form parameter 'label', 'text'.",
73+
):
6874
func(label="abcd", U="efg")
6975

70-
with pytest.raises(GMTInvalidInput, match="can't coexist"):
76+
with pytest.raises(
77+
GMTInvalidInput,
78+
match="Parameter in short-form 'U' conflicts with long-form parameter 'label', 'text'.",
79+
):
7180
func(text="efg", U="efg")

0 commit comments

Comments
 (0)