1414def _to_string (
1515 value : Any ,
1616 prefix : str = "" , # Default to an empty string to simplify the code logic.
17+ suffix : str = "" , # Default to an empty string to simplify the code logic.
1718 mapping : Mapping | None = None ,
1819 sep : Literal ["/" , "," ] | None = None ,
1920 size : int | Sequence [int ] | None = None ,
@@ -37,21 +38,23 @@ def _to_string(
3738 string that GMT accepts (e.g., mapping PyGMT's long-form argument ``"high"`` to
3839 GMT's short-form argument ``"h"``).
3940
40- An optional prefix (e.g., `"+o"`) can be added to the beginning of the converted
41- string.
41+ An optional prefix or suffix (e.g., `"+o"`) can be added to the beginning (or end)
42+ of the converted string.
4243
4344 To avoid extra overhead, this function does not validate parameter combinations. For
4445 example, if ``value`` is a sequence but ``sep`` is not specified, the function will
45- return a sequence of strings. In this case, ``prefix`` has no effect, but the
46- function does not check for such inconsistencies. The maintainer should ensure that
47- the parameter combinations are valid.
46+ return a sequence of strings. In this case, ``prefix`` and ``suffix`` have no
47+ effect, but the function does not check for such inconsistencies. The maintainer
48+ should ensure that the parameter combinations are valid.
4849
4950 Parameters
5051 ----------
5152 value
5253 The value to convert.
5354 prefix
5455 The string to add as a prefix to the returned value.
56+ suffix
57+ The string to add as a suffix to the returned value.
5558 mapping
5659 A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form.
5760 sep
@@ -90,6 +93,11 @@ def _to_string(
9093 >>> _to_string(False, prefix="+a")
9194 >>> _to_string(None, prefix="+a")
9295
96+ >>> _to_string("blue", suffix="+l")
97+ 'blue+l'
98+ >>> _to_string(True, suffix="+l")
99+ '+l'
100+
93101 >>> _to_string("mean", mapping={"mean": "a", "mad": "d", "full": "g"})
94102 'a'
95103 >>> _to_string("invalid", mapping={"mean": "a", "mad": "d", "full": "g"})
@@ -135,9 +143,9 @@ def _to_string(
135143 # None and False are converted to None.
136144 if value is None or value is False :
137145 return None
138- # True is converted to an empty string with the optional prefix.
146+ # True is converted to an empty string with the optional prefix and suffix .
139147 if value is True :
140- return f"{ prefix } "
148+ return f"{ prefix } { suffix } "
141149 # Any non-sequence value is converted to a string.
142150 if not is_nonstr_iter (value ):
143151 if mapping :
@@ -148,16 +156,16 @@ def _to_string(
148156 choices = mapping .keys (),
149157 )
150158 value = mapping .get (value , value )
151- return f"{ prefix } { value } "
159+ return f"{ prefix } { value } { suffix } "
152160
153161 # Return the sequence if separator is not specified for options like '-B'.
154162 # True in a sequence will be converted to an empty string.
155163 if sep is None :
156164 return [str (item ) if item is not True else "" for item in value ]
157165 # Join the sequence of values with the separator.
158- # "prefix" and "mapping" are ignored. We can enable them when needed.
166+ # "prefix", "suffix", and "mapping" are ignored. We can enable them when needed.
159167 _value = sequence_join (value , sep = sep , size = size , ndim = ndim , name = name )
160- return _value if is_nonstr_iter (_value ) else f"{ prefix } { _value } "
168+ return _value if is_nonstr_iter (_value ) else f"{ prefix } { _value } { suffix } "
161169
162170
163171class Alias :
@@ -172,6 +180,8 @@ class Alias:
172180 The name of the parameter to be used in the error message.
173181 prefix
174182 The string to add as a prefix to the returned value.
183+ suffix
184+ The string to add as a suffix to the returned value.
175185 mapping
176186 A mapping dictionary to map PyGMT's long-form arguments to GMT's short-form.
177187 sep
@@ -189,6 +199,10 @@ class Alias:
189199 >>> par._value
190200 '+o3.0/3.0'
191201
202+ >>> par = Alias("blue", suffix="+l")
203+ >>> par._value
204+ 'blue+l'
205+
192206 >>> par = Alias("mean", mapping={"mean": "a", "mad": "d", "full": "g"})
193207 >>> par._value
194208 'a'
@@ -203,17 +217,20 @@ def __init__(
203217 value : Any ,
204218 name : str | None = None ,
205219 prefix : str = "" ,
220+ suffix : str = "" ,
206221 mapping : Mapping | None = None ,
207222 sep : Literal ["/" , "," ] | None = None ,
208223 size : int | Sequence [int ] | None = None ,
209224 ndim : int = 1 ,
210225 ):
211226 self .name = name
212227 self .prefix = prefix
228+ self .suffix = suffix
213229 self ._value = _to_string (
214230 value = value ,
215231 name = name ,
216232 prefix = prefix ,
233+ suffix = suffix ,
217234 mapping = mapping ,
218235 sep = sep ,
219236 size = size ,
@@ -223,15 +240,28 @@ def __init__(
223240
224241class AliasSystem (UserDict ):
225242 """
226- Alias system for mapping PyGMT's long-form parameters to GMT's short-form options.
243+ Alias system mapping PyGMT long-form parameters to GMT short-form options.
244+
245+ This class inherits from ``UserDict`` so it behaves like a dictionary and can be
246+ passed directly to ``build_arg_list``. It also provides ``merge`` to update the
247+ alias dictionary with additional keyword arguments.
227248
228- This class is initialized with keyword arguments, where each key is a GMT option
229- flag, and the corresponding value is an ``Alias`` object or a list of ``Alias``
230- objects.
249+ Initialize with keyword arguments where each key is a GMT option flag and each value
250+ is an ``Alias`` instance or a list of ``Alias`` instances. For a single ``Alias``,
251+ we use its ``_value`` property. For a list, we check for suffixes:
231252
232- This class inherits from ``UserDict``, which allows it to behave like a dictionary
233- and can be passed to the ``build_arg_list`` function. It also provides the ``merge``
234- method to update the alias dictionary with additional keyword arguments.
253+ - If any ``Alias`` has a suffix, return a list of values for repeated GMT options.
254+ For example, ``[Alias("blue", suffix="+l"), Alias("red", suffix="+r")]`` becomes
255+ ``-Cblue+l -Cred+r``.
256+ - Otherwise, concatenate into a single string for combined modifiers. For example,
257+ ``[Alias("TL", prefix="j"), Alias((1, 1), prefix="+o")]`` becomes ``jTL+o1/1``.
258+
259+ ``alias._value`` is produced by ``_to_string`` and is one of: ``None``, ``str``, or
260+ a sequence of strings.
261+
262+ - ``None`` means the parameter is not specified.
263+ - A sequence of strings means this is a repeatable option and can only have one
264+ long-form parameter.
235265
236266 Examples
237267 --------
@@ -242,6 +272,8 @@ class AliasSystem(UserDict):
242272 ... par0,
243273 ... par1=None,
244274 ... par2=None,
275+ ... par3=None,
276+ ... par4=None,
245277 ... frame=False,
246278 ... repeat=None,
247279 ... panel=None,
@@ -254,6 +286,7 @@ class AliasSystem(UserDict):
254286 ... Alias(par2, name="par2", prefix="+o", sep="/"),
255287 ... ],
256288 ... B=Alias(frame, name="frame"),
289+ ... C=[Alias(par3, suffix="+l"), Alias(par4, suffix="+r")],
257290 ... D=Alias(repeat, name="repeat"),
258291 ... ).add_common(
259292 ... V=verbose,
@@ -265,13 +298,14 @@ class AliasSystem(UserDict):
265298 ... "infile",
266299 ... par1="mytext",
267300 ... par2=(12, 12),
301+ ... par3="blue",
302+ ... par4="red",
268303 ... frame=True,
269304 ... repeat=[1, 2, 3],
270- ... panel=(1, 2),
271- ... verbose="debug",
272- ... J="X10c/10c",
273305 ... )
274- ['-Amytext+o12/12', '-B', '-D1', '-D2', '-D3', '-JX10c/10c', '-Vd', '-c1,2']
306+ ['-Amytext+o12/12', '-B', '-Cblue+l', '-Cred+r', '-D1', '-D2', '-D3']
307+ >>> func("infile", panel=(1, 2), verbose="debug", J="X10c/10c")
308+ ['-JX10c/10c', '-Vd', '-c1,2']
275309 """
276310
277311 def __init__ (self , ** kwargs ):
@@ -281,22 +315,18 @@ def __init__(self, **kwargs):
281315 # Store the aliases in a dictionary, to be used in the merge() method.
282316 self .aliasdict = kwargs
283317
284- # The value of each key in kwargs is an Alias object or a sequence of Alias
285- # objects. If it is a single Alias object, we will use its _value property. If
286- # it is a sequence of Alias objects, we will concatenate their _value properties
287- # into a single string.
288- #
289- # Note that alias._value is converted by the _to_string method and can only be
290- # None, string or sequence of strings.
291- # - None means the parameter is not specified.
292- # - Sequence of strings means this is a repeatable option, so it can only have
293- # one long-form parameter.
294318 kwdict = {}
295319 for option , aliases in kwargs .items ():
296320 if isinstance (aliases , Sequence ): # A sequence of Alias objects.
297321 values = [alias ._value for alias in aliases if alias ._value is not None ]
298322 if values :
299- kwdict [option ] = "" .join (values )
323+ # Check if any alias has suffix - if so, return as list
324+ has_suffix = any (alias .suffix for alias in aliases )
325+ # If has suffix and multiple values, return as list;
326+ # else concatenate into a single string.
327+ kwdict [option ] = (
328+ values if has_suffix and len (values ) > 1 else "" .join (values )
329+ )
300330 elif aliases ._value is not None : # A single Alias object and not None.
301331 kwdict [option ] = aliases ._value
302332 super ().__init__ (kwdict )
0 commit comments