Skip to content

Commit b0f0d14

Browse files
committed
Improve the alias system to support single-letter option flags
1 parent cd536bb commit b0f0d14

File tree

3 files changed

+119
-8
lines changed

3 files changed

+119
-8
lines changed

pygmt/alias.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
"""
44

55
import dataclasses
6+
import warnings
67
from collections import defaultdict
78
from collections.abc import Mapping, Sequence
89
from typing import Any, Literal
910

10-
from pygmt.exceptions import GMTValueError
11+
from pygmt.exceptions import GMTInvalidInput, GMTValueError
1112
from pygmt.helpers.utils import is_nonstr_iter, sequence_join
1213

1314

@@ -247,7 +248,6 @@ def __init__(self, **kwargs):
247248
"""
248249
# Keyword dictionary with an empty string as default value.
249250
self.kwdict = defaultdict(str)
250-
251251
for option, aliases in kwargs.items():
252252
if not is_nonstr_iter(aliases): # A single alias.
253253
self.kwdict[option] = aliases._value
@@ -263,3 +263,38 @@ def __init__(self, **kwargs):
263263
# A repeatable option should have only one alias, so break.
264264
self.kwdict[option] = alias._value
265265
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):
276+
"""
277+
Merge additional keyword arguments into the existing keyword dictionary.
278+
279+
This method is necessary to allow users to use the single-letter parameters for
280+
option flags that are not aliased.
281+
"""
282+
# Loop over short-form parameters passed in kwargs.
283+
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:
294+
msg = (
295+
f"Short-form parameter {short_param!r} is not recommended. "
296+
f"Use long-form parameter {long_form} instead."
297+
)
298+
warnings.warn(msg, category=SyntaxWarning, stacklevel=2)
299+
self.kwdict[short_param] = value
300+
return self

pygmt/src/basemap.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
basemap - Plot base maps and frames.
33
"""
44

5+
from pygmt.alias import Alias, AliasSystem
56
from pygmt.clib import Session
67
from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias
78

89

910
@fmt_docstring
1011
@use_alias(
11-
R="region",
12-
J="projection",
1312
Jz="zscale",
1413
JZ="zsize",
15-
B="frame",
1614
L="map_scale",
1715
F="box",
1816
Td="rose",
@@ -23,8 +21,8 @@
2321
p="perspective",
2422
t="transparency",
2523
)
26-
@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence")
27-
def basemap(self, **kwargs):
24+
@kwargs_to_strings(c="sequence_comma", p="sequence")
25+
def basemap(self, region=None, projection=None, frame=None, **kwargs):
2826
r"""
2927
Plot base maps and frames.
3028
@@ -83,5 +81,12 @@ def basemap(self, **kwargs):
8381
{transparency}
8482
"""
8583
self._activate_figure()
84+
85+
alias = AliasSystem(
86+
R=Alias(region, name="region", separator="/", size=[4, 6]),
87+
J=Alias(projection, name="projection"),
88+
B=Alias(frame, name="frame"),
89+
).merge(kwargs)
90+
8691
with Session() as lib:
87-
lib.call_module(module="basemap", args=build_arg_list(kwargs))
92+
lib.call_module(module="basemap", args=build_arg_list(alias.kwdict))

pygmt/tests/test_alias_system.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Tests for the alias system.
3+
"""
4+
5+
import pytest
6+
from pygmt.alias import Alias, AliasSystem
7+
from pygmt.exceptions import GMTInvalidInput
8+
from pygmt.helpers import build_arg_list
9+
10+
11+
def func(projection=None, region=None, frame=None, label=None, text=None, **kwargs):
12+
"""
13+
A simple function to test the alias system.
14+
"""
15+
alias = AliasSystem(
16+
J=Alias(projection, name="projection"),
17+
R=Alias(region, name="region", separator="/", size=[4, 6]),
18+
B=Alias(frame, name="frame"),
19+
U=[Alias(label, name="label"), Alias(text, name="text", prefix="+t")],
20+
).merge(kwargs)
21+
return build_arg_list(alias.kwdict)
22+
23+
24+
def test_alias_system_one_alias():
25+
"""
26+
Test that the alias system works with a single alias.
27+
"""
28+
assert func(projection="X10c") == ["-JX10c"]
29+
assert func(projection="H10c", region=[0, 10, 0, 20]) == ["-JH10c", "-R0/10/0/20"]
30+
assert func(frame=["WSen", "xaf", "yaf"]) == ["-BWSen", "-Bxaf", "-Byaf"]
31+
32+
33+
def test_alias_system_one_alias_short_form():
34+
"""
35+
Test that the alias system works when short-form parameters coexist.
36+
"""
37+
# Long-form exists but is not given, and short-form is given.
38+
with pytest.warns(
39+
SyntaxWarning,
40+
match="Short-form parameter 'J' is not recommended. Use long-form parameter 'projection' instead.",
41+
):
42+
assert func(J="X10c") == ["-JX10c"]
43+
44+
# Coexistence of long-form and short-form parameters.
45+
with pytest.raises(GMTInvalidInput, match="can't coexist"):
46+
func(projection="X10c", J="H10c")
47+
48+
49+
def test_alias_system_multiple_aliases():
50+
"""
51+
Test that the alias system works with multiple aliases.
52+
"""
53+
assert func(label="abcd", text="efg") == ["-Uabcd+tefg"]
54+
55+
56+
def test_alias_system_multiple_aliases_short_form():
57+
"""
58+
Test that the alias system works with multiple aliases when short-form parameters
59+
are used.
60+
"""
61+
with pytest.warns(
62+
SyntaxWarning,
63+
match="Short-form parameter 'U' is not recommended. Use long-form parameter 'label', 'text' instead.",
64+
):
65+
assert func(U="abcd+tefg") == ["-Uabcd+tefg"]
66+
67+
with pytest.raises(GMTInvalidInput, match="can't coexist"):
68+
func(label="abcd", U="efg")
69+
70+
with pytest.raises(GMTInvalidInput, match="can't coexist"):
71+
func(text="efg", U="efg")

0 commit comments

Comments
 (0)